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:
| Slot | Element | Purpose |
|---|---|---|
__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 primitives — Stack, 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
| Role | Element | Notes |
|---|---|---|
| Thematic page block | <section> | Every major page region |
| Primary content wrapper | <main> | One per page |
| Site header / footer | <header>, <footer> | Page-level landmarks |
| Content constraint | CSS on __content | Not 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>
</>
);
}| Rule | Detail |
|---|---|
| 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 sidebar | BDS 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 classes | bds-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; }Related
- HTML Semantics — heading levels, no-unclassed-wrappers, stable IDs
- Composition Layers — Section / Layout / Container / Block / Component model
- Naming Principles — blueprint naming rules and closed allowlist
- Theming — Blueprints — blueprint families (
bds-hero,bds-cta, etc.)