Demo Type · 06

Component variants — one component, every flavor

Use this when a single component ships in several variants and sizes, and the reader needs to see them side by side and learn which to reach for.

Copyable exemplar. Lift the <section> blocks below into a lesson built from assets/lesson-template.html — keep the design tokens and the .tech-toggle pattern verbatim.

1

The big idea


A Button is one component, but it wears different outfits. A bold filled button shouts "do this"; a quiet outlined one says "maybe this"; a barely-there text button whispers "or this"; and a red one warns "careful." On top of that, each comes in three sizes. The matrix below lays out every outfit at once so you can compare them and pick the right one on purpose — not by accident.

Think of it like… traffic signs. The same sign shape carries different meaning by color and size: a big red STOP, a modest yield, a small advisory. You read intent at a glance — and you'd never paint a STOP sign green.

One component, two prop axes

The Button takes two orthogonal props: variant (primary | secondary | ghost | danger) drives color/emphasis, and size (sm | md | lg) drives padding and font-size. They compose: any variant works at any size, giving a 4×3 grid from a single CSS class set. Keeping the axes independent is what lets a design system stay small — you author 4 + 3 = 7 rules, not 12 bespoke buttons.

Accessibility & emphasis hierarchy

Exactly one primary per view is the rule of thumb: emphasis only reads as emphasis when it's scarce. danger never relies on red alone (it carries a verb like "Delete" and, ideally, a confirm step). Every variant keeps a visible :focus-visible ring for keyboard users, and color contrast is checked against the variant's own background, not the page.

2

The variant × size matrix


Read it like a grid: each row is a variant, each column is a size. The cell shows the real, live button at that combination — these are the same .btn elements the app renders.

Button — 4 variants × 3 sizes (live, rendered)
variant \ size sm md lg
primary
secondary
ghost
danger

Any cell = variant prop + size prop

The bottom-right cell of the table is just:

// React
<Button variant="danger" size="lg">Delete account</Button>

// rendered HTML
<button class="btn danger lg" type="button">Delete account</button>

Sizes change only padding and font-size; variants change only color and border. Because the two axes never touch, you can drop a new size="xl" without re-checking any variant.

3

Per-variant card — when to use which


The matrix shows what each looks like. These cards say when to use each one. Pick a size to preview every variant at that size; the matching row lights up.

primary

The one main action on the screen. Filled and loud so the eye lands on it first.

Avoid: two primaries fighting in one view.

secondary

The supporting action that sits beside primary — "Cancel", "Back". Outlined, quieter.

Avoid: using it for the action you actually want clicked.

ghost

The low-priority option — "Skip", toolbar icons, links-that-act. Almost invisible until hovered.

Avoid: on a critical or destructive action (too easy to miss).

danger

The destructive action — delete, remove, revoke. Red plus a clear verb plus a confirm step.

Avoid: red alone with vague copy like "OK".

One class set covers the whole grid

/* base + two independent axes */
.btn { font-weight:600; border-radius:8px; border:1.5px solid transparent; }

/* size axis */
.btn.sm { font-size:13px; padding:7px 13px; }
.btn.md { font-size:15px; padding:10px 18px; }
.btn.lg { font-size:17px; padding:13px 24px; }

/* variant axis */
.btn.primary   { background:#D97757; color:#fff; }
.btn.secondary { background:#fff; border-color:#D1CFC5; }
.btn.ghost     { background:transparent; color:#B85C3E; }
.btn.danger    { background:#B04A3F; color:#fff; }

The size picker above swaps only the size class on each demo button via JS — proof the axes are independent at runtime.