v2 · Precision + Atmosphere — active since 2026-06-11 · v1/V7 legacy applies only to unmigrated app surfaces → /v1/  ·  You are reading the agent guide
Agent & LLM Reference

Design system
for agents

Canonical URLs, migration state, hard rules, and executable code snippets for Codex, Claude, Cursor, ChatGPT, and any other coding agent building Outlex UI.

Section 01

Canonical links

These are the only valid URL forms for this design system. All other paths (repo paths, redirected aliases) are NOT canonical.

URL What it is For
https://design.outlex.ai/ v2 Brand Book homepage Agent · Human
https://design.outlex.ai/components Component Playground Agent · Human
https://design.outlex.ai/motion Motion Lab — budget + demos Agent · Human
https://design.outlex.ai/agents This page — agent guide Agent
https://design.outlex.ai/brand.md Single-file brand reference (Markdown)
Primary fetch target for agents without a skill file
Agent
https://design.outlex.ai/GRAMMAR.md Component grammar cheat-sheet — exact CSS recipes Agent
https://design.outlex.ai/tokens-v2.css v2 CSS custom properties (--ox-* prefix) Agent · Consumer
https://design.outlex.ai/design-tokens-v2.dtcg.json W3C Design Tokens (DTCG format) Tooling
https://design.outlex.ai/voice.md Brand voice (canonical) Agent · Copywriter
https://design.outlex.ai/voice-product.md In-product copy grammar Agent · Copywriter
https://design.outlex.ai/voice-marketing.md Long-form / marketing patterns Copywriter
https://design.outlex.ai/v1/ v1/V7 legacy archive — for reference only
Use when editing unmigrated product surfaces that still implement V7
Legacy

Not canonical: /tokens/tokens-v2.css · /brand-v2.md · /tokens/GRAMMAR.md · /tokens/design-tokens-v2.dtcg.json · brand/brand.md (repo path — not a URL)
These may redirect but are not the canonical form. Always use the live URL forms listed above.

Section 02

Migration state

The v2 system is active. The product app still implements v1/V7 on unmigrated surfaces. Read this before touching any product file.

v2 is the canonical standard for all NEW surfaces — active since 2026-06-11. It is the brand book, all mockups, and any net-new component or page.

The product (app.outlex.ai) still implements v1/V7 on un-migrated surfaces: cream-editorial --v7-* tokens, Fraunces headings, the "warm editorial" aesthetic. Those surfaces are archived at /v1/.

Rule for agents: editing an EXISTING product surface → match the code that's there (V7 legacy, do NOT mix --ox-* tokens in). Building a NEW surface, mockup, or anything in the migration plan → use v2 (this document).

Migration Order — tracked in mvp2 DESIGN_DIRECTION.md
1st Lawyer Portal — PRO-3318
2nd Website — PRO-3319
3rd User App (unmigrated surfaces) — PRO-3320–3323
Section 03

Current precedent

Update after each migration lands

The rule is: reuse before inventing. Before building any v2 component, check what's already been migrated and replicate it exactly.

Status as of 2026-06-11: NO production surface has been migrated to v2 yet. The first migration is the Lawyer Portal (PRO-3318).

The approved reference blueprints are the final mockups in mvp2 docs/research/2026-06-10-design-elevation/:

  • final-website-mockup.html — website / marketing surface
  • final-product-home-chats.html — product home + chat
  • final-product-hub-doc.html — legal hub / document surface

When the first migration (Lawyer Portal) ships, update this section with: the merged PR number, a link to a live URL or screenshot, and the specific patterns used.

Section 04

Hard rules

These are hard stops. Violating any of them produces a design-system failure that will be caught in review.

