When a component family earns its own tokens

A design token is a named value for one design decision, such as a color or a spacing size. Most token systems have three layers. Primitive tokens hold raw values, for example one specific blue. Semantic tokens give a value a meaning, for example the background of a raised surface. Component tokens apply to one component family only, for example the background of a primary button.

The component layer is the layer that grows out of control. If every component gets its own tokens, one change in meaning must be made in many places. This post gives the rule that I use to decide when a component family gets its own tokens. The example is a recent design system for a data-heavy product console. In that system, four families got component tokens. All other components use semantic tokens.

Three test rule

A component family gets its own tokens only if one or more of these conditions is true:

  1. Many components share one pattern. For example, one set of tokens styles 15 or more components.
  2. Many states repeat across the family and must stay the same. These states are hover, active, focus, disabled, error, and selected.
  3. The family intentionally differs from the semantic defaults.

If none of these conditions are true, the components should only use semantic tokens directly.

Four component families that pass this test (IMO)

The table shows each family, the components that it covers, and the test that it passed.

Family Covers Example tokens Why it gets tokens
button Button, CopyButton, ExportCsvButton, ClearFiltersButton, and the action buttons in form footers and confirm dialogs button/primary/bg, bg-hover, bg-active, text, the same set for secondary, destructive, outline, and ghost, and button/radius The family has many variants, and each variant has many states. The original buttons had no hover, focus, or loading state. The component tokens define these states one time.
control Input, PasswordInput, Textarea, Select, SearchableSelect, SearchInput, TagInput, DateTimeInput, TimeInput, DateRangePicker, Checkbox, RadioGroup, Switch, RangeSlider, and other form controls control/bg, bg-disabled, border, border-hover, border-error, text, placeholder, height-sm, height-md One set of tokens styles about 18 components. Checkbox, Select, and RadioGroup had no hover state. One change in this family corrects all three.
table DataTable, FilterableTable, ReportDataTable, and the row components table/header-bg, row-hover, row-selected, border, cell-padding-x, cell-padding-y, row-height Dense data tables are a main screen in the product. The same row states occur in every table.
badge (size only) About 14 pills, chips, and badges, for example severity chips, role badges, and count badges badge/height, padding-x, radius, gap The tokens give all badges the same shape. The colors come from semantic tokens, so no badge variant has its own color token.

Severity and verdict were the most important additions. About 31 components used hard-coded colors, such as amber, emerald, rose, violet, and sky. Most of these colors showed a severity or a verdict. Two semantic groups for these meanings corrected those components. Component tokens cannot correct this problem, because the same meaning occurs in many families.

A note on Density mode

Density mode is a setting that makes the interface more compact or more comfortable. It changes the size of controls and tables, for example control heights and table cell padding. If a product needs density, give the Components collection two modes, Comfortable and Compact. The modes change only the sizing tokens. The colors stay in the Theme collection.

Rule for documentation

Write one rule in the usage documentation for the system. With the rule, designers and engineers can decide without a new review each time. This is the rule for this system:

💡

Components use semantic tokens from the Theme collection directly. Only button, control, table, and badge have component tokens. Do not add a new component family unless many components share its pattern, or it needs states that the semantic layer cannot express.