Theme Framework How the LeaseLeads theme frontend works

The Rhythm System

lib/rhythm — a fluid clamp()-based vertical-spacing scale exposed as --vertical-* custom properties.

On this page 4

Vertical spacing on a page is not per-component guesswork. lib/rhythm defines one fluid scale — three clamp() steps that grow with the viewport — and every Module draws its top/bottom spacing from it. The result is consistent rhythm down the page and a single place for the editor to override it.

Two words: gutter and rhythm

The system distinguishes two kinds of vertical space, and the vocabulary matters:

  • gutter — spacing between and around Modules (the margin-block / padding-block a Module puts above and below itself).
  • rhythm — spacing between elements within a stack (the row-gap inside a content stack, a list, a form).

Both are exposed as --vertical-* CSS custom properties, in three fluid sizes plus a default:

scss
// lib/rhythm.scss (excerpt)
--vertical-gutter-sm: clamp(1.5rem, 1.379vw + 1.19rem, 2.5rem);
--vertical-gutter-md: clamp(2.5rem, 3.448vw + 1.724rem, 5rem);
--vertical-gutter-lg: clamp(2.5rem, 6.897vw + 0.948rem, 7.5rem);

--vertical-rhythm-sm: clamp(0.5rem, 1.034vw + 0.267rem, 1.25rem);
--vertical-rhythm-md: clamp(1.25rem, -0.345vw + 1.578rem, 1.5rem);
--vertical-rhythm-lg: clamp(1.5rem, 1.379vw + 1.19rem, 2.5rem);

How a Module applies it

A Module declares its default spacing with default(), then commits those defaults to real properties with apply():

scss
// modules/Intro/Intro.scss
@use 'lib/rhythm' as rhythm;

.intro {
  @include rhythm.default(sm, md, none, none);
  @include rhythm.apply();
}

default($padding_top, $padding_bottom, $margin_top, $margin_bottom) sets the --vertical-*-default custom properties (here: small top padding, medium bottom padding, no vertical margin). apply() then writes the actual box properties, reading each one with a fallback to its default:

scss
// lib/rhythm.scss
@mixin apply() {
  margin-block-start:  var(--vertical-margin-top,     var(--vertical-margin-top-default));
  margin-block-end:    var(--vertical-margin-bottom,  var(--vertical-margin-bottom-default));
  padding-block-start: var(--vertical-padding-top,    var(--vertical-padding-top-default));
  padding-block-end:   var(--vertical-padding-bottom, var(--vertical-padding-bottom-default));
}

That two-property shape — a bare --vertical-margin-top overriding a --vertical-margin-top-default — is the whole point.

Note

Why defaults and overrides are separate variables. apply() reads the override variable first and falls back to the default. The SCSS sets only the default, so the editor can set the override without touching your stylesheet. When an author picks a Spacing value, the Spacing Field emits --vertical-margin-top (etc.) inline on the element, and it wins over the default the Module declared — no front-end change needed. See Fields → CSS custom properties.

The rest of the toolkit

Mixin Use it for
default($pt, $pb, $mt, $mb) Declare a Module's default gutter (padding/margin block).
apply() Commit the gutter variables to real box properties.
stack($size) A vertical flex column with row-gap: var(--vertical-rhythm-#{$size}) — rhythm within a stack.
pile() Overlap children in one grid cell (grid-template-areas: 'pile').
gutter($location, $type, $size) Apply a single gutter value to one edge (top / bottom / both) as margin or padding.
negate($location) Pull back a gutter with a negative margin (e.g. to bleed a child full-width).

stack is how you get in-flow rhythm between elements:

scss
// lib/rhythm.scss
@mixin stack($size: default) {
  display: flex;
  flex-direction: column;
  row-gap: var(--vertical-rhythm-#{$size});
}
Important

Reach for gutter mixins (default/apply/gutter/negate) when you're spacing a Module against the page, and stack when you're spacing elements inside a component. Mixing them up — e.g. adding a gutter to get gap between list items — breaks the editor's Spacing control, because the Spacing Field only drives the gutter variables.