Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions docs/adr/0001-documentation-site-information-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# ADR 0001: Information architecture for the new Gamut documentation site

- **Status:** Proposed
- **Date:** 2026-08-18
- **Ticket:** GMT-1727
- **Deciders:** Gamut maintainers

## Context

Gamut's documentation lives in a Storybook (`packages/styleguide`) whose structure has grown organically. An audit against the [Diátaxis framework](https://diataxis.fr) identified structural problems:

1. **Navigation is organized by Gamut's internal taxonomy, not user need.** The sidebar (Atoms / Molecules / Organisms / Foundations / Layouts / Typography) follows Atomic Design. Readers must know how Gamut classifies a component before they can find it, and cross-cutting tasks (theming an app, building a form, supporting dark mode) have no home.
2. **Documentation modes are interleaved.** Component pages mix design guidance, task recipes, and API reference in inconsistent orders (e.g., `Alert` documents `closeButtonProps` as prose bullets between example sections; `Menu` buries accessibility-critical role guidance mid-page). Section vocabularies diverge per page ("Specifications", "HTML element", "Roles").
3. **There are no tutorials.** Nothing walks a newcomer from zero to a working page. `Installation` — the most task-critical page — is buried mid-list in a grab-bag "Meta" section.
4. **"Meta" is a junk drawer** spanning how-to (Installation, Contributing), reference (ESLint rules), and explanation (Brand, Best practices, FAQs), organized by authors' filing convenience rather than reader need.

We are building a new documentation website. This is the moment to fix the information architecture rather than port the current structure.
This document will serve as a blueprint for any framework to use and generate a website from.

Diátaxis distinguishes four documentation modes by user need — **tutorials** (learning), **how-to guides** (working toward a goal), **reference** (looking up facts), and **explanation** (understanding) — and holds that each unit of content should serve one mode, with navigation letting readers stay in their mode. It applies fractally: at site scale and again within a topic area such as a single component.

## Decision

### 1. Top-level navigation follows the Diátaxis modes

The sidebar's top level maps to the reader's lifecycle — learn it, do a task, look something up, understand it deeper:

```text
Getting started TUTORIALS — Installation, Build your first page, Using this site
Guides HOW-TO — Theming your app, Building forms, Supporting dark
mode, Writing UX copy, Migrating to logical
properties, Contributing to Gamut
Components REFERENCE — all components, grouped by function (see §2)
Reference REFERENCE — design tokens, themes, system props, icon/asset
catalogs, ESLint rules, tooling
Concepts EXPLANATION — architecture of the system, theming model, color
modes, brand, best practices, voice & tone, FAQs
```

The "Meta" section is dissolved: its pages distribute into Getting started (Installation, Usage guide), Guides (Contributing), Reference (ESLint rules, Deep Controls), and Concepts (Brand, Best practices, FAQs). "Foundations" splits along the same line: token values and theme palettes are Reference; the rationale behind them is Concepts.

### 2. Components are grouped by function, not by Atomic Design tier

Readers arrive with a need ("I have to tell the user something went wrong"), not knowledge of Gamut's composition hierarchy. The Atoms/Molecules/Organisms tiers are retired from navigation (the tier may survive as a metadata badge on component pages). Components are grouped by what they do:

- **Actions** — Button, CTAButton, FillButton, StrokeButton, TextButton, IconButton, Menu, Tag
- **Containers** — Box, FlexBox, GridBox, Card, ContentContainer, LayoutGrid, Disclosure, Drawer
- **Inputs & forms** — Input, TextArea, Checkbox, Radio, Toggle, Select, SelectDropdown, DatePicker; Form scaffolding (Form, FormGroup, FormGroupLabel, FormGroupDescription, FormRequiredText); ConnectedForm (ConnectedForm, ConnectedFormGroup, ConnectedFormInputs, SubmitButton); GridForm
- **Navigation** — Anchor, Breadcrumbs, Pagination, Tabs, SkipToContent
- **Feedback** — Alert, Toast, Toaster, Coachmark, Tips (ToolTip, InfoTip, PreviewTip)
- **Status** — Badge, ProgressBar, RadialProgress, Loaders (Spinner, Shimmer), FeatureShimmer
- **Overlays** — Modal, Dialog, Overlay, Popover, PopoverContainer, Flyout
- **Data display** — DataTable, DataList, List, BarChart, Markdown
- **Typography** — Text
- **Media & assets** — Icons (Regular, Mini), Illustrations, Patterns, Animations (ExpandInCollapseOut, FadeInSlideOut, Rotation), Video
- **Utilities** — FocusTrap, DelayedRenderWrapper

