Brik Design System
React Reference

Patterns

Cross-component recipes — how multiple BDS pieces compose into the product moments that show up in every Brik build.

Patterns answer the question "I'm building X — what does BDS recommend?" Each pattern is a reusable composition: which BDS components to reach for, the shape they take, and the accessibility + edge-case notes that don't fit on a single component page.

Patterns are recipes, not new components. If a recipe gets used in 3+ places without meaningful variation, it graduates to a real component (see Utilities → Adopt / Extend / Graduate).

Forms

The single most-built pattern across portal, renew-pms, and brikdesigns. BDS ships the full vocabulary — <Form>, <Field>, <FieldGrid>, the input primitives, plus the Addable family for tag/entry inputs.

Composition:

<Form onSubmit={handleSubmit}>
  <FieldGrid columns={2}>
    <Field label="First name" required>
      <Input name="firstName" />
    </Field>
    <Field label="Last name" required>
      <Input name="lastName" />
    </Field>
    <Field label="Role" helper="Used for in-product permissions">
      <Select name="role" options={roles} />
    </Field>
    <Field label="Tags">
      <AddableTagList suggestions={tagSuggestions} />
    </Field>
  </FieldGrid>
  <ButtonGroup>
    <Button variant="ghost" onClick={onCancel}>Cancel</Button>
    <Button variant="primary" type="submit">Save</Button>
  </ButtonGroup>
</Form>

Variants Brik builds repeatedly:

VariantWhen to useComposition note
Single-columnOnboarding, simple settings<FieldGrid columns={1}> — drop the grid for under 4 fields
Two-columnProfile / company / address forms<FieldGrid columns={2}> — required fields top-left first
Sheet-driven editEditing one record from a listWrap in <Sheet> with the useSheetStack hook for back/save flow
Multi-step wizardOnboarding, contractsTabs in the sheet config (see Hooks → useConfigureSheet)

Accessibility:

  • Every input lives inside a <Field> — the label association is automatic.
  • Required fields use the required prop on <Field>, not * characters in the label.
  • Errors surface via the error prop on <Field>, not as inline <p> siblings — placement + ARIA wiring is owned by <Field>.

Empty states

The shape an app takes when there's no data, the user lacks permission, search returns nothing, or a request errors. BDS ships <EmptyState> for the canonical case — patterns below show when to reach for it vs. extend.

Canonical:

<EmptyState
  icon={<InboxIcon />}
  title="No clients yet"
  description="Add your first client to start tracking projects."
  action={<Button variant="primary" onClick={openNewClient}>Add client</Button>}
/>

Four shapes that recur:

ShapeWhenWhat changes
No dataFirst run, fresh tablePrimary action that creates the first record
No resultsSearch / filter returned nothingSecondary action: "Clear filters" — never a primary CTA
Permission deniedRLS or role gateNo action — explain why, link to admin/owner contact
ErrorFetch failed, network issueSecondary action: "Retry" — keep the chrome around it (don't blank the page)

Don't blank a page on error. Render the empty state inside the existing layout chrome (sidebar + header still visible). Replacing the whole route with a white error screen erases the user's mental map.

Page templates

Three templates cover roughly 95% of the product surface. Treat them as a starting frame, not a constraint — every page in the portal and renew-pms is one of these.

TemplateUsed forBDS components
List + Detail SheetIndex pages where rows drill into a sheet (clients, contracts, leads)<Table> or <CardList> + <Sheet> + <SheetStackProvider>
Settings ShellMulti-section settings, profile, company config<SidebarNavigation> + <Sheet> for sub-edits
Dashboard ShellLanding pages, overview screens<Card> grid + <EmptyState> per cell when data missing

Layout chrome lives once, at app root<NavBar> + <SidebarNavigation> + <BrikDevBar> (dev only). Templates render inside that chrome via Next.js layout files. Don't re-mount the chrome inside a route.

Three navigation surfaces, three components — keep them straight. They are not interchangeable.

SurfaceComponentWhen
Top app bar<NavBar>Global brand + primary destinations + user menu
Section sidebar<SidebarNavigation>Within an app section (Settings, Admin) — collapsible groups
Within-page<Breadcrumb> + <Pagination>Position + page-by-page traversal

Breadcrumbs appear in detail views where the user drilled in (Clients › Acme › Contracts › #4821). They do not appear in flat lists or top-level pages — that creates noise.

Pagination is for paged data. For infinite scroll or load-more, use the relevant data-loading pattern (not in this doc).

Mobile nav collapses the top bar into a hamburger and the sidebar into a <Sheet> (default variant) — the sheet stack handles the drawer animation; the route layout decides when to mount it.

Notifications

Four notification surfaces — match severity and persistence to the pattern, not the other way around.

PatternComponentPersistenceWhen
Toast<Toast>Auto-dismiss (5s)Confirmation of a user action ("Saved", "Copied")
Banner<Banner>Until dismissed by userSystem-wide state (maintenance window, plan limit)
Inline alert<Banner tone="warning|error|information">Until resolvedPage-scoped state (form error, validation)
Modal confirmation<Sheet variant="floating"> with confirm + cancel actionsUntil decidedDestructive actions (delete, discard, sign-out)

Severity colors come from semantic surface tokens — --surface-success, --surface-warning, --surface-danger, --surface-info. Each notification component reads them automatically; you don't pick a hex.

Don't double-notify. A toast + banner + inline alert all firing for the same event is a bug, not thoroughness. Pick one surface based on persistence: ephemeral (toast), session (banner), page (inline alert), gate (modal).

Adding a pattern here

The bar for adding a new pattern doc is:

  1. It's been built ≥ 3 times across Brik repos with the same shape — recipes that have only happened once belong in the consumer's notes.
  2. It composes existing BDS components — if the recipe needs a brand-new component, build the component first, then document the recipe.
  3. It has decisions worth writing down — accessibility wiring, severity choice, mobile collapse rules. A pattern with no decisions is just example code.

Patterns that don't clear all three bars stay in the consumer codebase as-is.

  • Components — the building blocks every pattern composes
  • HooksuseSheetStack and useConfigureSheet power the sheet-driven recipes here
  • Theming → Blueprints — full-page layout primitives (different layer — patterns compose components, blueprints compose sections)

On this page