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/[email protected]/+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