Skip to content
ShineUI
Install

CopyButton

Copy a value, confirm at once.

<sui-copy-button>ButtonsStable

About

Copies value to the clipboard and confirms at once with a check glyph and, by default, the word "Copied". For reference numbers, keys and links beside the values they copy. label shows the value's name next to the glyph; the accessible name is "Copy" plus the label or the value.

Usage

<sui-copy-button label="INV-1043" value="INV-1043"></sui-copy-button> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-copy-button"); el.addEventListener("suiCopy", (e) => console.log(e.detail)); </script>
import { SuiCopyButton } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiCopyButton label="INV-1043" value="INV-1043" onSuiCopy={(e) => console.log(e.detail)} />; }
<script setup> import { SuiCopyButton } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiCopyButton label="INV-1043" value="INV-1043" @sui-copy="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiCopyButton } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiCopyButton], template: `<sui-copy-button label="INV-1043" value="INV-1043" (suiCopy)="onChange($event)"></sui-copy-button>`, }) export class Example { onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

ghost sits inline beside a value; outline stands alone in a toolbar. The accessible name is "Copy" plus the label, and a polite live region announces the confirmation.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-copy-button label="INV-1043" value="INV-1043"></sui-copy-button> <sui-copy-button value="https://example.com/share/k3v9q" variant="outline" label="Share link"></sui-copy-button> </div>

Sizes and states

copied is mutable, so the check state can be pinned or driven from outside. A disabled button is for a value that is not ready yet.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-copy-button label="Account" value="8841-0093"></sui-copy-button> <sui-copy-button label="Account" value="8841-0093" size="md" variant="outline"></sui-copy-button> <sui-copy-button label="Reference" value="REF-77" copied></sui-copy-button> <sui-copy-button label="Not ready" value="" disabled></sui-copy-button> </div>

Icon feedback

feedback="icon" confirms with the check glyph alone, for a tight table cell. copied-label changes the text and the announcement.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-copy-button label="INV-1043" value="INV-1043" feedback="icon"></sui-copy-button> <sui-copy-button value="4402" aria-label="Copy customer number" feedback="icon"></sui-copy-button> <sui-copy-button label="Payment link" value="https://example.com/pay/4402" variant="outline" copied-label="Link copied"></sui-copy-button> </div>

Reading from another element

from names an element id to read at press time instead of value: .property reads a property of it and [attribute] an attribute; bare, it takes the text. error-label is the text shown and announced when the clipboard refuses the write, in place of "Not copied".

INV-1043
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-input-text id="cb-total" label="Invoice total" value="$1,250.00" size="md"></sui-input-text> <sui-copy-button label="Copy total" from="cb-total.value" variant="outline"></sui-copy-button> <code id="cb-ref" data-ref="INV-1043" style="font:500 13px/1.6 var(--font-mono);color:var(--ink)">INV-1043</code> <sui-copy-button label="Copy reference" from="cb-ref[data-ref]" error-label="Clipboard blocked"></sui-copy-button> </div>

Events

suiCopy fires after the value reaches the clipboard, not on the press; a failed write shows the error label for a moment and fires suiCopyError. aria-label on the element replaces the derived name, so a key is not read aloud.

Not copied yet.
<div style="display:flex;gap:10px;align-items:center"> <sui-copy-button id="cb-live" label="API key" value="sk-live-9f3a2" variant="outline" aria-label="Copy API key"></sui-copy-button> <span id="cb-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Not copied yet.</span> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#cb-log'); const el = root.querySelector('#cb-live'); el.addEventListener('suiCopy', (e) => { log.textContent = `Copied ${e.detail.length} characters at ${new Date().toLocaleTimeString()}.`; }); el.addEventListener('suiCopyError', () => { log.textContent = 'The clipboard refused the write.'; }); }

Keyboard

KeyAction
TabLands on the button. A disabled button is skipped.
Enter SpaceCopies the value, shows the copied state for a moment, announces Copied through a polite live region and emits suiCopy; a blocked clipboard emits suiCopyError.

API

<sui-copy-button>

Properties

PropertyAttributeTypeDefaultDescription
copiedcopiedbooleanfalseCheck state. Set on press and cleared after 1.6 s; mutable so it can be pinned.
copiedLabelcopied-labelstringstring | undefinedConfirmation text shown while in the check state, and announced through a polite live region.
disableddisabledbooleanfalseBlocks the press and paints the disabled well, for a value that is not ready yet.
errorLabelerror-labelstringstring | undefinedText shown for 1.6 s when the write fails, and announced, so the person who pressed sees the refusal; suiCopyError still fires for a host that wants to offer more. Defaults to "Not copied".
feedbackfeedback"icon" | "text"'text'Success face: text (default) shows the check glyph plus copiedLabel; icon shows the check glyph only. The polite live region still announces copiedLabel in both modes.
fromfromstringstring | undefinedReads the text to copy from another element at press time instead of value: an element id, with .property to read a property of it (total.value) or [attribute] to read an attribute (total[data-amount]), and its text content when neither is named. Looked up in the document the button sits in, so an id inside a shadow root is out of reach.
labellabelstringstring | undefinedVisible name of the value, e.g. "INV-1043" or "Payment link".
sizesize"md" | "sm"'sm'Height: sm 28px (default, inline next to values), md 34px (toolbars). This is the one button in the library that defaults sm rather than md, because it is a tool beside a value rather than an action in a form, and a code pane header draws it at that size. Reflected.
valuevaluestringstring | undefinedText placed on the clipboard. An aria-label on the element replaces the derived "Copy ..." name: use it when the value is a URL, key or secret that should not be read aloud, and keep the visible label in it.
variantvariant"ghost" | "outline"'ghost'ghost (default, transparent) sits inline beside a value; outline (bordered on the surface) stands alone, e.g. in a toolbar. Reflected.

Events

EventDetailReactVueDescription
suiCopystringonSuiCopy@sui-copyFires after the value reaches the clipboard; detail is the copied text. The event and the check state follow the confirmed write, not the press. The write goes through navigator.clipboard.writeText, with a hidden textarea and execCommand('copy') as the fallback in insecure contexts.
suiCopyErrorstringonSuiCopyError@sui-copy-errorFires when the write failed. The button shows errorLabel for 1.6 s and announces it; the event is for a host that wants to offer its own recovery. Detail is the value that was not copied.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the button.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also