The complete visual language for the UX Design Library. Every token, component, and rule in one living reference. This page uses the same shared.css, what you see is what the site renders.
Four rules are non-negotiable. They keep Noir Book legible, calm, and readable for people with eye trouble. Break any of them and the design-system guardrail or an accessibility review will catch it.
Content (anything someone reads, card descriptions, body paragraphs, captions, labels) never renders below 15px. Nav chrome (nav links, breadcrumbs, topic-number codes, tier-counts, filter buttons, view toggles) can drop to 13px via --fs-nano, paired with a semibold or bolder weight to keep small text firm. Nothing anywhere goes below 13px. The --fs-* token scale is the whole vocabulary, hardcoded sizes are banned and caught by review.
Body copy runs at --ink-soft (#1F1C18, near-black on cream). Secondary copy and captions use --ink-medium (#3A362F), which is also the floor for any muted text. The legacy --ink-muted and --ink-light tokens are aliased to the same value so older CSS cannot re-introduce light grey by accident. --ink-faint is reserved for decorative non-text elements (icons, dashed borders). --ink-hairline is for dividers and card borders only, never for type. If you need a muted caption, use --ink-medium. Never lower the contrast; darken the background instead.
Default body weight is --fw-regular (400). Secondary and UI chrome use --fw-medium (500) or --fw-semibold (600) for firmness at small sizes. --fw-bold (700) is for emphasis inside titles via <strong>. --fw-thin (200) is reserved for display h1 and h2 where it pairs with a bold <strong> to create the signature weight-contrast look. Do not use thin on body copy, captions, or small UI, it disappears at low weight.
A hover lift, a drawn outline, or any transform belongs only on an element the user can actually click or select. On the homepage, topic cards lift and draw a full copper outline because they're links. Static display surfaces on topic pages, method cards, case studies, callouts, template boxes, at-a-glance blocks, pitfalls, are inert. They keep their copper corner strip at rest but do not animate on hover. If you add a hover effect to a new component, ask whether clicking it does something. If not, the hover comes off.
Two typefaces, one with attitude. Fira Sans carries both display and body roles, with weight contrast (thin 200 for editorial restraint, bold 700 for emphasis) doing the work a serif used to. JetBrains Mono handles code and the topic-number codes. Unifying on Fira means every page reads as one voice, with refinement coming from weight and spacing rather than typeface switching [commas, no em-dashes].
--fs-* tokens)The whole library renders through this scale. To shrink or grow everything, edit --fs-* values in shared.css :root, one line per role. Hardcoded sizes are banned and caught by review. 15px is the floor.
A warm, neutral palette inspired by ink on aged paper. Colours are defined as CSS custom properties and adapt automatically in dark mode. All text colours meet WCAG AA contrast requirements against their intended backgrounds.
Use --paper for the page. --paper-warm for cards, hover states, expandable headers. --paper-deep for deeper UI wells. --paper-white for inputs and template boxes.
Text hierarchy flows from --ink (headings) through --ink-soft (body strong) and --ink-medium (body) to --ink-muted (captions). All text tokens pass WCAG AA on --paper. Unlike v49, --ink is never used as a surface, the nav flips with theme now, so the ink stack is purely for text and icons.
v50 drops every legacy accent (rust, teal, navy, plum, gold) in favor of a single copper family. Copper covers links, hero emphasis, CTAs, card left-strips, nav active state, reading progress, check marks, bookmarks, source labels. Three intensities cover the range: --ink for Tier 1 and warnings, --ink for most UI, --ink for solid CTAs and larger fills, --ink reserved for use on dark surfaces.
The library's four disciplines need distinct categorical signal, but not at the cost of the one-accent rule. So they're desaturated earth tones, each 40-60% saturation, all warm enough to live next to copper without fighting it. Each discipline has a matching -bg token at ~8% alpha for very faint card tints.
Tier and content-type (theory / practical / examples) are mapped to three intensities of copper, not to hue. Tier 1 reads darker (more essential), Tier 4 fades to a warm neutral. This collapses 4+3 categorical colors to a single tonal progression.
Old library accent names survive as aliases so component CSS written against v49 still resolves. --rust = --disc-pm (burnt copper), --teal = --disc-ux (emerald), --navy = --disc-mk (slate), --plum = --disc-cs (mauve), --gold = --ink. No legacy accent introduces a new hue, they're all rewired onto the palette above.
Dark mode is the career-site dark section, treated as the opposite of the cream section. Paper flips to #0D1210 (near-black with green undertone), surface to #161A18, text to #EEF0EA and #C8D0CC, hairline to #2A302D. Copper shifts up to the bright variant. Discipline pigments shift up ~20% luminance to stay legible on deep dark. Shadows deepen to pure black with higher alpha.
Editorial structure first, soft modern affordances second. Most editorial surfaces (cards, callouts, templates) keep sharp ink-on-paper edges. Interactive controls and overlays (search inputs, dropdowns, toasts, theme toggle, burger panel) use a small radius scale and a discreet shadow scale to read as tappable.
| Weight | Value | Use |
|---|---|---|
| Standard | 1px solid --ink | Method cards, template boxes, expandables, case studies, topic header bottom, deep-dive separator |
| Accent | 1px solid [color] | Tier badges, split badges, callouts, NN/g pill, case study borders |
| Faint | 1px solid --ink-faint | h2 and h3 border-bottom, template header divider, inactive filter pills |
| Hairline | 1px solid --ink-hairline | Checklist separators, pitfall dividers, search result dividers, scrolled nav border |
| Heavy left | 3px solid --ink | Burger panel left edge |
| Dashed | 2px dashed --ink-faint | Template fields (fill-in areas) |
Editorial surfaces (cards, method blocks, callouts, badges) stay square. Radius is reserved for interactive controls, overlays, and pills. Use a token; don't invent intermediate values.
| Token | Value | Use |
|---|---|---|
| --radius-xs | 4px | Tier badges, logo mark, zero-suggest CTA |
| --radius-sm | 6px | Section result rows, small chip surfaces |
| --radius-md | 8px | Theme toggle, burger button, view toggles |
| --radius-lg | 10px | Search inputs, search dropdown, nav-search wrap |
| --radius-xl | 12px | Tier groups, search result blocks, collapsed tier headers |
| --radius-pill | 999px | Discipline pills, filter pills, footer pills, search phrase pills, toast |
Shadows are warm and low-intensity in light mode (rgba of --ink) and pure black in dark mode for legibility. The --shadow-* tokens auto-flip across themes; consumers always reference the token, never a raw rgba().
| Token | Use |
|---|---|
| --shadow-sm | Subtle lift, reserved (available for new components) |
| --shadow-md | Scrolled home-nav drop shadow |
| --shadow-lg | Topic-card hover lift |
| --shadow-focus | Search input focus, burger search input focus |
| --shadow-popover | Search dropdown popover |
| --scrim | Burger overlay backdrop (rgba scrim, not box-shadow) |
All stacking contexts use the --z-* scale so layering decisions live in one place. New floating UI should reuse an existing token, not invent a number.
| Token | Value | Use |
|---|---|---|
| --z-back-to-top | 90 | Back-to-top button on topic pages |
| --z-progress | 99 | Reading progress bar under nav |
| --z-nav | 100 | Sticky site-nav |
| --z-search | 200 | Search dropdown popover |
| --z-toast | 300 | Filter feedback toast |
| --z-burger-overlay | 300 | Burger menu scrim |
| --z-burger-panel | 301 | Burger menu panel (above its own scrim) |
Spacing follows a 4px-based scale exposed as --space-* tokens. Layout constraints (page width, content measure, sidebar, nav height) are exposed as named layout tokens.
Use a token, not a magic number. The scale is denser at the small end (where eye-distance differences matter most) and sparser at the large end. --space-7: 28px exists because the editorial card padding has historically been 28px; otherwise prefer 24 or 32.
| Token | Value | Typical use |
|---|---|---|
| --space-1 | 4px | Pill remove icon padding, search dropdown margin-top |
| --space-2 | 8px | TOC link padding, badge gap, pitfall icon gap |
| --space-3 | 12px | Cross-ref gap, tier group internal gaps |
| --space-4 | 16px | Container mobile padding, callout title bottom-margin, list padding |
| --space-5 | 20px | Tier group padding, search dropdown padding |
| --space-6 | 24px | Component vertical margin, callout block padding |
| --space-7 | 28px | Method card / template box internal padding (legacy editorial size) |
| --space-8 | 32px | v48 method card / case study padding (comfort pass) |
| --space-10 | 40px | Container desktop padding, nav-inner padding |
| --space-12 | 48px | Hero padding-bottom, footer padding |
| --space-14 | 56px | Topic-page section spacing (v48 comfort pass) |
| --space-16 | 64px | Topic layout sidebar gap, h2 top margin in topics, hero padding-top |
| --space-24 | 96px | Deep-dive section margin-top, site-footer margin-top |
| Token | Value | Use |
|---|---|---|
| --max-width | 1140px | Page container max-width (.container, .nav-inner) |
| --content-width | 680px | Max-width on body paragraphs (prevents overly long measure) |
| --sidebar-width | 200px | TOC sidebar on topic pages (desktop only) |
| --nav-height | 72px | Sticky nav height; used for sticky TOC offset and reading-progress top |
Homepage tier groups use a 3-column topic grid (compact view) or 2-column (full view), wrapped in a rounded .tier-group container with --paper-warm background and --radius-xl. Topic cards inside a tier group are bordered with hairline dividers (1px --ink-hairline) on right and bottom edges.
Topic pages use a 2-column grid: var(--sidebar-width) 1fr with align-items: start. The sidebar is position: sticky at top: calc(var(--nav-height) + var(--space-8)).
Every reusable element in the library. Components reference design tokens for all colour, shadow, radius, spacing, and z-index values. The build script verifies this on each build and warns on any raw colour literals introduced outside the :root token blocks.
Use .callout.tip for practical advice, .callout.warning for common mistakes, .callout.info for context or decision guidance. Always include a .callout-title. Callouts use a full 2px outline in the accent colour — never a single-side border.
This is a tip callout. Use it for actionable advice the reader can apply immediately.
This is a warning callout. Use it to flag pitfalls and errors to avoid.
This is an info callout. Use it for background context or decision guidance.
Tier badges identify priority level. Split badges show the theory/practical/examples content ratio. Both use border: 2px solid box style — never border-left.
Used in topic pages to describe specific methods or techniques. Warm background, ink border, with an optional method tag (inverted ink label).
Use when: Starting any project, validating assumptions, exploring a problem space.
One-on-one conversations with real or potential users to uncover what matters to people and why.
Highlights real-world examples. Uses rust accent colour to distinguish from method cards.
How Airbnb used design thinking to transform their service after a period of declining bookings.
Click items to toggle checked/unchecked state. Visual only — state does not persist across page loads. Designed for screenshot-based note-taking during project work.
Used for deep-dive appendix content and optional detail. Warm header, white content area, chevron indicator.
stroke-linecap: round and stroke-linejoin: round.border-radius for rounded ends. Tick sits visually centred-low within the checkbox.Fillable templates with dashed-border fields. Uses --paper-white background to stand out from the page.
Numbered steps with 32px ink-black squares and 20px Playfair Display numbers inside, nudged up 3px for optical centering. Used for sequential processes.
Understand the user's world through observation and interviews.
Synthesize findings into a clear problem statement.
Link to related topics at the bottom of each topic page. Hairline border, hover reveals ink border and warm background.
Used in "Common pitfalls" sections. Icon + text layout with .pitfall container, .pitfall-icon for the exclamation mark, and a nested div for the heading and description.
Overview block in the topic header, below content-splits. Gives readers a quick scan of what the topic covers before they commit to reading. Uses .at-a-glance with full 2px ink border. Items are color-coded by content type using data-type attributes (theory/practical/examples). Grid collapses from 2-col to 1-col at ≤960px.
Interactive behaviours and navigation patterns used across the site.
Sticky .site-nav at top, --nav-height (72px) tall, --ink background, no bottom border at rest. Logo uses Playfair Display 900 with a 40px --paper square mark (radius --radius-xs) and margin-right: auto. Nav links, discipline pills, search, theme toggle and burger button cluster on the right with --space-6 gap. Nav links are 11px uppercase Outfit 600 in --paper-deep. Theme toggle and burger button are 40px squares with --radius-md.
Subpage variant: the .subpage-nav modifier shows a smaller logo (32px mark), reveals the .nav-back-link and .nav-title, and reflows the right cluster (search, theme toggle, burger). On mobile, the search input expands when focused and the nav-title fades out to give it room (.search-expanded state).
A 3px --rust line fixed directly under the nav bar. Width updates on scroll from 0% to 100%. Topic pages only.
The .topic-sidebar element is position: sticky with align-self: start. Coloured dots (7px circles) indicate section type — gold for theory, teal for practical, plum for examples. Active item gets an ink-coloured left border and bold weight via scrollspy.
Available on all pages. Two placements: Homepage — full-width input in the filter bar, 2px ink border, 14px Outfit, placeholder "Search topics, methods, frameworks...". Topic pages — 220px input in the nav bar, same styling.
Behaviour: Typing 2+ characters opens a dropdown grouped by topic. Each result row shows a title and a type badge (theory / practical / examples / method). Maximum 8 topic groups displayed. Fuzzy matching via Levenshtein distance (threshold ≤1) catches typos on queries of 3+ characters. Dropdown closes on click outside, Escape key, or result selection.
Homepage dual sync: The filter bar search (#searchInput) and the scrolled nav search (#navSearchInput) stay synced — typing in either updates both. The filter bar input drives filtering; the nav input is a convenience duplicate visible after scrolling past 120px.
Search index: Built by build.js from content file metadata. Lives in js/search.js as SEARCH_INDEX — an array of topic objects, each containing topic (title), url, discipline, sections[] (with id, title, type, keywords), and methods[] (with name, section, keywords). Search matches against topic titles, section titles + keywords, and method names + keywords.
Clear button: An × button appears inside the search box (absolute positioned, right) when the input has a value. Class .has-query on the .search-box wrapper controls visibility.
Safety: All user input rendered in dropdown "no results" messages is escaped via escapeHtml() to prevent HTML injection.
Discipline toggles — four buttons in a row (UX Design, Product Management, Marketing & Growth, Content Strategy). Each has a coloured dot and a dynamically computed count. Active state uses the discipline colour for border and text; inactive state is 50% opacity with faint border. Single click toggles one discipline on/off. Double-click (within 400ms) isolates that discipline — or re-enables all if it was already isolated. First single click shows a hint toast.
Tier filter — text-link buttons: All, Core toolkit (Tier 1), Specialized (Tier 2), Situational (Tier 3), Reference (Tier 4). Active state is bold with ink underline. Only one tier active at a time. "All" shows every tier.
Sort — two text buttons (Discipline, Tier). Active sort is bold ink. A chevron SVG (▲ / ▼) indicates direction. Clicking the active sort reverses direction. When sorting by discipline, topic cards within each tier grid are reordered (UX → PM → MK → CS) and separator rows are inserted between disciplines. When sorting by tier, the tier <section> elements are reordered in the DOM.
Three toggle buttons in the controls row. Order: Connected, NN/g, Bookmarked. Each acts as an additive filter — when active, only matching cards are shown. Multiple filters can be active simultaneously (AND logic: a card must match all active filters). Default: all off (show everything).
Connected — shows topics that span multiple disciplines. Styled with teal border and teal background tint when active. A card matches when it has a data-cross attribute (listing the other disciplines it connects to, e.g. data-cross="pm mk"). Connected topics also display small discipline badges (e.g. PM, MK) in the card's top-right corner. These are auto-generated from the cross_discipline field in the content file's MANIFEST block.
NN/g — shows topics sourced from or enriched by Nielsen Norman Group material. Styled with orange border (--nng) and orange background tint when active. A card matches when it has data-sources="nng". This attribute is built from the sources field in the MANIFEST block.
Bookmarked — shows topics the user has bookmarked. Styled with gold border and gold background tint when active. Bookmarks are stored in localStorage under key bb_bookmarks as a JSON object keyed by topic number (e.g. {"UX.1.01": true}). A bookmark icon (SVG bookmark path, 16×20px) appears on each topic card — invisible at rest, visible on hover, always visible when bookmarked (gold fill).
Filter state (disciplines, tier, sort, sort direction, show-filters, search query, view mode) is saved to sessionStorage under key bb_filters on every change. Restored on page load so navigating to a topic and back preserves the user's filter context. Bookmarks use localStorage (persist across sessions). View mode (compact / full) is part of the session state.
Three-way toggle: system default, manual dark, manual light. Preference saved to localStorage. All colours adapt via CSS custom properties — no separate stylesheets. Toggle button in nav shows moon/sun icon.
44px ink-black square, fixed bottom-right, appears after 400px scroll. Hover changes to --rust. Topic pages only.
Homepage tier groups can be collapsed and expanded. Add .collapsible-section to the section, .collapsible-header with onclick="toggleSection(this)" to the header, an SVG chevron inside .collapse-chevron, and wrap the body content in .collapsible-body. The chevron is a stroked polyline (no fill) that rotates -90° when collapsed. Filter tab clicks auto-expand matching collapsed sections.
At ≤960px the burger button (.burger-btn, 40px square, --radius-md) appears in the right of the nav. Clicking opens .burger-overlay (a --scrim backdrop at --z-burger-overlay) plus a slide-in .burger-panel from the right (max 320px wide / 85vw, --paper background, 3px ink left border, --z-burger-panel). The panel contains a header with close button, a search input (which uses --shadow-focus on focus), inline page links, and the current page's TOC if applicable. Closes on backdrop click, close button, Escape, or link click. The legacy .mobile-toc-btn is kept as display:none for back-compat.
Four .disc-toggle pills inside .nav-filters (UX, PM, MK, CS). At rest: outlined with --paper-deep, 0.5 opacity. Hover lifts the border to --paper. Active state uses the --disc-{ux|pm|mk|cs} tokens for border + text and --disc-{ux|pm|mk|cs}-bg for the wash. Because the nav background is --ink (which flips), the discipline tokens flip the opposite way: bright pastels in light mode, deep saturated tones in dark mode. A small .disc-dot uses the same token for the leading bullet.
Pills shape: --radius-pill, font 12px Outfit 600. Each pill includes a 2-letter .disc-code suffix (UX/PM/MK/CS) at 0.85 opacity so the pill maps to the topic-number prefix.
Both the homepage filter-bar search and the topic-page nav search support phrase pills: typing a comma or pressing Enter commits the typed text as a removable pill chip and clears the input. Pills accumulate as the user refines a multi-term search, so each commit is preserved as a discrete filter token rather than a single concatenated string.
Pill markup is .search-phrase-pill (paper-warm background, ink-faint border, --radius-pill) with a small .pill-remove × button. A .search-clear button (×) appears on the right edge of the input when the box has any value (.has-query state on .search-box), clearing both the input and all committed pills.
In the nav-search variant (.nav-search-pills inside .nav-search-wrap), pills sit inline with the input behind a translucent white wash (the one intentional theme-invariant rgba in shared.css, since the nav background is always dark) and scroll horizontally if they overflow.
Any element with a data-source attribute renders a small uppercase label in the top-right corner via ::after, coloured --nng for NN/g sources. Targets: .method-card, .callout, .case-study, .template-box, .pitfall, and details.expandable. Section-level provenance (an entire <section data-source="…">) renders a visible inline .source-label next to the h2.
How the codebase is organised and where each concern lives.
shared.css owns all visual tokens (colour, spacing, radius, shadow, z-index, layout, typography) and all component styles. Page CSS files (home.css, topic.css) own only layout-specific rules and the v48 comfort pass. Inline styles in index.html own homepage-only filter bar mechanics. To change the visual identity globally, edit only the :root token blocks in shared.css.
The SEARCH_INDEX in js/search.js is regenerated by build.js from each content file's SEARCH_INDEX block, and the same is true for library-manifest.json entries. Hand-editing either file risks being overwritten on the next build.
A token guardrail in build.js scans css/ on every build and warns on raw hex / rgba colour literals declared outside the :root token blocks. The intent is that shared.css's :root remains the single source of truth for colour and shadow values.
Two breakpoints handle the transition from desktop to mobile. The site uses a mobile-first override approach — desktop styles are the default, with breakpoints scaling down.
| Breakpoint | Width | What changes |
|---|---|---|
| Tablet | max-width: 960px | Hero collapses to single column. Topic cards switch from 3-column to 2-column grid. Topic sidebar (TOC) is hidden — replaced by floating "Contents" button with slide-up TOC panel. At-a-glance grid collapses to 1-col. Nav link gap tightens. |
| Mobile | max-width: 768px | Nav links hidden, mobile back link appears. All grids collapse to single column. Type scale reduces. Container padding drops to 16px. Logo and theme toggle shrink. |
At ≤768px, .nav-links is hidden entirely. On topic pages, a .nav-back element (hidden on desktop) becomes visible to provide a way back to the homepage. This is a separate element from the nav-links back link — it lives outside the ul so it survives the display: none.
The logo shrinks to 1.2rem with a 32px mark. The theme toggle shrinks to 32px. These elements plus the back link must fit comfortably on a 375px screen.
| Element | Desktop | Mobile (≤768px) |
|---|---|---|
| Homepage h1 | 62px | 2rem (32px) |
| Homepage subtitle | 17px | 15px |
| Topic h1 | 3rem (48px) | 2rem (32px) |
| Topic subtitle | 1.125rem (18px) | 1rem (16px) |
| Card numbers | 52px | 36px |
| Logo | 1.5rem | 1.2rem |
| Grid | Desktop | Tablet (≤960px) | Mobile (≤768px) |
|---|---|---|---|
| Hero | 1fr 1fr | 1fr | 1fr |
| Topic cards | 3 columns | 2 columns | 1 column |
| Pathway cards | 3 columns | 2 columns | 1 column |
| Topic layout | sidebar + content | content only | content only |
| Prev/next nav | 1fr 1fr | 1fr 1fr | 1fr |
| Cross-ref cards | auto-fill 200px | auto-fill 200px | 1fr |
.nav-back element in the nav for mobile access, an .at-a-glance block in the header with 4–6 items covering what the topic contains, a .mobile-toc-btn and .mobile-toc-overlay panel mirroring the sidebar TOC, and a <script src="../js/mobile-toc.js"> include. Always test components at 375px width before shipping. Use relative units (rem, %) for anything that should scale — hardcoded px for things that shouldn't (borders, small icons).Contrast ratios and accessibility compliance for the Design System palette.
| Token | Light ratio | WCAG level |
|---|---|---|
| --ink on --paper | 15.7:1 | AAA — headings, strong text |
| --ink-soft on --paper | 10.2:1 | AAA — body text |
| --ink-muted on --paper | 4.7:1 | AA — nav links, labels, descriptions |
| --ink-light on --paper | 4.8:1 | AA — breadcrumbs, TOC links, status text |
| --ink-faint on --paper | 3.5:1 | AA Large — card numbers (52px), borders |
All text-bearing tokens pass WCAG AA (4.5:1) for normal text, except --ink-faint which is used only for large decorative text (52px card numbers) and passes AA Large (3:1). Dark mode equivalents are also verified.