Design System

Wordnerds Design System

Version: 0.2
Last updated: 2026-05-28
Authoritative token values: components/tokens.css
Component gallery: components/index.html (open in browser)
Production spec: production-css-spec.md

This file teaches AI tools the rules of the Wordnerds design system — not just the values, but when and how to apply them. Read this before generating any Wordnerds-branded output.


1. Brand Identity

Wordnerds is a professional text-analysis and Voice of Customer (VoC) platform used by enterprise insight teams. The visual language is bold but restrained: high-contrast dark chrome punctuated by a signature amber accent, with neutral greys forming the content canvas.

The emotional register is confident, data-first, and human. The design should feel like a serious analytical tool that people actually enjoy using. It is not playful or casual, but it is warm. The rounded typography and amber accent prevent it from feeling cold.

Everything is derived from the platform UI. The marketing site, presentations, diagrams, and lead magnets all use the same atomic vocabulary as the product itself. If something looks like it could be from a different brand, it is wrong.

The two design signals are: dark chrome + amber. Off-black (#222222) for structural chrome (navigation, sidebars, footers). Brand yellow (#FAB316) for the one thing that matters most on any given surface — the primary CTA, the active state, the thing the user should act on next. Everything else is grey.


2. Colour Palette

Token file: components/tokens.css
Gallery reference: components/index.html#colours

Primary brand

Token Hex Rule
--wn-brand-yellow #FAB316 Primary CTA and active states only. Do not use decoratively, as a background wash, or for text. One dominant yellow element per surface.
--wn-off-black #222222 Navigation, sidebars, and structural chrome. Never use pure #000000 — off-black has warmth that pure black lacks.
--wn-brand-blue #39B8E1 Secondary CTAs, info accents, automated/AI feature signalling. Never competes with yellow — use where yellow is already taken.

Rule: yellow is brand signal, not state signal

Yellow means "this is the primary action." It does not mean "success," "warning," or "positive." When something is active or selected, it gets off-black with a yellow accent — not a yellow background unless it is itself the primary CTA. A filter button goes yellow when active. A table row does not.

Rule: sentiment colours are data-only

The --wn-sent-* palette (green → grey → red) is reserved exclusively for visualising customer sentiment data. Never use these colours for UI states, success/error messages, decorative elements, or marketing. A very happy green on a non-sentiment element is always wrong.

Grey scale (use instead of tints)

Do not create tints of brand yellow or blue. Use the grey scale instead.

Token Hex Primary use
--wn-dark-grey #444444 Body text, icons, hover states
--wn-mid-grey #707070 Secondary text, descriptions
--wn-border-grey #b9b9b9 Borders, dividers
--wn-background-grey #EEEEEE Page background — the dominant surface
--wn-light-grey #EAF0F4 Card footers, form backgrounds, cool surfaces
--wn-lighter-grey #f8f8f8 Card backgrounds
--wn-lightest-grey #fafafa Elevated surfaces, modals
--wn-white #FFFFFF Card bodies, input backgrounds

Section background tints

Two warm/cool alternatives for marketing page rhythm. Section wrappers only — never use on components, cards, or UI elements.

Token Hex Class Use
--wn-section-yellow-tint #FEF4DC .wn-section--yellow-tint Warm golden tint — breaks up grey-scale section sequences with a brand-adjacent warmth without full yellow saturation
--wn-section-blue-tint #E1F4FB .wn-section--blue-tint Cool sky tint — provides a cooler rhythm break between neutral sections

Both colours inherit default text and eyebrow colours — no context overrides needed. The standard yellow primary CTA remains readable on both backgrounds.

These tokens are accent tools, not a rhythm pattern. Use them sparingly — at deliberate visual accent points — not to alternate mechanically between every section. Most sections on a page should be white; tints appear only where a background shift is intentionally earning its place. Do not create additional tints — use the existing palette.

Sentiment palette (data viz only)

Token Hex Meaning
--wn-sent-very-happy #6BB32D Very positive sentiment
--wn-sent-happy #95C11F Mildly positive sentiment
--wn-sent-neutral #CFD9DF Neutral sentiment
--wn-sent-sad #EA752B Mildly negative sentiment
--wn-sent-angry #E94C1E Very negative sentiment

Sentiment always reads green → grey → red, left to right in diverging bars. Never reverse the scale.

Do not introduce new colours

Any new colour requires amending tokens.css, tokens/tokens.json, and production-css-spec.md in the same change. Do not add one-off hex values to individual components or pages. The two section tints above are the approved additions — no further tints needed.


3. Typography

Gallery reference: components/index.html#typography

Two families, used by role — not by size

The rule is role, not size. A small heading uses Rounded. A large body paragraph uses Regular. Never swap them based on visual weight alone.

Type scale

Fluid type — Utopia methodology. Each step uses clamp(min, preferred, max) so sizes scale smoothly between 375px and 1440px viewports. No breakpoints needed.

Parameters: base 18px → 20px · ratio 1.2 (mobile) → 1.25 (desktop) · viewport 375–1440px

Token Mobile → Desktop HTML role Use
--fs-3xl 44.8px → 61.0px H1 hero Hero headline — largest marketing moment
--fs-2xl 37.3px → 48.8px H1 page Primary page title
--fs-xl 31.1px → 39.1px H2 Section headings, page sub-titles
--fs-lg 25.9px → 31.3px H3 Card headings, feature titles
--fs-md 21.6px → 25.0px H4 / overline Eyebrows, strong labels, small card titles
--fs-base 18.0px → 20.0px p / default All body copy, descriptions, list items
--fs-sm 15.0px → 16.0px caption Image captions, helper text, badges
--fs-xs 12.5px → 12.8px fine print Legal copy, timestamps, footnotes

Heading-to-body ratio: ~2.5:1 (mobile) → ~3.05:1 (desktop). Conservative — enterprise data platform, not a tool startup.

Do not create sizes outside this scale. Do not hardcode px or rem font sizes in website/marketing components — always use a --fs-* token.

Product UI exception: emoji characters, checkbox checkmarks, and compact tag labels inside fixed-dimension containers (≤24px) are exempt — their size is constrained by the container, not by reading comfort, and fluid scaling would cause overflow. These remain hardcoded (10–14px) until a separate product UI token set is defined in Phase 3.

Font–role pairing

The rule is role, not size. A small heading uses Rounded. A large body paragraph uses Regular.


4. Spacing

Gallery reference: components/index.html#spacing

Fluid space scale

Utopia methodology — same effective range as the type scale (360→1240px). Nine steps plus eight one-up pairs. Steps are multiples of the base space unit (18px mobile → 20px desktop).

Token Mobile → Desktop Primary use
--space-3xs 5px (static) Hairline gaps, icon nudges
--space-2xs 9px → 10px Icon padding, tight inline gaps
--space-xs 14px → 15px Small button padding, list indent
--space-s 18px → 20px Base unit — component internal padding
--space-m 27px → 30px Card gap, grid gap
--space-l 36px → 40px Component margin, form field gap
--space-xl 54px → 60px Between-component spacer
--space-2xl 72px → 80px Section padding (smaller)
--space-3xl 108px → 120px Section padding (generous) / hero

One-up pairs (min from lower step, max from upper step — transition faster than a single step, good for layout flex):

--space-3xs-2xs · --space-2xs-xs · --space-xs-s · --space-s-m · --space-m-l · --space-l-xl · --space-xl-2xl · --space-2xl-3xl

Key mappings from layout research:

Do not use arbitrary px or rem values for spacing in website/marketing components — always use a --space-* or --wn-space-* token.

Layout constants (not fluid)


5. Corner Radii

Gallery reference: components/index.html#spacing (radius section)

Rounded everything. Hard corners are rare and deliberate — used only when a component must look structural rather than interactive.

Token Value Use
--wn-radius-sm 6px Small chips, rule builder rows, table cells, tooltip bubbles
--wn-radius-md 1rem Logic pills, rule rows
--wn-radius-card 1.5rem Cards and buttons — the workhorse radius. Use for all cards, CTAs, and component containers unless a more specific token applies.
--wn-radius-input 2rem Form inputs — pill style
--wn-radius-input-lg 3rem Large form inputs
--wn-radius-pill 999px Fully oval — filter pills, tags, lozenges

--wn-radius-card (1.5rem) is the default. When in doubt, use it. Reach for --wn-radius-sm (6px) only for compact, data-dense components (table cells, tooltips). Never use hard right angles (0px) unless explicitly justified.


6. Shadows & Motion

Shadows

Token Value Use
--wn-shadow-card 0 2px 20px 0 rgba(0,0,0,0.1) All elevated surfaces: cards, modals, floating panels. The default shadow.
--wn-shadow-pop 0 4px 12px rgba(0,0,0,0.12) Tooltip bubbles, dropdown menus, picker overlays. Slightly more prominent to indicate floating above the card layer.

Shadows are soft and low-opacity. Depth is conveyed by shadow, not by hard borders. Never add borders to cards that already have shadows — pick one.

Motion

Token Value Use
--wn-motion-fast 0.3s Card hover lifts, component state transitions
--wn-motion-base 0.4s ease-in-out Sidebar link transitions, most interactive transitions
--wn-motion-slow 0.5s ease-in-out Sidebar expand/collapse, disclosure chevron rotation

Never exceed --wn-motion-slow. Animations faster than --wn-motion-fast should be limited to micro-interactions. Always respect prefers-reduced-motion — wrap reveal animations in @media (prefers-reduced-motion: no-preference).

Card hover

Cards lift on hover: transform: translateY(-10px) at --wn-motion-fast (0.3s). Project-style cards scale instead: transform: scale(1.1) at 0.3s ease-in-out.


7. Core Components

All components live in components/. Each has a self-contained HTML file showing every state. The component gallery at components/index.html shows them all on one page.

Do not re-implement components from scratch. Lift the markup from the component file and parameterise the content.

Naming convention

Root class: .wn-<component> (BEM-style)
State modifiers: .wn-<component>--<state> (--inactive, --hover, --active, --disabled)
Child elements: .wn-<component>__<element>

Component inventory

Component File Key rule
Filter button filter-button.html Black pill + yellow icon. Active = yellow fill + black text.
Dropdown dropdown.html Pill input with caret. Selected option shows yellow underline when closed.
Tooltip tooltip.html Circular ? glyph. Very muted inactive; bubble floats above on active.
Text link text-link.html Dark grey, underlined, weight 500. Hover thickens underline — no colour change.
Export button export-button.html Circular icon. Very muted inactive; active = yellow fill.
Chart row chart-row.html Diverging sentiment bar (green → grey → red). Hover = cool wash. Active = inverted to black.
Theme lozenge theme-lozenge.html Pill tag. Inactive = quiet border. Active = yellow fill + close glyph.
Table styles table-styles.html Three cell modes: text, wheel (donut, default yellow), bar (default blue).
Verbatim card verbatim-card.html White body on cool-grey surround. Always shown against the grey page background.
Form inputs form-inputs.html .wn-form-group wrapper; .wn-form-input / .wn-form-textarea (pill radius, --wn-radius-input); .wn-form-hint (mid-grey); .wn-form-error (border-grey + off-black, never sentiment red).
Buttons form-inputs.html .wn-btn base + --primary / --secondary / --ghost modifiers. See §7 Buttons (marketing pages).

Active state rule (universal)

When a component is active or selected: off-black fill + yellow accent. The exception is the filter button and lozenge, where the component itself becomes the primary CTA — in those cases, the whole component fills yellow. Never use a coloured fill (blue, green, red) to indicate active state. Never use yellow as a background for more than one element on the same surface.

Buttons (marketing pages)

Class: .wn-btn (base) + .wn-btn--primary / .wn-btn--secondary / .wn-btn--ghost
Component file: components/form-inputs.html

Primary CTA (.wn-btn--primary)

Secondary CTA (.wn-btn--secondary)

Ghost CTA (.wn-btn--ghost)

Arrow link (.cta-secondary)


8. Design Rationale

These decisions are load-bearing. Understand the why before deviating.

Why off-black and not pure black? Pure black (#000000) reads as harsh and digital. The off-black (#222222) has warmth that keeps the interface from feeling cold, while still providing maximum contrast against white surfaces.

Why off-black text on yellow (not white)? White-on-yellow fails WCAG AA contrast at smaller sizes and reads as washed-out against a bright background. Off-black on yellow meets contrast requirements and maintains the brand's high-contrast visual identity. This was tested against the white alternative and confirmed as the production choice.

Why are sentiment colours banned from UI states? Wordnerds' entire product value is built on nuanced sentiment analysis. Diluting the sentiment palette — using green for "success" or red for "error" — would undermine the semantic rigour that makes the product credible. Error states use the border-grey or off-black system, not the sentiment palette.

Why Museo Sans Rounded only at display sizes? Museo Sans Rounded is warm and distinctive at large sizes. At body sizes it reads as decorative rather than functional, and long paragraphs in Rounded feel effortful to read. Regular Museo Sans is more legible for sustained reading.

Why 1.5rem card radius everywhere? Consistency. The card radius is the most visible design decision on any surface — deviating from the token creates visual inconsistency that is immediately noticeable even to non-designers. If you think you need a different radius, you almost certainly want --wn-radius-sm (6px) for something compact, or --wn-radius-pill (999px) for a tag.

Why section tints but no component tints? B2B SaaS reference sites (Pitch, Loom, Notion) use background variation sparingly — a coloured section is a deliberate accent, not a mechanical every-other-section pattern. The two section tints (--wn-section-yellow-tint, --wn-section-blue-tint) are the approved tools for those accent moments. Do not use them to alternate backgrounds across consecutive sections — that reads as formulaic. At component level, tints of brand yellow or brand blue still feel accidental and inconsistent — the grey scale continues to apply inside cards and UI elements.

Why a stronger hero gradient? The .hero section gradient was previously very subtle (7–8% opacity). B2B SaaS reference sites with dark heroes (Pitch, Superlist) use clearly visible colour moments — a strong warm glow behind the product visual signals brand colour immediately. The updated gradient uses a 28% peak yellow ellipse positioned at the right-centre (behind where the product screenshot sits) and a 16% blue accent at bottom-left. Both are still atmospheric — not flat shapes — so the visual quality stays high.

Why not sentiment colours on the marketing site? See the sentiment rule above. Worth emphasising: Wordnerds users who land on the marketing site may already know the product. A green section background they've trained themselves to read as "very positive sentiment" creates genuine confusion. The two section tints are brand-derived and carry no semantic meaning outside this system.

Why is the content max-width 1280px? This was calibrated for the product UI sidebar-plus-content layout. Marketing pages without a sidebar may benefit from a slightly narrower column (1120–1180px) for better line length. The site renderer uses 1180px for this reason.

Why are there two <mark> variants with different semantics? Wordnerds' methodology (SYSTM) identifies value translation — the Step 2 → Step 3 move from capability to customer value — as the hardest and highest-leverage copy discipline. Most copy names the capability but never lands the value. Blue <mark> makes the value translation visible to the reader (and to reviewers) so it cannot be skipped. Yellow <mark> is already taken for customer voice inside blockquotes; blue is the natural complement (brand blue signals information/AI features elsewhere in the system). Having two semantically distinct highlights reinforces the distinction between what customers say (yellow) and what they get (blue).


9. Inline Emphasis — <mark> Semantics

Two <mark> variants exist. Each has a strict semantic scope. Do not use them interchangeably.

Variant CSS Background Text Semantic scope
<mark> (no class) blockquote p mark --wn-brand-yellow --wn-off-black Customer voice — the highlighted quote text inside a <blockquote>. Applies only inside blockquotes. Never use yellow <mark> outside a blockquote.
<mark class="wn-mark-blue"> .wn-mark-blue --wn-brand-blue white Customer value — the value-translation half of a capability→value pair, appearing in body copy (rich_text slots). See scope rule below.

Both use the same padding (0.06em 0.15em) and box-decoration-break: clone for multi-line wrapping.

Scope rule for blue <mark> (value translation)

A phrase earns .wn-mark-blue if and only if it is the value the customer gets as a direct result of a named Wordnerds capability. Specifically: a capability is named or implied on the left; the blue-highlighted phrase is what the customer receives on the right. This maps directly to the capability→value translation discipline in wordnerds-wiki/wiki/systm/concepts/capabilities-to-value-translation.md.

Earns blue mark:

Does NOT earn blue mark:

In-pipeline usage

The copy-composer and schema-composer write [[blue]]…[[/blue]] tokens in rich_text slot content. The renderer (lib/util.ts renderRichText) converts them to <mark class="wn-mark-blue">…</mark> at build time.


How to use this file

For Claude Code sessions

Add to CLAUDE.md at project root:

@/Users/pete/Code/wordnerds-site/design-system/DESIGN.md

Before generating any component, ask: does a component in components/ already do this? If yes, lift the markup and parameterise it. If no, follow the naming convention and token rules in this file.

Mandatory checks before any output

  1. Only use colours from tokens.css — no arbitrary hex values
  2. Use --wn-font-rounded for headings only, --wn-font-regular for everything else
  3. Brand yellow appears on at most one element per surface
  4. Sentiment colours appear nowhere except data visualisation
  5. All radii come from the radius token set — no arbitrary px values
  6. All spacing comes from --wn-space-* or --wn-gap — no arbitrary values
  7. Shadows use --wn-shadow-card or --wn-shadow-pop — no custom shadow values