Primitive · action
Button
Four variants, three sizes. Primary carries the brand accent and elevation; secondary and ghost step down; destructive reads from the danger token. Radius, shadow, and weight all bend with the active shape.
Accessibility
- Renders a native <button>; use <a> only when it navigates.
- Focus ring uses --color-ring at --border-heavy, never the accent alone, so it stays visible on low-contrast accents.
- Disabled sets aria-disabled + reduced opacity; hit target stays ≥ 44px at md/lg.
- Label text contrast is guaranteed by the tuned --color-accent-fg per palette.
Token contract
// Button.tsx — variants/sizes read tokens; no hardcoded color or size.
import { cva, type VariantProps } from "class-variance-authority";
const button = cva(
"inline-flex items-center justify-center gap-[var(--space-2)] font-[var(--font-body)] font-semibold
rounded-[var(--radius-md)] border-[length:var(--border-default)] border-transparent
transition focus-visible:outline focus-visible:outline-[length:var(--border-heavy)]
focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-ring)]
disabled:opacity-50 disabled:pointer-events-none",
{
variants: {
variant: {
primary: "bg-[var(--color-accent)] text-[var(--color-accent-fg)] shadow-[var(--shadow-md)]",
secondary: "bg-[var(--color-surface)] text-[var(--color-fg)] border-[color:var(--color-border)]",
ghost: "bg-transparent text-[var(--color-fg)]",
destructive: "bg-[var(--color-danger)] text-white shadow-[var(--shadow-md)]",
},
size: {
sm: "text-[var(--text-xs)] px-[var(--space-3)] py-[var(--space-2)]",
md: "text-[var(--text-sm)] px-[var(--space-5)] py-[var(--space-3)]",
lg: "text-[var(--text-base)] px-[var(--space-6)] py-[var(--space-4)]",
},
},
defaultVariants: { variant: "primary", size: "md" },
}
);
type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof button>;
export function Button({ variant, size, className, ...props }: Props) {
return <button className={button({ variant, size, className })} {...props} />;
}
Primitive · navigation
Link
Inline default, low-key subtle, and a standalone call-to-action with an arrow. On low-contrast accents (yellow, champagne) the inline link auto-mixes toward the foreground so it stays readable in body copy.
Our team has handled matters across three states. You can read the full case history or browse recent results at your own pace.
Book a consultationAccessibility
- Always underlined inline (not color-only), so it is distinguishable without relying on hue.
- Focus ring via --color-ring; underline thickens on hover and focus.
- Standalone arrow is decorative (::after), not announced to screen readers.
Token contract
// Link.tsx
type Props = React.AnchorHTMLAttributes<HTMLAnchorElement> & { tone?: "default" | "subtle" | "standalone" };
export function Link({ tone = "default", className = "", children, ...props }: Props) {
const base = "rounded-[2px] underline-offset-[3px] focus-visible:outline focus-visible:outline-[color:var(--color-ring)]";
const tones = {
default: "text-[var(--color-accent)] underline decoration-[1.5px] hover:decoration-[2.5px]",
subtle: "text-[var(--color-fg)] underline decoration-[color:var(--color-border)] hover:decoration-[color:var(--color-fg)]",
standalone: "font-semibold no-underline inline-flex items-center gap-[var(--space-2)] after:content-['→']",
};
return <a className={`${base} ${tones[tone]} ${className}`} {...props}>{children}</a>;
}Primitive · status & metadata
Badge & Tag
Badges signal status (solid, soft, and per-status tints from the success/warning/danger tokens). Tags are quieter metadata chips in the mono font, optionally removable.
Accessibility
- Status is never color-only: pair with the label text and an optional dot.
- Soft tints are generated with color-mix against --color-bg, so they hold AA in every palette.
- Removable tag's × is a real button with an aria-label.
Token contract
// Badge.tsx
const tones = {
accent: "bg-[var(--color-accent)] text-[var(--color-accent-fg)]",
soft: "bg-[color-mix(in_srgb,var(--color-accent)_16%,var(--color-bg))] text-[color-mix(in_srgb,var(--color-accent)_70%,var(--color-fg))]",
success: "bg-[color-mix(in_srgb,var(--color-success)_16%,var(--color-bg))] text-[color-mix(in_srgb,var(--color-success)_72%,var(--color-fg))]",
warning: "bg-[color-mix(in_srgb,var(--color-warning)_18%,var(--color-bg))] text-[color-mix(in_srgb,var(--color-warning)_72%,var(--color-fg))]",
danger: "bg-[color-mix(in_srgb,var(--color-danger)_16%,var(--color-bg))] text-[color-mix(in_srgb,var(--color-danger)_72%,var(--color-fg))]",
};
export const Badge = ({ tone = "accent", ...p }) =>
<span className={`inline-flex items-center gap-[var(--space-1)] uppercase tracking-wide text-[var(--text-xs)] font-bold
px-[var(--space-3)] py-[var(--space-1)] rounded-[var(--radius-pill)] ${tones[tone]}`} {...p} />;Primitive · structure
Divider
Horizontal hairline, a heavy rule, a labelled divider for sectioning, and a vertical rule for inline groups. Width follows --border-thin / --border-default.
Thin
Heavy
Accessibility
- Decorative rules use <hr>; a labelled divider keeps its text in the flow for context.
- Vertical divider is a presentational <span> with a border, carrying no semantics.
Token contract
// Divider.tsx
export function Divider({ label, weight = "thin" }: { label?: string; weight?: "thin" | "heavy" }) {
if (label) return (
<div role="separator" className="flex items-center gap-[var(--space-4)] text-[var(--text-xs)] uppercase tracking-wide
text-[var(--color-muted)] font-[var(--font-mono)] before:flex-1 before:border-t before:border-[var(--color-border)]
after:flex-1 after:border-t after:border-[var(--color-border)]">{label}</div>
);
return <hr className={`border-0 border-t-[length:var(--border-${weight === "heavy" ? "default" : "thin"})]
border-[color:var(--color-border)] my-[var(--space-5)]`} />;
}Primitive · form
Input
Text field, textarea, and select, plus help text and an error state driven by the danger token. Labels, focus rings, and required markers are built in.
Accessibility
- Every field has a real <label for>; required uses a visible * plus the field's required attr.
- Errors set aria-invalid and pair the danger border with text, not color alone.
- Focus ring uses --color-ring; placeholder contrast comes from --color-muted.
Token contract
// Input.tsx
export function Input({ label, required, error, id, ...props }: InputProps) {
return (
<div className="flex flex-col gap-[var(--space-2)]">
<label htmlFor={id} className="text-[var(--text-sm)] font-semibold text-[var(--color-fg)]">
{label} {required && <span className="text-[var(--color-danger)]">*</span>}
</label>
<input id={id} required={required} aria-invalid={!!error}
className={`w-full font-[var(--font-body)] text-[var(--text-sm)] text-[var(--color-fg)]
bg-[var(--color-surface)] rounded-[var(--radius-md)] px-[var(--space-4)] py-[var(--space-3)]
border-[length:var(--border-default)] border-[color:${error ? "var(--color-danger)" : "var(--color-border)"}]
focus-visible:outline focus-visible:outline-[color:var(--color-ring)]`} {...props} />
{error && <span className="text-[var(--text-xs)] font-semibold text-[var(--color-danger)]">{error}</span>}
</div>
);
}Primitive · form
Checkbox & Radio
Custom-styled controls that wrap native inputs, so keyboard and screen-reader behavior is free. Checked state fills with the accent; the focus ring tracks the hidden input.
Accessibility
- The real <input> is visually hidden but focusable; the styled box is decorative.
- Focus ring renders on the box via input:focus-visible + .box, tracking keyboard focus only.
- Whole label is clickable; radios share a name for arrow-key grouping.
Token contract
// Checkbox.tsx — native input + styled box, label wraps both.
export function Checkbox({ label, hint, ...props }: CheckboxProps) {
return (
<label className="inline-flex items-start gap-[var(--space-3)] cursor-pointer text-[var(--text-sm)]">
<input type="checkbox" className="peer sr-only" {...props} />
<span className="grid place-items-center w-5 h-5 rounded-[var(--radius-sm)] bg-[var(--color-surface)]
border-[length:var(--border-default)] border-[color:var(--color-border)]
peer-checked:bg-[var(--color-accent)] peer-checked:border-[var(--color-accent)]
peer-focus-visible:outline peer-focus-visible:outline-[color:var(--color-ring)]">
<CheckIcon className="opacity-0 peer-checked:opacity-100 text-[var(--color-accent-fg)]" />
</span>
<span>{label}{hint && <small className="block text-[var(--color-muted)]">{hint}</small>}</span>
</label>
);
}Primitive · utility
Icon wrapper & Focus ring
A consistent container for icons in three tones, and the shared focus-ring utility every interactive primitive borrows so keyboard focus is unmistakable in any palette.
Accessibility
- Decorative icons are aria-hidden; an icon-only control needs an aria-label.
- The focus utility is outline-based (not box-shadow), so it survives high-contrast and forced-colors modes.
- One ring definition, every primitive: --border-heavy solid --color-ring, offset 2–3px.
Token contract
// focus-ring.ts — one utility, reused by every interactive primitive.
export const focusRing =
"focus-visible:outline focus-visible:outline-[length:var(--border-heavy)]
focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-ring)]";
// IconWrap.tsx
const tones = {
soft: "bg-[color-mix(in_srgb,var(--color-accent)_16%,var(--color-bg))] text-[var(--color-accent)]",
solid: "bg-[var(--color-accent)] text-[var(--color-accent-fg)]",
ghost: "border-[length:var(--border-default)] border-[color:var(--color-border)] text-[var(--color-fg)]",
};
export const IconWrap = ({ tone = "soft", children }) =>
<span className={`grid place-items-center w-10 h-10 rounded-[var(--radius-md)] ${tones[tone]}`}>{children}</span>;