Skip to content

Commit f16ff9e

Browse files
committed
docs(design): add DESIGN.md design system documentation
Change-Id: I742119eadbc76ba4ed48c42ad07e04d23a5d16ae
1 parent 210981c commit f16ff9e

1 file changed

Lines changed: 367 additions & 0 deletions

File tree

DESIGN.md

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
# Docs Design System
2+
3+
This file documents **design intent and conventions** for `docs.mergify.com` — the "why" behind
4+
decisions, not the values themselves. For exact values (colors, spacing, typography), read the
5+
source files listed below; the code is the source of truth.
6+
7+
> Companion file to `CLAUDE.md`: `CLAUDE.md` covers project setup, commands, and codebase rules.
8+
> `DESIGN.md` is the design system this site is built from. Keep them aligned: when a design rule
9+
> changes here, update the matching section in `CLAUDE.md` (or vice-versa) so they don't drift.
10+
11+
## Source Files
12+
13+
| What | Where |
14+
| --- | --- |
15+
| Primitive tokens (gray scale + product palette) | [`src/styles/tokens.css`](./src/styles/tokens.css) |
16+
| Semantic tokens + dark-mode remaps | [`src/styles/theme.css`](./src/styles/theme.css) |
17+
| Typography utility classes | [`src/styles/typography.css`](./src/styles/typography.css) |
18+
| Global content rules + section-accent system | [`src/styles/index.css`](./src/styles/index.css) |
19+
| Product accent colors (canonical source) | [`mergify.com/DESIGN.md`](../mergify.com/DESIGN.md) — "Product Accent Colors" section |
20+
| Reusable Astro components | [`src/components/`](./src/components/) |
21+
| Page layout templates | [`src/layouts/`](./src/layouts/) |
22+
| Icons (Phosphor bold + custom SVGs) | Inline `?raw` imports; no icon component abstraction yet |
23+
| Agent instructions | [`AGENTS.md`](./AGENTS.md) — "Design System" section |
24+
| Claude instructions | [`CLAUDE.md`](./CLAUDE.md) — "Design System" section |
25+
26+
## Visual Theme
27+
28+
`docs.mergify.com` is a reference site for developers who are already using or evaluating Mergify.
29+
They arrive from search, GitHub links, and product UI. The goal is to answer their question quickly
30+
and get out of the way. It is **not** a conversion funnel — that is `mergify.com`'s job.
31+
32+
Built with Astro 5 + MDX + plain CSS. No Tailwind (the site predates the Tailwind migration on the
33+
marketing site; adding it is out of scope). Dark mode is supported via the `:root.theme-dark`
34+
class-based mechanism.
35+
36+
**Key characteristics:**
37+
38+
- Neutral foundation — primary text and buttons are `gray-900` / `gray-50` (black/white), not
39+
`--color-mergify-blue`. Product palette colors appear only when a surface is *about* that product.
40+
- Clean gray ramp — intentionally diverges from `mergify.com`'s gray scale, which has
41+
`gray-50 == gray-200` collisions. The docs ramp has no duplicates; `gray-50` through `gray-900`
42+
are distinct values you can reach for without reading a gotchas table.
43+
- Light + dark — the marketing site is light-only; docs ships both modes because developers working
44+
in dark terminals expect it.
45+
- Inter variable font, same as the marketing site. Self-hosted via `@fontsource/inter`.
46+
- Compact layout — content shares horizontal space with a left sidebar and right ToC. The hero
47+
typography cap is smaller than marketing's (`clamp(2.5rem, 4.5vw, 3rem)` vs. 64px) because the
48+
reading column is narrower.
49+
50+
## Color Tokens
51+
52+
All colors live in three layers. **Components consume only the semantic layer.**
53+
54+
### Layer 1 — Primitive (`tokens.css`)
55+
56+
Fixed hex values. Never changes between light and dark mode. Never referenced directly by
57+
components or content rules.
58+
59+
```css
60+
/* Neutrals */
61+
--color-white, --color-dark
62+
--color-gray-50 … --color-gray-900
63+
64+
/* Product palette — verbatim from mergify.com */
65+
--color-teal-400, --color-teal-700
66+
--color-purple-400, --color-purple-700
67+
/* …etc for orange, blue, coral, rose… */
68+
--color-blue-800 /* docs-only: link hover */
69+
```
70+
71+
The two exceptions where primitive tokens are allowed:
72+
73+
1. Inside `tokens.css` itself (where they are defined).
74+
2. Inside `theme.css` (where they are mapped to semantic names).
75+
76+
Nowhere else.
77+
78+
### Layer 2 — Semantic (`theme.css`)
79+
80+
Mode-aware names that components and content rules consume. Defined once for light, remapped once
81+
in `:root.theme-dark`. These are the **only** tokens components should touch.
82+
83+
```css
84+
--theme-text /* primary text */
85+
--theme-text-secondary /* supporting text */
86+
--theme-text-muted /* de-emphasized text, captions */
87+
--theme-bg /* page background */
88+
--theme-bg-content /* card / content surface */
89+
--theme-bg-offset /* raised surface (inline code bg, sidebar) */
90+
--theme-border /* borders, dividers */
91+
--theme-link /* link color */
92+
--theme-link-hover /* link hover */
93+
--theme-accent /* primary action / heading emphasis */
94+
```
95+
96+
### Layer 3 — Section accents (`index.css`)
97+
98+
A `--section-accent` custom property set on `<body>` by class. Each documentation section gets
99+
its own accent color; the ToC active-link and sidebar hover states consume it automatically. One
100+
rule block, not one block per section.
101+
102+
```css
103+
body { --section-accent: var(--theme-link); }
104+
body.section-merge-queue { --section-accent: var(--color-teal-700); }
105+
/* …etc… */
106+
```
107+
108+
### The discipline rule
109+
110+
**Components never reference primitive tokens directly.** If you find yourself writing
111+
`var(--color-gray-300)` inside a component, stop and use `var(--theme-border)` instead. The
112+
primitive layer exists so `theme.css` has legible, self-documenting mappings — not so consumers
113+
can bypass the semantic layer.
114+
115+
## Product Accent Colors
116+
117+
Product colors are verbatim from `mergify.com/DESIGN.md`. The docs site reuses the same palette
118+
without adding new tokens.
119+
120+
| Product | Dark / Label | Light / Accent | Hex (label) |
121+
| --- | --- | --- | --- |
122+
| Merge Queue | `--color-teal-700` | `--color-teal-400` | #1CB893 |
123+
| CI Insights | `--color-purple-700` | `--color-purple-400` | #4D59E0 |
124+
| Test Insights | `--color-orange-700` | `--color-orange-400` | #F27B2A |
125+
| Merge Protections | `--color-blue-700` | `--color-blue-400` | #43A7E5 |
126+
| Stacks | `--color-coral-700` | `--color-coral-400` | #E53935 |
127+
| Workflow Automation | `--color-rose-700` | `--color-rose-400` | #E61E71 |
128+
129+
One docs-only addition: `--color-blue-800` (#237caf) for link hover. It is a darker shade of
130+
`blue-700` and exists only because docs is text-heavy with many cross-references; the marketing
131+
site uses product colors for links far less often.
132+
133+
### Section-accent body classes
134+
135+
The `<body>` element receives a class based on the active documentation section. This drives the
136+
ToC active-link color and left-sidebar hover highlight.
137+
138+
| Body class | Section | Accent token |
139+
| --- | --- | --- |
140+
| (no class) | General / configuration | `--theme-link` (blue-700) |
141+
| `section-merge-queue` | Merge Queue | `--color-teal-700` |
142+
| `section-ci-insights` | CI Insights | `--color-purple-700` |
143+
| `section-test-insights` | Test Insights | `--color-orange-700` |
144+
| `section-merge-protections` | Merge Protections | `--color-blue-700` |
145+
| `section-stacks` | Stacks | `--color-coral-700` |
146+
| `section-workflow` | Workflow Automation | `--color-rose-700` |
147+
148+
The class is applied by the layout; do not hardcode accent colors inside section-specific
149+
components. Consume `var(--section-accent)` and `var(--section-accent-bg)` instead.
150+
151+
Note on link color vs. Merge Protections accent: both use `blue-700`. On a Merge Protections page,
152+
inline links and product pills share a hue but serve different roles (underlined inline link vs.
153+
solid pill). The visual conflation is minor and preferable to introducing a new shade.
154+
155+
## Dark Mode
156+
157+
### Mechanism
158+
159+
`:root.theme-dark` is added to the `<html>` element by a `<script is:inline>` in `HeadCommon.astro`
160+
before the first paint, based on `localStorage` or `prefers-color-scheme`. The `ThemeToggleButton`
161+
updates both `localStorage` and the class at runtime. No JavaScript framework is involved; the
162+
class toggle is pure DOM.
163+
164+
### Where remaps live
165+
166+
Every dark-mode remap is in one place: the `:root.theme-dark` block in `theme.css`. Nowhere else.
167+
168+
```
169+
tokens.css ← primitives (fixed, no dark remap)
170+
theme.css ← semantic layer
171+
:root ← light semantic values
172+
:root.theme-dark ← dark overrides (ONLY here)
173+
typography.css ← utility classes (mode-agnostic)
174+
index.css ← global rules, section accents (no mode-conditional CSS)
175+
```
176+
177+
Component-scoped styles never include `:root.theme-dark` blocks. They consume semantic tokens and
178+
dark mode works automatically.
179+
180+
### Product palette does not flip
181+
182+
`--color-teal-700`, `--color-purple-700`, and all other product tokens stay the same value in dark
183+
mode. Brand recognition and in-context UI legibility depend on these colors being stable; the
184+
surrounding surface contrast changes instead.
185+
186+
### Adding a new token
187+
188+
1. Add the primitive value to `:root` in `tokens.css`.
189+
2. Add a semantic name in the `:root` block in `theme.css`, pointing at the primitive.
190+
3. Add a dark-mode override in the `:root.theme-dark` block in `theme.css`, also pointing at a
191+
primitive (or a different primitive).
192+
4. Document the new token in this file under the appropriate section.
193+
5. Consume the semantic token — never the primitive — in components.
194+
195+
## Typography
196+
197+
Two font families:
198+
199+
- **Inter variable** — everything visible to users. Loaded via `@fontsource/inter` (400, 500,
200+
600, 700 weights).
201+
- **Poppins** — loaded but limited to decorative use (100, 200 weights). Prefer Inter for all
202+
body and heading copy.
203+
- **System mono** — code blocks and inline code.
204+
205+
### Utility classes
206+
207+
Defined in `typography.css`. Apply by class on custom-built pages and Astro components.
208+
MDX-rendered headings automatically get the same sizing via `#main-content h1..h6` selectors in
209+
`index.css` — authors writing MDX do not need to add classes.
210+
211+
| Class | Size | Weight | Line-height | Use |
212+
| --- | --- | --- | --- | --- |
213+
| `heading-hero` | clamp(2.5rem, 4.5vw, 3rem) | 500 | 1.2 | Homepage / hub-page H1. Smaller cap than marketing (64px) because the content column is narrower. |
214+
| `heading-page` | 2rem | 500 | 1.2 | Regular doc-page H1. |
215+
| `heading-section` | 1.5rem | 500 | 1.3 | Content H2. |
216+
| `heading-subsection` | 1.25rem | 500 | 1.3 | Content H3. |
217+
| `heading-detail` | 1.125rem | 500 | 1.4 | Content H4. |
218+
| `heading-minor` | 1rem | 500 | 1.4 | H5 / H6. |
219+
| `text-subtitle` | 1.125rem | 400 | 1.5 | Page description / lead paragraph. |
220+
| `text-eyebrow` | 0.75rem uppercase | 600 || Small label above section headings. Rarely used in prose docs; reserved for hub-page heroes (B-pass). |
221+
222+
Letter-spacing: `-0.025em` on `heading-hero`, `heading-page`, `heading-section`. `-0.01em` on
223+
`heading-subsection`. Not set on `heading-detail` and smaller. All headings use
224+
`text-wrap: balance`.
225+
226+
### When to use `heading-hero` vs `heading-page`
227+
228+
`heading-hero` is for the homepage and product hub pages (`/merge-queue`, `/ci-insights`, etc.)
229+
where the H1 is a marketing-register statement that needs visual weight. `heading-page` is for
230+
every regular reference page — configuration options, how-to guides, API docs. When in doubt, use
231+
`heading-page`.
232+
233+
### MDX content auto-styling
234+
235+
Authors writing MDX do not add classes. The `#main-content h2` selector in `index.css` mirrors
236+
the `heading-section` properties; `#main-content h3` mirrors `heading-subsection`; and so on
237+
through H6. When you change a utility's properties, update the corresponding `#main-content`
238+
selector too — they must stay in sync.
239+
240+
### Eyebrow rules
241+
242+
`text-eyebrow` is uppercase, letter-spaced, small. Keep eyebrow labels to 2–4 words. Use the
243+
section's accent color (`var(--section-accent)`) when the eyebrow labels a product feature;
244+
use `var(--theme-text-muted)` for neutral sections. Eyebrows are currently used only on the
245+
homepage and hub pages — do not add them to prose documentation pages.
246+
247+
## Layout & Spacing
248+
249+
The layout is a classic three-column docs shell: left sidebar, content column, right ToC. Key
250+
sizing variables live in `theme.css`.
251+
252+
| Variable | Value | Notes |
253+
| --- | --- | --- |
254+
| `--max-width` | 46em | Reading column max-width. Keeps prose lines short for readability. |
255+
| `--theme-sidebar-width` | ~18rem | Left sidebar width (see `theme.css` for exact breakpoint rules). |
256+
| `--theme-navbar-height` | 4rem | Sticky top nav height. Used in `calc()` for scroll offsets and sticky positioning. |
257+
| `--theme-mobile-toc-height` | varies | Height of the on-page mobile ToC bar, used in gradient calculations. |
258+
259+
Do not add new `max-width` values without updating this table. The 46em reading column is
260+
intentional — narrower than the marketing site's 1200px container because docs is prose-heavy.
261+
262+
## Components
263+
264+
Before building anything custom, check `src/components/`. The components below are the primary
265+
building blocks; reach for them before rolling your own.
266+
267+
| Component | Purpose |
268+
| --- | --- |
269+
| `Aside` | Callout boxes — `note`, `tip`, `caution`, `danger` variants. Use directive syntax in MDX: `:::note`. |
270+
| `Button` | Anchor or button element with `primary` (solid) and `outline` variants. Consumes product palette via `colorScheme` prop. |
271+
| `DocsetGrid` | Grid of doc-section cards, used on the homepage and hub pages. |
272+
| `CommunityButton` | "Join Slack" / community CTA button with Discord/Slack icon. |
273+
| `Breadcrumbs` | Page breadcrumb trail, rendered above the page title. |
274+
| `PageContent` | Content column wrapper — handles padding, max-width, ToC anchor injection. |
275+
| `Header` | Top navigation bar including the theme toggle and search trigger. |
276+
| `LeftSidebar` | Navigation sidebar with collapsible section groups. |
277+
| `RightSidebar` | On-page ToC with active-link tracking. |
278+
| `PageFeedback` | "Was this helpful?" widget at the bottom of every doc page. |
279+
280+
Astro component files live in `src/components/`. Layout wrappers live in `src/layouts/`.
281+
282+
## Voice
283+
284+
The docs site serves **the searcher**, not the funnel. A developer who lands on a configuration
285+
reference page needs an accurate, direct answer — not brand personality.
286+
287+
**Docs voice is informational + on-voice.** It is clear, neutral, and technically precise. It does
288+
not use Mergify's marketing voice ("fun + arrogant"). Marketing's confident-cocky register belongs
289+
on `mergify.com` product pages; on docs it reads as noise that buries the answer.
290+
291+
Concretely:
292+
293+
- Write for someone who arrived from a search query and needs to know what a config key does.
294+
- Skip openers like "Great news!" or "Simply add…". Cut to the content.
295+
- Do not editorialize ("This is the recommended way…") unless there is a genuine reason to steer
296+
the reader (safety, correctness, performance).
297+
- Use second person ("you") consistently. Avoid the royal "we".
298+
- Code samples should be minimal and runnable. Do not add features to the example that the text
299+
does not explain.
300+
301+
For marketing-register copy (homepage hero, hub-page openers), use the `mergify-internal:writing-style`
302+
skill — it enforces the marketing voice conventions. For all prose documentation, use the
303+
`proofread-style` skill — it enforces the docs-specific rules above and catches AI-generated
304+
patterns (banned words: "leverage", "robust", "seamlessly", em-dashes used as filler, etc.).
305+
306+
**Title case** for page titles and section headings. Sentence case for body copy. Capitalize the
307+
first part of a hyphenated compound modifier ("Auto-retry", not "Auto-Retry").
308+
309+
## Code Rules (STRICT)
310+
311+
These rules apply when writing or generating component, style, or page code.
312+
313+
### Use semantic tokens
314+
315+
- **Colors**: always `var(--theme-*)` or `var(--section-accent)` in component and content CSS.
316+
`var(--color-*)` primitives are only allowed inside `tokens.css` and `theme.css`.
317+
- **Inline SVG `fill`/`stroke`**: the only place raw hex is permitted outside `tokens.css`. Use
318+
`currentColor` wherever possible; fall back to a `var(--color-*)` primitive only when the SVG
319+
must carry its own color independent of the theme.
320+
- **Typography**: use the utility classes (`heading-section`, `text-subtitle`, etc.) on custom
321+
pages. Do not compose ad-hoc `font-size` / `font-weight` / `letter-spacing` combos from scratch.
322+
- **Dark mode**: never add a `:root.theme-dark` block inside a component-scoped `<style>`. Use
323+
semantic tokens and let `theme.css` handle the remap automatically.
324+
325+
### Do not use
326+
327+
- **Hex literals** in any file except `tokens.css` and inline SVG `fill`/`stroke`.
328+
- **`--chakra-colors-*`** — the Chakra color system was never the design system; all remaining
329+
references are migration debt to be removed.
330+
- **`--color-mergify-blue`** — retired. Use `var(--theme-link)` for link affordances or
331+
`var(--theme-accent)` for primary surfaces.
332+
- **Per-component `:root.theme-dark` rules** — all dark remaps live in `theme.css`.
333+
- **Ad-hoc heading CSS** — no `h2 { font-size: 1.5rem; font-weight: 500; }` rules scattered
334+
across component files. Use the typography utilities.
335+
336+
### Self-correction list
337+
338+
If you generate any of the following, **fix it immediately**:
339+
340+
- Hex literal in a component or content rule (e.g., `color: #1cb893`) → find the matching
341+
`--color-*` primitive, then replace with the appropriate `--theme-*` semantic token.
342+
- `var(--chakra-colors-*)` anywhere outside `theme.css` → replace with the semantic token
343+
(see the migration table in Phase 3 of the implementation plan).
344+
- `var(--color-mergify-blue*)` anywhere → replace with `var(--theme-link)` or `var(--theme-accent)`.
345+
- `:root.theme-dark { … }` inside a `<style>` block in a component → move the logic to a semantic
346+
token in `theme.css`.
347+
- `font-size: 1.5rem; font-weight: 500;` heading ad-hoc combo → use `heading-section` class.
348+
349+
No exceptions unless the user explicitly overrides.
350+
351+
## Future / Open Questions
352+
353+
- **Phosphor icon migration.** The docs site uses a mix of Octicons and inline SVGs. The marketing
354+
site standardized on Phosphor bold. Migrating docs to Phosphor is a separate scope — when
355+
undertaken, update the "Source Files" table and the "Components" section above.
356+
- **B-pass homepage rework.** The homepage (`src/content/docs/index.mdx`) is a documentation page
357+
masquerading as a landing page. B-pass will add a visual hero, one primary CTA per product,
358+
and remove the duplicate Reference grid. Class swaps (`content-title``heading-hero`, etc.)
359+
are landed in A-pass; the visual design work is deferred.
360+
- **C-pass reading experience.** Several UX issues deferred from A: the top nav disappears below
361+
1392px (pick a smaller breakpoint or move links to an overflow menu), hub-page heroes (adopt the
362+
eyebrow + product-color H1 pattern from marketing's `<ProductHero>`), right-sidebar empty state
363+
on short pages (sticky "ask a question" CTA or hide entirely), h2 auto-numbering review (keep on
364+
tutorials only or remove globally).
365+
- **Token sharing with the dashboard.** The dashboard has its own Figma-derived token set that
366+
diverges from both marketing and docs. Alignment across all three surfaces is a longer-term
367+
design-system project, not scoped to any single pass.

0 commit comments

Comments
 (0)