Hard Stop Mark / SVG container
The SVG symbol container must use width="0" height="0" style="position:absolute". NEVER display:none — it breaks gradient rendering in Safari and Firefox. Gradients on #outlex-mark will not paint.
Hard Stop Lexi = the mark
Lexi's visual identity is the Outlex mark (#ic-lexi for monochrome, #outlex-mark gradient for avatars). Sparkle icons (#ic-sparkles) are banned on ALL AI affordances. May remain in symbol set for non-AI decoration only.
Hard Stop No amber on AI
The AI/Lexi accent colour is #54D6A4 (teal dot via --ox-ai-fill). Amber is NEVER used as AI background, AI label colour, or AI identity. Amber is: warning dots, SLA deadline meta text, pending chips only.
Hard Stop One accent per element
A coloured icon and a coloured label on the same element is a violation. Pick one: the colour lives in the dot chip only. Icon containers are monochrome (rgba(255,255,255,0.06) base). icon-box-amber is removed.
Hard Stop Dot-chip grammar
Chip surface = neutral. Label = --ox-fg-secondary. Colour lives ONLY in the 4–5px leading dot. Dot palette: terracotta = high/overdue · amber = pending · sage = ready/done · teal = AI/citation. Never colour the whole chip background.
Hard Stop Typography weight
Inter (sans): max weight font-weight: 600. Newsreader (display): max weight font-weight: 400 (--ox-weight-display). V7/legacy Fraunces: max font-weight: 500. NEVER font-weight: 700 or font-bold with display/heading fonts.
Hard Stop 8px grid
NEVER gap-1 (4px) or smaller spacing in layout. Minimum is gap-2 (8px) = --ox-space-2. No -5 utilities (20px is not on the grid) — use -6 (24px).
Hard Stop No pure black
NEVER text-black, bg-black, border-black, or literal #000000 / #FFFFFF text. Always semantic tokens: --ox-fg-primary, --ox-surface-0. Shadows must be warm-tinted OKLCH, not rgba(0,0,0,N).
Hard Stop Gradient exclusive to mark
The blue→teal gradient is exclusive to the Outlex mark. Do not apply it to backgrounds, buttons, text, or any other element. CTAs use the sage gradient only (linear-gradient(180deg, #7BA081, #5F8266)).
Required OKLCH tokens only
All colours must use --ox-* tokens. Do NOT derive values from taste or hardcode hex. Copy exact values from GRAMMAR.md or tokens-v2.css. "Looks about right" is not acceptable — wrong colours are caught by the token linter.
Required Motion budget
Enter: 220ms var(--ox-ease). Exit: 130ms var(--ox-ease). Press: 80ms ease. Zero animation list: data tables, accordions, error states, reading contexts. Always respect prefers-reduced-motion with 0.01ms override.
Required Whisper amber / blue
Amber and blue are accent whispers — hairlines, dots, and meta text only. NEVER as background fills (amber fill = breaks whisper grammar). An amber surface for an AI card is a hard violation. Teal dot is the single allowed AI-identity fill.
Section 05

Copy-paste snippets

Executable blocks. All values are copied verbatim from GRAMMAR.md — do not derive or modify.

1 — Token import (CSS @import or <link>)
/* CSS @import — add before your own styles */
@import url('https://design.outlex.ai/tokens-v2.css');

/* OR as a <link> tag in <head> */
<link rel="stylesheet"
      href="https://design.outlex.ai/tokens-v2.css">
2 — Theme setup (dark-first + localStorage + prefers-color-scheme)
<!-- Default: data-theme="dark" on <html> -->
<html lang="en" data-theme="dark">

<!-- Theme toggle script (run before first paint if possible) -->
<script>
(function() {
  var saved = localStorage.getItem('ox-theme');
  var preferred = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', saved || preferred);
})();
</script>

/* Toggle button handler */
function setTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('ox-theme', theme);
}
3 — Primary sage CTA button (dark + light)
.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ox-space-2);            /* 8px */
  font-family: var(--ox-font-sans);
  font-size: 14px;
  font-weight: var(--ox-weight-medium);   /* 500 */
  letter-spacing: var(--ox-tracking-body);
  padding: 10px 20px;
  border: none;
  border-radius: var(--ox-radius-md);    /* 8px */
  cursor: pointer;
  text-decoration: none;

  /* Sage gradient — canonical CTA surface */
  background: linear-gradient(180deg, #7BA081, #5F8266);
  color: #F0EDE8;                    /* warm white on sage */

  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.25),
    0 1px 2px rgba(0,0,0,0.3),
    0 4px 16px oklch(0.60 0.10 150deg / 0.35);

  transition:
    transform var(--ox-press) ease,
    box-shadow var(--ox-enter) var(--ox-ease);
}
.btn-primary:hover {
  background: linear-gradient(180deg, #8BAD91, #6F9276);
  color: #F5F2ED;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.30),
    0 2px 4px rgba(0,0,0,0.35),
    0 6px 20px oklch(0.60 0.10 150deg / 0.45);
  transform: translateY(-1px);
}
.btn-primary:active {
  transform: translateY(1px) scale(0.99);
}
4 — Elevated card (dark + light)
/* ---- DARK ---- */
.card {
  background: linear-gradient(180deg,
    oklch(0.21 0.012 270deg),
    oklch(0.18 0.012 270deg)
  );
  border: 1px solid rgba(255,255,255,0.07);
  border-radius: var(--ox-radius-lg);          /* 12px */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),      /* inset-highlight signature */
    0 1px 2px rgba(0,0,0,0.40),
    0 4px 16px rgba(0,0,0,0.35),
    0 12px 40px rgba(0,0,0,0.25);
  transition:
    transform var(--ox-enter) var(--ox-ease),
    box-shadow var(--ox-enter) var(--ox-ease),
    border-color var(--ox-enter) var(--ox-ease);
}
.card:hover {
  transform: translateY(-2px);
  border-color: rgba(255,255,255,0.12);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.12),
    0 2px 4px rgba(0,0,0,0.50),
    0 8px 24px rgba(0,0,0,0.45),
    0 20px 60px rgba(0,0,0,0.35);
}

