Prop Axes
The four shared prop names — size, status, variant, appearance — that any BDS component with overlapping visual concerns must use.
A prop is an axis when more than one component exposes it. Axis names belong to the system, not any single component. When adding a new pill, chip, or indicator, reuse the axis names below — never invent kind, style, or flavor.
The four axes
| Axis | Values | Meaning | Components |
|---|---|---|---|
size | xs · sm · md · lg (indicators); tiny · sm · md · lg · xl (buttons) | Physical dimensions on a shared scale | Badge, Tag, Chip, Button, IconButton, ServiceTag, Field, Sheet, most form controls |
status | positive · warning · error · info · progress · brand | Semantic value judgment — which system color tier applies | Badge, Dot |
variant | component-specific (see matrix) | Shared prop name; meaning varies by component | Chip, Button, IconButton, LinkButton, Card, ServiceTag |
appearance | solid · subtle · outline | How the shape is filled vs bordered | Badge (solid / subtle), Tag (solid / subtle) |
variant vs appearance
appearancedescribes fill.variantis the shared prop name — its meaning varies by component.
- Interactive components (Chip, Button) use
variantonly. They have hierarchy to express. - Read-only indicators (Badge, Tag) use
appearanceonly. Fill form is the only lever.
Never cross this boundary.
Why solid, subtle, outline
dark | light was ambiguous with dark-mode theming. solid | subtle | outline names the fill form explicitly:
solid— filled background, typically saturated or neutralsubtle— tinted background, lower emphasisoutline— transparent background, visible border
Coverage matrix
Source of truth: manifest/component-axes.json — auto-generated via npm run typegen:axes (ADR-009). When the table and the manifest disagree, the manifest wins.
| Component | variant | appearance | size | status |
|---|---|---|---|---|
| Button | primary · outline · secondary · ghost · inverse · on-color · destructive · positive · danger · danger-outline · danger-ghost | — | tiny · sm · md · lg · xl | — |
| LinkButton | shares ButtonVariant | — | shares ButtonSize | — |
| IconButton | shares ButtonVariant | — | shares ButtonSize | — |
| Chip | primary · secondary | — | sm · md · lg | — |
| Card | outlined · brand · elevated · borderless | — | — | — |
| ServiceTag | text · icon-text · icon | — | sm · md · lg | — |
| Tag | — | solid · subtle | xs · sm · md · lg | — |
| Badge | — | solid · subtle | xs · sm · md · lg | positive · warning · error · info · progress · brand |
Notes:
selectedis not a variant — it is a boolean state prop on Button / IconButton (selected={true}), applied as a modifier on top of any variant. Never list it in the variant union.- Chip
appearanceremoved in v0.66 — Chip is interactive; emphasis lives onvariantonly. - Card
variantencodes border/elevation style — not a hierarchy or fill axis.CardPreset(control·summary·display·display-row) is orthogonal tovariant. - Badge
outlineappearance excluded — low contrast for status indicators. - Banner is not on the
statusaxis — it expresses the same semantics through its owntoneaxis (announcement·warning·error·information). Dot usesstatuswith its own union (default·positive·warning·error·info·neutral); the value sets are per-component (TypeScript is the gate).
TypeScript is the authoritative gate. Pass only values the component's declared union includes — never invent values. The manifest shows which types are exported.
Expression modes
The five expression-mode axes are the Style half of ADR-039. They are not component props — no component declares them. They are attributes set once on :root (the <html> element), and every component inherits the retuned tokens.
| Axis | Attribute on :root | Values | Default (no attribute) |
|---|---|---|---|
| Spacing | data-mode-spacing | compact · comfortable · spacious | default |
| Typography | data-mode-typography | compact · comfortable · spacious · expressive | default |
| Radius | data-mode-radius | sharp · round · pill | soft |
| Elevation | data-mode-elevation | flat · lifted · dramatic | subtle |
| Border width | data-mode-borderwidth | thin · bold | standard |
Each default is the attribute-ABSENT state — there is no [data-mode-radius="soft"] selector, and setting one does nothing. Omit the attribute to get the default. For the same reason the defaults are not members of the closed vocabularies below.
Closed value lists live in content-system/vocabularies/{spacing,typography,radius,elevation,borderwidth}-mode.ts, published as @brikdesigns/bds/content-system. They are surfaced to the portal's enum gate under the reserved @modes key in manifest/component-axes.json, alongside the component axes.
The authority is the CSS, not the TypeScript: tokens/modes-*.css is what ships, and content-system/vocabularies/expression-modes.test.ts parses it to hold each list equal to the selectors it declares. A Figma mode rename fails that test rather than drifting into a portal pick-list.
Motion has no mode axis. There is no data-mode-motion selector; motion is expressed through the AnimationTier vocabulary (subtle · moderate · expressive) and the reveal / contentMotion prop axes, with none meaning no motion classes are emitted.
Blueprint shortlist weights
resolveBlueprintShortlist ranks the blueprint library against a company profile. The weights are exported as SHORTLIST_WEIGHTS from content-system/blueprints/resolver.ts so the portal runs the same math on the same numbers — this table documents them, the export is the gate.
| Term | Weight | Counts |
|---|---|---|
perPersonalityHit | 2 | once per overlapping personality |
perVisualStyleHit | 1 | once per overlapping visual style |
industrySlugMatch | 3 | once |
universalBonus | 5 | once, for a universal blueprint |
atmosphereMatch | 3 | once |
animationTierMatch | 2 | once |
spacingModeMatch | 1 | once |
The three design-decision terms descend by how much of a section's rendering the axis governs: the atmosphere sets the whole visual register and is a peer of the industry match, the animation tier governs motion only, the spacing mode governs density alone. Combined they cap at 6, just above universalBonus — deliberately, so a blueprint matching every decision the client made outranks an evergreen one matching none.
Each is matched against an optional affinity array on the blueprint (atmospheres, animation_tiers, spacing_modes). Every term is additive and none filters, so a blueprint that declares no affinity scores exactly what it scored before these existed, and an affinity miss can re-rank a shortlist but never shrink it.
Related
- Components — each component implements the axes documented here
- Slot Vocabulary — slot names that pair with these axis props
- Client Themes — the four Theming Dimensions the expression modes sit inside