Domphy

Spacing System

themeSpacing(n) — the base scale

themeSpacing(n) returns n * 0.25rem (n/4 em at 16px root = 4px steps):

import { themeSpacing } from "@domphy/theme"

themeSpacing(1)   // "0.25rem" = 4px
themeSpacing(2)   // "0.5rem"  = 8px
themeSpacing(3)   // "0.75rem" = 12px
themeSpacing(4)   // "1rem"    = 16px
themeSpacing(5)   // "1.25rem" = 20px
themeSpacing(6)   // "1.5rem"  = 24px
themeSpacing(8)   // "2rem"    = 32px
themeSpacing(12)  // "3rem"    = 48px
themeSpacing(16)  // "4rem"    = 64px

Never hardcode spacing literals ("16px", "1.5rem") — @domphy/doctor flags those as raw-spacing-value.

Density-aware spacing

For bounded controls (buttons, inputs, badges), multiply by themeDensity(l) so the component scales with the user's density preference:

import { themeSpacing, themeDensity } from "@domphy/theme"

const Button = {
  button: "Click me",
  style: {
    paddingBlock:  (l) => themeSpacing(themeDensity(l) * 1),  // 4px × density
    paddingInline: (l) => themeSpacing(themeDensity(l) * 3),  // 12px × density
    borderRadius:  (l) => themeSpacing(themeDensity(l) * 1),
  },
}

Density scale (dataDensity):

DensityMultiplierTypical use
decrease-40.75Ultra-compact (data grids)
decrease-21.0Compact
inherit / decrease-11.25Default
increase-11.5Comfortable
increase-22.0Touch targets
increase-42.5Accessible large
const CompactTable = {
  div: TableContent,
  dataDensity: "decrease-2",   // all descendants inherit compact density
}

Layout vs component spacing

UseRule
Component internal padding (button, input, card)themeSpacing(density * n)
Gap between components in a layoutthemeSpacing(n) (no density)
Section/page paddingthemeSpacing(n) (no density)
Between tightly-related elementsthemeSpacing(1–2)
Between loosely-related sectionsthemeSpacing(4–8)

The reason: density should affect UI chrome (button size, input height) but NOT the page's structural rhythm. If density affected both, a "compact" page would have a broken layout grid.

Common spacing values

SpacingValueUse
themeSpacing(0.5)2pxHairline gap between tightly stacked items
themeSpacing(1)4pxIcon-to-label gap, tight list gap
themeSpacing(2)8pxInternal button padding, small component gap
themeSpacing(3)12pxMedium gap, form label-to-input
themeSpacing(4)16pxDefault content padding, card padding
themeSpacing(6)24pxSection padding, between cards
themeSpacing(8)32pxBetween sections
themeSpacing(12)48pxLarge section breaks

Using spacing in grid/flex layouts

const CardGrid = {
  div: (l) => cards.get(l).map((card) => ({ div: card.title, _key: card.id })),
  style: {
    display: "grid",
    gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
    gap: themeSpacing(4),   // 16px gap between cards
    padding: themeSpacing(6),   // 24px page padding
  },
}

Padding shorthand

// All sides
padding: themeSpacing(4)

// Block and inline separately
paddingBlock: themeSpacing(2),
paddingInline: themeSpacing(4),

// Individual sides
paddingTop: themeSpacing(2),
paddingRight: themeSpacing(4),
paddingBottom: themeSpacing(2),
paddingLeft: themeSpacing(4),

Domphy's doctor watches these properties: margin, padding, gap, rowGap, columnGap and all their longhand variants.

Border radius

Follow the bounded control pattern:

// Tight radius (small components)
borderRadius: (l) => themeSpacing(themeDensity(l) * 0.5),

// Medium radius (cards, modals)
borderRadius: themeSpacing(2),   // 8px (no density — structural, not control)

// Large radius (pills, chips)
borderRadius: "9999px",   // exception: fully-round is categorical, not a spacing value

Spacing in scroll containers

const ScrollArea = {
  div: Content,
  style: {
    overflowY: "auto",
    padding: themeSpacing(4),
    // Prevent content from touching scrollbar
    paddingRight: themeSpacing(6),
  },
}

inset shorthand

For overlays and sticky elements:

const Overlay = {
  div: null,
  style: {
    position: "fixed",
    inset: 0,   // shorthand for top/right/bottom/left: 0 (no spacing needed)
    background: "rgba(0,0,0,0.5)",
  },
}