Placement rulings for components that could live in two categories (the losing category's landing page cross-links them):

| Component | Ruling | Rationale |
| --------- | ---------- | ------------------------------------------------------------------------------------------------------------- |
| Menu | Actions | The action-list role is the richer half of its API; nav role is cross-linked |
| Tag | Actions | Docs emphasize interactive selection/removal; read-only overlap with Badge is arbitrated by "When NOT to use" |
| Tips | Feedback | Readers think "the system explaining something"; Overlays is reserved for things you open and dismiss |
| Drawer | Containers | Collapses within page flow, unlike Flyout which floats above it (Overlays) |
| Anchor | Navigation | Functionally navigation despite its typography implementation |

Existing multi-component group index pages (Buttons, Tips, Modals, Loaders, etc.) survive as collapsible group nodes within their category.

### 3. Component pages re-apply Diátaxis at page scale

Every component page follows one template with a fixed section order grouped by mode — explanation, then how-to, then reference — so a reader in any mode can jump to their zone and stay there:

```text
Header title, subtitle, status, Figma link, source link
── EXPLANATION ──
Usage when to reach for it
Best practices design/UX guidance (not API guidance)
When NOT to use every bullet names and links the alternative component
Anatomy labeled diagram of the component's parts
── HOW-TO ──
Patterns goal-first recipes requiring real wiring (state, callbacks,
composition) — one intent sentence + one live example each
── REFERENCE ──
Variants exhaustive: one rendered example per prop value, no persuasion
Accessibility keyboard behavior, ARIA, focus handling, RTL
Props the canonical API table — prose prop documentation is banned
elsewhere on the page
Playground live editable example
```

Template rules:

1. **Required always:** Header, Usage (with When NOT to use), Variants, Props, Playground. **Optional but fixed-position:** Anatomy (skip for non-visual utilities), Patterns (skip for stateless components), Accessibility (skip only when nothing exceeds native semantics).
2. **The heading vocabulary is closed.** No page invents new top-level sections ("Specifications", "HTML element"); new headings require updating the template first.
3. **Patterns vs. Variants dividing line:** if the interesting part of an example is the code _around_ the component (state, handlers, composition), it is a Pattern; if it is a prop value, it is a Variant.
4. **Tutorials never appear on component pages** — learning-oriented material lives only in Getting started.

If a component outgrows a single page (heavy guidelines plus a large API, e.g. GridForm), it splits into per-mode child pages along the template's section boundaries.

## Consequences

### Positive

- Readers navigate by need: newcomers land in Getting started, task-driven developers in Guides, fact-lookups in Components/Reference — without knowing Gamut's internal taxonomy.
- Predictable component pages: a fixed section order and closed heading vocabulary make every page scannable the same way, and give doc authors an unambiguous checklist.
- The migration is mostly mechanical: nearly every existing page has a destination in the new structure. Only two pages are net-new ("Build your first page" tutorial; "Architecture of the system" concept, largely extractable from existing Foundations prose).
- Cross-cutting guides (theming, forms, dark mode) finally have a home instead of being fragmented under component folders.

### Negative / risks

- **Categorization is a judgment call.** Functional categories have ambiguous edges (see placement rulings). Mitigation: cross-links from the losing category, plus site search for readers who already know a component's name.
- **Deep links break.** Every existing Storybook URL path changes. Mitigation: redirect map from old story IDs to new site URLs.
- **Mixed-mode pages persist by design.** A component page containing explanation, how-to, and reference on one page bends strict Diátaxis (one mode per page). We accept this: the practical harm Diátaxis targets is _interleaving_, which the fixed section order eliminates, and one-page-per-component matches how design-system consumers work.
- **Template enforcement needs tooling.** Without a check, heading vocabularies will drift again. Mitigation: lint page headings against the allowed list in CI.

## Alternatives considered

1. **Port the current Atomic Design structure to the new site.** Rejected: reproduces every audited problem; the migration is the cheapest moment to fix IA.
2. **Flat alphabetical component list** (early proposal). Rejected in favor of functional grouping: alphabetical requires knowing the component's name, functional matches need-driven arrival; search covers name-based lookup. Industry precedent (Adobe Spectrum's Actions/Containers/Feedback/... grouping) favors functional.
3. **Strict Diátaxis: one page per mode per component** (à la Carbon/Material per-component tabs). Rejected for now: quadruples page count and maintenance for many small components. The template's section boundaries are the designated split points if a component outgrows one page.
4. **Organize top-level by audience (designers vs. engineers).** Rejected: most Gamut readers wear both hats within a single task; mode-based navigation serves the actual switching behavior.

## References

- Diátaxis framework: <https://diataxis.fr>
- Adobe Spectrum component grouping (functional-category precedent): <https://spectrum.adobe.com/page/components/>
- Current styleguide source: `packages/styleguide/src/lib`
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"prepare": "husky",
"prettier": "prettier --ignore-path .prettierignore \"./**/*.{mdx,js,ts,tsx,json,css,scss}\"",
"start": "yarn && yarn start:storybook",
"start:nextra-site": "yarn workspace @codecademy/nextra-site dev",
"start:storybook": "nx storybook styleguide",
"test": "nx run-many --target=test --all",
"test:storybook": "nx run styleguide:storybook-test",
Expand Down
7 changes: 7 additions & 0 deletions packages/nextra-site/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# next.js
/.next/
/out/

