DEV Community

Cover image for Implementing Copy-to-Clipboard in Rails 8: The Modern Way
Zil Norvilis
Zil Norvilis

Posted on

Implementing Copy-to-Clipboard in Rails 8: The Modern Way

Here is a robust, modern Stimulus controller for Rails 8 to handle copying text to the clipboard.

It uses the modern navigator.clipboard API and includes user feedback (changing the button text to "Copied!" temporarily).

1. Generate the Controller

Run the Rails generator command:

bin/rails generate stimulus clipboard
Enter fullscreen mode Exit fullscreen mode

2. The JavaScript Code

Update app/javascript/controllers/clipboard_controller.js.

This implementation handles copying from input fields, textareas, or standard HTML elements (like a div or span).

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["source", "button"]
  static values = {
    successDuration: { type: Number, default: 2000 }
  }

  connect() {
    // Save the original button text so we can revert it later
    if (this.hasButtonTarget) {
      this.originalText = this.buttonTarget.innerText
    }
  }

  copy(event) {
    event.preventDefault()

    const text = this.textToCopy()

    // Copy to clipboard
    navigator.clipboard.writeText(text).then(() => {
      this.showSuccess()
    }).catch(() => {
      console.error("Failed to copy text")
    })
  }

  // Helper to determine what text to copy
  textToCopy() {
    if (this.sourceTarget.nodeName === "INPUT" || this.sourceTarget.nodeName === "TEXTAREA") {
      return this.sourceTarget.value
    } else {
      return this.sourceTarget.textContent.trim()
    }
  }

  // Visual feedback
  showSuccess() {
    if (!this.hasButtonTarget) return

    this.buttonTarget.innerText = "Copied!"
    this.buttonTarget.disabled = true

    setTimeout(() => {
      this.buttonTarget.innerText = this.originalText
      this.buttonTarget.disabled = false
    }, this.successDurationValue)
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Usage Examples (ERB)

Scenario A: Copying from an Input Field

This is useful for API keys or shareable URLs.

<div data-controller="clipboard">
  <!-- The Source -->
  <input type="text" 
         value="https://myapp.com/invite/123" 
         readonly 
         class="border p-2 rounded" 
         data-clipboard-target="source">

  <!-- The Trigger -->
  <button type="button" 
          class="bg-blue-500 text-white p-2 rounded" 
          data-action="clipboard#copy" 
          data-clipboard-target="button">
    Copy Link
  </button>
</div>
Enter fullscreen mode Exit fullscreen mode

Scenario B: Copying Static Text

This is useful for copying codes or IDs displayed in a div or span.

<div data-controller="clipboard" data-clipboard-success-duration-value="1000">
  <p>
    Discount Code: 
    <strong data-clipboard-target="source">SUMMER2025</strong>
  </p>

  <button type="button" 
          data-action="clipboard#copy" 
          data-clipboard-target="button">
    Copy Code
  </button>
</div>
Enter fullscreen mode Exit fullscreen mode

Important Notes for Production

  1. HTTPS Requirement: The navigator.clipboard API allows writing to the clipboard only in a Secure Context. This means it works on localhost, but on production, your site must be served over HTTPS. If you use HTTP, the copy function will fail.
  2. Icons: If your button contains an icon (like an SVG) instead of text, this.buttonTarget.innerText = "Copied!" will remove the icon.
    • Solution: Instead of changing innerText, toggle a CSS class (e.g., hidden) on two different span elements inside the button (one for the icon, one for the "Copied" text).

Top comments (0)