javascript scss erb 41 lines · 4 tabs

Custom turbo_stream action tag: highlight an updated element

Shared by codesnips Jan 2026
4 tabs
import { StreamActions } from "@hotwired/turbo"

StreamActions.highlight = function () {
  const className = this.getAttribute("data-highlight-class") || "flash-highlight"
  const duration = parseInt(this.getAttribute("data-highlight-duration"), 10) || 1500

  this.targetElements.forEach((element) => {
    // Force a reflow so the class add always triggers the transition.
    element.classList.remove(className)
    void element.offsetWidth
    element.classList.add(className)

    window.setTimeout(() => {
      element.classList.remove(className)
    }, duration)
  })
}
4 files · javascript, scss, erb Explain with highlit

Turbo Streams ship with a fixed vocabulary of actions — append, replace, update, remove, and a few more — but the underlying StreamActions object is an ordinary JavaScript map that can be extended. This snippet registers a custom highlight action so the server can tell the browser to briefly flash an element after it changes, giving users a visual cue that a value updated without any bespoke controller wiring per feature.

The first tab, highlight_stream_action.js, defines the action by assigning a function to StreamActions.highlight. Inside that function this is the <turbo-stream> element itself, so this.targetElements resolves the CSS selector in the target/targets attribute to real DOM nodes. Custom data-* attributes on the stream tag flow through via this.getAttribute, which is how data-highlight-class and data-highlight-duration are read. The action toggles a class, then removes it after a timeout so the CSS transition can animate back to the resting state. Because it is registered once at import time, every stream the app renders — over WebSocket, SSE, or a form response — can use it.

The second tab, application.js, is the entrypoint that imports the action module for its side effect. It must run before any stream referencing highlight arrives; importing it alongside the Turbo setup guarantees StreamActions.highlight exists when Turbo processes an incoming frame.

The third tab, flash.scss, backs the effect. The base class defines a transition on background-color, and the .flash-highlight modifier sets the highlighted color. Putting the animation in CSS keeps the JavaScript trivial and lets designers tune timing independently.

The fourth tab, update.turbo_stream.erb, shows the server side: a standard turbo_stream.replace swaps the row, and a second, hand-written <turbo-stream action="highlight"> targets the same DOM id to trigger the flash. Note the duration passed as data-highlight-duration must comfortably exceed the CSS transition, otherwise the class is stripped before the animation finishes. This pattern is worth reaching for when many views need the same post-update affordance; a custom action centralizes the behavior instead of scattering Stimulus controllers everywhere.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs

Share this code

Here's the card — post it anywhere.

Custom turbo_stream action tag: highlight an updated element — share card
Link copied