A House Divided A House DividedDocumentation
Changelog
Engineering/Design System

Design System — Components

Last updated 2026-08-21
Source files

Companion to design-system.md. This document specifies the contracts for every primitive in src/components/ui/* — what the component does, what its props mean, and what the common misuses are.

If you need a primitive that isn't in this list, check first. If still missing, open an issue before rolling your own — most "new" primitives end up being a misuse of an existing one.

Index#


Button#

import { Plus } from "lucide-react";

<Button variant="primary" size="md">Run for office</Button>
<Button variant="secondary" size="sm">Cancel</Button>
<Button variant="ghost">Skip</Button>
<Button variant="destructive" isLoading>Resign</Button>
<Button variant="primary" iconOnly aria-label="Add"><Plus /></Button>

Props

Prop Type Default Purpose
variant "primary" | "secondary" | "ghost" | "destructive" "primary" Visual weight.
size "sm" | "md" | "lg" "md" Heights: 28 / 36 / 44px.
iconOnly boolean false Square button; requires aria-label.
isLoading boolean false Replaces children with a spinner; disables the button.
Standard <button> props onClick, disabled, type, etc.

When to use each variant

Size defaults

Do

Don't


Badge#

The Badge family covers status chips, vote tallies, count bubbles, bill-ID tags, and live indicators. All colors resolve through the token system's 10% fill / 30% border tint convention — no component hard-codes hex outside the colorMap table.

<Badge>#

<Badge color="success">Passed</Badge>
<Badge color="warning" dot>Pending vote</Badge>
<Badge color="primary" live>Live debate</Badge>
<Badge color="info" variant="outline">House</Badge>
<Badge color="secondary" variant="tag">HR-1234</Badge>
Prop Type Default Purpose
color "default" | "primary" | "secondary" | "success" | "warning" | "error" | "info" "default" Semantic color family.
variant "subtle" | "solid" | "outline" | "tag" "subtle" Render style.
dot boolean false Small colored dot prefix.
live boolean false Pulsing dot (overrides dot).

Variants

<LiveDot color="primary" />#

Standalone pulsing dot. Useful inline with text without the rest of the badge chrome.

<BadgeCount count={7} color="primary" />#

Compact numeric count chip — sits inline next to labels (e.g. inbox badges, notification counts).

<TallyBadge yea={218} nay={205} abstain={12} />#

Split chip for vote tallies. Always renders Y / N; abstain is optional.


Input#

Standard text input with consistent chrome. See the file directly for supported types, but the key rules:

Label#

Form-row label. Always associate via htmlFor. The eyebrow/all-caps look is for section labels, not form labels — use <SectionLabel> for those.


Full overlay modal. Honors escape-to-close, backdrop-click-to-close, scroll lock, and focus trap.

Usage

<Modal open={open} onClose={onClose} title="Resign from office">
  <p className="body">Resigning triggers a snap election in {stateName}.</p>
  <div className="flex justify-end gap-2 mt-4">
    <Button variant="secondary" onClick={onClose}>
      Cancel
    </Button>
    <Button variant="destructive" onClick={onConfirm}>
      Resign
    </Button>
  </div>
</Modal>

Do

Don't


Slider#

Styled range input. Used for policy positions, budget allocations, bet sizing.

Color variants: primary / warning / error / success / secondary / muted via ahd-slider-<color> classes on the root <input>.

Invariant: The slider thumb is always --foreground bordered in the active color. Thumb is the focus target. The track is 8px, the thumb is 22px — this ratio is intentional and should not be overridden per-use.


Toast#

Ephemeral notification. Fired via useToast() hook.

const toast = useToast();
toast.success("Bill submitted — resolves turn 47");
toast.error("Not enough funds — need $12,000 more");
toast.info("NPP voted against HR-2031");

Slides up from the bottom-right, auto-dismisses after 4s (error: 6s). Supports a click action for "undo" patterns.

Do

Don't


Tooltip#

Hover/focus tooltip. Positioned via Floating UI; auto-flips near viewport edges.

Invariant: Tooltips must be keyboard-accessible — wrap an element that can receive focus. Don't use tooltips as the sole carrier of critical information; they are supplementary.


Skeleton#

Shimmer placeholder used while data loads. Prefer Skeleton over spinners for page content — a spinner in a data-dense dashboard is uninformative.

Do

Don't


EmptyState#

Zero-data view for tables, lists, and dashboards. Props accept a title, description, and optional CTA.

Do


Loading states#

Three distinct patterns:


ResponsiveTable#

Table that reflows to cards on narrow viewports. Use it instead of hand-rolling a <table>.

Do

Don't


MobileSelect#

Native <select> on mobile; styled dropdown on desktop. Used for single-choice inputs with 5+ options.

Rationale: Native select is faster on touch devices and respects OS accessibility settings. The custom dropdown is only mounted on md: and up.


SectionLabel#

Eyebrow label with a 2px primary-color left accent. Used above section headings.

<SectionLabel>Current cabinet</SectionLabel>
<h2 className="h2 mt-1">Your appointees</h2>

Invariant: Always uppercase with tracking-caps (0.1em). This is the one place in the product where we override Sentence Case — eyebrows are uppercase by convention.


HeroStatsStrip#

Horizontal strip of key metrics shown under a page hero. Used on dashboard, state pages, and corporation pages.

Invariant: The hero image above it is always h-[175px] w-full sm:h-[220px]. Do not override — this is enforced across 14 surfaces.


Creating a new primitive#

If you're tempted to add to src/components/ui/:

  1. Prove the reuse. Two usages is coincidence; three is a pattern.
  2. Name from the job. BadgeCount, not NumberBubble.
  3. Consume tokens. No raw hex. No Tailwind-named colors (bg-red-500). Only semantic utilities (bg-primary, bg-error).
  4. Props describe state, not style. isLoading, disabled, open — not className="compact".
  5. Add to .
  6. Document it here with props table, usage examples, and common misuses.
  7. Add to the design-system skill at .claude/skills/ahd-design-system/SKILL.md if it's something an agent should reach for automatically.

Connected pages

References →
docsDesign System
← Referenced by
docsDesign System