Playground
A small icon button that copies its value to the clipboard and confirms with a check mark, or says so when the browser refuses. Every code block on this site uses it.
Examples
Basic
<sb-button>Launch</sb-button>
<code><sb-button>Launch</sb-button></code>
<sb-copy-button value="<sb-button>Launch</sb-button>"></sb-copy-button>
Copy a Datastar signal
Bind value to a signal with data-attr, and the button always copies the current value.
<div data-signals:_coords="'51.4779° N, 0.0015° W'">
<span data-text="$_coords"></span>
<sb-copy-button data-attr:value="$_coords" label="Copy coordinates"></sb-copy-button>
</div>
React to copies
The sb-copy event bubbles out of the shadow root, so data-on works on any ancestor.
<div data-signals:_copies="0" data-on:sb-copy="$_copies++">
<sb-copy-button value="ignition"></sb-copy-button>
<span data-text="'Copied ' + $_copies + ' times'"></span>
</div>
When copying fails
Browsers can refuse the clipboard: outside a secure context (plain http://), in an iframe without the clipboard-write permission, or when the document isn't focused. The button then shows a cross and failed-label, and emits sb-copy-error with { value, error } (e.g. "NotAllowedError"), so a page can offer another way, like selecting the text:
<sb-copy-button value="go run ." data-on:sb-copy-error="console.warn('copy refused:', evt.detail.error)"></sb-copy-button>
Accessibility
The button's accessible name is label. The result ("Copied!" or "Copy failed") goes to a role="status" region next to the button, so screen readers announce it without moving focus. It sits outside the button on purpose: a button's children are presentational, so a live region inside one may never be read out. The same element is the visual tip (::part(tip)).
Installation
Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.
<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>
<div style="display: flex; align-items: center; gap: 8px"><code>go run .</code><sb-copy-button value="go run ."></sb-copy-button></div>
<!-- In production, pin today's catalog instead of the latest: the browser then
refuses any file that changed. Add "integrity" to the import map above: the
hashes from https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/importmap.json and Datastar's, below. -->
<!--
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" },
"integrity": { "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js": "sha384-vUxZojLrF1Ar3de5h7VINqhJXBgjyZS4U49pHvGa87kum6j5Xn6JRziuARNw5ELG", "…": "…from importmap.json" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/autoloader.js" integrity="sha384-IvSCUjEWPS+wwXunU+48x96KmyWDx/Y4LeCwCL8cWFqJ/zHmTsTOsSMrzTOcpW3D"></script>
-->
<!-- Or load just this component, pinned to this version: -->
<!-- <script type="module" src="https://starbase.zweiundeins.gmbh/c/copy-button@63dcadb0a39b/copy-button.js" integrity="sha384-V+FUHNRnsITpz/2n9wPk/L6C0xlDcKorzJ2ex/tT6K47XyQKZYYt+x5jpa++V9/D"></script> -->Size
Each file compressed on its own, the way it is served (gzip -9, brotli -11). Datastar and Rocket are shared by every component and not counted.
| File | Original | gzip | brotli |
|---|---|---|---|
copy-button.js | 5.0 kB | 2.0 kB | 1.7 kB |
API reference
Props
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | "" | The text copied to the clipboard. |
label | string | "Copy to clipboard" | Accessible label of the button. |
copied-label | string | "Copied!" | Shown and announced after copying. |
failed-label | string | "Copy failed" | Shown and announced when the browser refuses the clipboard (no secure context, permissions policy…). |
reset-ms | number | 1600 | How long the copied or failed state lasts, in ms. |
Events
| Name | Description |
|---|---|
sb-copy | After copying. detail: { value }. |
sb-copy-error | When the browser refuses the clipboard write. detail: { value, error } (the error's name, e.g. "NotAllowedError"). |