Copy Button — <mv-copy-button>

Copy button with odometer feedback: icon and label roll up to a check (or drop down to an alert on failure) inside fixed windows, while a tinted wash fills the button and drains over the timeout as a countdown; icon buttons get a status tag.

CategoryMicro-interactions
TypeWeb Component (<mv-copy-button>)
Statusstable
Keywordscopy, clipboard, roll, odometer, success, feedback, code

When to use

Avoid when

Install

node scripts/add.mjs copy-button --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["copy-button"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/element.js, core/motion.js, components/copy-button/copy-button.js, components/copy-button/copy-button.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div style="display:grid;gap:1rem;width:100%;max-width:520px">
  <div style="display:flex;align-items:center;gap:.5rem;padding:.4rem .4rem .4rem .9rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-bg-subtle);font:500 .85rem/1 var(--mv-font-mono)">
    <span style="color:var(--mv-fg-subtle)">$</span>
    <code style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">node scripts/add.mjs confetti</code>
    <mv-copy-button value="node scripts/add.mjs confetti" data-variant="ghost" label="Copy command"></mv-copy-button>
  </div>

  <div style="position:relative;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-surface);overflow:hidden">
    <div style="display:flex;justify-content:space-between;align-items:center;padding:.4rem .5rem .4rem .9rem;border-bottom:1px solid var(--mv-border);font-size:.75rem;color:var(--mv-fg-muted)">
      <span>index.html</span>
      <mv-copy-button target="#mv-copy-demo-code">Copy code</mv-copy-button>
    </div>
    <pre style="margin:0;padding:.85rem 1rem;font:.8rem/1.6 var(--mv-font-mono);overflow:auto"><code id="mv-copy-demo-code">&lt;mv-like count="128"&gt;&lt;/mv-like&gt;
&lt;script type="module" src="like-button.js"&gt;&lt;/script&gt;</code></pre>
  </div>

  <div style="display:flex;flex-wrap:wrap;gap:.6rem;align-items:center">
    <input class="mv-input" id="mv-copy-demo-link" value="https://lumen.studio/invite/7QX2" readonly style="flex:1;min-width:200px">
    <mv-copy-button target="#mv-copy-demo-link" success-label="Link copied">
      <button class="mv-button" data-variant="outline">Copy link</button>
    </mv-copy-button>
    <mv-copy-button target="#mv-copy-demo-introuvable" label="Copy (missing target: error state)"></mv-copy-button>
  </div>
</div>

API

Attributes

NameTypeDefaultDescription
valuestringText to copy.
targetCSS selectorOtherwise: copies the value (field) or the text of the targeted element.
labelstringCopyAccessible name of the icon button.
success-labelstringCopied!Label / bubble / announcement on success.
error-labelstringCopy failedSame, on failure.
timeoutnumber2000Duration of the copied / error state (ms).
disabledbooleanDisables the button.
data-variantghostNo border or background (command bars, code blocks).
data-bubblebottomBubble below the button instead of above.
data-stateidle | copied | errorSet by the component (for styling).

Properties

NameTypeDescription
textstringText that will be copied (read-only).

Methods

NameDescription
copy()Copies and plays the feedback; Promise<boolean>.

Events

NameDescription
mv-copySuccess; detail = { text }.
mv-copy-errorFailure; detail = { error }.

Content structure

NameDescription
(text)Visible label; without text, an icon button with a bubble.
buttonOr your own child <button> (e.g. .mv-button): the icon and cross-fade are injected into it.

CSS classes

NameDescription
mv-copy-button-trigger / -icons / -reel / -slot / -label / -text / -wash / -bubbleGenerated parts (the wash and reel are aria-hidden).

CSS variables

NameDefaultDescription
--mv-copy-button-successvar(--mv-success)Check color.
--mv-copy-button-errorvar(--mv-danger)Error color.
--mv-copy-button-icon-size1.2emIcon size.

Accessibility

A real <button>; the icon button gets an aria-label (“Copy”). The result is announced by a polite role=status region (“Copied!” / “Copy failed”); alternate labels, icons, wash and tag are aria-hidden to avoid duplicates. Clipboard API with an execCommand fallback (insecure contexts); repeated copies restart the timer. Reduced motion: no roll or drain, the state swaps instantly and the tint stays static until reset.