<!-- Generated from src/docs/primitives-guide.mdx by scripts/build-docs.ts. Do not edit. -->

# Primitives

Layout vocabulary. `Stack`, `Inline`, `Box`, `Grid`, `Container`, `Text`. Use these instead of writing `flex flex-col gap-4` so layouts read by intent.

## Use a primitive when

A `<div>` whose classes are only `flex`, `grid`, `gap`, `items-*` or `justify-*` utilities is a primitive call site.

- You need a column of children → `Stack`
- You need a row → `Inline`, with `wrap` to let it wrap and `as` to change the element
- You need padding or a radius around content → `Box`, with `padding`, `paddingX`, `paddingY` and `radius`
- You need a width-constrained shell → `Container`
- You need a two-dimensional layout → `Grid`
- You're rendering text with a size/tone/weight → `Text`, with `as`, `size`, `tone` and `weight`

```tsx
import {Box, Container, Grid, Inline, Stack, Text} from '@tryghost/shade/primitives';
```

## Spacing scale

Use semantic gaps (`gap="md"`), not raw numbers (`gap-4`). The steps resolve to the shared spacing scale:

| Step   | Tailwind | Use for                        |
| ------ | -------- | ------------------------------ |
| `none` | `gap-0`  | Flush                          |
| `xs`   | `gap-1`  | Inline icons next to text      |
| `sm`   | `gap-2`  | Dense lists, badges            |
| `md`   | `gap-3`  | Default row and column spacing |
| `lg`   | `gap-5`  | Section spacing                |
| `xl`   | `gap-6`  | Between major blocks           |
| `2xl`  | `gap-8`  | Page-level rhythm              |

Padding uses the same steps: `<Box padding="md">` is `p-3`, and `paddingX`/`paddingY` on `Box` and `paddingX` on `Container` follow suit.

## `className`

Primitives forward `className` and merge it with `cn()`. Use it for one-offs the props don't cover, such as `className="border border-border-default"`. Set direction, gap, alignment and justification through the props, not through `className`.

## Don't

- Reach for a primitive when a pattern exists for the shape. Building a page header? Use `PageHeader`. Building a list page? Use `ListPage`.
- Put product logic inside a primitive. If a wrapper is starting to know about Ghost data, it's a Pattern.
- Wrap content in a bare `<div>` carrying only `flex / grid / gap` utilities — that's exactly what primitives replace.
- Pass raw spacing classes to a primitive, such as `<Stack className="gap-4">`. Use `gap="md"`.

The one place for a layout-only element is inside a primitive's own implementation in Shade.

Full rules and decision flow: [Layers](https://github.com/TryGhost/Ghost/blob/main/apps/shade/CONTRIBUTING.md#layers), in Contributing to Shade.

## Example

A settings row — label and description on the left, control on the right:

```tsx
import {Switch} from '@tryghost/shade/components';
import {Box, Inline, Stack, Text} from '@tryghost/shade/primitives';

<Box padding="lg" radius="md" className="border border-border-default">
    <Inline align="center" gap="md" justify="between">
        <Stack gap="xs">
            <Text weight="semibold">Email notifications</Text>
            <Text size="sm" tone="secondary">Get notified when your posts get engagement.</Text>
        </Stack>
        <Switch />
    </Inline>
</Box>
```

Without primitives, the same layout reads as a class string. This is what they replace:

```tsx
<div className="flex flex-col gap-2">
    <div className="font-semibold">Email notifications</div>
    <div className="text-sm text-gray-600">Get notified when your posts get engagement.</div>
</div>
```

Browse the sidebar for each primitive's props and live stories.
