Skip to content

Layout primitives

Nine classes compose almost every page. They are deliberately unopinionated about what they contain, and they are meant to be put on a component rather than around it:

<div class="ads-card__foot ads-cluster"></div>

Why layout.css loads last

Both selectors above are one class, so the cascade is decided by file order. With layout first, the card's own display: flex would win and the cluster would silently do nothing. A layout primitive that loses to the thing it is applied to is not a primitive.

The nine

ads-stack
Vertical rhythm. Children are spaced by one gap; no per-element margins to collapse or double. The default container for a form, a page, a card body.
ads-cluster
A horizontal group that wraps. Buttons in a toolbar, filters above a table, tags in a row.
ads-spread
A row with the first child pushed left and the last pushed right. A card head with a title and an action.
ads-grid
Auto-fitting columns. The track is minmax(min(var(--ads-col, 16rem), 100%), 1fr) — the min(…, 100%) is what keeps it from overflowing a 320px screen. Set --ads-col to change the target column width, or use ads-col-sm / ads-col-lg.
ads-with-aside
Content plus a sidebar that drops below when there is no room, with no media query — the main pane has flex-grow: 999 so it takes everything until the aside's basis no longer fits.
ads-split
Two panes that scroll independently and fill the viewport height. A mail client, a master-detail view. Collapses to one column below 62em.
ads-fill
Take the remaining room in a flex row, and allow truncation (min-width: 0).
ads-push
Push this item and everything after it to the far end.
ads-center / ads-centered
Centre a block horizontally; ads-centered is the full-page version used by sign-in and error pages.

Modifiers

<div class="ads-stack ads-gap-sm"></div>   <!-- tighter -->
<div class="ads-grid ads-col-lg"></div>    <!-- wider columns -->

Gaps come from the space scale, so they follow --ads-space-scale like everything else.

Utilities

ads-muted
Secondary text colour.
ads-truncate
One line, ellipsis. Needs a parent that allows shrinking — pair it with ads-fill.
ads-toned
Take the colour of the current tone (see below).
ads-skip
The skip-to-content link. Put it first in <body>; it is visually hidden until focused.

Tones instead of variants

Components that can carry an intent read --ads-tone rather than growing a modifier per intent:

<span class="ads-status ads-tone-warning">Degraded</span>
<div class="ads-alert ads-tone-danger"></div>
<div class="ads-progress ads-tone-success"></div>

Six tones — brand, success, warning, danger, info, neutral — each setting five variables the component reads: --ads-tone (readable ink), --ads-tone-solid (the fill), --ads-tone-tint, --ads-tone-fill and --ads-tone-line. Adding a seventh intent is one line in derive.css, not one line per component.

Not everything is a card

Border, fill, radius and shadow each say separate object. Spending all four on every block flattens the hierarchy until nothing stands out. Lead with a card when the content really is a discrete object; otherwise a heading and an ads-stack are enough.

Reflow

Everything here is reflow-safe at 320 CSS pixels, which is a 1280px window at 400% zoom — the setting someone with low vision actually uses. If you add a layout of your own, the rule that catches most mistakes is: no absolute length may act as a floor. min-width: 22rem and minmax(22rem, 1fr) both overflow a 20rem viewport; min(22rem, 100%) does not.