Clipboard Button
The <x-wirekit:: component copies a value to the clipboard on click and temporarily shows a "Copied!" confirmation. It uses the Clipboard API and announces the copy action to screen readers via a live region.
Usage
Inline with a code snippet:
<x-wirekit::row justify="between" style="background: var(--color-wk-bg-muted); border-radius: var(--radius-wk-lg); padding: var(--padding-wk-y-sm) var(--padding-wk-x-md);">
<code style="font-family: var(--font-wk-mono); font-size: var(--text-wk-sm);">composer require pushery/wirekit</code>
<x-wirekit::clipboard-button value="composer require pushery/wirekit">
Copy
</x-wirekit::clipboard-button>
</x-wirekit::row>
Custom Confirmation Text
Change the text shown after copying:
Custom Duration
Control how long the "Copied!" state persists (in milliseconds):
Copying Dynamic Values
Use Blade expressions to copy dynamic content:
<x-wirekit::clipboard-button :value="$user->api_key">
Copy API Key
</x-wirekit::clipboard-button>
Code Block with Copy
Combine with other components to create a copyable code snippet:
composer require pushery/wirekit
Visual Behavior
- Default state — shows a clipboard icon + your slot content (e.g., "Copy Link")
- After click — icon swaps to a green checkmark, label changes to
copiedText - After duration — reverts to the default state automatically
The icon swap is handled by Alpine's x-show directives. The checkmark icon uses --color-wk-success-text for the green color.
When the browser refuses the write, the button takes the confirmation back and shows the refusal: a warning icon replaces the check, and a labeled button reads Copy failed in place of its label, both for the same duration. A screen reader hears the refusal as an alert.
Requirements
The clipboard button uses the Clipboard API (navigator.), which requires:
- HTTPS or localhost — the Clipboard API is only available in secure contexts
- User gesture — the copy happens inside a click handler, which satisfies the browser's user activation requirement
If neither condition is met, the copy fails, and the button shows it the way it shows a refused write — see Visual Behavior. All modern browsers (Chrome, Firefox, Safari, Edge) support the Clipboard API.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value |
string |
'' |
The text to copy to the clipboard |
copiedText |
string |
'Copied' |
Label shown temporarily after copying. Translated through the wirekit::Copied catalog key, so overriding it in lang/vendor/wirekit/ reaches every locale at once |
duration |
int |
2000 |
How long (ms) the "Copied!" state persists before reverting |
iconOnly |
bool |
false |
Render a bare icon-only button (no border/label) — gray glyph that pops green on copy. Requires aria-label. |
scope |
string|null |
null |
Scoped personalization key |
Events
The button dispatches wirekit:copied once the write has succeeded or been refused. That is later than the label, which changes at once: event. is the result, not the guess. The event bubbles, its target is the button, and every copy dispatches it, a second one with the same result as well. The code block's copy button dispatches the same event.
document.addEventListener('wirekit:copied', (event) => {
countCopy(event.target, event.detail.succeeded);
});
Accessibility
- The button is a native
<button type="button">— fully keyboard-accessible - Icons:
aria-hidden="true"on both SVGs (decorative — the text label is the accessible name) - Live region:
role="status"+aria-live="polite"announces "Copied to clipboard" to screen readers when the copy succeeds - Focus ring visible via
focus-visible:ring
The slot content (e.g., "Copy Link") serves as the button's accessible name. If you need a different accessible name, pass an aria-label attribute.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move focus to the button |
Enter / Space |
Activate the button |
Pitfalls
- Don't bind
wire:clickon the button — use the component's built-in:valueprop. The Alpine handler writes via the Clipboard API; awire:clickserver roundtrip would race the clipboard write.
Design Tokens
| Token | Used for |
|---|---|
--font-wk-sans |
Button font family |
--font-wk-body-weight |
Font weight |
--text-wk-md |
Font size |
--color-wk-text |
Default label text |
--color-wk-success-text |
Copied checkmark color |
--color-wk-bg-elevated |
Button background |
--color-wk-bg-hover |
Hover background |
--color-wk-border |
Button border |
--color-wk-ring |
Focus ring |
--ring-wk-width |
Focus ring width |
--border-wk-width |
Border width |
--radius-wk-md |
Border radius |
--shadow-wk-sm |
Subtle shadow |
--gap-wk-sm |
Icon-to-label gap |
--padding-wk-x-md / --padding-wk-y-sm |
Button padding |
--transition-wk-duration / --transition-wk-easing |
Hover / icon-swap transition |
Personalization
Override classes globally via WireKit::. Pass a closure, not a string — a
closure receives the shipped block and you add to it; a string replaces it outright, and the
shipped block is where focus-visible:ring-[var(--color-wk-ring)] lives:
use Pushery\WireKit\WireKit;
WireKit::personalize('clipboard-button', [
// The vendor default arrives as $vendor. Append, and the focus ring, the hover
// surface and the transition tokens all survive the change.
'base' => fn (string $vendor): string => $vendor
.' rounded-[var(--radius-wk-full)] px-[var(--padding-wk-x-lg)]',
]);
A string override is legitimate and costs more than it looks. It makes the block yours from that moment on, so every later improvement to it stops arriving — and nothing reports that, because nothing is wrong. See Class overrides via config for both forms.
Further Reading
- MDN: Clipboard API — the browser API used for copying
- MDN:
navigator.— the specific method calledclipboard. writeText() - MDN:
role="status"— live region role for the copy announcement - Can I Use: Clipboard API — browser support table