Example Components
Copy Button
Copies a text value to the clipboard and announces the result to screen readers. Each instance copies its own value.
Usage
text is the exact text to copy. label is the button text and defaults to "Copy".
true
examples/components/copy-button/copy-button.html
html
<copy-button data-bascik-prop-text="npm create bascik@latest" data-bascik-prop-label="Copy command" /> html examples/components/copy-button/copy-button.html
<!-- @bascik
Copies a text value to the clipboard and announces the result to screen readers.
@prop text - The exact text to copy.
@prop label - Button text. Defaults to "Copy".
-->
<style>
.copy-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.button {
padding: 0.25rem 0.75rem;
border: 1px solid var(--color-border, color-mix(in srgb, currentColor 30%, transparent));
border-radius: var(--radius, 0.5rem);
background: var(--color-surface, color-mix(in srgb, currentColor 8%, transparent));
color: inherit;
font: inherit;
cursor: pointer;
}
.button:hover {
border-color: var(--color-accent, currentColor);
}
.status {
color: var(--color-muted, inherit);
font-size: 0.875em;
}
</style>
<span class="copy-button">
<button class="button" id="button" type="button" data-bascik-attr-data-text="text">
<span data-bascik-prop-label>Copy</span>
</button>
<span class="status" id="status" role="status"></span>
</span>
<script>
const button = document.getElementById('button');
const status = document.getElementById('status');
let timer;
button.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(button.dataset.text ?? '');
status.textContent = 'Copied';
} catch {
// Clipboard access needs a secure context (HTTPS or localhost) and can be denied.
status.textContent = 'Copy failed. Select the text and copy it manually.';
}
clearTimeout(timer);
timer = setTimeout(() => {
status.textContent = '';
}, 2000);
});
</script> Output is shown unminified (HTML, CSS, JS, and identifier names) for readability.
How It Works
data-bascik-attr-data-text="text"binds thetextprop to the button'sdata-textattribute, and the script readsbutton.dataset.text. The value travels with each instance, so there is no shared state.navigator.clipboard.writeText()needs a secure context (HTTPS orlocalhost) and can be denied. Both outcomes are written to arole="status"element, which screen readers announce, and cleared after two seconds.
Adapt It
- To copy the text of a code block, read
textContentfrom an element the component finds by ID instead of a prop. - Show the result as an icon change instead of text, but keep the
role="status"message for screen readers.