busy-office-ui
Menu

Layout primitives

Every screen is built from 3 layout primitives — .bo-stack (vertical rhythm), .bo-cluster (a row that wraps), .bo-grid (cards that reflow) — inside the app shell. Each takes custom-property knobs you set inline or in your own CSS.

Choosing one

UseWhenKnobs
.bo-stackVertical rhythm between blocks — form fields, sections, card contents--bo-stack-gap
.bo-clusterA horizontal row that wraps — toolbars, button groups, tag lists--bo-cluster-gap
.bo-gridEqual-width cards that reflow by available width--bo-grid-min, --bo-grid-gap
.bo-app-shellThe page skeleton — header, sidebar, maincontainer-driven

Stack

View code
<div class="bo-stack" style="max-inline-size: 20rem">
  <button class="bo-btn bo-btn--secondary">Default gap</button>
  <button class="bo-btn bo-btn--secondary">— space-4 between blocks</button>
</div>
<div class="bo-stack bo-stack--tight" style="max-inline-size: 20rem; margin-block-start: 1.5rem">
  <button class="bo-btn bo-btn--secondary">--tight</button>
  <button class="bo-btn bo-btn--secondary">— space-2, for dense field groups</button>
</div>
<div class="bo-stack bo-stack--loose" style="max-inline-size: 20rem; margin-block-start: 1.5rem">
  <button class="bo-btn bo-btn--secondary">--loose</button>
  <button class="bo-btn bo-btn--secondary">— space-6, for page sections</button>
</div>

One gap, three sizes. --tight and --loose work by setting --bo-stack-gap — so an inline knob on a --tight element wins over the modifier.

Cluster

Draft
Purchase orders
View code
<!-- default: everything packs to the start and wraps -->
<div class="bo-cluster">
  <button class="bo-btn">Save</button>
  <button class="bo-btn bo-btn--secondary">Cancel</button>
  <span class="bo-badge bo-badge--warning">Draft</span>
</div>

<!-- --split: first item left, rest right — the toolbar shape -->
<div class="bo-cluster bo-cluster--split" style="margin-block-start: 1.5rem">
  <strong>Purchase orders</strong>
  <span class="bo-cluster">
    <button class="bo-btn bo-btn--secondary">Export</button>
    <button class="bo-btn">New order</button>
  </span>
</div>

<!-- --end: everything right — dialog footers -->
<div class="bo-cluster bo-cluster--end" style="margin-block-start: 1.5rem">
  <button class="bo-btn bo-btn--secondary">Cancel</button>
  <button class="bo-btn">Approve</button>
</div>

Items wrap instead of overflowing, so a toolbar degrades gracefully on narrow screens. --split pushes the last child to the far edge (nest a plain cluster to keep several buttons together); --end right-aligns everything.

Grid

Card 1
Card 2
Card 3
Card 4
View code
<!-- --bo-grid-min sets the smallest a column may get before
     the grid drops to fewer columns. Resize the window to see it reflow. -->
<div class="bo-grid" style="--bo-grid-min: 10rem">
  <div class="bo-widget"><div class="bo-widget__body">Card 1</div></div>
  <div class="bo-widget"><div class="bo-widget__body">Card 2</div></div>
  <div class="bo-widget"><div class="bo-widget__body">Card 3</div></div>
  <div class="bo-widget"><div class="bo-widget__body">Card 4</div></div>
</div>

Two behaviours worth knowing. Columns are auto-fill, not auto-fit: a lone card keeps its column width instead of stretching across the row — deliberate, so a dashboard doesn't reflow into one enormous card when a filter empties it (use your own auto-fit grid if you want stretching). And the column floor is min(--bo-grid-min, 100%), which is what stops a wide minimum from overflowing a narrow container — so you can set --bo-grid-min aggressively and trust it.

App shell

<div class="bo-app-shell">          <!-- container: bo-shell -->
  <header class="bo-navbar bo-app-shell__header">…</header>
  <nav class="bo-sidebar-nav bo-app-shell__sidebar">…</nav>
  <main class="bo-app-shell__main">…</main>
</div>

The shell is a named container (bo-shell), so the sidebar collapses to icon-only once the shell is narrow — not the viewport, which is why it behaves correctly inside a split view. Labels stay in the accessibility tree when collapsed. The band itself is stated once, from source, in the shell contract; details on Container queries.

Composing them

Cost centre CC-4021
Open
14
Approved
62
Spend
$184,200
View code
<!-- The whole recipe: shell holds the page, stack gives it
     vertical rhythm, a split cluster is the toolbar, a grid holds cards.
     In a real shell this wrapper is the page's <main> (see the shell
     markup below); it is a div here so this preview does not add a
     second main landmark to the docs page. -->
<div class="bo-app-shell__main">
  <div class="bo-stack">
    <div class="bo-cluster bo-cluster--split">
      <strong style="font-size: var(--bo-font-size-lg)">Cost centre CC-4021</strong>
      <span class="bo-cluster">
        <button class="bo-btn bo-btn--secondary">Export</button>
        <button class="bo-btn">New order</button>
      </span>
    </div>
    <div class="bo-grid" style="--bo-grid-min: 12rem">
      <div class="bo-widget"><div class="bo-widget__body">
        <dl class="bo-kv bo-kv--rows"><div><dt>Open</dt><dd class="bo-u-tabular">14</dd></div></dl>
      </div></div>
      <div class="bo-widget"><div class="bo-widget__body">
        <dl class="bo-kv bo-kv--rows"><div><dt>Approved</dt><dd class="bo-u-tabular">62</dd></div></dl>
      </div></div>
      <div class="bo-widget"><div class="bo-widget__body">
        <dl class="bo-kv bo-kv--rows"><div><dt>Spend</dt><dd class="bo-u-tabular">$184,200</dd></div></dl>
      </div></div>
    </div>
  </div>
</div>

This is the whole system: the shell holds the page, a stack gives it rhythm, a split cluster is the toolbar, a grid holds the cards. Every ERP screen in Patterns is this shape with more content.

Knobs generated from the CSS

KnobDefaultApplies to
--bo-stack-gap var(--bo-space-4) .bo-stack
--bo-cluster-gap var(--bo-space-2) .bo-cluster
--bo-grid-min 16rem), 100% .bo-grid
--bo-grid-gap var(--bo-space-4) .bo-grid

Set them inline for a one-off (style="--bo-grid-min: 12rem") or in your own CSS for a whole region. Screen-reader-only text is .bo-visually-hidden, documented with the other utilities.

↑↓ move Enter open Esc close