Brik Design System
Build Standards

Naming Principles

Every name in BDS describes a role or slot — not a visual shape — and must match the canonical BEM pattern.

Intent, not shape

Every word in BDS describes a role or a slot. It does not dictate visual shape and does not dictate the HTML element. The HTML element is chosen by semantic intent at the call site.

This distinction is load-bearing for titles and headings — get it wrong and you ship outline-invisible content that looks correct visually. See HTML Semantics for the element-selection rules.

One word, one meaning

A word earns one meaning in the vocabulary. When the same word names two unrelated shapes, every reader has to disambiguate by context, and the second use was a lie the moment it shipped — the same failure the blueprint rules below catch for layout words.

Picker was the live case. It had drifted onto three unrelated things:

UseWhat it isReserved / resolved to
Input controlResolves to one value from a constrained domain — a form controlDatePicker, TimePicker — the one reserved use
Selection compositionA set of selectable children the reader selects — a composition, not a controlServiceTagSelect (was ServiceTagPicker); the SelectableMediaTile grid story (was Picker)
AddableA member of the addable-list family that matches and adds from a catalogAddableCatalogList (was CatalogPicker); AddableCatalogListSize = AddableEntryListSize, AddableCatalogList.tsx:35

The definition: Picker is reserved for the input control — a component that resolves to a single value from a constrained domain (DatePicker, TimePicker). The other two uses were drift and have been renamed (#2427): an addable is named for its family (AddableCatalogList), and a selection composition is named for what it composes (ServiceTagSelect, and the media-tile grid), not "picked".

Slot pattern

Per ADR-017, slot names are governed by a structural pattern, not an enumerated list. A bds-* class is canonical if and only if it matches the ADR-008 §4 BEM grammar:

bds-<block>
bds-<block>--<modifier>
bds-<block>__<slot>
bds-<block>__<slot>--<modifier>

Every segment is kebab-case (__item-label, never __itemLabel or __item_label). __ separates a slot; -- separates a modifier. No single underscores, no other separators, no doubled __/--, no uppercase.

A well-formed slot is canonical because it is well-formed — there is no registry to maintain and no PR needed to introduce a new one. Only malformed shapes fail.

The gate is scripts/slot-pattern-check.mjs — a pure source scan wired into .husky/pre-commit (staged) and CI (full-tree, via .github/workflows/slot-pattern-check.yml). It supersedes the closed enumerated allowlist, whose enforcing lint (ADR-008 Phase C) never shipped: by 2026-07 only 101 of the 396 slots in use were listed, so enumeration was unpayable. docs/SLOT-ALLOWLIST.md is repurposed from a list into the pattern spec (grammar + examples); the filename is kept to preserve inbound links.

Naming a new slot

  1. Identify the role the slot expresses; pick the most generic word that fits.
  2. Grep Slot Vocabulary for an existing slot that already covers the role — reuse before invent.
  3. Write it kebab-case. A well-formed name passes the gate automatically — no canon edit required.
  4. Keep it structural, not layout-specific (bds-hero__title, never bds-hero__hero-title) and not visual. Cross-block-generic slots (__title, __label, __icon) are expected and fine.

Blueprint naming rules

Blueprints (content-system/blueprints/*) are page-level layouts — the Blueprint layer of the Composition Layers model. Their naming follows the same pattern canon.

A blueprint key names layout, never content domain

A blueprint has two names, and this rule governs both: its manifest key in blueprints/blueprint-library.json, and the component that renders it.

A name describes layout shape only. The same three-up grid renders services, team, blog and product collections — so a name that says services is a lie the first time it is reused, and a name that says dark is a lie the first time the atmosphere changes.

A name may carryBecauseExamples
Layout wordsThe shape is what the blueprint issplit, grid, centered, two_column, bar, overlay
Section typeWhich slot on the page it fillshero_*, cta_*, stats_*
A name must NOT carryWhere it belongs instead
Content domain — services, team, about, support planThe library entry's industries / moods, and the site content passed in
Brand or atmosphere — dark, branded, lightTokens and the resolved atmosphere
Column counts and ratios — 3col, 60_40A prop

So card_grid, hero_split, cta_centered, story_split, two_column_detail — a vocabulary a client site can adopt without inheriting another client's content model.

Enforced by scripts/lint-blueprint-naming.mjs as blueprint-key-context-word, on both pre-commit and the validate chain. It reads the two places a key is declared — blueprints/blueprint-library.json and content-system/blueprints/astro/types.ts — and, for the component name, every blueprint component filename (react/*.tsx, astro/*.astro) and the export names in both barrels (react/index.ts, astro/index.ts), splitting PascalCase into words. It ships with no allowlist. blueprints/blueprint-roadmap.json is out of scope by design: its entries are design candidates, not contracts, and a candidate becomes a contract when it graduates into the library — which is exactly when the rule bites.

Source: ADR-037 §4.

Two structural vocabularies, not one

BDS names structure along two orthogonal axes, and they are not interchangeable (ADR-037 §5):

AxisQuestion it answersVocabulary
Composition layerHow do I reason about this piece of structure?Blueprint → Layout → Container → Block → Component
Storybook taxonomyWhere does this story file?Components / Cards / Forms / Containers / Layouts / Navigation / Blueprints / Tools

Block is a composition layer and not a Storybook bucket — ADR-006 retired that bucket on 2026-07-29. Cards is a Storybook bucket and not a composition layer — a Card is a Container.

The one-sentence form, for agent prompts and orientation:

Blueprints compose the page. Containers hold collections. Blocks fill containers. Layouts arrange. Components are atoms.

The rails

Blueprints ship on two renderers. Astro is canonical (ADR-037 §1) — it carries every published client site, so where the rails disagree Astro is the reference. React is a supported rail for CMS-backed sites and brikdesigns.com, and is the source of the layout-as-prop API both rails share: a component is a block, and the layout it renders in is a prop, never a filename. See Blueprints.

Class naming

  1. Single namespace. Every class uses bds-. The bp- prefix is deprecated — no new bp-* classes after ADR-008.
  2. One block per family. bds-hero, bds-cta, bds-services, bds-features, bds-narrative, bds-callout-panel. Each family is one block with structural modifiers, not N separate blueprints.
  3. Well-formed slots. Every bds-{block}__{slot} must match the kebab-case BEM pattern above. The slot-pattern gate fails on malformed shapes, not on new well-formed words.
  4. Structural-only modifiers. Modifier names describe structure (--split-image, --two-column) — never appearance (--dark) or theme (--inverse). A "dark" CTA themed light makes the name a lie. Placement is the exception: alignment is a curated closed-enum axis the component writes (--align-center, --align-left), because a re-theme preserves placement even as it changes appearance — ADR-039 narrows ADR-008 §3 to exempt it.
  5. Compose, don't reimplement. A blueprint .tsx that imports nothing from components/ui/* is almost always drift.
  6. Use Frame for aspect-locked images. Never hardcode aspect-ratio in blueprint CSS.

The bp-* prefix is being migrated to bds-* across existing blueprints — Phase D, one family per PR. Until Phase F, both prefixes coexist.

Retired blueprint names

Kept so a search for the old name finds the current one. These are history, not alternatives — no code, doc, or manifest should reintroduce them.

RetiredCurrentWhy
SupportPlan, bds-support-planCalloutPanel, bds-callout-panelContent domain in a component name; the layout is a section header above a callout panel, and nothing about it is plan-specific (#2305)
support_plan_callout_splitcallout_splitSame rule, applied to the manifest key (#2303)
services_3col_card_gridcard_gridContent domain + a column count that is a prop
services_detail_two_columntwo_column_detailContent domain
about_story_splitstory_splitContent domain
cta_dark_centeredcta_centereddark is an atmosphere token
features_3col_branded_darkfeature_gridBrand + atmosphere + a column count
hero_split_60_40hero_splitThe ratio is a prop
stats_dark_barstats_bardark is an atmosphere token
About, bds-aboutNarrative, bds-narrativeContent domain in a component name; the block is a narrative column with an optional pull-quote, nothing about it is about-page-specific. The key stays story_split (#2793)
StatsDarkBar, bds-stats-dark-barStatsBar, bds-stats-bardark is an atmosphere token; bar is a layout word. The key stays stats_bar (#2793)
  • docs/SLOT-ALLOWLIST.md — the pattern spec (grammar + examples); enforced by scripts/slot-pattern-check.mjs
  • ADR-017 — why the pattern gate superseded the closed allowlist
  • ADR-008 — the naming canon (§1 namespace, §3 structural-only modifiers, §4 grammar); §2's closed allowlist is superseded by ADR-017
  • ADR-039 — narrows ADR-008 §3 to exempt placement (alignment as a curated prop axis) and adds the motion axis
  • Slot Vocabulary — what each canonical slot means and when to use it

On this page

💬