Brik Design System
Build Standards

Page Structure

The HTML skeleton and BEM section pattern for Brik pages in Astro and Next.js — from page shell to inner slot anatomy.

Page skeleton

Every Brik page follows this semantic shell. The landmark elements (<header>, <main>, <footer>) are fixed; the sections inside <main> are where blueprint families live.

<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <header><!-- site nav --></header>

    <main>
      <section class="bds-hero">...</section>
      <section class="bds-features">...</section>
      <section class="bds-cta">...</section>
    </main>

    <footer><!-- site footer --></footer>
  </body>
</html>
  • <header> and <footer> are page-level landmarks — one of each per page.
  • <main> wraps all page sections — one per page.
  • Each thematic block inside <main> is a <section> with a BDS blueprint class.

Section pattern

A section is a <section> element carrying a blueprint BEM block name. The valid block names are defined in Naming Principles: bds-hero, bds-cta, bds-services, bds-features, bds-about, bds-support-plan.

<section class="bds-features">
  <div class="bds-features__header">
    <h2 class="bds-features__title">Powerful Features</h2>
    <p class="bds-features__subtitle">Everything your team needs.</p>
  </div>

  <div class="bds-features__content">
    <!-- layout primitive or grid of blocks -->
  </div>

  <div class="bds-features__actions">
    <!-- Button or ButtonGroup -->
  </div>
</section>

Slot anatomy

Every slot name comes from the closed allowlist. The common slots inside a section:

SlotElementPurpose
__header<div>Groups title + subtitle (+ optional badge/tag)
__title<h2> (or <div>/<p> — see below)Primary text of the section
__subtitle<p>Supporting line under the title
__content<div>Main content area — grid, list, or layout primitive
__actions<div>Holds <Button> or <ButtonGroup>
__media<div>Image or video using <Frame>

__title heading level follows outline position — <h2> when the section is a direct child of <main>. See HTML Semantics — Heading element selection.

Structural modifiers

Use -- modifiers only for structure, never for appearance or theme:

<!-- ✅ structural modifier -->
<section class="bds-features bds-features--two-column">...</section>

<!-- ❌ appearance modifier — theme via CSS custom properties, not class names -->
<section class="bds-features bds-features--dark">...</section>
<section class="bds-features bds-features--centered">...</section>

Layout inside sections

Use Layout primitivesStack, Cluster, Grid, Split, Row — to arrange blocks inside __content. Do not hand-roll flex/grid in section CSS when a layout primitive covers the case.

<!-- Grid of feature cards -->
<div class="bds-features__content">
  <div class="bds-grid bds-grid--3col">
    <div class="bds-card">...</div>
    <div class="bds-card">...</div>
    <div class="bds-card">...</div>
  </div>
</div>

<!-- Split (image + text) -->
<div class="bds-about__content">
  <div class="bds-split">
    <div class="bds-about__media"><img .../></div>
    <div class="bds-about__body">...</div>
  </div>
</div>

Container widths

Sections span full viewport width. Content is constrained by an inner container slot — this is CSS, not a wrapper element with a .container class.

.bds-features {
  /* section fills viewport */
}

.bds-features__content {
  max-width: var(--layout-content-max-width);
  margin-inline: auto;
  padding-inline: var(--space-lg);
}

Variants (narrow, wide, full-bleed) are expressed as structural modifiers on the section, not as a separate .container--narrow wrapper element.

Astro

In Astro, sections map to .astro components with named slots.

---
// components/sections/Features.astro
const { title, subtitle } = Astro.props;
---
<section class="bds-features">
  <div class="bds-features__header">
    <h2 class="bds-features__title">{title}</h2>
    {subtitle && <p class="bds-features__subtitle">{subtitle}</p>}
  </div>
  <div class="bds-features__content">
    <slot />
  </div>
  {Astro.slots.has('actions') && (
    <div class="bds-features__actions">
      <slot name="actions" />
    </div>
  )}
</section>

File placement: src/components/sections/ for blueprint-level section components. src/components/ui/ for atoms and blocks reused across sections.

Next.js

In Next.js, sections are .tsx components. Prefer children for the content slot; use named render props or additional props only when the slot has non-trivial composition rules.

// components/sections/Features.tsx
interface FeaturesProps {
  title: string;
  subtitle?: string;
  actions?: React.ReactNode;
  children: React.ReactNode;
}

export function Features({ title, subtitle, actions, children }: FeaturesProps) {
  return (
    <section className="bds-features">
      <div className="bds-features__header">
        <h2 className="bds-features__title">{title}</h2>
        {subtitle && <p className="bds-features__subtitle">{subtitle}</p>}
      </div>
      <div className="bds-features__content">{children}</div>
      {actions && <div className="bds-features__actions">{actions}</div>}
    </section>
  );
}

File placement: components/sections/ for blueprint-level sections. components/ui/ for atoms and blocks.

Element selection quick reference

RoleElementNotes
Thematic page block<section>Every major page region
Primary content wrapper<main>One per page
Site header / footer<header>, <footer>Page-level landmarks
Content constraintCSS on __contentNot a .container wrapper element
Section title<h2>Outline node in <main>
Section subtitle<p>Never <h3> unless it's an outline node
Inline label / badge<span>Sparingly — badges, icons, text fragments
Lists<ul> / <ol>Prefer semantic list elements over <div> + <span>
Images<Frame> wrapping <img>Aspect-locked; never hardcode aspect-ratio in CSS

Product app shell (Next.js portal)

Product apps (like brik-client-portal) use a sidebar + main-content shell rather than marketing-style sections. The landmark rules still apply; the blueprint section pattern does not.

// app/(auth)/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {/* Skip-link must be the first focusable element — WCAG 2.4.1 */}
      <a href="#main-content" className="skip-link">Skip to content</a>
      <div style={{ display: 'flex', minHeight: '100vh' }}>
        {/* BDS SidebarNavigation renders <aside> + <nav> — correct landmarks */}
        <SidebarNavigation ... />
        <main id="main-content" style={{ flex: 1 }}>
          {children}
        </main>
      </div>
    </>
  );
}
RuleDetail
Skip-link first<a href="#main-content" className="skip-link"> must be the first child of the layout — before the sidebar — so keyboard users can bypass nav (WCAG 2.4.1)
id="main-content" on <main>Required as the skip-link target. Every app layout that renders a sidebar must have this.
<aside> + <nav> for sidebarBDS SidebarNavigation provides this automatically. Never replace with a bare <div>.
No <header> / <footer>Product apps have no site header or footer landmark — the sidebar is the persistent nav landmark.
No blueprint classesbds-hero, bds-features etc. are for marketing sections. Content inside <main> uses BDS Page / PageContent layout primitives.

Skip-link CSS — add once to globals.css, using canonical BDS tokens:

.skip-link {
  position: absolute;
  top: -48px;
  left: 0;
  z-index: 9999;
  padding: 8px 16px;
  background: var(--background-brand-primary);
  color: var(--text-inverse);
  text-decoration: none;
  font-weight: 600;
  border-radius: 0 0 4px 0;
}
.skip-link:focus { top: 0; }

On this page