erb javascript scss 108 lines · 3 tabs

Copy-to-clipboard button with Stimulus

Shared by codesnips Jan 2026
3 tabs
<div class="copy-field"
     data-controller="clipboard"
     data-clipboard-content-value="<%= content %>"
     data-clipboard-success-value="Copied!">

  <input type="text"
         class="copy-field__input"
         value="<%= content %>"
         readonly
         aria-label="Value to copy">

  <pre class="copy-field__preview"><code data-clipboard-target="source"><%= content %></code></pre>

  <button type="button"
          class="copy-field__button"
          data-clipboard-target="button"
          data-action="clipboard#copy">
    <%= label || "Copy" %>
  </button>
</div>
3 files · erb, javascript, scss Explain with highlit

This snippet shows a small but complete copy-to-clipboard feature built with Stimulus, the JavaScript framework that ships with Hotwire and Rails. It is split so the markup and behaviour stay cleanly separated: the ERB partial declares the controller and its targets/values declaratively, while the Stimulus controller holds the actual logic.

In _copy_button.html.erb, the wrapping div is annotated with data-controller="clipboard", which tells Stimulus to instantiate the controller when that element enters the DOM. The text to copy is passed through a value attribute, data-clipboard-content-value, rather than being read from the visible DOM — this keeps the source of truth explicit and works even when the displayed text is truncated or formatted. The <code> element is marked data-clipboard-target="source" as an alternative source, and the button carries data-clipboard-target="button" plus an action that maps its click event to the controller's copy method. The readonly input mirrors the value so screen-reader users and keyboard users can still select it.

In clipboard_controller.js, static targets and static values register the connections declared in the markup, giving typed accessors like this.contentValue and this.buttonTarget. The copy action prefers the modern asynchronous navigator.clipboard.writeText, which returns a promise, and falls back to fallbackCopy using a hidden textarea and document.execCommand for older or insecure (non-HTTPS) contexts where the Clipboard API is unavailable.

The feedback pattern is worth noting: copied swaps the button label to the successValue, then uses a tracked timeout so rapid repeated clicks reset the timer instead of stacking. disconnect clears that timeout to avoid a callback firing on a removed element, a common leak when Turbo swaps the page. This approach is idiomatic because it treats markup as configuration and JavaScript as behaviour, degrades gracefully, and remains resilient to Turbo navigation. It is the pattern to reach for whenever a discrete UI affordance needs a little transient client-side state.


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
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Semantic HTML Example</title>

Semantic HTML5 elements and accessibility best practices

html html5 semantics
by Alex Chang 2 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

Share this code

Here's the card — post it anywhere.

Copy-to-clipboard button with Stimulus — share card
Link copied