# typescript
next-env.d.ts
*.tsbuildinfo
29 changes: 29 additions & 0 deletions packages/nextra-site/app/[[...mdxPath]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { generateStaticParamsFor, importPage } from 'nextra/pages';

import { useMDXComponents as getMDXComponents } from '../../mdx-components';

export const generateStaticParams = generateStaticParamsFor('mdxPath');

type PageProps = {
params: Promise<{ mdxPath?: string[] }>;
};

export async function generateMetadata(props: PageProps) {
const params = await props.params;
const { metadata } = await importPage(params.mdxPath);
return metadata;
}

const Wrapper = getMDXComponents().wrapper!;

export default async function Page(props: PageProps) {
const params = await props.params;
const result = await importPage(params.mdxPath);
const { default: MDXContent, toc, metadata } = result;

return (
<Wrapper toc={toc} metadata={metadata}>

Check warning on line 25 in packages/nextra-site/app/[[...mdxPath]]/page.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

Props should be sorted alphabetically
<MDXContent {...props} params={params} />
</Wrapper>
);
}
54 changes: 54 additions & 0 deletions packages/nextra-site/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'nextra-theme-docs/style.css';

Check failure on line 1 in packages/nextra-site/app/layout.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

Do not create anymore stylesheets, please see Gamut's Storybook documentation for current best practices

import type { Metadata } from 'next';
import { Head } from 'nextra/components';
import { getPageMap } from 'nextra/page-map';
import { Footer, Layout, Navbar } from 'nextra-theme-docs';

export const metadata: Metadata = {
title: {
default: 'Gamut',
template: '%s – Gamut',
},
description: "Codecademy's design system documentation",
};

const navbar = (
<Navbar
logo={<b>Gamut</b>}
projectLink="https://github.com/Codecademy/gamut"
/>
);

const footer = (
<Footer>
MIT {new Date().getFullYear()} © Codecademy. Built from{' '}
<a href="https://github.com/Codecademy/gamut/blob/main/docs/adr/0001-documentation-site-information-architecture.md">
ADR 0001
</a>
.
</Footer>
);

export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" dir="ltr" suppressHydrationWarning>

Check warning on line 39 in packages/nextra-site/app/layout.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

Props should be sorted alphabetically
<Head />
<body>
<Layout
navbar={navbar}
pageMap={await getPageMap()}
docsRepositoryBase="https://github.com/Codecademy/gamut/tree/main/packages/nextra-site"

Check warning on line 45 in packages/nextra-site/app/layout.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

Props should be sorted alphabetically
footer={footer}

Check warning on line 46 in packages/nextra-site/app/layout.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

Props should be sorted alphabetically
sidebar={{ defaultMenuCollapseLevel: 1 }}
>
{children}
</Layout>
</body>
</html>
);
}
10 changes: 10 additions & 0 deletions packages/nextra-site/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function NotFound() {
return (
<div>
<h1>404: Page not found</h1>
<p>
Try the <a href="/">home page</a> or the sidebar navigation.
</p>
</div>
);
}
33 changes: 33 additions & 0 deletions packages/nextra-site/components/ButtonAnatomy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const part = {
border: '1px dashed #9ca3af',
borderRadius: 6,
padding: '.5rem .75rem',
fontSize: '.8rem',
color: '#4b5563',
};

/**
* A labeled diagram of the component's parts (ADR 0001 §3 Anatomy).
* Buttons in this family are visual, so unlike a stateless utility
* this section isn't skipped.
*/
export const ButtonAnatomy = () => (
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>

Check failure on line 15 in packages/nextra-site/components/ButtonAnatomy.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

The use of inline styles is discouraged — consider using styled components, design system utilities, or CSS classes instead
<div
style={{

Check failure on line 17 in packages/nextra-site/components/ButtonAnatomy.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

The use of inline styles is discouraged — consider using styled components, design system utilities, or CSS classes instead
display: 'flex',
alignItems: 'center',
gap: '.5rem',
border: '2px solid #111827',
borderRadius: 8,
padding: '.5rem 1rem',
}}
>
<span style={part}>icon (optional)</span>

Check failure on line 26 in packages/nextra-site/components/ButtonAnatomy.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

The use of inline styles is discouraged — consider using styled components, design system utilities, or CSS classes instead
<span style={{ fontWeight: 600 }}>Label</span>

Check failure on line 27 in packages/nextra-site/components/ButtonAnatomy.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

The use of inline styles is discouraged — consider using styled components, design system utilities, or CSS classes instead
</div>
<span style={{ fontSize: '.8rem', color: '#6b7280' }}>

Check failure on line 29 in packages/nextra-site/components/ButtonAnatomy.tsx

View workflow job for this annotation

GitHub Actions / lint (lint)

The use of inline styles is discouraged — consider using styled components, design system utilities, or CSS classes instead
← container: sets variant color, size, and focus/hover states
</span>
</div>
);
Loading
Loading