EXSA
Generate custom CSS
Layouts
EXSA ships with one composable layout base — General (the paper model) — plus a dashboard app-frame focus layer, and store & blog recipes built from the paper pieces. Link the base (add the focus layer for app frames), structure your HTML with the documented zones, and compose the recipe you need.

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 mode class to the sheet — .layout__page--app for the dashboard focus layer (link dashboard.css after general.css), .layout__grid--centered for blog.
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 paper model
Element-driven page shell: the markup is the declaration. A sheet (.layout__page) carries the page's look, a grid (.layout__grid) cuts it into pieces, and components fill the pieces. Stacking keys on the sheet's own width — correct even when embedded.

Zones & Modifiers

ClassEffect
.layout__pageThe sheet — width, look, and responsive context (set --layout-page-w for e.g. 80%)
.layout__gridThe pieces container — all zones below are optional
.layout__header / .layout__footerFull-width bands above / below the pieces
.layout__aside-start / --endSide pieces (logical — RTL-safe)
.layout__contentThe main piece — takes all leftover space
.layout__heroFull-width hero band above the pieces (pair with hero.css)
.layout__topbarIn-page sticky topbar (pair with the .topbar component)
.layout__aside-start--sticky / --end--stickySide piece sticks while scrolling (auto-clears the topbar)
.layout__grid--centeredContent piece reads --layout-measure
.layout__page--autoSheet sizes to content (sticky footer off)
.layout__page--frameOutside topbar + footer adopt the sheet's width
.sidebar--collapsed on a side pieceIcon-width rail — the track auto-matches its 56px width (--sidebar-collapsed-w); sub-menus open in a tray

CSS Tokens

TokenDefaultDescription
--layout-page-w100%Sheet width (80% centers it on the table)
--layout-page-min-h100vh/dvhSheet height — footer pinned to the bottom
--layout-page-bg / --border / --radius / --shadowsurface tokensThe sheet's paper look
--layout-page-pad20pxSheet inner padding
--layout-aside-start-w / --layout-aside-end-wclamp(220px, 25%, 300px)Side piece widths
--layout-gap16pxSpacing between pieces
--layout-hero-min-h55vhHero band height
--layout-measure--width-contentCentered content width
--layout-sticky-topautoSticky offset — syncs to the topbar height

Zone Diagram

TOPBAR — .topbar outside (full-bleed) or .layout__topbar inside (sticky)
HERO — .layout__hero (optional band)
ASIDE
.layout__aside-start
(clamp width, sticky opt-in)
CONTENT
.layout__content
(1fr, takes the rest)
ASIDE
.layout__aside-end
(clamp width, sticky opt-in)

Starter HTML

Show starter template
HTML
<!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 has-topbar">

  <!-- Topbar — outside the sheet, full-bleed -->
  <header class="topbar topbar--xl">
    <div class="topbar__left">...</div>
    <div class="topbar__right">...</div>
  </header>

  <!-- The sheet + its pieces -->
  <div class="layout__page">

    <div class="layout__grid">
      <aside class="sidebar layout__aside-start layout__aside-start--sticky">
        <ul class="sidebar__nav">...</ul>
      </aside>

      <main class="layout__content">
        <!-- Your page content -->
      </main>
    </div>

    <footer class="footer layout__footer">...</footer>

  </div>

</body>
</html>
Blog & Reading (Shell Recipe)
Reading-optimized pages are a shell recipe, not a separate layout: the universal shell composes the column, the TOC, and the stickiness. Link only dist/layouts/general.css (+ dist/components/sidebar.css for a styled TOC).

Classes & Tokens

ClassEffect
.layout__grid--centeredSingle centered reading column (width = --layout-measure)
--layout-measureReading width override (default 1080px; 720px is the classic blog measure)
.layout__aside-endEnd piece for the table of contents
.layout__aside-end--stickyTOC sticks on scroll (stacks at ≤860px)
Topbar/hero/footer: the shared .topbar + has-topbar, .layout__hero, and .layout__footer pieces.

