Brik Design System
Build Standards

Composition Layers

Six conceptual layers — Section, Layout, Container, Block, Control, Component — and the decision rules for assembling them.

A page in BDS is composed in six layers. Each has a single responsibility, a single vocabulary, and only knows about the layer below it. Block and Control sit at the same depth — both are fixed-slot shapes one step above Component; one holds content, the other holds controls. This model is pedagogical — it explains how to think about a page — and is intentionally non-binding on the BEM canon (ADR-008).

The layers

LayerResponsibilityVocabularyExamples
SectionPage role with surrounding structure (vertical rhythm, container, background surface)Page-level semanticHero, Content, CTA
LayoutPure composition primitive — arranges children. No styling beyond structure.CompositionStack, Cluster, Grid, Split, Row
ContainerStyled holder that composes blocks into a self-contained unit. Carries border / padding / elevation / radius.Bounded unitCard, List, Form, Accordion, Tabs
BlockComposed content unit — fixed slot shape filled with atoms.Slot + atomsContentBlock, MediaBlock, ListItem, FormField, Stat
ControlInteractive bar that operates on an adjacent display or heads a page. Fixed slot shape filled with controls, not content.Slot + controlsFilterBar, PageHeader, TabBar, SubNavigation, Pagination
ComponentSingle primitive atom.One primitiveButton, Input, Image, Badge, Icon

Where each layer lives

LayerDirectoryNotes
Sectioncontent-system/blueprints/{react,astro}/Blueprint families per ADR-008 play the Section role
Layoutcomponents/ui/Stack, Cluster, Grid, Split, Row
Containercomponents/ui/Card, Accordion, List
Blockcomponents/ui/Field, Card preset="summary"
Controlcomponents/ui/FilterBar, PageHeader, TabBar, SubNavigation, Pagination
Componentcomponents/ui/Atomic primitives

Block vs container

If the thing is described primarily by how its children are styled and bounded (border, elevation, padding, max-width) → it's a container. If it's described by what slots it offers and which atoms fill them (heading, body, kicker, action) → it's a block.

Card passes "styled and bounded" → container. ContentBlock passes "slots and atoms" → block.

Card is a styled container

A Card encodes only its container styling (border, radius, padding, elevation). Orientation comes from the layout primitive inside it, not a Card variant.

// ✅ Stacked card — Stack layout inside Card
<Card>
  <Stack>
    <MediaBlock />
    <ContentBlock />
  </Stack>
</Card>

// ✅ Horizontal card — Split layout inside Card
<Card>
  <Split>
    <MediaBlock />
    <ContentBlock />
  </Split>
</Card>

// ❌ Don't bake orientation into Card
<Card variant="horizontal">...</Card>
<Card layout="stacked">...</Card>

This keeps CardImageLeft, CardStacked, CardHorizontal from existing — they're all "Card + a layout child."

Stat is a block

Stat (value + label, two slots, no border styling) is a block, not a container. The bordered "stat tile" look is a Card containing a Stat.

// Bordered stat tile
<Card>
  <Stat value="75%" label="of website credibility comes from design" />
</Card>

// Card with content + metric
<Card>
  <ContentBlock title="First impressions" body="..." />
  <Stat value="0.05s" label="to make a first impression" />
</Card>

If you find yourself reaching for a StatCard, you're conflating block and container — write it as Card + Stat instead.

Control vs block and container

If the thing is described by which atoms fill its slots → it's a block. If it's described by what it does to something else on the page (filter, paginate, switch, act) → it's a control.

A control never holds the content it governs. FilterBar heads the table it filters; that table is its sibling, not its child. Pagination pages a list it does not contain. TabBar switches a panel it knows nothing about. That is the line against Container: a container bounds its children, a control operates on a neighbour and bounds nothing.

