Clipboard Rails Components
Copy text, URLs, code snippets, and more to the user's clipboard with a single click. Perfect for sharing links, copying API keys, and code examples.
Installation
1. Stimulus Controller Setup
Start by adding the following controller to your project:
import { Controller } from "@hotwired/stimulus"; // import ClipboardJS from "clipboard"; Removed ClipboardJS import import { computePosition, offset, flip, shift, arrow, autoUpdate } from "@floating-ui/dom"; export default class extends Controller { static values = { successMessage: { type: String, default: "Copied!" }, // Success message to show when text is copied errorMessage: { type: String, default: "Failed to copy!" }, // Error message to show when text is not copied showTooltip: { type: Boolean, default: true }, // Whether to show the tooltip tooltipPlacement: { type: String, default: "top" }, // Placement(s) of the tooltip, e.g., "top", "top-start", "top-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end", "right", "right-start", "right-end" tooltipOffset: { type: Number, default: 8 }, // Offset of the tooltip tooltipDuration: { type: Number, default: 2000 }, // Duration of the tooltip }; static targets = ["copyContent", "copiedContent"]; connect() { // this.clipboard = new ClipboardJS(this.element); // this.clipboard.on("success", (e) => this.handleSuccess(e)); // this.clipboard.on("error", (e) => this.handleError(e)); this.boundCopyTextToClipboard = this._copyTextToClipboard.bind(this); this.element.addEventListener("click", this.boundCopyTextToClipboard); this.cleanupAutoUpdate = null; this.tooltipElement = null; this.arrowElement = null; this.hideTooltipTimeout = null; this.removeTooltipDOMTimeout = null; this.intersectionObserver = null; } disconnect() { // if (this.clipboard) { // this.clipboard.destroy(); // } if (this.boundCopyTextToClipboard) { this.element.removeEventListener("click", this.boundCopyTextToClipboard); } clearTimeout(this.hideTooltipTimeout); clearTimeout(this.removeTooltipDOMTimeout); if (this.intersectionObserver) { this.intersectionObserver.disconnect(); this.intersectionObserver = null; } this._removeTooltipDOM(); // This also handles cleanupAutoUpdate } handleSuccess(e) { if (this.hasCopyContentTarget && this.hasCopiedContentTarget) { this.showCopiedState(); } if (this.showTooltipValue) { this._showFloatingTooltip(this.successMessageValue); } // if (e && typeof e.clearSelection === "function") { Removed e.clearSelection() // e.clearSelection(); // } } handleError(e) { if (this.showTooltipValue) { this._showFloatingTooltip(this.errorMessageValue); } } showCopiedState() { if (this.hasCopyContentTarget && this.hasCopiedContentTarget) { this.copyContentTarget.classList.add("hidden"); this.copiedContentTarget.classList.remove("hidden"); setTimeout(() => { if (this.hasCopyContentTarget && this.hasCopiedContentTarget) { // Check targets still exist this.copyContentTarget.classList.remove("hidden"); this.copiedContentTarget.classList.add("hidden"); } }, this.tooltipDurationValue); } } async _copyTextToClipboard() { const textToCopy = this.element.dataset.clipboardText || this.element.getAttribute("data-clipboard-text"); if (textToCopy === null || typeof textToCopy === "undefined") { console.warn("No text to copy. Missing data-clipboard-text attribute on element:", this.element); this.handleError({ message: "No text to copy specified." }); // Pass a mock error object or just the message return; } try { await navigator.clipboard.writeText(textToCopy); this.handleSuccess(); // Removed 'e' argument as it's not provided by navigator.clipboard } catch (err) { console.error("Failed to copy text: ", err); this.handleError(err); // Pass the actual error object } } _createOrUpdateTooltipDOMSafer(message) { if (!this.tooltipElement) { this.tooltipElement = document.createElement("div"); this.tooltipElement.className = "tooltip-content pointer-events-none shadow-sm border rounded-lg border-white/10 absolute bg-[#333333] text-white text-sm py-1 px-2 z-[1000] opacity-0 transition-opacity duration-150"; // Example: this.tooltipElement.style.maxWidth = `${this.maxWidthValue}px`; // If you add a maxWidthValue const messageSpan = document.createElement("span"); messageSpan.setAttribute("data-tooltip-message-span", ""); this.tooltipElement.appendChild(messageSpan); // Create arrow container with padding to prevent clipping at viewport edges this.arrowContainer = document.createElement("div"); this.arrowContainer.className = "absolute z-[1000]"; this.arrowElement = document.createElement("div"); this.arrowElement.className = "tooltip-arrow-element bg-[#333333] w-2 h-2 border-white/10"; this.arrowElement.style.transform = "rotate(45deg)"; this.arrowContainer.appendChild(this.arrowElement); this.tooltipElement.appendChild(this.arrowContainer); const appendTarget = this.element.closest("dialog[open]") || document.body; appendTarget.appendChild(this.tooltipElement); } const messageSpan = this.tooltipElement.querySelector("[data-tooltip-message-span]"); if (messageSpan) { messageSpan.textContent = message; } } _removeTooltipDOM() { if (this.cleanupAutoUpdate) { this.cleanupAutoUpdate(); this.cleanupAutoUpdate = null; } if (this.intersectionObserver) { this.intersectionObserver.disconnect(); this.intersectionObserver = null; } if (this.tooltipElement && this.tooltipElement.parentElement) { this.tooltipElement.remove(); } this.tooltipElement = null; this.arrowContainer = null; this.arrowElement = null; } async _showFloatingTooltip(message) { if (!this.showTooltipValue) return; clearTimeout(this.hideTooltipTimeout); clearTimeout(this.removeTooltipDOMTimeout); // Ensure any old instance is fully gone before creating a new one // This handles cases where the tooltip is shown again quickly. if (this.tooltipElement) { this._removeTooltipDOM(); } this._createOrUpdateTooltipDOMSafer(message); if (!this.tooltipElement) return; // Should not happen if _createOrUpdateTooltipDOMSafer worked this.tooltipElement.style.visibility = "visible"; // Ensure autoUpdate is cleaned up if it was somehow active without a tooltipElement (defensive) if (this.cleanupAutoUpdate) this.cleanupAutoUpdate(); const referenceElement = this.element; this.cleanupAutoUpdate = autoUpdate( referenceElement, this.tooltipElement, async () => { if (!this.tooltipElement || !this.arrowContainer) { if (this.cleanupAutoUpdate) { this.cleanupAutoUpdate(); this.cleanupAutoUpdate = null; } return; } // Parse placement value to support multiple placements const placements = this.tooltipPlacementValue.split(/[\s,]+/).filter(Boolean); const primaryPlacement = placements[0] || "top"; const fallbackPlacements = placements.slice(1); const middleware = [ offset(this.tooltipOffsetValue), flip({ fallbackPlacements: fallbackPlacements.length > 0 ? fallbackPlacements : undefined, }), shift({ padding: 5 }), ]; middleware.push(arrow({ element: this.arrowContainer, padding: 2 })); const { x, y, placement, middlewareData } = await computePosition(referenceElement, this.tooltipElement, { placement: primaryPlacement, middleware: middleware, }); Object.assign(this.tooltipElement.style, { left: `${x}px`, top: `${y}px`, }); if (this.arrowContainer && this.arrowElement && middlewareData.arrow) { const { x: arrowX, y: arrowY } = middlewareData.arrow; const basePlacement = placement.split("-")[0]; const staticSide = { top: "bottom", right: "left", bottom: "top", left: "right" }[basePlacement]; // Apply appropriate padding based on placement direction this.arrowContainer.classList.remove("px-1", "py-1"); if (basePlacement === "top" || basePlacement === "bottom") { this.arrowContainer.classList.add("px-1"); // Horizontal padding for top/bottom } else { this.arrowContainer.classList.add("py-1"); // Vertical padding for left/right } // Position the arrow container Object.assign(this.arrowContainer.style, { left: arrowX != null ? `${arrowX}px` : "", top: arrowY != null ? `${arrowY}px` : "", right: "", bottom: "", [staticSide]: "-0.28rem", // Matches tooltip_controller.js arrow positioning }); // Style the arrow element within the container this.arrowElement.classList.remove("border-t", "border-r", "border-b", "border-l"); if (staticSide === "bottom") this.arrowElement.classList.add("border-b", "border-r"); else if (staticSide === "top") this.arrowElement.classList.add("border-t", "border-l"); else if (staticSide === "left") this.arrowElement.classList.add("border-b", "border-l"); else if (staticSide === "right") this.arrowElement.classList.add("border-t", "border-r"); } }, { animationFrame: true } ); // Setup intersection observer to hide tooltip when trigger element goes out of view if (this.intersectionObserver) { this.intersectionObserver.disconnect(); } this.intersectionObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) { this._hideFloatingTooltip(); } }); }, { threshold: 0 } // Hide as soon as any part goes out of view ); this.intersectionObserver.observe(this.element); requestAnimationFrame(() => { if (this.tooltipElement) { this.tooltipElement.classList.remove("opacity-0"); this.tooltipElement.classList.add("opacity-100"); } }); this.hideTooltipTimeout = setTimeout(() => { this._hideFloatingTooltip(); }, this.tooltipDurationValue); } _hideFloatingTooltip() { if (!this.tooltipElement || !this.tooltipElement.classList.contains("opacity-100")) { if (!this.tooltipElement) { // If element is already gone, ensure related properties are nulled. this._removeTooltipDOM(); } return; } this.tooltipElement.classList.remove("opacity-100"); this.tooltipElement.classList.add("opacity-0"); if (this.cleanupAutoUpdate) { this.cleanupAutoUpdate(); this.cleanupAutoUpdate = null; } if (this.intersectionObserver) { this.intersectionObserver.disconnect(); this.intersectionObserver = null; } this.removeTooltipDOMTimeout = setTimeout(() => { this._removeTooltipDOM(); }, 150); // Transition duration (duration-150) } }
2. Floating UI Installation
The clipboard component relies on Floating UI for intelligent tooltip positioning. Choose your preferred installation method:
pin "@floating-ui/dom", to: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.3/+esm"
npm install @floating-ui/dom
yarn add @floating-ui/dom
Examples
Basic clipboard button
A simple clipboard button that copies text to the user's clipboard.
<button class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Hello from Rails Blocks!"> Copy Text </button>
Clipboard with icon states
A clipboard button that shows different icons for copy and copied states with visual feedback.
<button class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="https://railsblocks.com" data-clipboard-success-message-value="URL Copied!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <span>Copy URL</span> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 sm:size-4" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5 text-green-400 dark:text-green-500"> <span>Copied!</span> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 sm:size-4" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> </div> </button>
Clipboard with input field
An input field with an attached copy button for copying predefined text or URLs.
<div class="flex max-w-md"> <input type="text" id="url-input" value="https://railsblocks.com" readonly class="flex-1 rounded-l-lg border border-r-0 border-neutral-300 bg-neutral-50 px-3 py-2 text-sm text-neutral-600 dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 dark:focus-visible:outline-neutral-200"> <button class="rounded-r-lg border border-l border-neutral-300 bg-white px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 dark:border-neutral-600 dark:bg-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="https://railsblocks.com" data-clipboard-success-message-value="Link copied to clipboard!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> </div> </button> </div>
Clipboard for code blocks
A code block with a positioned copy button for copying code snippets and commands.
Your code...
<div class="relative max-w-lg w-full"> <pre class="bg-neutral-900 text-neutral-100 p-4 rounded-lg overflow-x-auto text-sm"><code>Your code...</code></pre> <button class="absolute top-2 right-4 flex items-center gap-1.5 rounded-lg px-3.5 py-2 text-sm font-medium shadow-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 bg-neutral-800/50 text-neutral-50 hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200 border border-neutral-700" data-controller="clipboard" data-clipboard-text="Your code..." data-clipboard-success-message-value="Code copied!" data-clipboard-show-tooltip-value="true"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>Copy</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div>
Icon-only clipboard button
A minimal icon-only clipboard button with tooltip support.
<button class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-200 bg-white/90 p-2.5 text-xs font-medium whitespace-nowrap text-neutral-800 shadow-xs transition-all duration-100 ease-in-out select-none hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-50 dark:hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Copy this secret text!" data-clipboard-success-message-value="Secret copied!" data-clipboard-show-tooltip-value="true"> <div data-clipboard-target="copyContent"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 sm:size-4" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> </div> <div data-clipboard-target="copiedContent" class="hidden"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 sm:size-4 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> </div> </button>
Clipboard without tooltip
A clipboard button with tooltips disabled, showing only the visual state changes.
<button class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-200 bg-white/90 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-neutral-800 shadow-xs transition-all duration-100 ease-in-out select-none hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-50 dark:hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Rails Blocks - Premium UI components for Rails" data-clipboard-show-tooltip-value="false" data-clipboard-success-message-value="Description copied!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <span>Copy Description</span> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 sm:size-4" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <span>Copied!</span> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 sm:size-4 text-green-500" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> </div> </button>
Clipboard with different tooltip positions
Demonstrate the four available tooltip positions: top (default), bottom, left, and right using the "tooltipPlacement"
configuration value.
<div class="space-y-6"> <div class="flex flex-col sm:grid grid-cols-3 gap-4 place-items-center py-8"> <!-- Top row - top positions --> <div class="flex flex-col items-center space-y-2 w-full"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Top start tooltip!" data-clipboard-tooltip-placement-value="top-start" data-clipboard-success-message-value="Top start shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>top-start</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <div class="flex flex-col items-center space-y-2 w-full"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Top tooltip!" data-clipboard-tooltip-placement-value="top" data-clipboard-success-message-value="Top tooltip shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>top</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <div class="flex flex-col items-center space-y-2 w-full"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Top end tooltip!" data-clipboard-tooltip-placement-value="top-end" data-clipboard-success-message-value="Top end shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>top-end</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <%# Middle row - left positions %> <div class="w-full col-start-1 flex gap-4"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Left start tooltip!" data-clipboard-tooltip-placement-value="left-start" data-clipboard-success-message-value="Left start shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>left-start</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <%# Center - demonstrates the reference point %> <div class="relative hidden sm:block"> </div> <%# Middle row - right positions %> <div class="w-full col-start-3 flex gap-4"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Right start tooltip!" data-clipboard-tooltip-placement-value="right-start" data-clipboard-success-message-value="Right start shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>right-start</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <%# Second middle row %> <div class="w-full col-start-1 flex gap-4"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Left tooltip!" data-clipboard-tooltip-placement-value="left" data-clipboard-success-message-value="Left tooltip shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>left</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <div class="hidden sm:block"></div> <div class="w-full col-start-3 flex gap-4"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Right tooltip!" data-clipboard-tooltip-placement-value="right" data-clipboard-success-message-value="Right tooltip shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>right</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <%# Third middle row %> <div class="w-full col-start-1 flex gap-4"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Left end tooltip!" data-clipboard-tooltip-placement-value="left-end" data-clipboard-success-message-value="Left end shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>left-end</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <div class="hidden sm:block"></div> <div class="w-full col-start-3 flex gap-4"> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Right end tooltip!" data-clipboard-tooltip-placement-value="right-end" data-clipboard-success-message-value="Right end shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>right-end</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> <%# Bottom row - bottom positions %> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Bottom start tooltip!" data-clipboard-tooltip-placement-value="bottom-start" data-clipboard-success-message-value="Bottom start shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>bottom-start</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Bottom tooltip!" data-clipboard-tooltip-placement-value="bottom" data-clipboard-success-message-value="Bottom tooltip shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>bottom</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> <button class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200" data-controller="clipboard" data-clipboard-text="Bottom end tooltip!" data-clipboard-tooltip-placement-value="bottom-end" data-clipboard-success-message-value="Bottom end shown!"> <div data-clipboard-target="copyContent" class="flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M12.75,2h-.275c-.123-.846-.845-1.5-1.725-1.5h-3.5c-.879,0-1.602,.654-1.725,1.5h-.275c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75h7.5c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75Zm-5.75,.25c0-.138,.112-.25,.25-.25h3.5c.138,0,.25,.112,.25,.25v1c0,.138-.112,.25-.25,.25h-3.5c-.138,0-.25-.112-.25-.25v-1Zm4.75,10.25H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H6.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h5.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path> </g> </svg> <span>bottom-end</span> </div> <div data-clipboard-target="copiedContent" class="hidden flex items-center gap-1.5"> <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 text-green-600 dark:text-green-400" width="18" height="18" viewBox="0 0 18 18"> <g fill="currentColor"> <path d="M6.75,15h-.002c-.227,0-.442-.104-.583-.281L2.165,9.719c-.259-.324-.207-.795,.117-1.054,.325-.259,.796-.206,1.054,.117l3.418,4.272L14.667,3.278c.261-.322,.732-.373,1.055-.111,.322,.261,.372,.733,.111,1.055L7.333,14.722c-.143,.176-.357,.278-.583,.278Z"></path> </g> </svg> <span>Copied!</span> </div> </button> </div> </div>
Configuration
The clipboard component uses the native Navigator Clipboard API and provides customizable tooltips, success messages, and visual feedback through a Stimulus controller.
Controller Setup
Basic clipboard structure with required data attributes:
<button data-controller="clipboard" data-clipboard-text="Text to copy"> <div data-clipboard-target="copyContent">Copy</div> <div data-clipboard-target="copiedContent" class="hidden">Copied!</div> </button>
Configuration Values
Prop | Description | Type | Default |
---|---|---|---|
successMessage | Message displayed in tooltip when copy succeeds | String | "Copied!" |
errorMessage | Message displayed in tooltip when copy fails | String | "Failed to copy!" |
showTooltip | Controls whether tooltips are displayed | Boolean | true |
tooltipPlacement | Position of the tooltip (top, bottom, left, right) | String | "top" |
tooltipOffset | Distance between tooltip and button in pixels | Number | 8 |
tooltipDuration | How long tooltip and copied state persist (milliseconds) | Number | 2000 |
Targets
Target | Description | Required |
---|---|---|
copyContent | The element shown when ready to copy (usually contains copy icon/text) | Optional |
copiedContent | The element shown when copy is successful (usually contains checkmark/success text) | Optional |
Data Attributes
Attribute | Description | Required |
---|---|---|
data-clipboard-text | The text content that will be copied to the user's clipboard when the button is clicked | Required |
Browser Support
- Modern Browsers: Uses Navigator Clipboard API for secure, async copying
- HTTPS Required: Clipboard API requires secure context (HTTPS or localhost)
- Error Handling: Graceful fallback with error messages for unsupported browsers
Accessibility Features
- Keyboard Accessible: Buttons are focusable and activate with Enter/Space keys
- Screen Reader Friendly: Clear visual state changes and meaningful button text
- Focus Management: Proper focus indicators and focus-visible styles