Foundations

Spacing.

Give the details room to breathe.

Numeric primitives set the rhythm. Semantic relationships and density give each layout the room it needs.

The primitive scale

The suffix is a percentage of an 8px base unit: space.100 is 8px and space.200 is 16px. The 16 steps include zero, fine 2px adjustments, and 96px / 160px layout extensions. Values use rem; pixel labels assume a 16px root. Appearance and density never rewrite these primitives.

selected-token.css
.example {
  padding-inline: var(--dixy-space-200);
}

Meaning between the numbers

Primitives describe a value. Semantic tokens describe a relationship. Component aliases describe a local decision, such as --dixy-card-padding → --dixy-spacing-inset-md → --dixy-space-200. Change semantic mappings on a density scope to customize relationships.

Default semantic mappings
FamilyUseSize → primitive
spacing.inline.*Related items in a row
xs → space.025
sm → space.050
md → space.100
lg → space.150
xl → space.200
spacing.stack.*Related items in a column
xs → space.050
sm → space.100
md → space.200
lg → space.300
xl → space.400
spacing.section.*Major content sections
sm → space.400
md → space.600
lg → space.800
xl → space.1200
spacing.inset.*Internal container padding
xs → space.100
sm → space.150
md → space.200
lg → space.300
xl → space.400

Use inline xs–sm for icon/label relationships, stack sm–md for fields and groups, and section sm–xl for major regions. Section and inline mappings stay fixed across the initial density modes. Inset and stack use explicit adjacent scale steps, with no global multiplier.

Try density and layout

Your workspace

Spacing follows density. Controls keep their height.

example.tsx
import { Button, Heading, Inline, Stack, Text, Theme } from "@dixy-ui/react";
import "@dixy-ui/react/styles.css";

export function Settings() {
  return (
    <Theme density="default" dir="ltr">
      <Stack gap="md" style={{ padding: "var(--dixy-card-padding)" }}>
        <Heading as="h2" size="sm">Your workspace</Heading>
        <Text>Spacing follows density. Controls keep their height.</Text>
        <Inline gap="sm" align="center">
          <Button>Save changes</Button>
          <Button variant="outline">Cancel</Button>
        </Inline>
      </Stack>
    </Theme>

Compare all three

compact

A shared 40px control height.

default

A shared 40px control height.

comfortable

A shared 40px control height.

Nested scopes

Compact parent · 12px inset

Comfortable child · 20px inset

example.tsx
<Theme density="compact">
  <Theme density="comfortable">
    <Stack gap="md">Comfortable spacing inside a compact layout.</Stack>
  </Theme>
</Theme>

Plain HTML can use data-dixy-density="compact", "default", or "comfortable" at any nested boundary. Use Theme’s density prop in React so Dialog portals receive the same density. CSS ancestor selectors alone do not propagate across portals. A nested default scope resets all semantic mappings. Component aliases are rebound at each boundary.

CSS and TypeScript

The token package has no React or Tailwind dependency. React’s stylesheet already includes the spacing foundation; standalone consumers import it directly. Helpers return CSS references, so density continues to work at runtime. Put custom semantic mappings on the density root.

app.css
@import "@dixy-ui/tokens/spacing.css";

.settings[data-dixy-density] {
  --dixy-spacing-inset-md: var(--dixy-space-250);
}
.settings {
  padding-inline: var(--dixy-spacing-inset-md);
  padding-block: var(--dixy-spacing-inset-sm);
  gap: var(--dixy-spacing-stack-md);
}
spacing.ts
import { space, spacing, spacingPrimitives, type SpaceToken, type Density } from "@dixy-ui/tokens";

const token: SpaceToken = "200";
const density: Density = "compact";
const padding = space(token); // "var(--dixy-space-200)"
const gap = spacing("stack", "md"); // semantic, density-aware
const referencePixels = spacingPrimitives[token]; // 16

Stack and Inline contracts

Stack defaults to gap="md" from spacing.stack; Inline defaults to gap="sm", align="center", and wrap=true from spacing.inline. Both accept xs, sm, md, lg, xl, native attributes, refs, className and style. Choose a semantic HTML element with as, such as section, nav, ul, or form. Each primitive renders one element. Inline wraps by default and follows the element’s writing direction. Style overrides take precedence over the generated gap rules.

Tailwind integration

This project uses plain CSS. Tailwind is optional and is not installed. In a Tailwind v4 consumer, add only the tokens you use to its theme; leave the default spacing scale intact. This example is provided for integration and has not been compiled against Tailwind in this repository.

tailwind.css
@import "tailwindcss";
@import "@dixy-ui/tokens/spacing.css";

@theme inline {
  --spacing-dixy-100: var(--dixy-space-100);
  --spacing-dixy-200: var(--dixy-space-200);
}
/* <div class="p-dixy-200 gap-dixy-100"></div> */

Spacing, sizing, and optical adjustments

Spacing defines relationships around content. Control heights, icon sizes, focus rings and minimum interaction targets are separate sizing decisions. Compact density must not shrink those targets or typography. Prefer logical padding and margin properties for RTL, wrapping layouts on narrow screens, and rem values that follow the reader’s root font size. Test reflow and browser zoom at 200% and 400% with real content.

Use a 2px or 6px primitive for a deliberate optical adjustment and explain the exception locally. Avoid arbitrary values when a token fits. Negative gaps and padding are invalid; no negative token variants are provided. An explicit Bleed primitive and negative-margin use cases remain future work. Radius stays an independent Theme setting documented under Theming.

Migrating the alpha scale

Replace --dixy-space-1…8 with --dixy-space-050, -100, -150, -200, -300, -400, -600, and -800 respectively. Prefer semantic aliases in components. The old names are removed to keep one numeric vocabulary. Button gap now follows inline.sm (4px), input padding follows inset.sm (12px), and dialog padding follows inset.lg (24px) at the default density.