Skip to content

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-xsxl) 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…xl tokens that cap Container and drive the Grid/Split container-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 × = 4rem20 × = 8rem
1 × data-density-shift5 × = 2rem10 × = 4rem
2 × data-density-shift2 × = 0.8rem5 × = 2rem
3 × data-density-shift1 × = 0.4rem2 × = 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.

sveltesupabasepwa

Course Atlas

Community-maintained maps of local courses.

sveltekitmaplibre

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.

TokenValue
--hz-width-sm640px
--hz-width-md968px
--hz-width-lg1200px
--hz-width-xl1440px
--hz-width-full100%

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.