You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/clipboard
dev a60d040282
Consolidate Soma provider runtimes
5 months ago
..
components remove backward-compat re-export shims 5 months ago
README.md morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired 6 months ago
clipboard-provider.svelte.ts Consolidate Soma provider runtimes 5 months ago
exports.ts morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired 6 months ago
index.ts morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired 6 months ago
langs.ts morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired 6 months ago
types.ts morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired 6 months ago

README.md

Clipboard

A navigator.clipboard.writeText wrapper with a copied state that auto-resets after a configurable timeout. Consumers compose a Trigger button (copy on click) and an optional Indicator that renders only while the copy is fresh.

navigator.clipboard is required — there is no execCommand('copy') fallback. 98%+ of browsers in 2026 support the async API, and the legacy API's synchronous-gesture requirement is incompatible with Svelte 5's microtask-based event handling.

Anatomy

<Clipboard.Provider value="npm install soma">
	<Clipboard.Trigger>Copy</Clipboard.Trigger>
	<Clipboard.Indicator>Copied!</Clipboard.Indicator>
</Clipboard.Provider>

Parts

Part Element Description
Provider <div> Root context. Holds value + copied + imperative copy().
Trigger <button> Copies on click. Announces data-copied for visual feedback.
Indicator <span> Renders only while copied=true (unless forceMount). Decorative.

Props

Provider

Prop Type Default Description
id string auto DOM id.
value string — Required. String to copy.
timeout number 2000 Ms to keep copied=true after a successful copy.
onCopy (value: string) => void — Fires on successful copy.
onError (err: unknown) => void — Fires when the clipboard API throws (permissions, blur, missing API).

Snippet props: { value, copied, copy }.

Trigger

Prop Type Default Description
id string auto DOM id.
aria-label string translated Copy to clipboard / Copied (swaps with state) Accessible name.

Indicator

Prop Type Default Description
id string auto DOM id.
forceMount boolean false Keep in DOM even when copied=false, for CSS exit transitions.

ARIA

Part Attribute Value
Trigger type button
Trigger aria-label Translated Copy to clipboard → Copied on transition
Trigger aria-live polite — announces the label change
Indicator aria-hidden true (decorative; the Trigger's label is the source of truth)

Data Attributes

Part Attribute Values
Provider data-clipboard Always present
Provider data-copied Present (empty string) while fresh
Trigger data-clipboard-trigger Always present
Trigger data-copied Present while fresh
Indicator data-clipboard-indicator Always present when rendered
Indicator data-copied Always present (Indicator only renders while copied)

Keyboard

Key Action
Enter / Space Clicks the Trigger → calls copy()

i18n

Key English Spanish
copy Copy to clipboard Copiar al portapapeles
copied Copied Copiado

Override per-instance via aria-label on Trigger.

Comparison

Feature Soma Radix Ark UI bits-ui
Dedicated component ✅ ❌¹ ✅ ❌
navigator.clipboard.writeText ✅ — ✅ —
Auto-reset copied flag ✅ — ✅ —
Configurable timeout ✅ — ✅ —
onCopy / onError callbacks ✅ — ✅ —
Indicator conditional mount ✅ — ✅ —
data-copied attr ✅ — ✅ —
aria-live on Trigger label ✅ — ❌ —
Translated label swap ✅ — ❌ —
execCommand fallback ❌² — ❌ —

¹ Radix does not ship a Clipboard primitive. Consumers roll their own navigator.clipboard call + useState(copied). ² Dropped by design — see the intro note. The legacy API requires a synchronous gesture path that Svelte 5 does not provide.

Usage

Button with state swap

<Clipboard.Provider value={code}>
	{#snippet children({ copied })}
		<Clipboard.Trigger>
			{copied ? '✓ Copied!' : 'Copy'}
		</Clipboard.Trigger>
	{/snippet}
</Clipboard.Provider>

Separate Indicator (icon swap)

<Clipboard.Provider value={code}>
	<Clipboard.Trigger>
		<CopyIcon />
	</Clipboard.Trigger>
	<Clipboard.Indicator>
		<CheckIcon />
	</Clipboard.Indicator>
</Clipboard.Provider>

Imperative copy (no button)

<Clipboard.Provider value={shareUrl}>
	{#snippet children({ copy, copied })}
		<p>Link copied automatically</p>
		<button onclick={() => copy()}>Share</button>
		{#if copied}<span role="status">Done!</span>{/if}
	{/snippet}
</Clipboard.Provider>

Styling with data-copied

[data-clipboard-trigger] {
	transition: background 150ms;
}
[data-clipboard-trigger][data-copied] {
	background: #dcfce7;
	color: #166534;
}

Error handling

<script>
	let error = $state<string | null>(null);
</script>

<Clipboard.Provider
	value={code}
	onError={(err) => {
		error = err instanceof Error ? err.message : 'Could not copy';
	}}
>
	<Clipboard.Trigger>Copy</Clipboard.Trigger>
</Clipboard.Provider>
{#if error}<p role="alert">{error}</p>{/if}

Permission denial (e.g. the browser blocks the API in insecure contexts) reaches onError; the Provider doesn't swallow it.

Powered by TurnKey Linux.