Product design skills

references/grammars-and-scales.md

A supporting file of the token-naming skill.

Grammars, tiers, scales and composites

Read at steps 2, 3 and 4 of token-naming. Which statements come from the source page and which are registry decisions is recorded in source-notes.md.

Three approaches the page names

The page lists these as "well-known approaches" and recommends none. Its own examples are in the third column.

ApproachSegment orderPage example
C-T-Icategory, type, itemcolor-background-primary
BEMblock, element, modifiercolor-background-primary
Functionalpractical use firstprimary-background-color, tab-color-text-hover

Notes:

  • C-T-I is the Style Dictionary convention; the segment order is fixed and the category comes first, so a sorted list groups by category.
  • BEM is a CSS class convention (.block__element--modifier). The page reuses the C-T-I example for it. In this registry ui-design-system applies BEM to component classes, not to tokens; treat it as a class grammar unless a project has deliberately adopted it for tokens.
  • Functional order puts the thing a reader is looking for first: the component or the role. It reads well in code but sorts badly.

The same four tokens in each order, to make the trade-off concrete:

TokenC-T-IFunctional
Page backgroundcolor-background-pagepage-background-color
Body textcolor-text-primarytext-primary-color
Button hover backgroundcolor-button-background-hoverbutton-background-color-hover
Small radiusradius-smsm-radius

Small radius is the tell: functional order makes scale steps awkward, which is why most systems use C-T-I order for scaled categories even when they put the component first for component tokens.

Grammars already declared in this registry

Each is a valid declared grammar. Do not merge them; pick the one that matches the project's existing tokens, or the one the project declares.

SkillGrammarExampleWhat primary means there
design-tokens{category}.{property}.{variant}-{state}, dotted, three tiers prefixed primitive, semantic, componentsemantic.text.primary, component.button.primary-bg-hoveraction.primary is the main affirmative action colour
better-colors--color-{role}-{variant}-{state}, CSS custom properties, two tiers (primitives by hue and step, semantics by role) plus an optional component tier for documented exceptions--color-text-secondary, --color-accent-solid-hover"the most prominent of its group"; the brand colour is accent
ui-design-system{category}-{property}-{variant}-{state}, hyphenatedcolor-primary-500, spacing-md, radius-lgthe brand ramp itself

The primary column is a real conflict between vendored skills. The resolution is procedural, not a preferred meaning: the project declares which meaning it uses in its token documentation, uses it everywhere, and when better-colors is in play its rule governs colour tokens.

Choosing a grammar

Answer three questions, then write the answer into the token docs.

  1. Who reads the names most? Developers reading code want the role first and short segments. Designers browsing a Figma variables panel want the category first, because the panel groups by the first segment.
  2. Which tool constrains characters? Figma variables use / as the group separator and allow spaces; CSS custom properties allow - and _ only; Style Dictionary and DTCG JSON nest objects and join with . on output. A slash path in Figma, a dotted path in JSON and a hyphen name in CSS are three renderings of one grammar when the segment order and words are identical. Declare the segments, then map separators per platform in the build (see token-build).
  3. Are custom properties consumed directly? If templates reference var(--…) by hand, the CSS rendering is the public name and must read well on its own. If a build emits them, optimise the source grammar.

Declare the result as one line in the docs, for example: {tier}.{category}.{role}.{variant}.{state}, dotted in source, hyphenated in CSS, slashed in Figma. Every token in the set follows it. A mixed set gets a migration with aliases, never a second grammar.

Tiers

The page lists six token types: raw value, primitive, semantic, component, computational, adaptive (light, dark). It defines none of them. The registry's working model, shared with design-tokens and better-colors:

  • Primitive names a value: the hue and step, the pixel size, the weight number. primitive.blue.600, --neutral-200. Never applied in a component.
  • Semantic names a job: semantic.text.primary, --color-bg-surface. Points at a primitive. Themes (light, dark, brand, density) swap values at this tier, which is what the page calls adaptive tokens.
  • Component names a slot on one component: component.button.bg-hover. Points at a semantic token. Add one only where a component diverges from the system on purpose; many component tokens mean the semantic tier is missing roles (the better-colors rule).

