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
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)
}
}
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>
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>
Important Notes for Production
- HTTPS Requirement: The
navigator.clipboardAPI allows writing to the clipboard only in a Secure Context. This means it works onlocalhost, but on production, your site must be served over HTTPS. If you use HTTP, the copy function will fail. - 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).
- Solution: Instead of changing
Top comments (0)