/* ---- LIGHT ---- */
[data-theme="light"] .card {
  background: linear-gradient(180deg, #FFFFFF, #FDFBF8);
  border-color: var(--ox-border);              /* oklch(0.86 0.015 80deg) */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.95),
    0 1px 2px rgba(45,38,28,0.07),
    0 4px 10px rgba(45,38,28,0.08),
    0 12px 32px rgba(45,38,28,0.09);
}
[data-theme="light"] .card:hover {
  border-color: oklch(0.82 0.016 80deg);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.95),
    0 2px 4px rgba(45,38,28,0.08),
    0 8px 24px rgba(45,38,28,0.11),
    0 20px 60px rgba(45,38,28,0.10);
}
5 — Scroll reveal utility (IntersectionObserver)
/* CSS */
[data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity var(--ox-enter) var(--ox-ease),
    transform var(--ox-enter) var(--ox-ease);
}
[data-reveal].revealed {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* JS */
document.querySelectorAll('[data-reveal]').forEach(function(el, i) {
  el.style.transitionDelay = (i * 40) + 'ms';
});
new IntersectionObserver(function(entries) {
  entries.forEach(function(e) {
    if (e.isIntersecting) e.target.classList.add('revealed');
  });
}, { threshold: 0.1 }).observe(document.querySelector('[data-reveal]'));
/* Note: call .observe() on each element in real usage */
6 — Lexi bubble — full anatomy (HTML + CSS)
<!-- HTML -->
<div class="lexi-bubble">
  <div class="lexi-header">
    <div class="lexi-avatar">
      <!-- gradient mark avatar (NOT monochrome for avatars) -->
      <svg width="16" height="16"><use href="#outlex-mark"/></svg>
    </div>
    <span class="lexi-label">LEXI</span>
    <span class="lexi-timestamp">Just now</span>
  </div>
  <p>Your response body goes here.</p>
</div>

/* CSS — neutral surface, NOT amber-tinted */
.lexi-bubble {
  background: linear-gradient(180deg,
    oklch(0.21 0.012 270deg),
    oklch(0.18 0.012 270deg)
  );
  border: 1px solid rgba(255,255,255,0.07);
  border-radius: var(--ox-radius-lg);          /* 12px — NOT asymmetric */
  padding: 18px 20px;
  font-size: 14px;
  line-height: var(--ox-leading-loose);        /* 1.75 — AI prose breathes */
  color: var(--ox-fg-primary);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    0 1px 3px rgba(0,0,0,0.30),
    0 4px 16px rgba(0,0,0,0.20);
}
[data-theme="light"] .lexi-bubble {
  background: linear-gradient(180deg, #FFFFFF, #FDFBF8);
  border-color: var(--ox-border);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.95),
    0 1px 2px rgba(45,38,28,0.07),
    0 4px 10px rgba(45,38,28,0.08),
    0 12px 32px rgba(45,38,28,0.09);
}
.lexi-header {
  display: flex; align-items: center;
  gap: 10px; margin-bottom: var(--ox-space-3);
}
.lexi-avatar {
  width: 28px; height: 28px;
  border-radius: var(--ox-radius-md);          /* 8px */
  background: var(--ox-surface-2);
  border: 1px solid rgba(255,255,255,0.07);
  display: flex; align-items: center; justify-content: center;
}
.lexi-label {
  font-family: var(--ox-font-mono);
  font-size: 11px; letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ox-fg-muted);                   /* neutral muted — NOT amber */
}
.lexi-timestamp {
  font-family: var(--ox-font-mono);
  font-size: 10px; color: var(--ox-fg-muted);
  margin-left: auto;
}
7 — macOS product-frame staging (CSS only)
/* Outer container — staged positioning context */
.product-stage {
  position: relative;
  padding: 48px 40px 32px;
  border-radius: 20px;
  background: linear-gradient(160deg,
    oklch(0.22 0.012 270deg),
    oklch(0.16 0.010 270deg)
  );
  box-shadow:
    0 0 0 1px oklch(0.36 0.008 270deg),
    0 24px 80px oklch(0.05 0.02 270deg / 0.60),
    0 8px 24px oklch(0.05 0.02 270deg / 0.40);
}
/* Window chrome — 3 traffic-light dots */
.product-stage::before {
  content: '';
  position: absolute;
  top: 18px; left: 20px;
  width: 52px; height: 12px;
  border-radius: 6px;
  background: radial-gradient(circle at 8px 6px, #FF5F57 0%, #FF5F57 5px, transparent 5px),
              radial-gradient(circle at 26px 6px, #FEBC2E 0%, #FEBC2E 5px, transparent 5px),
              radial-gradient(circle at 44px 6px, #28C840 0%, #28C840 5px, transparent 5px);
}
/* Screenshot / content container */
.product-content {
  border-radius: 12px;
  overflow: hidden;
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.08),
    inset 0 1px 0 rgba(255,255,255,0.10);
}
/* Ambient glow below the stage */
.product-stage::after {
  content: '';
  position: absolute;
  bottom: -40px; left: 10%; right: 10%;
  height: 60px;
  border-radius: 50%;
  background: oklch(0.57 0.15 270deg / 0.20);
  filter: blur(30px);
  pointer-events: none;
}
Section 06

Issue template

Copy this block into Linear issues that touch UI. It ensures agents know what to read first and what the routing rules are.

Ready-to-paste Linear issue block (Markdown)
## Design system context

**Read first (in order):**
1. `DESIGN_DIRECTION.md` in mvp2 root — migration state + which surfaces are V7 vs v2
2. https://design.outlex.ai/brand.md — canonical single-file brand reference
3. https://design.outlex.ai/GRAMMAR.md — exact CSS component recipes
4. Relevant mockup in `docs/research/2026-06-10-design-elevation/` (see DESIGN_DIRECTION.md)

**Routing:**
- Editing an EXISTING app surface → match V7 code already there (`--v7-*` tokens)
- Building a NEW surface or mock → use v2 (`--ox-*` tokens, GRAMMAR.md recipes)
- When in doubt: ask before mixing token systems

**Hard-rule reminders:**
- SVG symbol container: `width="0" height="0" style="position:absolute"` — NEVER `display:none`
- Lexi = the Outlex mark (`#ic-lexi`). No sparkles on AI affordances
- No amber fills on AI cards — teal dot (`#54D6A4`) is the AI accent
- One accent per element — coloured icon + coloured label = violation
- Dot chips only: colour in the dot, label always `--ox-fg-secondary` (neutral)
- Display font (Newsreader): max weight 400. Inter: max weight 600. No `font-weight: 700`
- 8px grid minimum — never `gap-1` (4px)
- No pure black — use semantic `--ox-*` tokens

**Acceptance criteria (add to this section):**
- [ ] Screenshots vs blueprint mockup attached to PR
- [ ] Token linter passes (0 hardcoded hex values in changed files)
- [ ] Reduced-motion tested
Section 07

Common mistakes

Real mistakes caught in this project's code reviews and sessions.

display:none on the SVG symbol container
The most common gotcha. Any SVG <use href="#outlex-mark"/> will render as a blank box in Safari and Firefox if the container has display:none. The gradient definitions inside cannot be resolved. Use width="0" height="0" style="position:absolute" exclusively.
Relative hrefs to root-only assets from sub-pages
Assets like tokens-v2.css, GRAMMAR.md, brand.md exist only at the root. Using href="tokens-v2.css" from /components resolves to /components/tokens-v2.css → 404. Always use root-absolute paths: href="/tokens-v2.css" or the full https://design.outlex.ai/tokens-v2.css.
Repo paths used as URLs
tokens/tokens-v2.css, brand/brand-v2.md, tokens/GRAMMAR.md are file system paths inside the source repo. They are NOT valid URLs. The live URLs are https://design.outlex.ai/tokens-v2.css, /brand.md, /GRAMMAR.md. This causes agents to 404 when fetching docs.
Amber as AI identity
Using amber (--ox-amber-*) to style AI cards, Lexi panels, or AI labels. Amber is exclusively: SLA deadline meta text, pending/review dot chips, focus rings. The AI accent is teal #54D6A4 via --ox-ai-fill. An amber-tinted Lexi bubble is the most common AI grammar violation.
Sparkle icons on AI affordances
ic-sparkles is in the symbol set for legacy / non-AI decoration only. Placing it on "Ask Lexi", "Generate", or any AI call-to-action is a banned pattern. Use ic-lexi (the Outlex mark in monochrome) for all AI buttons and inline AI labels.
Coloured icon + coloured label on the same element
One accent per element. Icon containers are monochrome — rgba(255,255,255,0.06) background, icon inherits --ox-fg-secondary. The only colour is the leading 5px dot in the chip. Example of violation: sage icon container + sage chip label + sage dot all on the same row item.
Deriving values instead of copying GRAMMAR.md
Writing rgba(0,0,0,0.4) because it "looks like a dark shadow" — but the canonical recipe is oklch(0.05 0.02 270deg / 0.50) (warm-tinted). Or writing #7B8C81 because it "looks sage" instead of copying the exact gradient from GRAMMAR.md. Always fetch and copy; never derive from appearance.
Applying v2 tokens to unmigrated V7 surfaces
Mixing --ox-* tokens into a component that still uses --v7-* tokens creates visual inconsistency (different surface colours, shadow warmth, font weights). Check DESIGN_DIRECTION.md for the current migration state of the surface. If it's V7 → stay V7 until it's promoted.
font-weight: 700 with display or heading fonts
Newsreader (v2 display) max is --ox-weight-display = 400. Fraunces (V7 heading) max is font-medium = 500. Semibold or bold with either creates visual shouting that breaks editorial restraint. Inter body max is 600 (--ox-weight-semibold). NEVER 700 or font-bold on headings.
Editing .github/workflows/claude-code-review.yml in mvp2 feature PRs
This file has a byte-equality gate — any edit (including whitespace) causes every claude-review run to 401 Unauthorized for that PR. Workflow changes must ship in standalone PRs to main only. This is a mvp2-specific CI constraint, not a design system rule — but it catches agents in PRs that happen to touch this file.