Skip to content

Copy button

Copy a value in one press and see whether it reached the clipboard.

INV-2040-0613

Invoice was sent to Acme Corp on 3 October.

<script setup lang="ts">import { UiCard, UiStack, UiCopyButton, UiField, UiButtonGroup, UiInput } from 'grani'</script> <template>  <UiCard title="INV-2040-0613" class="w-full max-w-md">    <UiStack>      <p class="text-sm text-fg-muted">        Invoice        <UiCopyButton          value="INV-2040-0613"          label="INV-2040-0613"          variant="inline"          ariaLabel="Copy INV-2040-0613"        />        was sent to Acme Corp on 3 October.      </p>      <UiField label="Share link">        <UiButtonGroup>          <UiInput model-value="https://example.test/share/41" readonly />          <UiCopyButton            value="https://example.test/share/41"            variant="outline"            ariaLabel="Copy the share link"          />        </UiButtonGroup>      </UiField>    </UiStack>  </UiCard></template>

Also called copy to clipboard, clipboard button.

Usage

<UiCopyButton :value="value" />

With GraniResolver in the Vite config, a template needs no import: set it up once.

API

Name · typeDefaultDescription
Behavior
value*stringWhat goes to the clipboard, whole. Empty, the button is off and says there is nothing to copy.
labelstringVisible text beside the glyph, and the name as it is ("Copy link"); for a value shown as itself pass ariaLabel too. It wins over truncate.
truncatenumberShows value cut in the middle to about this many characters, in monospace; the copy is still whole.
flushboolean= falsefalseIn a cell: the text starts on the column line, the button's own padding and border taken back. ghost only.
disabledReasonstringWhy it cannot copy now, while disabled: it stays in the Tab order and says the reason in a tooltip and as its description, as UiButton's. An empty value says "Nothing to copy" by itself.
toastboolean= falsefalseAlso say a success as a toast, where the button leaves the view once pressed; a failure always is one.
Look
variantinline | outline | ghost= "ghost""ghost"ghost is text alone, its glyph on hover, for an id in a table; outline is a framed button with its glyph always shown; inline is text inside a line of text, its size and ink, with a small glyph after it.
sizexs | sm | mdStep of the control ladder. Unset, the app's size (createGrani({ size })), sm by default.
iconComponentThe glyph at rest, a Lucide component; Copy when unset. Two copy buttons side by side tell what they copy by it: Code, Bot.
Form and access
disabledboolean= falsefalseNo copy; the button stays in place.
ariaLabelstringAccessible name; defaults to "Copy" for a glyph alone and "Copy <text>" for a truncated value.

Anything else lands on the root element: native attributes, aria-*, data-testid.

When to use another component

When you needTake
A glyph that does anything elseIcon button
An action with textButton
A value cut to fit with nothing to copyTruncate
A secret shown as text on the screenNever; it is copied with neither label nor truncate