|
| 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