Layouts
EXSA ships with three composable page layouts and two starter templates — drop-in CSS files that turn semantic HTML zones into complete page shells.
Each layout is a single CSS file. Link the one you need, add its classes to <body>, and structure your HTML with the documented zones.
Compositional page shell with toggle classes. Topbar, hero, sidebar, content, footer — turn any zone on/off.
Centered, reading-optimized layout. Optional sticky table of contents and typography boost.
Full-viewport app shell. Fixed sidebar, auto icon-collapse + mobile drawer, density profiles, KPI primitives.
Ecommerce product catalog. Announcement bar, cart topbar, filter sidebar, product grid.
Single-viewport landing page. Video/image or seamless panned backdrop, centered brand, social icon rings, copyright.
Multi-section marketing landing page. Photo hero + nav dropdowns, banner, carousel, features grid, dark footer.
How Layouts Work
Every layout follows the same pattern:
1. Link the layout CSS after exsa.css (and a theme, if you use one — themes are optional).
2. Add the layout's body classes to <body>.
3. Structure your HTML with the documented zone elements.
Layouts use the topbar component's --topbar-height variable, so body padding automatically matches your topbar size (topbar--sm, topbar--xl, etc.).
General Layout
The compositional page shell. Add toggle classes to <body> to activate zones — topbar, hero, sidebar, content, footer.
Designed for documentation sites, reference pages, and content-heavy UIs.
Body Classes
| Class | Effect |
| layout--has-topbar | Reserves space for a fixed topbar |
| layout--has-hero | Full-bleed hero below topbar |
| layout--aside-left | Left sidebar + content flex row |
| layout--aside-right | Right sidebar + content flex row |
| layout--has-sidebar | Shorthand for layout--aside-left |
Dashboard-style modes (layout--aside-full, layout--full-height, layout--content-scroll, layout--aside-collapsed) moved to dist/layouts/dashboard.css — use dash--sidebar, dash--topbar, dash--sidebar-sm. |
| layout--centered | Single column, max-width constrained |
| layout--sticky-footer | Footer at viewport bottom |
| layout--aside-sticky | Sidebar sticks on scroll |
| layout--topbar-full | Topbar spans full viewport width |
| layout--topbar-content | Topbar aligns with content area |
| layout--footer-full | Footer spans full viewport width |
| layout--footer-content | Footer aligns with content area |
CSS Tokens
| Token | Default | Description |
| --topbar-height | 56px | Synced with topbar.css size variant |
| --layout-aside-left-w | 260px | Left sidebar width |
| --layout-aside-right-w | 260px | Right sidebar width |
| --layout-hero-min-h | 55vh | Hero minimum height |
Zone Diagram
TOPBAR — .topbar (fixed, z-index: 1000)
HERO — <header> (classless, full-bleed, optional)
ASIDE
.layout__aside-left
(260px, sticky)
CONTENT
.layout__content
(flex: 1, scrolls)
ASIDE
.layout__aside-right
(260px, sticky)
Starter HTML
Show starter template
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="exsa/dist/exsa.css">
<link rel="stylesheet" href="exsa/dist/themes/breeze.css">
<link rel="stylesheet" href="exsa/dist/components/topbar.css">
<link rel="stylesheet" href="exsa/dist/components/sidebar.css">
<link rel="stylesheet" href="exsa/dist/components/footer.css">
<link rel="stylesheet" href="exsa/dist/layouts/general.css">
</head>
<body class="exsa layout--has-topbar layout--aside-left layout--aside-sticky">
<!-- Topbar -->
<header class="topbar topbar--xl">
<div class="topbar__left">...</div>
<div class="topbar__right">...</div>
</header>
<!-- Shell: sidebar + content -->
<div class="layout__shell">
<aside class="sidebar layout__aside-left">
<ul class="sidebar__nav">...</ul>
</aside>
<main class="layout__content">
<!-- Your page content -->
</main>
</div>
<footer class="footer">...</footer>
</body>
</html>
Blog Layout
Centered, reading-optimized layout for articles, documentation, and prose-heavy pages.
Blog mode is merged into dist/layouts/general.css — use the shared layout--*
classes for the topbar/hero and blog--* classes for the prose features.
Body Classes
| Class | Effect |
| blog--has-toc | Sticky table-of-contents sidebar |
| blog--prose | Typography boost (larger text, generous spacing) |
| blog--wide | Wider content (720px → 980px) |
| blog--fullwidth | Remove max-width constraint |
Topbar/hero/footer: use the shared layout--has-topbar, layout--has-hero, layout--sticky-footer classes. |
CSS Tokens
| Token | Default | Description |
| --topbar-height | 56px | Synced with topbar.css |
| --layout-content-w | 720px | Readable line length |
Starter HTML
Show starter template
<link rel="stylesheet" href="exsa/dist/layouts/general.css">
<body class="exsa layout--has-topbar blog--has-toc blog--prose">
<header class="topbar">...</header>
<div class="blog__shell">
<main class="blog__content">
<article>
<h1>My Blog Post</h1>
<p>Article content...</p>
</article>
</main>
<aside class="blog__toc">
<a href="#">Introduction</a>
<a href="#">Section 1</a>
</aside>
</div>
<footer class="footer">...</footer>
</body>
Dashboard Layout
Full-viewport admin panel / app shell. Fixed sidebar, scrollable content area, optional right detail panel.
Body is 100dvh tall (mobile-safe) with hidden overflow — the content pane handles its own scroll.
Responsive: below 1024px the sidebar auto-collapses to icon mode (opt out with dash--sidebar-fixed);
below 768px it becomes an overlay drawer with backdrop (toggle dash--sidebar-open on <body>).
Body Classes
| Class | Effect |
| dash--sidebar | Fixed full-height left sidebar |
| dash--sidebar-sm | Always-collapsed icon-only sidebar |
| dash--sidebar-fixed | Opt out of auto-collapse / drawer (never collapses) |
| dash--sidebar-open | Mobile: drawer expanded (JS toggles; shows backdrop) |
| dash--sidebar-dark | Dark background sidebar variant |
| dash--topbar | Fixed topbar above content |
| dash--topbar-full | Topbar spans full width (over sidebar) |
| dash--panel | Optional right detail panel |
| dash--dense | Compact density — overrides the scaling factors |
CSS Tokens
| Token | Default | Description |
| --dash-sidebar-w | 260px | Sidebar width (scales with --space-factor, min 224px) |
| --dash-sidebar-sm | 56px | Collapsed sidebar width (scaled, min 52px) |
| --topbar-height | 56px | Topbar height (from topbar.css; scaled by dashboard) |
| --dash-panel-w | 320px | Right panel width (scaled, min 280px) |
| --dash-padding | 32px | Content gutter (.dash__content-inner) |
| --dash-panel-padding | 24px | Right panel padding |
| --dash-gap | 16px | Grid / card / toolbar spacing |
| --dash-col-min | 180px | Min column width for .dash-grid |
| --dash-toolbar-z | 89 | Sticky toolbar layer |
Starter HTML
Show starter template
<link rel="stylesheet" href="exsa/dist/layouts/dashboard.css">
<body class="exsa dash--sidebar dash--topbar dash--sidebar-dark dash--dense">
<aside class="dash__sidebar">
<div class="dash__sidebar-head">Admin</div>
<ul class="sidebar__nav">...</ul>
</aside>
<div class="dash__backdrop"></div> <!-- mobile drawer backdrop (≤767px) -->
<header class="topbar">
<button class="topbar__toggle">☰</button> <!-- opens the drawer on mobile -->
...
</header>
<main class="dash__content">
<div class="dash__content-inner">
<!-- .dash-grid > .dash-card.dash-stat | .dash__toolbar + table -->
</div>
</main>
</body>
Responsive & Density
Auto icon-collapse — below 1024px the sidebar shrinks to icon mode automatically; add
dash--sidebar-fixed to keep it expanded.
Mobile drawer — below 768px the sidebar slides in over the content as a drawer with a backdrop.
Toggle dash--sidebar-open on <body> (the topbar toggle button works out of the box;
close via backdrop click or Escape).
Density — the whole shell (sidebar, topbar, paddings, gaps) consumes --space-factor and
--font-factor. Use dash--dense or data-profile="compact" for control-heavy apps,
data-profile="spacious" for consumer dashboards.
Content Primitives
| Class | Purpose |
| .dash-card | Panel card — sizes --sm/--lg, slots __head, __title, __sub |
| .dash-grid | Auto-fit responsive grid — tune min column width via --dash-col-min |
| .dash-stat | KPI card — __label, __value (+ --num mono/tabular), __delta (+ --up/--down) |
| .dash__toolbar | Sticky filter bar — sits just below the topbar above tables |
Store Layout
Ecommerce product catalog with announcement bar, cart topbar, filter sidebar, product grid, and sticky footer.
Body Classes
| Class | Effect |
| shop--announce | Promo announcement bar above topbar |
| shop--topbar | Fixed topbar with cart/search |
| shop--hero | Full-bleed promo banner |
| shop--sidebar | Left filter/category sidebar |
| shop--sidebar-right | Right sidebar (cart summary) |
| shop--sticky-footer | Footer at viewport bottom |
CSS Tokens
| Token | Default | Description |
| --topbar-height | 56px | Synced with topbar.css |
| --shop-sidebar-w | 260px | Filter sidebar width |
| --shop-announce-h | 40px | Announcement bar height |
| --shop-hero-min-h | 40vh | Hero minimum height |
Starter HTML
Show starter template
<link rel="stylesheet" href="exsa/dist/layouts/store.css">
<body class="exsa shop--announce shop--topbar shop--sidebar shop--sticky-footer">
<div class="shop__announce">🚀 Free shipping on orders over $50</div>
<header class="topbar">
<!-- Cart, search, account links -->
</header>
<div class="shop__shell">
<aside class="shop__aside">
<div class="shop__aside-group">
<div class="shop__aside-title">Categories</div>
<a href="#">Clothing</a>
</div>
</aside>
<main class="shop__content">
<div class="shop__grid">
<div class="shop__card">
<img class="shop__card-image" src="product.jpg">
<div class="shop__card-body">
<div class="shop__card-title">Product</div>
<span class="shop__card-price">$24.99</span>
</div>
</div>
</div>
</main>
</div>
<footer class="footer">...</footer>
</body>
Fullpage Layout
Single-viewport landing page. Full-screen backdrop — a video/image cover or a seamless panned tile — under a centered brand block with social icon rings and a bottom copyright gradient.
Optional logo mark, nav row, and pure-CSS :target content panels (Dimension-style). Pure CSS with a staggered entrance, prefers-reduced-motion safe. Starter template with a hardcoded palette — copy the dist/templates/fullpage/ folder, open index.html, and edit.
Body Class & Zones
| Class | Effect |
| layout--fullpage | Activates the fullpage shell (add to <body>) |
| .fullpage__backdrop | Full-viewport backdrop — first img/video covers |
| .fullpage__backdrop--pan | Seamless scrolling tile (set --fullpage-pan-image) |
| .fullpage__veil | Pattern + gradient above the backdrop |
| .fullpage__content | Centered brand block (h1 + tagline) |
| .fullpage__social | Icon rings under the tagline (needs icons.css) |
| .fullpage__logo | Optional CSS-diamond mark above the title |
| .fullpage__nav | Link row under the tagline |
| .fullpage__panel | Full-screen overlay — opens via :target (href="#panel-id") |
| .fullpage__panel-close | ✕ that closes panels — link it to the shell id (#home) |
| .fullpage__copyright | Bottom gradient footer |
Tokens
| Token | Default | Purpose |
| --fullpage-font | var(--font-family) | Brand font |
| --fullpage-base-font-size | 15pt | Page scale (13pt / 12pt below 1680 / 1280px) |
| --fullpage-pan-image | none | Seamless tile for --pan mode (required) |
| --fullpage-pan-distance | 2250px | Tile width (1500px below 1680px) |
| --fullpage-pan-duration | 60s | One scroll loop |
| --fullpage-pan-color | #348cb2 | Base color behind the tile |
| --fullpage-pattern | none | Optional texture over the backdrop |
| --fullpage-overlay | dark gradient | Gradient above the backdrop |
| --fullpage-social-size | 2.5em | Icon ring diameter |
| --fullpage-logo-size | 5.5em | Logo ring diameter |
| --fullpage-nav-gap | clamp(10px,2vw,24px) | Nav link spacing |
| --fullpage-panel-bg | rgba(14,15,21,.97) | Panel background |
| --fullpage-panel-width | 620px | Panel content width |
Starter HTML
Copy-paste starter
<body class="exsa layout--fullpage">
<!-- Backdrop: media cover -->
<div class="fullpage__backdrop" aria-hidden="true">
<video src="bg.mp4" muted autoplay loop playsinline></video>
</div>
<!-- OR seamless tile:
<div class="fullpage__backdrop fullpage__backdrop--pan" aria-hidden="true"></div>
-->
<div class="fullpage__veil" aria-hidden="true"></div>
<main class="fullpage__content">
<header class="fullpage__brand">
<h1>Your Name</h1>
<p>Tagline • goes • here</p>
<nav class="fullpage__social" aria-label="Social links">
<a href="#" aria-label="X"><span class="ic ic-social-x"></span></a>
<a href="#" aria-label="GitHub"><span class="ic ic-social-github"></span></a>
<a href="#" aria-label="Email"><span class="ic ic-mail"></span></a>
</nav>
</header>
</main>
<footer class="fullpage__copyright">
<span>© 2026 Your Name.</span>
</footer>
</body>
Starter with panels (Dimension-style)
<main class="fullpage__content" id="home">
<header class="fullpage__brand">
<div class="fullpage__logo" aria-hidden="true"></div>
<h1>Title</h1>
<p>Tagline</p>
<nav class="fullpage__nav">
<a href="#intro">Intro</a>
<a href="#contact">Contact</a>
</nav>
</header>
</main>
<div class="fullpage__panels">
<a href="#home" class="fullpage__panel-close" aria-label="Close">✕</a>
<article class="fullpage__panel" id="intro">
<div class="fullpage__panel-inner">
<h2>Intro</h2>
<p>Panel content — text, images, forms.</p>
</div>
</article>
<article class="fullpage__panel" id="contact">
<div class="fullpage__panel-inner">
<h2>Contact</h2>
<form method="post" action="#">
<div class="fullpage__fields">
<div><label for="name">Name</label><input type="text" id="name"></div>
<div><label for="email">Email</label><input type="email" id="email"></div>
<div class="full"><label for="message">Message</label><textarea id="message" rows="4"></textarea></div>
</div>
<button type="submit" class="btn btn--primary">Send</button>
</form>
</div>
</article>
</div>
<!-- Panels are pure CSS: #intro opens the panel, ✕ (#home) closes it. -->
Onepage Layout
Multi-section marketing landing page — full-viewport photo hero with a top nav and nested hover dropdowns, headline banner, carousel reel, featured article, card grid, and a dark 3-column footer.
Pure CSS (dropdowns, smooth anchor scrolling), no JavaScript required except the optional carousel component. Starter template with a hardcoded palette — copy the dist/templates/onepage/ folder, open index.html, and edit.
Body Class & Zones
| Class | Effect |
| layout--onepage | Activates the onepage shell (add to <body>) |
| .onepage__header | Full-viewport photo hero (nav + brand + action) |
| .onepage__nav | Top nav with nested hover dropdowns (pure CSS) |
| .onepage__hero | Brand block: logo, hr, tagline, circled Start button |
| .onepage__banner | White headline band |
| .onepage__section | White content band (--alt = #fafafa) |
| .onepage__section-head | Centered section title + subtitle |
| .onepage__grid | Responsive card grid |
| .onepage__article | Featured article (image + copy + button) |
| .onepage__footer | Dark footer: columns, icons, copyright |
Tokens
| Token | Default | Purpose |
| --onepage-max-w | var(--width-content) | Container width |
| --onepage-body-bg | #f0f4f4 | Page background |
| --onepage-section-bg | #fff | Content band background |
| --onepage-heading | #483949 | Heading color |
| --onepage-accent | #ef8376 | Link / highlight color |
| --onepage-accent-hover | #df7366 | Accent hover |
| --onepage-header-bg | photo + veil | Hero backdrop (gradient + image) |
| --onepage-footer-bg | #2b252c | Footer background |
| --onepage-icon-size | 2.5em | Circled icon diameter |
Starter HTML
Copy-paste starter
<link rel="stylesheet" href="exsa/templates/onepage.css">
<body class="exsa layout--onepage">
<header class="onepage__header" id="header">
<nav class="onepage__nav" aria-label="Main">
<ul>
<li><a href="#header">Home</a></li>
<li><a href="#work">Work</a></li>
</ul>
</nav>
<div class="onepage__hero">
<h1><a href="#header" class="onepage__logo">Brand</a></h1>
<hr>
<p>Your tagline</p>
<a href="#banner" class="btn">Start</a>
</div>
</header>
<section class="onepage__banner" id="banner">
<h2>Hi. You're looking at <strong>Brand</strong>.</h2>
<p>A short intro paragraph.</p>
</section>
<section class="onepage__section" id="work">
<div class="onepage__container">
<header class="onepage__section-head">
<h2>What we do</h2>
<p>Section subtitle</p>
</header>
<div class="onepage__grid">
<article class="card card--hoverable">
<img class="card__image" src="img.jpg" alt="">
<div class="card__body">
<h3 class="card__title">Feature</h3>
<p class="card__text">Copy...</p>
</div>
</article>
</div>
</div>
</section>
<footer class="onepage__footer">
<div class="onepage__container">
<div class="onepage__cols">
<section class="onepage__col">
<header class="onepage__col-head">Column</header>
<ul class="onepage__list">
<li>Item <span class="onepage__timestamp">2 days ago</span></li>
</ul>
</section>
</div>
</div>
</footer>
</body>
Background System
Token-driven image backgrounds for any page or section — works with every layout. Add bg-page and set
--bg-image; size, position, repeat, parallax, veils, blend modes, and filters are one class each.
Link dist/components/background.css after exsa.css + your theme.
Quick Setup
Copy-paste starter
<link rel="stylesheet" href="exsa/dist/components/background.css">
<!-- Page background: cover + fixed parallax + dark readability veil -->
<!-- Note: use root-relative paths — urls resolve against the CSS that consumes them -->
<body class="exsa bg-page bg-cover bg-fixed bg-veil--dark"
style="--bg-image:url('/img/bg.jpg')">
<!-- Section hero: centered, no repeat, custom overlay -->
<section class="bg-page bg-center bg-no-repeat"
style="--bg-image:url('/img/hero.jpg'); --bg-overlay:rgba(17,139,238,.25); min-height:60vh;">
...
</section>
<!-- Pattern tile: dot-grid, repeat -->
<div class="bg-page bg-pattern">...</div>
<!-- Multiple layers + blend -->
<body class="exsa bg-page bg-blend--multiply"
style="--bg-image:url('/img/photo.jpg'), linear-gradient(var(--color-secondary), var(--color-link))">
</body>
CSS Tokens
| Token | Default | Description |
| --bg-image | none | Background image — comma-separate for layers. Use root-relative paths (/img/bg.jpg): urls resolve against the consuming stylesheet |
| --bg-color | var(--color-bg) | Color behind the image (loading fallback) |
| --bg-size | cover | cover | contain | auto | custom |
| --bg-position | center | center | top | bottom | left | right |
| --bg-repeat | no-repeat | no-repeat | repeat | repeat-x | repeat-y |
| --bg-attachment | scroll | scroll | fixed (parallax) | local |
| --bg-overlay | transparent | Veil color/gradient above the image |
| --bg-blend | normal | normal | multiply | screen | overlay | soft-light |
| --bg-filter | none | Image filter — grayscale(1), sepia(.65), blur(4px)… |
Utility Classes
| Class | Effect |
| .bg-page | Activates the background (image + veil pseudo-layers) |
| .bg-cover / .bg-contain / .bg-auto | Image sizing |
| .bg-center / .bg-top / .bg-bottom / .bg-left / .bg-right | Image position |
| .bg-repeat / .bg-repeat-x / .bg-repeat-y / .bg-no-repeat | Image tiling |
| .bg-fixed / .bg-scroll / .bg-local | Parallax / attachment — fixed auto-falls-back under prefers-reduced-motion |
| .bg-veil--dark / .bg-veil--light / .bg-veil--gradient / .bg-veil--color | Readability overlays above the image |
| .bg-blend--multiply / .bg-blend--screen / .bg-blend--overlay / .bg-blend--soft | Blend the image with the layers beneath it |
| .bg-blur | Backdrop blur only — apply to your own panels |
| .glass / .glass--strong / .glass--dark | Complete glass panels — translucent bg + blur + light border + shadow |
| .bg-grayscale / .bg-sepia | Filters the image only — content stays untouched |
| .bg-pattern | Tiled SVG dot-grid — override --bg-pattern-image / --bg-pattern-size |
| .bg-none | Remove the background |
Combining Layouts
Layouts are independent CSS files with their own namespaces — they don't conflict. You can link multiple layout files on one page if you need hybrid behavior, but typically you choose one layout per page.
| If you need... | Use |
| Documentation site with sidebar | general.css |
| Blog, articles, prose | general.css (blog mode) |
| Admin panel, dashboard | dashboard.css |
| Web store, product catalog | store.css |
| Single-viewport landing page | dist/templates/fullpage/ |
| Multi-section marketing landing page | dist/templates/onepage/ |
Quick Rules
- One layout per page. General + blog on the same body will fight over padding. Pick one.
- Link order matters. Layout CSS goes after
exsa.css, theme, and component CSS.
- Topbar height auto-syncs. Both
general.css and store.css use --topbar-height with :has() — swap topbar--sm for topbar--xl and body padding updates automatically.
- Components work in any layout. Sidebar.css, footer.css, card.css — they're layout-agnostic. Use them everywhere.
- Tokens are your knobs. Override
--topbar-height, --layout-aside-left-w, etc. in your theme to customize spacing.