A raw value is what a primitive holds, not a tier of its own. A "computational" token (one derived by formula) is named on the page without definition; treat a derived value as a primitive whose $description records the formula.

Scale types

The page's scale panel, with its own labels:

ScalePage labelExample
Numeric, shortnumeric1 2 3 4 5
Numeric, hundredsnumeric100 200 300 400 500
T-shirtsizesxs s m l xl xxl
Emphasis wordsemphasislow medium high
Levelslevelslevel-1 level-2 level-3

Three further word sets appear on the page without a label. Their categories below are an interpretation, not the page's claim:

Word setRead here as
regular active heading strongemphasis or weight steps for text
sharp pill medium card small fullradius or shape names
instant slow medium fastduration names

Which scale for which category

Registry defaults, chosen to match the scales other skills here already use. A project with an existing scale keeps it; the rule is consistency, not this table.

CategoryDefault scaleWhy
Colour ramp stepsnumeric hundreds, 50 to 950Room to insert (150), matches design-tokens and Tailwind; better-colors maps these to roles
Spacingnumeric, either the pixel value (4, 8, 16) or hundreds (100, 200)Spacing is added to constantly; t-shirt runs out at 3xl
Sizing (controls, icons)t-shirt xs to xlFew steps, read by designers and developers alike
Radiusword set: none sm md lg full (pill as an alias of full if the project says so)Shape words carry meaning a number does not
Shadow or elevationlevels level-1 to level-n, or sm md lgOrdered, small, rarely extended
Motion durationword set instant fast normal moderate slow deliberateMatches motion-system; durations are chosen by feel, not by step
Font sizet-shirt with numeric extension (xs to 7xl) or hundredsMatches design-tokens' Major Third scale
Font weightword set regular medium semibold boldWeight names are conventional across tools
Z-indexlevels or role words (dropdown, modal, toast)Ordering matters more than magnitude
Opacitynumeric percent (10, 50, 90)The number is the meaning

Two rules that hold regardless of table

  • Siblings share one scale type. radius.sm, radius.2, radius.pill under one parent cannot be read without the definitions. If shape words are wanted, every sibling is a shape word.
  • Choose for room to grow. Where insertions are likely, use hundreds or t-shirt with 2xl, 3xl; 1 to 5 forces a renumber the first time a step is needed between 2 and 3.

Composite tokens

The page's anatomy, with the DTCG type each maps to:

CompositeParts on the pageDTCG $type
Text stylefont-size, weight, family, line-heighttypography
Bordercolor, width, styleborder
Gradientcolorsgradient (an array of stops)
Shadowcolor, blur, spread, directionshadow (offset x and y instead of direction)

The composite is named with the same grammar as its parts, at the semantic tier, and every part is an alias. A raw number inside a composite is a value that no theme can swap.

DTCG form, as design-tokens and token-build expect it:

{
  "semantic": {
    "typography": {
      "heading": {
        "$type": "typography",
        "$description": "Section headings. Parts alias primitives so themes can retune them.",
        "$value": {
          "fontFamily": "{primitive.font.family.sans}",
          "fontSize": "{primitive.font.size.700}",
          "fontWeight": "{primitive.font.weight.bold}",
          "lineHeight": "{primitive.font.lineHeight.tight}"
        }
      }
    }
  }
}

The playbook's own form, re-typed from the page (its snippet uses hyphenated flat names and is shown unclosed there):

"header-text-style": {
  "font-family": "{font-family-header}",
  "font-size": "{font-size-header}",
  "font-weight": "{font-weight-bold}"
}

Both say the same thing: a text style is a bundle of references, not a bundle of values.

On this page