DESIGN_DIRECTION.md template
Live at the repository root. Linked from the README near the top. Linked from CONTRIBUTING.md.
# Design direction
This file is the source of truth for creative
direction in this repository. Every PR that touches
UI, copy, or design tokens reads this file.
## Synthesis
[One paragraph in present tense describing what
this combination produces in practice.]
## Axes
| Axis | Position | Why |
| ------------ | -------------------- | ------------------ |
| Tone | Professional | [one sentence] |
| Aesthetic | Editorial Restrained | [one sentence] |
| Relationship | Peer | [one sentence] |
| Sensory | Considered | [one sentence] |
## What this brief excludes (rejection list)
- [specific phrasing, structure, or visual move
this brief excludes]
- [next item]
- [next item]
## Inspiration references
- [URL] - [one sentence on what specifically resonates]
- [URL] - [next reference]
## Open questions
- [anything still unresolved that downstream PRs
will need answered]
---
For platform-agnostic patterns see
[/integrations/agile-creative-direction].
For the framework see
[/framework/creative-direction].
PR template (.github/pull_request_template.md)
GitHub auto-loads this template on every new PR. The Direction section is what makes the integration load-bearing instead of decorative.
## Summary
<!-- One or two sentences on what this PR changes
and why. Start with the verb. -->
## What changed
<!-- Bullet list of meaningful changes. Skip the
mechanical stuff (lockfile bumps, formatting). -->
-
-
## How to test
<!-- Steps a reviewer follows to verify the change
on their machine or in preview. -->
1.
2.
## Direction
<!-- Required for any PR that touches UI, copy, or
design tokens. Skip with N/A only if the PR is
purely backend or infrastructure. -->
This PR answers DESIGN_DIRECTION.md on the relevant
axes. Mark each row honestly:
- Tone: [ ] yes [ ] no [ ] partial [ ] N/A
- Aesthetic: [ ] yes [ ] no [ ] partial [ ] N/A
- Relationship: [ ] yes [ ] no [ ] partial [ ] N/A
- Sensory: [ ] yes [ ] no [ ] partial [ ] N/A
Rejection list check:
- [ ] I re-read CONTRIBUTING.md "Won't merge"
and confirm nothing in this PR violates it.
If anything is "no" or "partial", explain in the
comments below what would close the gap, or open
an issue tagged direction:drift.
## Screenshots
<!-- For UI changes. Before and after if applicable. -->
## Linked issues
Closes #
Relates to #Tailwind config (tailwind.config.ts)
Axis prefixes on token names. The variable picker in the editor surfaces axis position the moment a developer types className=“text-”.
import type { Config } from "tailwindcss";
export default {
content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
theme: {
extend: {
colors: {
// Tone-bound palette.
"tone-professional": {
ink: "#1c1917",
body: "#44403c",
muted: "#78716c",
accent: "#0a0a0a",
},
"tone-conversational": {
ink: "#292524",
body: "#57534e",
muted: "#a8a29e",
accent: "#047857",
},
},
spacing: {
// Aesthetic-bound spacing scale.
"aesthetic-restrained-xs": "4px",
"aesthetic-restrained-sm": "8px",
"aesthetic-restrained-md": "16px",
"aesthetic-restrained-lg": "32px",
"aesthetic-restrained-xl": "96px",
},
transitionTimingFunction: {
"sensory-considered": "cubic-bezier(0.4, 0, 0.2, 1)",
"sensory-resonant": "cubic-bezier(0.16, 1, 0.3, 1)",
},
transitionDuration: {
"sensory-considered-fast": "150ms",
"sensory-resonant-hero": "720ms",
},
},
},
plugins: [],
} satisfies Config;Storybook story naming convention
Story title is the load-bearing field. Forward-slash hierarchy turns into Storybook's sidebar tree; putting axis at the top groups the catalog by axis position automatically.
// components/Button/Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
const meta = {
// The title path drives Storybook's sidebar.
// Tone first because tone is the most visible axis.
title: "Tone/Professional/Buttons/Primary",
component: Button,
tags: ["autodocs"],
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: { children: "Read more" },
};
export const Loading: Story = {
args: { children: "Read more", loading: true },
};
// In a working file, naming convention enforces
// axis visibility. Reviewers reading the catalog
// see Tone/Professional, Tone/Conversational, etc.
// at the top level.GitHub Actions workflow: direction-status label
A workflow that auto-applies a direction-status label based on whether the PR description still has unchecked axis boxes. Keeps the label in sync with the PR body without manual labeling.
# .github/workflows/direction-status.yml
name: Direction status
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
label:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || "";
const hasNo = /-\s+(Tone|Aesthetic|Relationship|Sensory):.*\[x\]\s*no/i.test(body);
const hasPartial = /-\s+(Tone|Aesthetic|Relationship|Sensory):.*\[x\]\s*partial/i.test(body);
const hasUnchecked = /-\s+(Tone|Aesthetic|Relationship|Sensory):.*\[\s\]/i.test(body);
const labels = ["direction:aligned", "direction:drift", "direction:review"];
const remove = (l) => github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
name: l,
}).catch(() => null);
await Promise.all(labels.map(remove));
let target = "direction:aligned";
if (hasNo) target = "direction:drift";
else if (hasPartial || hasUnchecked) target = "direction:review";
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: [target],
});Code review checklist (CONTRIBUTING.md)
Inline this in the contributing guide. The PR template links to it under the Rejection list check.
# Reviewer checklist (UI / copy / design-token PRs)
Before approving:
- [ ] Re-read DESIGN_DIRECTION.md (synthesis +
axis positions)
- [ ] Re-read the "Won't merge" section below
For each visible change in this PR:
- [ ] The change uses axis-prefixed Tailwind tokens
(no raw hex outside theme.ts, no raw spacing
values outside the spacing scale)
- [ ] New components have a Storybook story with a
title path starting with the axis
- [ ] Copy added or changed reflects the brief's
tone position
- [ ] Motion added or changed uses the
sensory-prefixed easing/duration tokens
# Won't merge
The brief excludes the following. PRs that
introduce any of these get rejected at review:
- Three-icon feature rows on landing surfaces
- Stock-photo style hero photography
- Exclamation marks in product copy
- Generic CTA copy ("Get started", "Learn more",
"Click here")
- Skeumorphic shadows on cards or buttons
- [add the rest of the rejection list from
DESIGN_DIRECTION.md]