Spacing & Sizing
Three sizing systems, each answering a different question: a fixed spacing scale for explicit distances, density spacing that tightens as content nests, and breakpoint width tokens.
- Spacing scale: fixed steps (
--hz-space-xs…xl) for explicit, context-independent distances; they back the component defaults. - Density spacing: two context-aware distances (
near/away), derived from one grid unit, that tighten automatically as content nests. Use these for page rhythm instead of picking steps by hand. - Breakpoint widths: the
--hz-width-sm…xltokens that capContainerand drive theGrid/Splitcontainer-query thresholds.
Spacing tokens
--hz-space-none 0 --hz-space-xs 0.5rem --hz-space-sm 1rem --hz-space-md 2rem --hz-space-lg 4rem --hz-space-xl 8rem Density spacing
An alternate spacing model, adapted from Complementary Space. Instead of picking from a scale, use two distances: --hz-space-near between
related things and --hz-space-away between unrelated things. Both derive from the --hz-density grid unit (0.4rem), so overriding one custom property
retunes every distance on the page.
The body starts un-shifted — depth 0 in the table below. Each data-density-shift on an ancestor moves everything inside it down one row, so nested
regions read denser without introducing new spacing values. Depth 3 is the floor: a fourth nested
shift keeps depth 3's values. The near multipliers walk the 1-2-5-10 ladder, so a shifted region's
away always equals its parent's near. | Shift depth | --hz-space-near | --hz-space-away |
|---|---|---|
| none (body) | 10 × = 4rem | 20 × = 8rem |
| 1 × data-density-shift | 5 × = 2rem | 10 × = 4rem |
| 2 × data-density-shift | 2 × = 0.8rem | 5 × = 2rem |
| 3 × data-density-shift | 1 × = 0.4rem | 2 × = 0.8rem |
Live demo
A real composition built from Stack and Cluster: page, sections, cards, tag rows. The code is
what you'd write on a fresh top-level page. Every distance is gap="near", gap="away", or padding="near", and each nested region adds one data-density-shift so the whole hierarchy tightens on its own.
Projects
Flight Tracker
Round scoring and disc flight stats for every throw.
Course Atlas
Community-maintained maps of local courses.
Archive
Retired experiments live here.
<Stack gap="away"> <!-- sections: away = 8rem -->
<Stack gap="near" data-density-shift> <!-- section: 1 shift → near = 2rem -->
<h2>Projects</h2>
<Cluster gap="near" align="stretch">
<Stack padding="near"> <!-- card inherits the shift: 2rem -->
<Stack gap="near" data-density-shift> <!-- card rhythm: 2 shifts → 0.8rem -->
<h3>Flight Tracker</h3>
<p>Round scoring and disc flight stats.</p>
<Cluster gap="near" data-density-shift> <!-- tags: 3 shifts → 0.4rem -->
<span>svelte</span>
<span>supabase</span>
<span>pwa</span>
</Cluster>
</Stack>
</Stack>
<!-- … more cards … -->
</Cluster>
</Stack>
<Stack gap="near" data-density-shift>
<h2>Archive</h2>
<p>Retired experiments live here.</p>
</Stack>
</Stack>The preview compensates for where it sits. This section is already two density levels deep:
the docs shell and this page section each add a shift. Each ambient level costs one rung on
the ladder, so the live version swaps each near for away (one rung back
up) and drops the shifts the shell already provides. Every distance then renders at its fresh-page
value: heading and cards at 2rem, card rhythm at 0.8rem, tag gaps at 0.4rem. The one exception is
the 8rem section spacing, which the tightest level caps at 2rem here. Three levels is as tight as
the scale goes: a fourth nested shift keeps the third level's values.
Width / breakpoint tokens
Overriding these tokens retunes Container max-widths, Split's stackBelow threshold, and Grid's fluid { min } mode, because they all resolve via var(). The one
exception is Grid's band breakpoints (base/sm/md/lg): they mirror these values but remain
literal system constants, because CSS cannot read custom properties inside media or container
queries.
| Token | Value |
|---|---|
--hz-width-sm | 640px |
--hz-width-md | 968px |
--hz-width-lg | 1200px |
--hz-width-xl | 1440px |
--hz-width-full | 100% |
Override the spacing scale, the density unit, or a width token with a plain CSS custom
property, or set space, density, and width in the hyzer config; see Theming → Tokens & Overrides.
Logical axes
Where a component takes spacing per axis (the layout primitives' paddingInline and paddingBlock props), the names are the CSS logical
properties they set, not physical x/y. The inline axis runs along
the line of text; the block axis runs across it. That keeps the props correct in every writing
mode: in RTL layouts the inline axis flips with the text, and in vertical writing modes it
runs top-to-bottom, where a physical “x” would pad the wrong edges.
The library's own CSS follows the same rule (centering is margin-inline: auto,
gutters are padding-inline), so each prop maps 1:1 onto the property it drives. padding remains the both-axes shorthand; the per-axis longhands win where set.
Positioning follows the same logical-first rule for placing a tooltip, popover, or dropdown menu next to its trigger.