Docs
Next

Spacing model

How composition decides spacing: rich-text rhythm, section padding and when to reach for Stack.

On this page

In Systhema you rarely set a margin. Spacing follows from how components are nested: content containers space their children from tokens, and an Article spaces its sections. This page explains the two mechanisms and the one tool for everything else, Stack.

A typical page shell:

src/app/layout.tsx
import type { ReactNode } from 'react'
import { Article, FooterSimpleInstance, HeaderInstance, SysthemaProvider } from '@systhemaui/next'

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en" data-theme="default">
      <body className="bg-layout-main">
        <SysthemaProvider>
          <HeaderInstance />
          <main id="main-content">
            <Article>{children}</Article>
          </main>
          <FooterSimpleInstance />
        </SysthemaProvider>
      </body>
    </html>
  )
}

Each page then renders Sections with headings, paragraphs and components inside, and none of them carries a margin class.

Rich-text rhythmLink to this section

Every content container is a rich-text box: it carries the richtext class, and the .richtext > * rules space its direct children from tokens. Section wraps its children in container > richtext; Article, Card, Column, Row and FeatureContent carry richtext themselves, and the hero content slot uses the same rhythm.

Direct childSpace above and below
Heading (h1–h6)1.2ch above, 0.6ch below
Paragraph or list0.7lh each side
Quote--typography-block-margin-y
Separator--typography-block-margin-y plus --separator-margin-y
Figure1.5 × --typography-block-margin-y, 2 × from md
Block component (Columns, Stack, cards, accordions)2 × --typography-block-margin-y
The same block component twice in a row1 × between them
First and last childno outer margin

Headings and paragraphs scale with their own font size (ch, lh), and the block spacing follows the typography.blockMarginY token per breakpoint.

Stack replaces the rhythmLink to this section

Stack is a flex container with a token gap. Its children are no longer direct children of the rich-text box, so the rhythm stops applying to them and the gap you chose applies instead. That is right for a row of chips, buttons or icons, or a row of cards. It is wrong around prose: the heading and paragraphs in the second band below lose their rhythm.

Rich-text rhythm, then the same content inside a Stack
import { Chip, Heading, Paragraph, Section, Stack } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="w-full">
      <Section layoutBackground="main">
        <Heading.h3>Direct children of the section</Heading.h3>
        <Paragraph>Each element is spaced by the rich-text rules, from tokens.</Paragraph>
        <Paragraph>A paragraph gets 0.7lh above and below, a heading 1.2ch above.</Paragraph>
        <Stack direction="row" gap="xs" wrap>
          <Chip>Tokens</Chip>
          <Chip>Rhythm</Chip>
          <Chip>Stack for the row</Chip>
        </Stack>
      </Section>
      <Section layoutBackground="alternative">
        <Stack direction="col" gap="xs">
          <Heading.h3>The same content in a Stack</Heading.h3>
          <Paragraph>The rich-text rules no longer reach these elements.</Paragraph>
          <Paragraph>Only the Stack gap separates them.</Paragraph>
        </Stack>
      </Section>
    </div>
  )
}

Section paddingLink to this section

Between sections, Article decides:

  • Each Section has --section-padding-y of padding inside it (py-section), and the article adds the same amount as margin around a run of sections. Adjacent sections sit flush, with no margin between them.
  • Two adjacent sections with neither a theme nor a layoutBackground read as one band: the padding between them is removed.
  • Two adjacent sections with the same theme and layoutBackground also merge.
  • A section, or a full-width figure, that is the first or last child of the article drops the article's own padding on that side.

So a page of plain sections reads as continuous content, and a section with a background starts a new band. Give one of two neighbours a layoutBackground (or a theme) when they should read as two. See Themes and backgrounds.

What this means for your markupLink to this section

  • Put headings, paragraphs, lists and block components directly inside Section, Card or Column. Do not wrap prose in a Stack or a div.
  • Use Stack for a row of inline elements or of cards. A single button among paragraphs goes inside a Paragraph, as the Payload editor produces it.
  • Do not fight the rhythm with margin utilities such as mt-8 or !mb-0. If the rhythm is wrong for the whole project, change the token.

Changing the rhythmLink to this section

The rhythm and the padding come from responsiveSizing tokens, one value per breakpoint. Change them in Figma, or override them in systhema.config.ts:

systhema.config.ts
const config: SysthemaConfig = {
  customTokens: {
    responsiveSizing: {
      lg: {
        typography: { blockMarginY: '28px' },
        section: { paddingY: '120px' },
      },
    },
  },
}

The relevant tokens are typography.blockMarginY, section.paddingY, separator.marginY and the gap.* scale. Write lengths with a unit ('28px'); see Custom tokens. How those pixel values become CSS, fixed or fluid, is on Responsive sizing.