Button
Compact button with 6 variants (indigo primary with inner highlight, ink secondary, outline, ghost, danger, link), 4 sizes, icons, keyboard-shortcut hint, loading state and link mode.
minimal/ui/buttonSource
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from "react";
import { cn } from "@/lib/utils";
export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "danger" | "link";
export type ButtonSize = "sm" | "md" | "lg" | "icon";
interface BaseProps {
variant?: ButtonVariant;
size?: ButtonSize;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
/** Keyboard hint rendered at the end, e.g. "⌘↵". Decorative only. */
shortcut?: string;
loading?: boolean;
className?: string;
children?: ReactNode;
}
export type ButtonProps =
| (BaseProps & Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps> & { href?: undefined })
| (BaseProps & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseProps> & { href: string });
const VARIANTS: Record<ButtonVariant, string> = {
primary: "bg-da-primary text-da-primary-fg hover:bg-da-primary/90 [box-shadow:inset_0_1px_0_rgb(255_255_255/0.12),var(--da-shadow-sm)]",
secondary: "bg-da-fg text-da-bg hover:bg-da-fg/85",
outline: "da-stroke bg-da-surface text-da-surface-fg shadow-da-sm hover:bg-da-muted",
ghost: "text-da-muted-fg hover:bg-da-fg/5 hover:text-da-fg",
danger: "bg-da-danger text-da-danger-fg shadow-da-sm hover:bg-da-danger/90",
link: "h-auto px-0 text-da-fg underline-offset-4 hover:underline",
};
const SIZES: Record<ButtonSize, string> = {
sm: "h-7 gap-1.5 rounded-da-sm px-2.5 text-[13px] [&_svg]:size-3.5",
md: "h-9 gap-2 rounded-da-md px-3.5 text-sm [&_svg]:size-4",
lg: "h-11 gap-2 rounded-da-md px-5 text-[15px] [&_svg]:size-4",
icon: "size-9 rounded-da-md [&_svg]:size-4",
};
export function buttonClasses({ variant = "primary", size = "md", className }: { variant?: ButtonVariant; size?: ButtonSize; className?: string } = {}): string {
return cn(
"da-focus da-transition inline-flex shrink-0 cursor-pointer items-center justify-center font-medium whitespace-nowrap select-none active:scale-[0.98]",
"disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
SIZES[size],
VARIANTS[variant],
className,
);
}
function Spinner() {
return (
<svg aria-hidden viewBox="0 0 16 16" fill="none" className="animate-spin motion-reduce:animate-none">
<circle cx="8" cy="8" r="6" stroke="currentColor" strokeOpacity="0.25" strokeWidth="2" />
<path d="M14 8a6 6 0 0 0-6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}
export function Button(props: ButtonProps) {
const { variant, size, leftIcon, rightIcon, shortcut, loading = false, className, children, ...rest } = props;
const classes = buttonClasses({ variant, size, className });
const content = (
<>
{loading ? <Spinner /> : leftIcon}
{children}
{rightIcon}
{shortcut && (
<kbd aria-hidden className="-mr-1 ml-0.5 rounded-[4px] bg-current/15 px-1 font-da-mono text-[10px] leading-4 opacity-80">
{shortcut}
</kbd>
)}
</>
);
if (rest.href !== undefined) {
return (
<a {...(rest as AnchorHTMLAttributes<HTMLAnchorElement>)} className={classes} aria-disabled={loading || undefined}>
{content}
</a>
);
}
const { type = "button", disabled, ...buttonProps } = rest as ButtonHTMLAttributes<HTMLButtonElement>;
return (
<button {...buttonProps} type={type} disabled={disabled || loading} aria-busy={loading || undefined} className={classes}>
{content}
</button>
);
}
export default Button;
modules/minimal/ui/button/index.tsx
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "primary" | "secondary" | "outline" | "ghost" | "danger" | "link" | "primary" | Visual style. |
| size | "sm" | "md" | "lg" | "icon" | "md" | Size preset; `icon` is square — pass aria-label. |
| href | string | — | Renders an <a> instead of a <button>. |
| leftIcon / rightIcon | ReactNode | — | Icons around the label (left icon becomes a spinner while loading). |
| shortcut | string | — | Decorative keyboard hint, e.g. "⌘↵". |
| loading | boolean | false | Spinner + disabled + aria-busy. |
| ...rest | ButtonHTMLAttributes | AnchorHTMLAttributes | — | Native attributes. |
Other button variants in Minimal
Loading Button
Async action button that morphs through pending (spinner), success (green check) and error (red cross) states, then resets; all labels share one grid cell so the width never jumps.
Shortcut Button
Button displaying its keyboard shortcut as keycaps; pressing the shortcut anywhere on the page clicks it (ignored while typing in a field). Primary, secondary and ghost styles.
Toggle Group
Single-choice segmented toolbar on a muted track with a raised active segment (Radix ToggleGroup): text, icon-only or mixed options, two sizes, controlled or uncontrolled.
Button in other art directions
Button
Pressable button with 6 variants (primary, secondary, ink, outline, ghost, danger), 4 sizes, icons, loading state and link mode (href). Hard shadow collapses on hover/press.
Copy Button
Code field with an attached yellow copy button that flips to green 'Copied' with a check; optional secret masking for API keys and custom display text for commands.
Segmented Control
Ruled segmented control for mutually exclusive options: mono uppercase segments split by strokes, ink selected segment, yellow hover. Controlled or uncontrolled, typed values.
Split Button
Yellow primary action glued to a chevron trigger that opens a ruled dropdown of secondary actions with descriptions (Radix DropdownMenu). Both halves flip to ink on hover.
Button
Pill button with 5 variants (primary→accent gradient, frosted glass, solid ink, ghost, danger), 4 sizes, icons, loading state and link mode. Lifts on hover with a slight overshoot.
Icon Button
Round frosted icon button with a tooltip; optional toggle mode (aria-pressed) that swaps the icon and fills with the gradient or danger color — ideal for call controls.
Record Button
Record toggle pill: red dot inside a soft halo that morphs into a stop square with a pulsing ring and a running mm:ss timer while recording.
Shine Button
Gradient pill button with a glossy top highlight and a bright light sweep crossing it on hover or keyboard focus (CSS only); renders an anchor when href is set. Two sizes.
Arrow Link
Text link with an ↗ that nudges up-right on hover and an ink underline that wipes in from the left; optional mono index prefix; scales from inline to display size.
Button
Square monochrome button: ink primary, outlined (inverts on hover), ghost and underline (signal-colored underline) variants; three sizes and an optional trailing glyph.
Icon Button
Square icon-only button with a hairline border (darkens on hover), ghost or solid ink variant; required label for aria-label and title.
Text Toggle
Typographic toggle: options are plain mono words separated by slashes; the active one is ink with a signal underline, others muted — radio semantics with arrow keys.
Button
Corporate button: 6px radius, five variants (blue primary, bordered white secondary, ghost, danger, link), three sizes, leading/trailing icons.
Button Group
Attached segmented button group with shared borders (radio semantics) — for period pickers and view switchers. Selected segment gets a surface-2 fill and bold label.
Icon Button
Square icon-only button (secondary, ghost, primary, danger) with required aria-label, title tooltip and optional count/dot badge.
Loading Button
Async action button: idle → spinner + “Processing…” (aria-busy, width locked) → green check “Sent” for 2s → idle. Default simulates a 1.4s payment run.
Arrow Button
Link-button with a round arrow badge at its end: on hover the badge grows and the arrow turns from ↗ to →, a signature call-to-action for the DA.
Button
Soft pill button: sage primary, sage-tint soft, terracotta clay, outlined ink and ghost variants; three sizes, icons, gentle hover.
Icon Button
Round icon-only button in soft sage, sage, cream (with shadow) or ghost; required label used for aria-label and title.
Segmented
Pill segmented control on a sand track: the selected pill is cream with a soft shadow and slides with a sliding indicator; radio semantics with arrow keys.
Button
Friendly pill button: six variants (periwinkle primary, soft lavender, outline, ghost, danger, ink), three sizes, left/right icons, loading spinner and link mode, with a tiny bounce on hover.
Fab
Floating action button: a big round periwinkle “+” that rotates into “×” and fans out labelled pastel speed-dial actions with a staggered bounce; closes on Escape or outside click.
Icon Button
Round pastel icon button in six tones (neutral, peach, mint, lavender, butter, primary) and three sizes, with optional red counter bubble and a springy press.
Toggle Chips
Row of pill toggle chips with emoji; selected chips fill periwinkle and show a popping check. Multiple or single selection, controlled or not.