Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Clipboard copy and paste via data attributes. Add data-copy, data-copy-target, or data-paste-target to any button for clipboard functionality with visual and screen-reader feedback.
The data-copy attribute enhances native buttons with clipboard copy behavior. No wrapper element needed — just add the attribute directly to a <button>.
<button data-copy="npm install vanilla-breeze">Copy install command</button>
Add one of these attributes to any <button>:
data-copy — the attribute value is the text to copydata-copy-target — a CSS selector pointing to another element whose textContent will be copieddata-copy-target + data-copy-attr — copies the named attribute of the target instead of its textContentdata-paste-target — a CSS selector for an element to receive the clipboard text on clickOn click, the text is written to the clipboard, data-state="copied" is set for 1.5 seconds, a screen reader announcement fires, and a copy custom event is dispatched.
| Attribute | Type | Description |
|---|---|---|
data-copy |
string | Static text to copy to the clipboard. |
data-copy-target |
string | CSS selector for the element whose textContent to copy. |
data-copy-attr |
string | When paired with data-copy-target, copies the named attribute of the target instead of its textContent. Missing attributes copy an empty string. |
data-paste-target |
string | CSS selector for the element to receive the pasted clipboard text. Form controls get .value; other elements get .textContent. |
data-state |
string | Set to "copied" after a successful copy, or "pasted" after a successful paste. Held for 1.5s. Use in CSS for feedback styling. |
data-copy-init |
boolean | Set automatically to prevent double-binding. Do not set manually. |
Use data-copy-target with a CSS selector to copy text from another element on the page.
const greeting = "Hello, world!";
<pre><code id="code-block">const greeting = "Hello, world!";</code></pre> <button data-copy-target="#code-block">Copy code</button>
For state-bearing elements where the value you want is stored in an attribute rather than the text content, add data-copy-attr alongside data-copy-target. The button reads target.getAttribute(attrName) at click time.
<output id="swatch" value="#3366cc">#3366cc</output> <button data-copy-target="#swatch" data-copy-attr="value">Copy hex value</button>
Common targets:
<output value="..."> — computed values that don't appear as text<color-picker value="...">)Note: Form controls' live .value property does not reflect to the value attribute after user input. data-copy-attr="value" on an <input> copies the initial markup value, not what the user has typed. For live form state, use the JavaScript API.
Pair with <icon-wc> for a more visual button.
<button data-copy="https://vanilla-breeze.dev"> <icon-wc name="copy" size="sm"></icon-wc> Copy link</button>
The button dispatches a copy event on successful clipboard write.
| Event | Detail | Description |
|---|---|---|
copy |
{ text: string } |
Fired after text is copied. The detail.text property contains the copied string. |
paste |
{ text: string } |
Fired after text is pasted into the target. The detail.text property contains the pasted string. |
const btn = document.querySelector('[data-copy]'); btn.addEventListener('copy', (e) => { console.log('Copied:', e.detail.text);});
Position a copy button inside a code block:
npm install vanilla-breeze
<div style="position: relative;"> <pre><code id="snippet">npm install vanilla-breeze</code></pre> <button data-copy-target="#snippet" class="ghost small" style="position: absolute; top: 0.5rem; right: 0.5rem;"> <icon-wc name="copy" size="sm"></icon-wc> </button></div>
Quick share-by-link button:
<button data-copy="https://vanilla-breeze.dev/docs" class="secondary"> <icon-wc name="link" size="sm"></icon-wc> Share link</button>
The data-state="copied" attribute is set for 1.5 seconds after copying. Use it to style the copied state:
[data-copy][data-state="copied"],[data-copy-target][data-state="copied"] { color: var(--color-success);}
The default styles change the button text color to --color-success during the copied state.
For components that need to copy computed strings (formatted output, JSON, CSS), import copyText() from the same module. It performs the clipboard write with the same visual feedback, screen-reader announcement, and copy event dispatch as the attribute pattern.
import { copyText } from '/src/utils/copy-init.js'; // Minimal — text only, announces to document.bodyawait copyText('hello'); // With a button — applies data-state="copied" and dispatches the copy eventawait copyText(generateCSS(), { button: myButton }); // Custom announcement + durationawait copyText(json, { button: myButton, announceMessage: 'Settings exported', duration: 2000});
| Option | Type | Default | Description |
|---|---|---|---|
button |
HTMLElement | — | Receives data-state="copied" and dispatches the copy event. Omit for "fire and forget" copies. |
announceMessage |
string | "Copied to clipboard" |
Screen-reader announcement text. |
duration |
number | 1500 |
Milliseconds to hold data-state="copied". |
Returns Promise<boolean> — resolves to true on success, false if the Clipboard API is unavailable or permission is denied. Never throws.
The symmetric counterpart to data-copy-target. Add data-paste-target="<selector>" to a button to read the clipboard and write it into another element on click. Form controls (input, textarea, select) receive .value; other elements receive .textContent. An input event is dispatched on the target so framework listeners notice the change.
<input id="dest" type="text"> <button data-paste-target="#dest">Paste</button>
On success, data-state="pasted" is set for 1.5s, the screen reader hears "Pasted from clipboard", and a paste CustomEvent fires from the button with detail.text. Failures (permission denied, no clipboard text) are silent.
Most browsers prompt the user the first time a page calls navigator.clipboard.readText(). The button stays inert if the user denies permission.
import { pasteFromClipboard } from '/src/utils/copy-init.js'; // Read clipboard into an elementconst text = await pasteFromClipboard(document.querySelector('#dest'), { button: myButton,}); // Or read without writing anywhere — just inspect/transformconst raw = await pasteFromClipboard(null);if (raw?.startsWith('http')) { /* ... */ }
Returns Promise<string | null> — the pasted text on success, null on failure. Never throws.
For payloads richer than text — copying a color as both hex and a swatch image, or a QR code as both the URL and a PNG — import copyRich(). It writes a ClipboardItem with any combination of text/plain, text/html, and a binary Blob (typically image/png). When the browser doesn't support ClipboardItem or rejects a specific MIME type, it transparently falls back to text-only via copyText().
import { copyRich } from '/src/utils/copy-init.js'; // Text + HTML — pastes as styled into rich-text targets, plain into code editorsawait copyRich({ text: 'Vanilla Breeze', html: '<strong>Vanilla Breeze</strong>',}, { button: myButton }); // Text + image (e.g. a color swatch)const blob = await new Promise(r => canvas.toBlob(r, 'image/png'));await copyRich({ text: '#3366cc', blob,}, { button: myButton, announceMessage: 'Color copied as text + image' });
Receiving apps choose the format they understand. Pasting into Slack or Figma uses the image; pasting into a code editor uses the text. The screen-reader announcement, data-state="copied" hook, and copy event behave exactly as with copyText().
VB's <qr-code> uses copyRich() internally via its copy() method.
Buttons added to the DOM after page load are automatically enhanced via a MutationObserver. No manual initialization is needed.
<section> <h2>Accessibility</h2> <ul> <li>Uses <code>aria-live="polite"</code> to announce "Copied to clipboard" to screen readers</li> <li>The announcement element is visually hidden with <code>sr-only</code> and removed after 1 second</li> <li>Works with any focusable button element, preserving keyboard accessibility</li> <li>No wrapper element — the button <em>is</em> the interactive element, improving the accessibility tree</li> <li>Falls gracefully when the Clipboard API is unavailable or permission is denied</li> </ul> </section>