Demos / Design system

Built once, after the third time.

I don’t abstract until something shows up three times. The buttons, toggles and tags on this site hit three, so they became components.

Color

Every color is a CSS variable with a light and a dark value, and components only use the variables. Each swatch shows both: light on the left, dark on the right. In dark mode, raised things get lighter, so a card sits above the page. The printed résumé skips the variables and uses plain black on white.

  • --c-bgPage background
  • --c-surfaceTinted band
  • --c-cardCards, inputs
  • --c-borderHairlines
  • --c-inkBody text
  • --c-mutedSecondary text
  • --c-accentActions, focus
  • --c-accent-softHover wash

Button

Starts an action. With href it becomes a link, because anything that goes to another page should be one.

Used in: primary for Download PDF on the résumé; secondary for the Résumé link in the header and Build palette

Primary, secondary, secondary small: as used on the site
Loading (static, then one to click) and disabled

Props

PropTypeDefaultDescription
variant"secondary" | "primary""secondary"Primary is for the one main action on a page.
size"sm" | "md""md"sm for the header and toolbars, md next to other controls.
iconIcon name—Leading icon from the site's Icon set.
loadingbooleanfalseShows a spinner, ignores clicks, stays focusable.
disabledbooleanfalseNative disabled: skipped by Tab.
hrefstring—Renders a link instead of a button.
downloadstring—With href: the saved filename. Renders a plain <a>.

Why loading isn’t disabled

A disabled button drops focus, so a keyboard user who presses Save loses their place and their next Tab starts from the top of the page. Loading uses aria-disabled instead: the button keeps focus and ignores clicks until it’s done.

IconButton

A button with only an icon. It’s separate from Button because it has no visible text, so label is required.

Used in: theme toggle, menu, close menu, clear text

Ghost in three sizes, and outline

Props

PropTypeDefaultDescription
labelstringrequiredWhat the button does, read out by screen readers.
variant"ghost" | "outline""ghost"Outline gives it a frame when it sits alone.
size"sm" | "md" | "lg""md"28, 32 or 40px square.

SegmentedControl

Picks one of a few options, all visible at once. Each option is a button with aria-pressed, so a screen reader says which one is on.

Used in: the Web Worker and List virtualization demos

Click to switch

Props

PropTypeDefaultDescription
labelstring—Names the group for screen readers.
options{ id, label }[]—The choices, in order.
valuestring—The selected id.
onChange(id) => void—Called with the id that was pressed.
disabledboolean—Locks every option, e.g. while a demo runs.

Tag

A small label. Not a button, so it has no hover state. There’s one look, and only the size changes: small for topics on a card, medium for a list of skills you’re meant to read.

Used in: demo topics, skills

PerformanceReact
Small and medium

Props

PropTypeDefaultDescription
size"sm" | "md""sm"sm for metadata, md when the tags are the content.
aselement"span"Use "li" inside a list.

The mobile menu and the text box in the Web Worker demo are one-offs, so they live with the feature that uses them. If one shows up three times, it moves here.