The line against Block is subtler, because both are fixed-slot shapes at the same depth. Ask what the slot content is about:

  • A ContentBlock with a <Button> in its action slot is still a block — the button acts on the block's own content.
  • A FilterBar with a title in its title slot is still a control — the title names the display underneath it.

Reaching for a Cluster with justify="between" to build a bar is the tell that a control is missing. Cluster is a Layout primitive: it arranges, it carries no vocabulary, and every hand-rolled instance diverges. Use the control, or file for one.

Control bar or section header

A control bar and a section header both render a title at --heading-sm, so the rendered output gives you no signal about which produced it. Pick by what the title is over:

The title headsReach forComponent
A display — table, list, board, card gridA control barFilterBar
A record — fields in read or edit modeThe section container's own headerDataSection title, SheetSection heading
The whole pageThe page headerPageHeader
// ✅ Collection — the control bar heads the display it filters
<FilterBar title="Service plans" label="plans" total={42} filtered={12}>
  <FilterButton ... />
</FilterBar>
<Table ... />

// ✅ Record — the section container owns its own header
<DataSection title="Billing details">
  <FieldGrid>
    <Field label="Plan">Growth</Field>
  </FieldGrid>
</DataSection>

// ❌ Don't head a record with a control bar
<FilterBar title="Billing details" label="fields" total={6} filtered={6} />
<FieldGrid>...</FieldGrid>

// ❌ Don't hand-roll a control bar out of a section header
<DataSection title="Service plans" actions={<Button>Clear filters</Button>}>
  <Badge>12 of 42</Badge>
  <Table ... />
</DataSection>

The last case is the one that ships. A section header plus a hand-placed count and clear button is a FilterBar reimplemented without its collapse behaviour, its counter status, or its accessible name — the mixture found on one product page in brik-client-portal, where bds-data-section__header, bds-filter-bar, and bds-cluster--justify-between all did this job at once.

SectionHeader is not the section header this rule means. It is the centered marketing section intro (title + description + content measure), not a record header. On a product page the record header is the section container's own title slot.

Container reference

ContainerPurposeWhen
DataSectionTitled block of read-mode data on a pageOverview / profile tabs, client-detail pages
SheetSectionTitled block inside a sheet body (uppercase label heading)Any grouping inside <Sheet>
Card (and variants)Bordered, self-contained content unitGrids of comparable items, dashboards, marketing
Board (BoardColumn, BoardCard)Kanban-style containerTask boards
Dialog / Modal / SheetOverlay containersFocused interactions

Don't reach for a Card when a DataSection is right. Cards are self-contained units in a grid; DataSection is one region of a larger page.

Identify the layer before you target it

When a change names an element ("the card", "the section", "the button"), find the target by its layer, reading the DOM top-down (Section → Layout → Container → Block or Control → Component) — never by selector-name resemblance.

A BEM block name identifies the blueprint family; it does not define the element's layer. A block whose name contains "card" can still be a Section. The Card is whichever nested element plays the Container role.

bp-hero-img-card is a Section (<section>). The Card is its nested Container, aside.bp-hero-img-card__media-card. They own different surfaces: the Section carries the page-role/band surface; the Container (Card) carries its own bounded surface (its --bds-hero-img-card-media-bg hook). A surface meant for a service-identified card — e.g. ADR-020's --surface-service-{line}-inverse — is a Container surface; applying it to the Section repaints the whole band, not the card. (Regression caught in brikdesigns#637.)

The block-vs-container decision rule above answers "what layer is this?"; apply it to the element you're about to change, not to the name that looks closest.

  • Naming Principles — the slot pattern and blueprint naming rules
  • Page Structure — how sections are structured in HTML
  • Content Rhythm — the spacing scale between these layers, from text pairings to section breaks, and which layer owns the separator between sections
  • Display Choice — which display a Control layer bar heads
  • Page Archetypes — the four assembled product pages these layers compose into
  • Theming — Blueprints — blueprint families that play the Section role

On this page

💬