.agents/skills/shade-use-primitives/SKILL.md
A <div> whose className is only flex / grid / gap / items-* / justify-* utilities is a primitive call site. Use a Shade primitive instead so layouts read by intent, not by class string.
import {Stack, Inline, Box, Grid, Container, Text} from '@tryghost/shade/primitives';
| You need | Use | Element |
|---|---|---|
| Column of children | Stack | flex-col |
| Row of children | Inline | flex-row (with optional wrap, as) |
| Padding / radius around content | Box | div with padding, radius props |
| Two-dimensional layout | Grid | display: grid |
| Width-constrained shell | Container | max-width wrapper |
| Text with size/tone/weight | Text | as, size, tone, weight |
gap="md" reads as intent and resolves to the shared spacing scale. Mapping:
| Step | Tailwind | Use for |
|---|---|---|
none | gap-0 | flush |
xs | gap-1 | inline icons-to-text |
sm | gap-2 | dense lists, badges |
md | gap-3 | default row/column spacing |
lg | gap-5 | section spacing |
xl | gap-6 | between major blocks |
2xl | gap-8 | page-level rhythm |
The same scale applies to Box padding (padding="md" → p-3).
<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 about engagement.</Text>
</Stack>
<Switch />
</Inline>
</Box>
// BAD — bare div doing flex layout
<div className="flex flex-col gap-2">
<div className="font-semibold">Title</div>
<div className="text-sm text-gray-600">Hint</div>
</div>
// BAD — raw gap-4 instead of gap="md"
<Stack className="gap-4">
PageHeader, list page → ListPage).Primitives forward className and merge with cn(). Use it for one-offs the prop API doesn't cover (e.g. className="border border-border-default"). Don't reach for className to set flex, gap, align, or justify — that's what the props are for.