CSS Tokens

TokenDefaultDescription
--layout-measure--width-content (1080px)Reading column width for --grid--centered

Starter HTML

Show starter template
HTML
<link rel="stylesheet" href="exsa/dist/layouts/general.css">
<link rel="stylesheet" href="exsa/dist/components/sidebar.css">

<body class="exsa has-topbar" style="--layout-measure:720px">

  <header class="topbar">...</header>

  <div class="layout__page">
    <div class="layout__grid layout__grid--centered">
      <main class="layout__content">
        <article>
          <h1>My Blog Post</h1>
          <p>Article content...</p>
        </article>
      </main>
      <aside class="layout__aside-end layout__aside-end--sticky">
        <ul class="sidebar__nav">
          <li><a class="sidebar__link" href="#">Introduction</a></li>
          <li><a class="sidebar__link" href="#">Section 1</a></li>
        </ul>
      </aside>
    </div>
  </div>

  <footer class="footer layout__footer">...</footer>

</body>
Dashboard Focus Layer (app frame)
Dashboards get their own geometry, shipped as a focus layer over the paper model: .layout__page--app (in dist/layouts/dashboard.css) turns the sheet into a full-viewport app frame — no body scroll, a fixed full-height rail, and a scrolling content pane. Link it after dist/layouts/general.css — same cascade layer, so an app frame never stacks. No drawer, no auto-collapse, no panel: the layout is only the frame; anything fancier is your application's concern.

Pieces

ClassEffect
.layout__page--appSheet = the viewport (100dvh, no body scroll)
.layout__aside-startFixed full-height rail (pair with the .sidebar component for its chrome)
.layout__contentThe scrolling pane (clears the rail)
.topbar + has-topbarOptional outside topbar — rail and pane clear it automatically

CSS Tokens

TokenDefaultDescription
--layout-aside-start-wclamp(220px, 25%, 300px)Rail width
--topbar-height56pxTopbar clearance (synced automatically)

Starter HTML

Show starter template
HTML
<link rel="stylesheet" href="exsa/dist/layouts/general.css">
<link rel="stylesheet" href="exsa/dist/layouts/dashboard.css">
<link rel="stylesheet" href="exsa/dist/components/sidebar.css">

<body class="exsa has-topbar">

  <header class="topbar">
    <!-- App topbar — rail and pane clear it automatically -->
  </header>

  <div class="layout__page layout__page--app">
    <div class="layout__grid">

      <aside class="sidebar layout__aside-start">
        <ul class="sidebar__nav">
          <li><a class="sidebar__link" href="#">Dashboard</a></li>
          <li><a class="sidebar__link" href="#">Reports</a></li>
        </ul>
      </aside>

      <main class="layout__content">
        <!-- The scrolling pane — dash cards, grids, stats (components/dashboard.css) -->
      </main>

    </div>
  </div>

</body>
Store Recipe (paper model)
Ecommerce is a recipe, not a separate layout: the announce strip, a sticky filter aside, and the .shop__grid product cards — all on the paper model. Link dist/layouts/general.css + dist/components/product-card.css.

Pieces

ClassEffect
.layout__announceFixed full-bleed promo strip — body padding, topbar position, and sticky offsets follow it automatically
.topbar + has-topbarFixed cart/search topbar, below the announce strip
.layout__aside-start--stickyFilter/category sidebar, sticking below announce + topbar
.layout__contentThe main piece — hosts the product grid
.shop__grid / --compactResponsive product grid (components/product-card.css)
.shop__cardProduct tile (components/product-card.css)

CSS Tokens

TokenDefaultDescription
--layout-announce-h40pxAnnounce strip height
--layout-aside-start-wclamp(220px, 25%, 300px)Filter sidebar width
--layout-page-w100%Sheet width (set on :root/body when the topbar should match)
--layout-hero-min-h55vhPromo hero height (via .layout__hero)

