Shade use primitives
DevelopmentShade use primitives is an Agent Skill for development published in the TryGhost/Ghost repository on GitHub: a SKILL.md instruction file an AI agent can load to do this task.
Replace bare divs that only carry flex/grid/gap utilities with Shade primitives (Stack, Inline, Box, Grid, Container, Text). Use semantic gap="md" instead of gap-4. Trigger when editing TSX in Shade-consuming apps.
How to use this skill
Bring this guide into your coding agent with a prompt tailored to the tool you use.
- Open your project in Codex.
- Copy and paste the prompt below.
- Review files and risks before approving any action.
Inspect the Agent Skill at https://github.com/TryGhost/Ghost/blob/HEAD/.agents/skills/shade-use-primitives/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/shade-use-primitives, but wait for my approval before writing files or running scripts.
Copying this prompt does not install or run the skill. Review third-party files before use.
Shade — use primitives, not flex divs
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';
Picking the primitive
| 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 |
Use the semantic gap scale, not raw numbers
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).
Correct
<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>
Incorrect
// 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">
When NOT to reach for a primitive
- A pattern already exists for the shape (page header →
PageHeader, list page →ListPage). - The wrapper is starting to know about Ghost data — that's a Pattern, not a primitive composition.
- You're inside a Shade primitive's own implementation.
className still works
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.