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:
| Use | What it is | Reserved / resolved to |
|---|---|---|
| Input control | Resolves to one value from a constrained domain — a form control | DatePicker, TimePicker — the one reserved use |
| Selection composition | A set of selectable children the reader selects — a composition, not a control | ServiceTagSelect (was ServiceTagPicker); the SelectableMediaTile grid story (was Picker) |
| Addable | A member of the addable-list family that matches and adds from a catalog | AddableCatalogList (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
- Identify the role the slot expresses; pick the most generic word that fits.
- Grep Slot Vocabulary for an existing slot that already covers the role — reuse before invent.
- Write it kebab-case. A well-formed name passes the gate automatically — no canon edit required.
- Keep it structural, not layout-specific (
bds-hero__title, neverbds-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 carry | Because | Examples |
|---|---|---|
| Layout words | The shape is what the blueprint is | split, grid, centered, two_column, bar, overlay |
| Section type | Which slot on the page it fills | hero_*, cta_*, stats_* |
| A name must NOT carry | Where it belongs instead |
|---|---|
| Content domain — services, team, about, support plan | The library entry's industries / moods, and the site content passed in |
| Brand or atmosphere — dark, branded, light | Tokens and the resolved atmosphere |
| Column counts and ratios — 3col, 60_40 | A 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):
| Axis | Question it answers | Vocabulary |
|---|---|---|
| Composition layer | How do I reason about this piece of structure? | Blueprint → Layout → Container → Block → Component |
| Storybook taxonomy | Where 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
- Single namespace. Every class uses
bds-. Thebp-prefix is deprecated — no newbp-*classes after ADR-008. - 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. - 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. - 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. - Compose, don't reimplement. A blueprint
.tsxthat imports nothing fromcomponents/ui/*is almost always drift. - Use
Framefor aspect-locked images. Never hardcodeaspect-ratioin 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.
| Retired | Current | Why |
|---|---|---|
SupportPlan, bds-support-plan | CalloutPanel, bds-callout-panel | Content 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_split | callout_split | Same rule, applied to the manifest key (#2303) |
services_3col_card_grid | card_grid | Content domain + a column count that is a prop |
services_detail_two_column | two_column_detail | Content domain |
about_story_split | story_split | Content domain |
cta_dark_centered | cta_centered | dark is an atmosphere token |
features_3col_branded_dark | feature_grid | Brand + atmosphere + a column count |
hero_split_60_40 | hero_split | The ratio is a prop |
stats_dark_bar | stats_bar | dark is an atmosphere token |
About, bds-about | Narrative, bds-narrative | Content 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-bar | StatsBar, bds-stats-bar | dark is an atmosphere token; bar is a layout word. The key stays stats_bar (#2793) |
Related
docs/SLOT-ALLOWLIST.md— the pattern spec (grammar + examples); enforced byscripts/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