Starter HTML

Show starter template
HTML
<link rel="stylesheet" href="exsa/dist/layouts/general.css">
<link rel="stylesheet" href="exsa/dist/components/product-card.css">

<body class="exsa has-topbar">

  <div class="layout__announce">🚀 Free shipping on orders over $50</div>

  <header class="topbar">
    <!-- Cart, search, account links -->
  </header>

  <div class="layout__page">
    <div class="layout__grid">

      <aside class="layout__aside-start layout__aside-start--sticky">
        <div class="shop__aside-group">
          <div class="shop__aside-title">Categories</div>
          <a href="#">Clothing</a>
        </div>
      </aside>

      <main class="layout__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 layout__footer">...</footer>
  </div>

</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
HTML
<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

TokenDefaultDescription
--bg-imagenoneBackground image — comma-separate for layers. Use root-relative paths (/img/bg.jpg): urls resolve against the consuming stylesheet
--bg-colorvar(--color-bg)Color behind the image (loading fallback)
--bg-sizecovercover | contain | auto | custom
--bg-positioncentercenter | top | bottom | left | right
--bg-repeatno-repeatno-repeat | repeat | repeat-x | repeat-y
--bg-attachmentscrollscroll | fixed (parallax) | local
--bg-overlaytransparentVeil color/gradient above the image
--bg-blendnormalnormal | multiply | screen | overlay | soft-light
--bg-filternoneImage filter — grayscale(1), sepia(.65), blur(4px)…

Utility Classes

ClassEffect
.bg-pageActivates the background (image + veil pseudo-layers)
.bg-cover / .bg-contain / .bg-autoImage sizing
.bg-center / .bg-top / .bg-bottom / .bg-left / .bg-rightImage position
.bg-repeat / .bg-repeat-x / .bg-repeat-y / .bg-no-repeatImage tiling
.bg-fixed / .bg-scroll / .bg-localParallax / attachment — fixed auto-falls-back under prefers-reduced-motion
.bg-veil--dark / .bg-veil--light / .bg-veil--gradient / .bg-veil--colorReadability overlays above the image
.bg-blend--multiply / .bg-blend--screen / .bg-blend--overlay / .bg-blend--softBlend the image with the layers beneath it
.bg-blurBackdrop blur for your own panels — reads the active skin's --surface-backdrop, so it blurs only under a frosted skin (glossy/glow)
.glass / .glass--strong / .glass--darkComplete glass panels — translucent bg + light border + shadow; the blur follows the active skin's frost tokens (--surface-backdrop / -strong)
.bg-grayscale / .bg-sepiaFilters the image only — content stays untouched
.bg-patternTiled SVG dot-grid — override --bg-pattern-image / --bg-pattern-size
.bg-noneRemove the background
Combining Layouts
The paper model is the base; the dashboard focus layer extends it — same namespace by design. Link general.css alone for sites, stores, and blogs; add dashboard.css after it for app frames.
If you need...Use
Documentation site with sidebargeneral.css
Blog, articles, prosegeneral.css (blog mode)
Admin panel, dashboardgeneral.css + dashboard.css (.layout__page--app app frame)
Web store, product cataloggeneral.css + product-card.css (store recipe)
Single-viewport landing pagegeneral.css (100dvh sheet + .layout__hero)
Multi-section marketing landing pagegeneral.css + hero.css (.layout__header + .layout__hero + pieces)

Quick Rules

  • One base, one focus. Link general.css always; add dashboard.css after it only for app frames. The recipes are classes on the same sheet — they don't fight.
  • Link order matters. Layout CSS goes after exsa.css, theme, and component CSS — and dashboard.css goes after general.css.
  • Topbar height auto-syncs. general.css uses --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 --layout-page-w, --layout-aside-start-w, --layout-gap, etc. in your theme to customize the sheet and its pieces.