Part 04 of 14 · · 3 min read
Building a component library, one Button at a time
How to build a design system component: audit, anatomy, variants and every state, a documentation template, API rules shared by Figma and code, and Storybook.
A good component is more than the right look: its anatomy, states, API, behaviour and documentation are all complete. A button that looks right but has no focus style, no loading state and a different prop name in code is a source of bugs, not a shared part.
This part follows one component, the Button, from audit to release. The same process works for any component.
The process
- Audit. Collect every version of the component that already exists in the product, as screenshots on one page. Brad Frost calls this an interface inventory. It often turns up many more button styles than anyone expected.
- Requirements. Agree with engineers and product managers which use cases the component has to cover.
- Anatomy. Name each part of the component.
- Variants and states. Design every form and every state.
- Accessibility check. Contrast, focus, keyboard use and screen reader labels.
- API alignment. Give Figma properties and code props the same names.
- Document, review, publish and log the change.
Button anatomy
text
┌──────────────────────────────────┐
│ [icon] Label text [icon] │ ← container: radius.md, padding
└──────────────────────────────────┘
① leading ② label ③ trailingNaming the parts gives designers, engineers and the documentation one vocabulary. “The leading icon is too close to the label” is a clear bug report; “the thing on the left looks off” is not.
Variants and properties
| Property | Values | Code prop |
|---|---|---|
| Variant | primary, secondary, ghost, danger | variant |
| Size | sm (32), md (40), lg (48) | size |
| State | default, hover, pressed, focus, disabled, loading | CSS pseudo-classes, disabled, loading |
| Leading icon | boolean plus instance swap | iconLeft |
| Full width | boolean | block |
Note that most states are not props. Hover, pressed and focus come from the browser through CSS; only disabled and loading are set by the developer.
Don’t skip a state
- Default.
- Hover, for mouse users.
- Pressed (active).
- Focus-visible, for keyboard users, for example a 2px outline with a 2px offset.
- Disabled.
- Loading: a spinner replaces or joins the label, the width stays the same so the layout doesn’t jump, and repeat clicks are blocked.
Other kinds of component bring their own states:
| Component type | Extra states |
|---|---|
| Form fields | Filled, error, success, read-only |
| Data views | Empty, loading or skeleton, error, partial data, overflow (very long text) |
A documentation template
Give every component page the same sections, so people know where to look:
- Overview: one line on what it does.
- When to use, and when not to. For example: use a danger button for a destructive action; use a link, not a button, for navigation.
- Anatomy.
- Variants and sizes, and when to pick each.
- States.
- Behaviour: keyboard interaction, responsive behaviour, text overflow.
- Do and don’t, with images.
- Accessibility: role, ARIA attributes, contrast, focus order.
- Content: for a button label, start with a verb, use sentence case and keep it to a few words.
- Code: the props table and a link to Storybook.
- Related components.
Rules for the component API
- One name in both places. If the Figma property is
variant=secondary, the code prop isvariant="secondary". - Small and composable beats one giant component. A card with thirty props is hard to learn;
Card,CardHeaderandCardBodythat fit together are easy. - Sensible defaults. With no props at all, the component should still look and behave correctly.
- An escape hatch, documented. Allow a custom class or slot for the rare case the system doesn’t cover, and explain why it should stay rare.
html
<!-- Defaults cover the common case -->
<Button>Save</Button>
<!-- Props match the Figma properties -->
<Button variant="danger" size="sm" loading>Delete project</Button>Storybook, the library on the code side
Developers usually show each component in isolation in Storybook, with every variant as its own story. For a designer it is the place to check the built component against the Figma one, side by side, state by state. Tools such as Chromatic add visual regression tests, so an unintended change to a component is caught before release.
Try this
Run a small interface inventory. Take screenshots of every button in one product you use, paste them on a single page and group the ones that are really the same. Then write the variants table for the button that should replace them all.
Next in the series
A finished component still has to stay finished as teams ask for changes. Part 5 covers governance: team models, how contributions get in, versioning and changelogs, and how to measure whether the system is actually used.
Sources and further reading
See a design system in practice: the AppsCode Design System case study