Button
Anatomy
Calibration
Metrics
- Rendered height
- 00px
- Minimum inline × block
- 00px × 00px
- Padding block / inline
- 00px / 00px
- Label size / leading
- 00px / 00px
- Label weight
- 000
- Tracking
- 000000
- Radius
- 00px
- Icon box inline × block
- 00px × 00px
- SVG stroke value
- 0.00px computed
- Icon–label gap
- 00px
- Refresh
- Load + button resize
Target
40 reference
44 selected
States
Default
Hover
Pressed
Focus
Pending
Disabled
Variations
Label only
Leading icon
Trailing icon
Themes
Light
Dark
Primary override
Content
Examples
Failure → retry
Sustained pending
Capture: 0 · Bubble: 0
View code
"use client";
import { Button } from "@baseline/ui";
import { useEffect, useRef, useState } from "react";
import { flushSync } from "react-dom";
type CopyOperation = {
id: number;
phase: "idle" | "pending" | "success" | "error";
};
const pendingVisualDelay = 160;
function useDelayedOperationVisual() {
const timer = useRef<number | null>(null);
const [visualPhase, setVisualPhase] = useState<CopyOperation["phase"]>("idle");
useEffect(() => () => {
if (timer.current !== null) window.clearTimeout(timer.current);
}, []);
function begin() {
if (timer.current !== null) window.clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
timer.current = null;
setVisualPhase("pending");
}, pendingVisualDelay);
}
function settle(phase: "idle" | "success" | "error") {
if (timer.current !== null) window.clearTimeout(timer.current);
timer.current = null;
setVisualPhase(phase);
}
return { begin, settle, visualPhase };
}
export function CopyAction() {
const nextOperationId = useRef(0);
const [operation, setOperation] = useState<CopyOperation>({ id: 0, phase: "idle" });
const operationVisual = useDelayedOperationVisual();
async function copyExample() {
const id = ++nextOperationId.current;
flushSync(() => setOperation({ id, phase: "pending" }));
operationVisual.begin();
try {
await navigator.clipboard.writeText("Complete example");
setOperation({ id, phase: "success" });
operationVisual.settle("success");
} catch {
setOperation({ id, phase: "error" });
operationVisual.settle("error");
}
}
const status =
operation.phase === "pending"
? "Copying…"
: operation.phase === "success"
? "Copied to clipboard."
: operation.phase === "error"
? "Clipboard unavailable. Try again."
: "";
return (
<>
<Button
leadingIcon={
operationVisual.visualPhase === "pending"
? "pending"
: operationVisual.visualPhase === "success"
? "success"
: operationVisual.visualPhase === "error"
? "retry"
: "copy"
}
onClick={copyExample}
pending={operation.phase === "pending"}
>
Copy example
</Button>
<span
aria-live="polite"
className="bl-visually-hidden"
data-operation-id={operation.id}
data-phase={operation.phase}
role="status"
>
{operation.id > 0 ? (
<span key={operation.id + "-" + operation.phase}>{status}</span>
) : null}
</span>
</>
);
}