Asana
MIT
Enterprise work-management design system with sharp modernist aesthetic, dark-mode focus and purposeful colour accents for product teams
Layout StudioImport this kit into a Studio project and start editing.
CLI installRun it in any project. No account needed.
npx @layoutdesign/context install asanaUI themeReskin shadcn or Layout UI components to match Asana.
npx shadcn add https://layout.design/r/asana/theme.json# layout.md — Asana Design System
> Extracted from asana.com · Token source: extracted-css-vars · Confidence: high (351 CSS custom properties)
---
## 0. Quick Reference
**Stack:** Next.js + Bootstrap · Primary font: TWK Lausanne · Token source: extracted CSS variables (high confidence)
**How to apply:** Use as `var(--token-name)` in CSS, `style={{ prop: 'var(--token-name)' }}` in JSX, or `bg-[var(--token-name)]` in Tailwind.
```css
/* ── Core Colour Tokens ── */
--darkmode-bg: #1e1f21; /* Page background (dark) */
--lightmode-bg: var(--white); /* Page background (light) */
--white: #ffffff;
--black-1000: #0d0d0d; /* Near-black for dark-on-light text */
--darkmode-text: rgba(255,255,255,0.85); /* Primary text on dark */
--darkmode-text-strong: var(--white); /* High-emphasis text on dark */
--darkmode-button-primary: #f5f4f3; /* Primary button background (dark) */
--darkmode-button-primary-hover: #f06a6a; /* Primary button hover */
--darkmode-button-disabled: #38393d; /* Disabled button background */
--darkmode-border: rgba(217,217,217,0.3); /* Default border */
--darkmode-border-hover: rgba(217,217,217,0.4); /* Border on hover */
--glow-coral: #ff584a; /* Brand accent / CTA highlight */
--glow-purple: #df5dff; /* Decorative accent */
--glow-green: #22b455; /* Decorative accent */
--red: #a82424; /* Error state */
--amber: #7a4b00; /* Warning state */
--green: #0c6410; /* Success state */
/* ── Core Typography ── */
/* Primary: TWK Lausanne, "Helvetica Neue", Helvetica, sans-serif */
/* h1: 102px / 300 / -2.55px tracking / line-height 91.8px */
/* h2: 54px / 300 / -1.08px tracking / line-height 54px */
/* body: 13px / 300 / 0.26px tracking / line-height 19.5px */
/* ── Core Spacing ── */
--spacing-1half: 8px; --spacing-1: 16px; --spacing-1plus: 24px;
--spacing-2: 32px; --spacing-3: 48px; --spacing-4: 64px;
--spacing-5: 80px; --spacing-6: 96px; --spacing-8: 128px;
--gutter: 32px; --columns: 12; --max_width: 1920px;
/* ── Motion ── */
--transition-duration-short: 75ms; --transition-duration: 150ms;
--transition-duration-med: 225ms; --transition-duration-long: 300ms;
/* ── Radius ── */
--border-radius: 3px; /* Default UI radius — brand uses SHARP corners, NOT rounded */
/* Pill buttons use border-radius: 100px–146px (role_tab, dropdown) */
```
```tsx
// Primary CTA Button — correct token usage + all states
<button
className="asana-btn-primary"
disabled={isDisabled}
aria-busy={isLoading}
>
{isLoading ? <Spinner /> : children}
</button>
/* CSS:
.asana-btn-primary {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 16px; font-weight: 400; line-height: 28px;
background: var(--darkmode-button-primary);
color: var(--darkmode-button-text-primary);
border-radius: 100px; padding: 16px 32px; border: none;
transition: background-color var(--transition-duration) ease,
color var(--transition-duration) ease;
}
.asana-btn-primary:hover { background: var(--darkmode-button-primary-hover); color: var(--darkmode-button-text-primary-hover); }
.asana-btn-primary:active { box-shadow: none; outline: 0; }
.asana-btn-primary:disabled { background: var(--darkmode-button-disabled); color: var(--darkmode-button-text-disabled); cursor: not-allowed; }
*/
```
**NEVER rules:**
1. **NEVER** use `border-radius > 8px` on cards or surfaces — Asana UI is sharp-cornered; pill shape is reserved exclusively for buttons and pill-tabs.
2. **NEVER** use a font other than TWK Lausanne for UI text. Inter, Roboto, and Arial are forbidden.
3. **NEVER** use hardcoded hex values — always reference the extracted CSS custom properties.
4. **NEVER** use spacing values not on the `--spacing-*` scale (8px base units).
<!-- Quick Reference truncated to fit the 75-line cap. See later sections for the full design system. -->
## 1. Design Direction & Philosophy
### Character & Aesthetic
Asana's marketing site is **dark, dramatic, and technically confident**. The dominant surface is near-black (`#1e1f21`), offset by typographic contrast at extreme scale (h1 at 102px, weight 300). The aesthetic is editorial and purposeful — wide whitespace, strict grid discipline, and expressive typography doing most of the visual work.
### Personality
- **Precision over decoration.** Sharp corners everywhere on surfaces (3px max on cards, 0px on most). Pill shapes appear *only* on interactive controls (buttons, tabs).
- **Contrast-led hierarchy.** Headlines are ultra-light weight at massive size. Body copy is small (13px) and muted. The tension between these two creates visual drama without graphic embellishment.
- **Motion as signal, not spectacle.** Animations are functional (fadeIn, moveUp, progress) and brief (75ms–300ms). They confirm state changes and reveal content — they never loop indefinitely for decoration.
- **Coral as the single action colour.** `#ff584a` / `#f06a6a` is the brand signature. Every CTA, hover state, and interactive accent traces back to this hue.
### What This Design Explicitly Rejects
- **Warm rounded UI.** No `border-radius > 8px` on any surface, card, or container. The rounded soft aesthetic of consumer apps is out of character.
- **Heavy weights for body copy.** Body text is weight 300 (TWK Lausanne Light). Semibold (500) is the ceiling for running text.
- **Colour-heavy interfaces.** The brand palette includes teals, purples, greens — but these are illustration/decoration accents, not UI surface colours. UI backgrounds are achromatic.
- **System fonts.** TWK Lausanne is self-hosted and mandatory. Never fall back to Inter, Roboto, or Arial as the intended stack.
---
## 2. Colour System
### Tier 1: Primitive Palette
```css
/* ── Neutrals ── */
--white: #ffffff;
--black-20: #fafafa;
--black-50: #f3f3f3;
--black-100: #e7e7e7;
--black-200: #cfcfcf;
--black-300: #b6b6b6;
--black-400: #9e9e9e;
--black-500: #868686;
--black-600: #6e6e6e;
--black-700: #565656;
--black-800: #3d3d3d;
--black-900: #252525;
--black-950: #191919;
--black-980: #121212;
--black-1000: #0d0d0d;
--black-1100: #000000;
/* ── Blues (muted, desaturated — used for badges, focus rings, accents) ── */
--blue-0: #cbefff; --blue-20: #c8ebfc; --blue-50: #c3e5f8;
--blue-100: #badbf1; --blue-200: #a9c7e3; --blue-300: #98b3d6;
--blue-400: #879fc8; /* A11y focus ring */ --blue-500: #778bba;
--blue-600: #6678ac; --blue-700: #55649e; --blue-800: #445091;
--blue-900: #333c83; --blue-950: #2a327c; --blue-980: #252c78;
--blue-1000: #222875;
/* ── Coral (desaturated palette scale — not the same as brand coral) ── */
--coral-0: #ffeaec; --coral-100: #f0d3d9; --coral-500: #b4758e;
--coral-1000: #690031;
/* ── Green palette scale ── */
--green-0: #c9fcdb; --green-100: #b5e9ca; --green-1000: #004232;
/* ── Purple palette scale ── */
--purple-0: #ffdcff; --purple-100: #f0c6f3; --purple-1000: #6a0085;
/* ── Brand illustrative colour families ── */
--green-1: #e2fffa; --green-2: #b0fff1; --green-3: #58f5d9;
--green-4: #25e8c8; --green-5: #00bf9c;
--teal-1: #edf8ff; --teal-2: #95e0ff; --teal-3: #32c1ff;
--teal-4: #14aaf5; --teal-5: #008ce3;
--purple-1: #f0f0ff; --purple-2: #aba3ff; --purple-3: #9287ff;
--purple-4: #796eff; --purple-5: #635ac7;
--gold-1: #fffede; --gold-2: #fff78f; --gold-3: #ffe01b;
--gold-4: #fcbd01; --gold-5: #f2a100;
--orange-1: #f9dc00; --orange-2: #ffba00; --orange-3: #ff7f00;
--coral-1: #ffedef; --coral-2: #ff9ca5; --coral-3: #ff7381;
--coral-4: #ff5263; --coral-5: #ed4758;
```
### Tier 2: Semantic Aliases
```css
/* ── Light Mode Semantic Tokens ── */
--lightmode-bg: var(--white); /* Page/surface background */
--lightmode-bg-inverted: var(--black-20); /* Inverted surface */
--lightmode-bg-med: var(--black-50); /* Elevated surface */
--lightmode-bg-strong: var(--black-50); /* Strong elevated surface */
--lightmode-bg-hover: var(--black-20); /* Surface hover state */
--lightmode-bg-med-hover: var(--black-100); /* Medium surface hover */
--lightmode-bg-strong-hover: var(--black-300); /* Strong surface hover */
--lightmode-bg-active: var(--black-20); /* Surface active state */
--lightmode-bg-med-active: var(--black-100); /* Medium surface active */
--lightmode-bg-strong-active: var(--black-300); /* Strong surface active */
--lightmode-text-weak: var(--black-600); /* Secondary/muted text */
--lightmode-text: var(--black-800); /* Default body text */
--lightmode-text-strong: var(--black-1000); /* High-emphasis text */
--lightmode-border-weak: var(--black-50); /* Subtle divider */
--lightmode-border: var(--black-200); /* Default border */
--lightmode-border-strong: var(--black-200); /* Strong border */
--lightmode-border-hover: var(--black-400); /* Border on hover */
--lightmode-border-active: var(--black-200); /* Border on active */
--lightmode-icon: var(--black-1000); /* Icon colour */
--lightmode-icon-hover: var(--black-800); /* Icon hover */
--lightmode-icon-disabled: var(--black-400); /* Icon disabled */
--lightmode-button-primary: var(--black-1000); /* Primary button bg */
--lightmode-button-primary-hover: var(--black-800); /* Primary btn hover */
--lightmode-button-disabled: var(--black-400); /* Disabled button bg */
--lightmode-button-secondary: var(--white); /* Secondary button bg */
--lightmode-button-secondary-hover: var(--black-600); /* Secondary hover */
--lightmode-button-text-primary: var(--white); /* Text on primary button */
--lightmode-button-text-primary-hover: var(--white);
--lightmode-button-text-disabled: var(--white);
--lightmode-button-text-secondary: var(--black-1000);
--lightmode-button-text-secondary-hover: var(--white);
--lightmode-link-text: var(--black-1000);
--lightmode-link-text-weak: var(--black-600);
--lightmode-link-text-weak-hover: var(--black-600);
--lightmode-link-text-weak-disabled: var(--black-400);
--lightmode-inline-link-text: var(--black-1000);
--lightmode-link-hover: var(--black-1000);
--lightmode-link-disabled: var(--black-400);
--lightmode-error: var(--red); /* Error state colour */
--lightmode-warning: var(--amber); /* Warning state colour */
--lightmode-success: var(--green); /* Success state colour */
--lightmode-a11y-focus-primary: var(--blue-400); /* Focus ring colour */
--lightmode-a11y-focus-black: var(--black-1000);
--lightmode-a11y-focus-white: var(--white);
/* ── Dark Mode Semantic Tokens ── */
--darkmode-bg: #1e1f21; /* Primary dark surface */
--darkmode-bg-inverted: var(--white); /* Inverted on dark */
--darkmode-bg-med: #252628; /* Elevated dark surface */
--darkmode-bg-strong: #2a2b2d; /* Strong elevated dark */
--darkmode-bg-hover: #38383a; /* Dark surface hover */
--darkmode-bg-med-hover: #3e3f41; /* Medium dark hover */
--darkmode-bg-strong-hover: #434445; /* Strong dark hover */
--darkmode-bg-active: #4c4d4e; /* Dark active state */
--darkmode-bg-med-active: #525254; /* Medium dark active */
--darkmode-bg-strong-active: #565658; /* Strong dark active */
--darkmode-text-weak: rgba(255, 255, 255, 0.55); /* Muted text on dark */
--darkmode-text: rgba(255, 255, 255, 0.85); /* Default text on dark */
--darkmode-text-strong: var(--white); /* High-emphasis on dark */
--darkmode-border-weak: rgba(217, 217, 217, 0.2); /* Subtle dark border */
--darkmode-border: rgba(217, 217, 217, 0.3); /* Default dark border */
--darkmode-border-strong: var(--white); /* Strong dark border */
--darkmode-border-hover: rgba(217, 217, 217, 0.4); /* Dark border hover */
--darkmode-border-active: rgba(217, 217, 217, 0.7); /* Dark border active */
--darkmode-icon: rgba(245, 244, 243, 0.6); /* Icon on dark */
--darkmode-icon-hover: #f5f4f3; /* Icon hover on dark */
--darkmode-icon-disabled: rgba(245, 244, 243, 0.15);
--darkmode-button-primary: #f5f4f3; /* Primary button bg (dark) */
--darkmode-button-primary-hover: #f06a6a; /* PRIMARY HOVER — coral */
--darkmode-button-disabled: #38393d; /* Disabled button (dark) */
--darkmode-button-secondary: #0d0e10; /* Secondary button bg */
--darkmode-button-secondary-hover: #f06a6a; /* Secondary hover — coral */
--darkmode-button-text-primary: #0d0e10; /* Text on dark primary btn */
--darkmode-button-text-primary-hover: #0d0e10;
--darkmode-button-text-disabled: #6a696a; /* Disabled text (dark) */
--darkmode-button-text-secondary: #f5f4f3; /* Text on dark secondary */
--darkmode-button-text-secondary-hover: #0d0e10;
--darkmode-link-text: #dbdbdb; /* Link colour on dark */
--darkmode-link-text-weak: #989898; /* Muted link on dark */
--darkmode-link-text-weak-hover: var(--white);
--darkmode-link-text-weak-disabled: #6a696a;
--darkmode-inline-inline-link-text: #dbdbdb;
--darkmode-inline-link-hover: #f06a6a; /* Inline link hover — coral */
--darkmode-inline-link-disabled: #6a696a;
--darkmode-link-hover: #f06a6a; /* Link hover — coral */
--darkmode-link-disabled: #6a696a;
--darkmode-error: var(--red);
--darkmode-warning: var(--amber);
--darkmode-success: var(--green);
--darkmode-a11y-focus-primary: var(--blue-400);
--darkmode-a11y-focus-black: var(--black-1000);
--darkmode-a11y-focus-white: var(--white);
```
### Tier 3: Status & Glow/Accent Tokens
```css
/* ── Status (semantic intent) ── */
--red: #a82424; /* Error — lightmode and darkmode both reference this */
--amber: #7a4b00; /* Warning */
--green: #0c6410; /* Success */
--blue-0: #cbefff; /* Info background */
/* ── Glow / Decorative Accents (illustrations, gradients, not UI surfaces) ── */
--glow-coral: #ff584a; /* Brand mark primary — svg fills, hero glows */
--glow-purple: #df5dff; /* Decorative gradient accent */
--glow-green: #22b455; /* Decorative gradient accent */
--glow-blue: #4786ff; /* Decorative gradient accent */
--glow-white: #0d0d0d; /* Note: named "white" but is near-black — do not use as white */
--glow-gray: #0d0d0d; /* Matches glow-white — dark surface glow value */
/* ── Google Auth Brand Colours (third-party, do not generalise) ── */
--google-sign-up-blue: #174ea6;
--google-sign-up-blue-light: #2568d4;
/* ── Bright Variants (high-chroma decorative palette — tile backgrounds, illustration) ── */
--bright-blue-1: #b8e8fe; --bright-blue-2: #ade6ff;
--bright-blue-3: #3c45b1; --bright-blue-4: #2b328e;
--bright-coral-1: #ffdde1; --bright-coral-2: #ffd7db;
--bright-coral-3: #911c51; --bright-coral-4: #7e1444;
--bright-green-1: #b9f4ce; --bright-green-2: #a7f4c2;
--bright-green-3: #09614b; --bright-green-4: #0a4d3d;
--bright-purple-1: #fbccfb; --bright-purple-2: #ffc4ff;
--bright-purple-3: #8a16a8; --bright-purple-4: #780a94;
```
### Colour Usage Table
| Token | Value | Use |
|---|---|---|
| `--darkmode-bg` | `#1e1f21` | **Primary page background** |
| `--darkmode-button-primary-hover` | `#f06a6a` | **All button hovers** |
| `--glow-coral` | `#ff584a` | Brand mark, hero CTA highlight |
| `--lightmode-bg` | `#ffffff` | Light surface |
| `--darkmode-text` | `rgba(255,255,255,0.85)` | Body text on dark |
| `--blue-400` | `#879fc8` | Focus rings (a11y) |
---
## 3. Typography System
### Font Stack
```css
/* ── Primary UI Font (self-hosted, mandatory) ── */
/* TWK Lausanne — weights: 250, 300, 350, 400, 450, 500, 550 */
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
/* ── Display/Editorial (self-hosted) ── */
/* PP Editorial New — weight 400, used for editorial/blog contexts */
font-family: "pp-editorial", serif;
/* ── Monospace (code blocks, data) ── */
--fontFamily-mono: "Consolas", monospace;
/* ── Print / Fallback ── */
--fontFamily-print: "Georgia", "Times New Roman", "Times", serif;
/* ── Icon Font ── */
/* asanaIcons — woff, weight 200 — use for product UI icons only */
font-family: "asanaIcons";
```
### Weight Scale
```css
--fontWeight-thin: 300; /* Body copy, hero headlines */
--fontWeight-normal: normal; /* Equivalent to 400 for base elements */
--fontWeight-medium: 400; /* Default UI text, buttons, links */
--fontWeight-semibold: 500; /* Emphasis, labels, badge text */
/* Note: 600 weight available in proxima-nova (email CTAs, legacy contexts only) */
/* Note: TWK Lausanne 700 is NOT in the font face declarations — do not use bold/700 */
```
### Composite Typography Groups
```css
/* ── DISPLAY / H1 ── */
/* TWK Lausanne · 102px · weight 300 · line-height 91.8px · tracking -2.55px */
/* Use: Hero section only. Center aligned. */
.t-display {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 102px;
font-weight: 300;
line-height: 91.8px; /* ~0.9 — intentionally tight */
letter-spacing: -2.55px;
color: rgb(13, 14, 16); /* or --darkmode-text-strong on dark bg */
text-align: center;
}
/* ── SECTION HEADING / H2 ── */
/* TWK Lausanne · 54px · weight 300 · line-height 54px · tracking -1.08px */
/* Use: Section titles. Center aligned. Usually white on dark bg. */
.t-section-heading {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 54px; /* maps to --asana-font-size-3xl */
font-weight: 300;
line-height: 54px; /* 1:1 ratio */
letter-spacing: -1.08px;
color: var(--darkmode-text-strong);
text-align: center;
}
/* ── SUBSECTION HEADING / H3 ── */
/* TWK Lausanne · 36px · weight 400 · line-height 43.2px · tracking -0.36px */
/* Use: Feature/product sub-sections. Left aligned. */
.t-subsection-heading {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 36px;
font-weight: 400;
line-height: 43.2px; /* 1.2 ratio */
letter-spacing: -0.36px;
color: var(--darkmode-text-strong);
text-align: start;
}
/* ── CARD HEADING / H4 ── */
/* TWK Lausanne · 30px · weight 400 · line-height 36px · tracking 0 */
/* Use: Stat callouts, feature card titles */
.t-card-heading {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 30px; /* maps to --asana-font-size-2xl */
font-weight: 400;
line-height: 36px; /* matches --asana-line-height-loose */
letter-spacing: normal;
}
/* ── ITEM TITLE / H5 ── */
/* TWK Lausanne · 24px · weight 400 · line-height normal */
/* Use: Feature item names, AI agent titles */
.t-item-title {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 24px; /* maps to --asana-font-size-xl */
font-weight: 400;
line-height: normal;
letter-spacing: normal;
}
/* ── LABEL HEADING / H6 ── */
/* TWK Lausanne · 13px · weight 300 · line-height 19.5px · tracking 0.26px */
/* Use: Section eyebrows, micro-labels */
.t-label {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 13px; /* maps to --asana-font-size-xs */
font-weight: 300;
line-height: 19.5px; /* maps to --asana-line-height-tight */
letter-spacing: 0.26px;
}
/* ── BODY ── */
/* TWK Lausanne · 13px · weight 300 · line-height 19.5px · tracking 0.26px */
/* Default document body. Very small — not typical for marketing. */
.t-body {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 13px;
font-weight: 300;
line-height: 19.5px;
letter-spacing: 0.26px;
color: rgb(110, 110, 110); /* ≈ --black-600 */
}
/* ── BODY LARGE (feature descriptions) ── */
/* TWK Lausanne · 20px · weight 400 · line-height ~30px */
.t-body-lg {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 20px; /* maps to --asana-font-size-lg */
font-weight: 400;
line-height: 30px;
letter-spacing: normal;
}
/* ── BUTTON / NAV LINK ── */
/* TWK Lausanne · 16px · weight 400 · line-height 28px */
.t-button {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 16px; /* --fontSize-base */
font-weight: 400;
line-height: 28px;
letter-spacing: normal;
}
/* ── MODAL / BODY-SM ── */
/* TWK Lausanne · 14px · weight 400 · line-height 24.5px */
.t-body-sm {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 14px; /* maps to --asana-font-size-sm */
font-weight: 400;
line-height: 24.5px;
letter-spacing: normal;
}
/* ── BADGE ── */
/* TWK Lausanne · 11px · weight 700 · line-height 20px · tracking 0.5px */
.t-badge {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 11px;
font-weight: 700;
line-height: 20px;
letter-spacing: 0.5px;
}
/* ── PILL TAB ── */
/* TWK Lausanne · 20px · weight 400 · line-height 30px */
.t-pill-tab {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 20px;
font-weight: 400;
line-height: 30px;
letter-spacing: normal;
}
```
### Extracted Font Size Variables
```css
--fontSize-base: 16px;
--fontSize-larger: 3.2rem; /* 51.2px */
--fontSize-large: 2.2rem; /* 35.2px */
--fontSize-medium: 2rem; /* 32px */
--fontSize-regular: 1.6rem; /* 25.6px */
--fontSize-small: 1.4rem; /* 22.4px */
--fontSize-smaller: 1.2rem; /* 19.2px */
/* Unitless scale (multiply by font-size-base to get px): */
--fontSize-xl: 82; /* 82px */
--fontSize-h1: 56; /* 56px */
--fontSize-h2: 56; /* 56px */
--fontSize-h3: 45; /* 45px */
--fontSize-h4: 31; /* 31px */
--fontSize-h5: 22; /* 22px */
--fontSize-h6: 16; /* 16px */
```
### Typography Pairing Rules
- **Never pair h1 (102px) with body-lg (20px) without a sub-heading bridge.** The scale jump requires an intermediate h3/h4 to land the hierarchy.
- **H2 and H3 are always TWK Lausanne weight 300/400.** Never bold a section heading.
- **Badge text uses weight 700 only** — the single exception to the ≤500 weight rule.
- **PP Editorial New** is reserved for blog/editorial pages. NEVER use in product UI or marketing modules.
---
## 4. Spacing & Layout
```css
/* ── Base Spacing Scale (8px grid) ── */
--spacing-0: 0;
--spacing-1half: 8px; /* xs — icon gaps, tight internal padding */
--spacing-1: 16px; /* sm — component internal padding */
--spacing-1plus: 24px; /* sm+ — card padding, form field spacing */
--spacing-2: 32px; /* md — gutter width, section internal gap */
--spacing-3: 48px; /* lg — component separation */
--spacing-4: 64px; /* xl — section padding */
--spacing-5: 80px; /* 2xl — generous section spacing */
--spacing-6: 96px; /* 3xl — major section vertical rhythm */
--spacing-7: 112px; /* 4xl — hero/feature vertical spacing */
--spacing-8: 128px; /* 5xl — maximum section padding */
--spacing-9: 144px; /* 6xl — extreme separation (rare) */
--spacing-10: 160px; /* 7xl — full-height hero internal spacing */
/* ── Grid System ── */
--columns: 12; /* 12-column grid */
--grid-columns: 12; /* alias */
--gutter: 32px; /* inter-column gap */
--grid-gutter-width: 32px; /* alias */
--max_width: 1920px; /* max container width */
/* ── Layout Helpers ── */
--background-width: calc(100% + var(--gutter)); /* Full-bleed helper */
--repeating-width: calc(100% / var(--columns));
--column-width: calc((100% / var(--columns)) - var(--gutter));
```
### Canonical Breakpoints
The site uses a dense breakpoint set. The named semantic breakpoints are:
| Name | Token | Value | Usage |
|---|---|---|---|
| xsmall | `--media-xsmall` | 480px | Phones portrait |
| small | `--media-small` | 768px | Tablets portrait |
| medium | `--media-medium` | 960px | Tablets landscape |
| large | `--media-large` | 1120px | Small desktop |
| xlarge | `--media-xlarge` | 1280px | Desktop |
| xxlarge | `--media-xxlarge` | 1440px | Wide desktop |
| xxxlarge | `--media-xxxlarge` | 1920px | Ultrawide |
```css
/* Usage note: the site defines --media-*-min tokens using calc(), but contains a typo "cacl()" */
/* Use explicit px values in media queries until the token typo is corrected */
@media (max-width: 768px) { /* mobile */ }
@media (min-width: 960px) { /* tablet landscape+ */ }
@media (min-width: 1120px) { /* desktop */ }
@media (min-width: 1440px) { /* wide */ }
```
### Flex vs Grid Decision Rules
- **Navigation:** `display: grid` (extracted from `role_navigation`)
- **Buttons, CTAs, modals:** `display: flex; flex-direction: row; align-items: center; justify-content: center`
- **Button primary container:** `display: flex; flex-direction: column` (wrapping CTA group)
- **Page grid:** 12-column CSS Grid with `--gutter: 32px`
- **Feature cards:** Flex rows within a grid column span
---
## 5. Page Structure & Layout Patterns
*Derived from LAYOUT DIGEST. No screenshots were provided. All section rows marked "(inferred)" are inferred from the component inventory, typographic hierarchy, and computed structural styles. The nav section is confirmed from the digest.*
### 5.1 Section Map
| # | Section | Layout Type | Approx. Height | Key Elements |
|---|---|---|---|---|
| 1 | **Global Navigation** | `display: grid` | 60–72px | Logo, nav links (86 instances), dropdown trigger (pill shape, 100px radius), CTA buttons, hamburger (mobile) |
| 2 | **Hero** (inferred) | Full-width, centered flex column | 80–100vh | H1 display text (102px/300), sub-heading H2 (54px), email sign-up input (pill 50px radius), primary CTA button (pill 100px), glow/coral brand mark SVG |
| 3 | **Social Proof / Logo Bar** (inferred) | Flex row, centered, `gap: 32px` | 80–120px | Partner/customer logos, `85% of Fortune 100` stat (H4 30px) |
| 4 | **Feature Showcase / AI Section** (inferred) | 12-col grid, alternating 6/6 split | 400–600px | H2 section heading, H3 sub-heading, body-lg (20px) description, product screenshot/animation |
| 5 | **Pill Tab / Product Switcher** (inferred) | Flex row, centered, `gap: 16px` | 60px + panel | `role_tab` pill tabs (146px radius, 16px 32px padding), tab panels with product UI |
| 6 | **Stats / Social Proof Module** (inferred) | Flex row or 3-col grid | 200–300px | H4 stat numbers (30px), short descriptor body copy (13px) |
| 7 | **Feature Grid / Cards** (inferred) | 12-col grid, 3–4 column cards | 400–600px | Badge (blue-20 bg, 4px radius), card headline (H3 36px), body-lg (20px), card surface (sharp, ≤8px radius) |
| 8 | **Integration Logos** (inferred) | Flex wrap, centered | 200–300px | Logo grid with hover shadow (`--shadow-10`) |
| 9 | **CTA / Sign-up Section** (inferred) | Centered flex column | 300–400px | H2 heading, email input (pill 50px), primary CTA (coral on hover) |
| 10 | **Footer** (inferred) | 12-col grid, multi-column links | 300–400px | Nav link groups, legal text (13px body), Asana wordmark |
### 5.2 Layout Patterns
**Navigation:**
```
display: grid
padding: 12px 0
background: var(--lightmode-bg) /* #fff */
max-width: var(--max_width) /* 1920px */
Columns: [Logo] [Nav links flex-row gap:32px] [CTA group flex-row]
Link margin-right: 32px (from computed styles)
Mobile: hamburger replaces nav links (animated open/close keyframes)
```
**Hero Section (inferred):**
```
Full-width, centered column
Vertical padding: var(--spacing-8) /* 128px */ top, var(--spacing-6) /* 96px */ bottom
H1 center-aligned, margin-bottom: 28px (from computed h1)
Input: border-radius 50px, padding 6px 35px 6px 15px, border 1px solid #707070
CTA button: border-radius 100px, padding 16px 32px
Background: var(--darkmode-bg) /* #1e1f21 */ with glow-coral decorative element
```
**Feature Grid (inferred):**
```
12-col grid, gutter: 32px
Card items: 4-col span (desktop), 6-col (tablet), 12-col (mobile)
Card gap: var(--spacing-2) /* 32px */
Card surface: background var(--darkmode-bg-med) /* #252628 */, border-radius ≤8px
Badge top of card: border-radius 4px (--radius-lg)
```
**Pill Tab Switcher (inferred):**
```
Flex row, justify-content: center, gap: 8px
Tab: border-radius 146px, padding 16px 32px, font-size 20px
Active tab: background var(--white), color var(--black-1000)
Inactive tab: background transparent, color muted
Transition: background-color 0.15s, color 0.15s ease-in-out
```
**Modal / Dialog:**
```
display: flex, flex-direction: row, justify-content: center, align-items: center
Font: 14px / 400 / line-height 24.5px
Color: rgb(243, 243, 243) on dark background
Transition: color 0.3s
```
### 5.3 Visual Hierarchy
- **H1 at 102px weight 300 is the dominant visual element** — no other element competes at this scale.
- **CTAs use coral on hover** (`#f06a6a`) as the universal hover signal — any button, link, or interactive element reaching for attention resolves to this hue.
- **Whitespace between sections is large:** `--spacing-6` to `--spacing-8` (96px–128px) between major sections.
- **Badge signals "new/coming soon"** — blue-20 background (`#c8ebfc`), muted blue text, 4px radius.
- **Glow effects (coral, purple, green)** are decorative only — they appear in hero/background illustrations, never on UI components.
### 5.4 Content Patterns
**Repeating pattern — Feature Row (inferred):**
```
[Eyebrow label 13px uppercase] →
[H2 section title 54px center] →
[Body description 20px center] →
[Product screenshot or animated UI panel]
```
**Repeating pattern — Feature Card:**
```
[Badge (optional)] →
[H3 card title 36px] →
[Body copy 20px] →
[Optional CTA link with coral hover underline]
```
**Repeating pattern — Stat Callout:**
```
[H4 number 30px bold-ish] →
[Short descriptor 13px muted]
Arranged in 3-column horizontal row
```
**Link hover convention (global):**
```
color → rgb(205, 72, 72) / #cd4848 (light context)
→ #f06a6a (dark context)
border-bottom: 2px solid currentColor on hover (inline links)
Transition: color 0.3s, border 0.3s
```
---
## 6. Component Patterns
### 6.1 Button — Primary (Pill)
**Anatomy:** `[optional icon] [label text]` inside a pill container.
**Token mapping:**
| State | Background | Text colour | Border | Notes |
|---|---|---|---|---|
| Default | `var(--darkmode-button-primary)` `#f5f4f3` | `var(--darkmode-button-text-primary)` `#0d0e10` | none | Pill: `border-radius: 100px` |
| Hover | `var(--darkmode-button-primary-hover)` `#f06a6a` | `var(--darkmode-button-text-primary-hover)` `#0d0e10` | none | Coral transition |
| Active | same as hover | same as hover | none | `box-shadow: none` |
| Focus | same as default | same as default | `outline: 2px solid var(--blue-400)` | A11y ring |
| Disabled | `var(--darkmode-button-disabled)` `#38393d` | `var(--darkmode-button-text-disabled)` `#6a696a` | `1px solid #d5dce0` | `cursor: not-allowed` |
| Loading | same as default | transparent | none | Show `<Spinner />` overlay |
```tsx
// Button Primary — production-ready with all states
interface ButtonPrimaryProps {
children: React.ReactNode;
isLoading?: boolean;
disabled?: boolean;
onClick?: () => void;
variant?: 'dark' | 'light';
}
export function ButtonPrimary({
children,
isLoading = false,
disabled = false,
onClick,
variant = 'dark',
}: ButtonPrimaryProps) {
return (
<button
onClick={onClick}
disabled={disabled || isLoading}
aria-busy={isLoading}
aria-disabled={disabled || isLoading}
style={{
fontFamily: '"TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif',
fontSize: '16px',
fontWeight: 400,
lineHeight: '28px',
letterSpacing: 'normal',
borderRadius: '100px',
padding: '16px 32px',
border: 'none',
cursor: disabled || isLoading ? 'not-allowed' : 'pointer',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
transition: [
`background-color var(--transition-duration) ease`,
`color var(--transition-duration) ease`,
].join(', '),
// State is handled via CSS classes below
}}
className={[
'asana-btn-primary',
variant === 'light' ? 'asana-btn-primary--light' : '',
disabled ? 'asana-btn-primary--disabled' : '',
isLoading ? 'asana-btn-primary--loading' : '',
].filter(Boolean).join(' ')}
>
{isLoading ? (
<span className="asana-btn-spinner" aria-hidden="true" />
) : null}
<span style={{ visibility: isLoading ? 'hidden' : 'visible' }}>
{children}
</span>
</button>
);
}
/*
CSS to accompany:
.asana-btn-primary {
background: var(--darkmode-button-primary); /* #f5f4f3 */
color: var(--darkmode-button-text-primary); /* #0d0e10 */
}
.asana-btn-primary:hover:not(:disabled) {
background: var(--darkmode-button-primary-hover); /* #f06a6a */
color: var(--darkmode-button-text-primary-hover); /* #0d0e10 */
}
.asana-btn-primary:active:not(:disabled) {
box-shadow: none;
outline: 0;
}
.asana-btn-primary:focus-visible {
outline: 2px solid var(--blue-400);
outline-offset: 2px;
}
.asana-btn-primary--disabled,
.asana-btn-primary:disabled {
background: var(--darkmode-button-disabled); /* #38393d */
color: var(--darkmode-button-text-disabled); /* #6a696a */
border: 1px solid #d5dce0;
cursor: not-allowed;
}
.asana-btn-primary--light:hover:not(:disabled) {
background: var(--darkmode-button-primary-hover); /* #f06a6a */
}
.asana-btn-spinner {
width: 16px; height: 16px;
border: 2px solid rgba(13,14,16,0.3);
border-top-color: #0d0e10;
border-radius: 50%;
animation: loader-rotation 0.8s linear infinite;
}
*/
```
---
### 6.2 Input Field
**Anatomy:** `[label] [input with placeholder] [optional clear/submit icon]`
| State | Background | Border | Text | Notes |
|---|---|---|---|---|
| Default | `#ffffff` | `1px solid #707070` | `rgb(100,111,121)` | `border-radius: 50px` pill |
| Hover | `#ffffff` | `1px solid #707070` | same | No explicit hover change extracted |
| Focus | `#ffffff` | `2px solid #f06a6a` (coral) | same | `outline: 0` |
| Active | `#ffffff` | `2px solid #f06a6a` | same | |
| Disabled | `var(--gray-1)` `#f6f8f9` | `1px solid #edf1f2` | muted | `cursor: not-allowed` |
| Error | `#ffffff` | `2px solid var(--red)` `#a82424` | same | |
```css
.asana-input {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 12.8px;
font-weight: 400;
line-height: normal;
color: rgb(100, 111, 121);
background: #ffffff;
border: 1px solid #707070;
border-radius: 50px;
padding: 6px 35px 6px 15px;
transition: border-color var(--transition-duration-long) ease;
outline: 0;
}
.asana-input:focus,
.asana-input:active {
border: 2px solid #f06a6a; /* --darkmode-button-primary-hover */
outline: 0;
}
.asana-input:disabled {
background: #f6f8f9;
border-color: #edf1f2;
cursor: not-allowed;
}
.asana-input[aria-invalid="true"] {
border: 2px solid var(--red);
}
```
---
### 6.3 Navigation Item (Link)
**Anatomy:** `[text label] [optional dropdown chevron]`
| State | Colour | Border | Notes |
|---|---|---|---|
| Default | `rgb(13,14,16)` | none | 16px / 500 / line-height 24px |
| Hover | `rgb(205, 72, 72)` | `border-bottom: 2px solid rgb(205,72,72)` | Colour + underline |
| Active | same as hover | same | |
| Disabled | `var(--black-400)` `#9e9e9e` | none | |
| Focus | Default colour | `outline: 2px solid var(--blue-400)` | |
Dropdown trigger (`role_tab` on nav): `border-radius: 100px`, `padding: 8px 24px`, `gap: 4px`, `font-size: 14px`.
```css
.asana-nav-link {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 16px;
font-weight: 500;
line-height: 24px;
color: rgb(13, 14, 16);
text-decoration: none;
border-bottom: 2px solid transparent;
transition: color var(--transition-duration-long),
border-color var(--transition-duration-long);
display: flex;
align-items: center;
margin-right: 32px;
}
.asana-nav-link:hover {
color: rgb(205, 72, 72);
border-bottom-color: rgb(205, 72, 72);
}
.asana-nav-link:focus-visible {
outline: 2px solid var(--blue-400);
outline-offset: 2px;
}
```
---
### 6.4 Badge
**Anatomy:** `[text label]`
| State | Background | Text | Border radius | Notes |
|---|---|---|---|---|
| Default | `rgb(200,235,252)` ≈ `--blue-20` | `rgb(85,100,158)` ≈ `--blue-700` | `4px` | 11px / 700 / 0.5px tracking |
| Hover | no explicit change | — | — | Not interactive |
```tsx
export function Badge({ children }: { children: React.ReactNode }) {
return (
<span
style={{
fontFamily: '"TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif',
fontSize: '11px',
fontWeight: 700,
lineHeight: '20px',
letterSpacing: '0.5px',
color: 'rgb(85, 100, 158)', /* --blue-700 equivalent */
backgroundColor: 'rgb(200, 235, 252)', /* --blue-20 */
borderRadius: '4px', /* --radius-lg */
padding: '0 8px',
display: 'inline-flex',
alignItems: 'center',
}}
>
{children}
</span>
);
}
```
---
### 6.5 Pill Tab (Role Tab Switcher)
**Anatomy:** `[tab strip: flex row] > [tab button × n]`
| State | Background | Text | Radius | Transition |
|---|---|---|---|---|
| Default (inactive) | transparent | `rgb(13,13,13)` muted | `146px` | bg 0.15s, color 0.15s ease-in-out |
| Active/selected | `rgb(255,255,255)` | `rgb(13,13,13)` | `146px` | — |
| Hover | `var(--darkmode-bg-hover)` `#38383a` | same | `146px` | |
```css
.asana-pill-tab {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 20px;
font-weight: 400;
line-height: 30px;
border-radius: 146px;
padding: 16px 32px;
border: none;
background: transparent;
color: rgb(13, 13, 13);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color var(--transition-duration-short) ease-in-out,
color var(--transition-duration-short) ease-in-out;
}
.asana-pill-tab[aria-selected="true"] {
background: rgb(255, 255, 255);
}
.asana-pill-tab:hover:not([aria-selected="true"]) {
background: var(--darkmode-bg-hover);
}
.asana-pill-tab:focus-visible {
outline: 2px solid var(--blue-400);
outline-offset: 2px;
}
```
---
### 6.6 Dropdown Trigger (Nav)
**Anatomy:** `[label] [chevron icon]`
```css
.asana-dropdown-trigger {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 14px;
font-weight: 500;
line-height: 36px;
color: rgb(13, 13, 13);
background: transparent;
border-radius: 100px;
padding: 8px 24px;
border: none;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
cursor: pointer;
transition: background var(--transition-duration-long);
}
.asana-dropdown-trigger:hover,
.asana-dropdown-trigger[aria-expanded="true"] {
background: var(--darkmode-bg-hover); /* #38383a */
}
```
---
### 6.7 Alert / Inline Message
**Anatomy:** `[optional icon] [message text]`
```css
.asana-alert {
font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif;
font-size: 16px;
font-weight: 300;
line-height: 24px;
letter-spacing: 0.32px;
color: rgb(100, 111, 121);
display: block;
}
.asana-alert--error { color: var(--red); }
.asana-alert--warning { color: var(--amber); }
.asana-alert--success { color: var(--green); }
```
---
## 7. Elevation & Depth
```css
/* ── Shadow Scale ── */
/* Shadow colour is always based on --shadow-color: rgb(36, 50, 66) */
--shadow-10: 0 3px 5px 0 rgba(36, 50, 66, 0.2); /* Subtle lift — logo hover, card resting */
--shadow-20: 0 11px 12px 0 rgba(36, 50, 66, 0.12); /* Card hover, popover */
--shadow-30: 0 20px 30px 0 rgba(36, 50, 66, 0.14); /* Modal, elevated panel */
--shadow-40: 0 50px 60px 0 rgba(36, 50, 66, 0.15); /* Fullscreen overlay, hero floating element */
/* ── Usage Map ── */
/* Resting card: no shadow (flat) */
/* Card on hover: var(--shadow-10) */
/* Logo grid item on hover: var(--shadow-20) */
/* Textblock pop on hover: var(--shadow-30) */
/* Social card on hover: rgba(36,50,66,0.1) 0 30px 50px 0 (between 30 and 40) */
/* Video play icon on hover: rgba(36,50,66,0.2) 0 13px 15px 0 */
/* ── Border Radius Scale (sharp brand UI) ── */
--border-radius: 3px; /* Default global UI radius */
/* Mined from page elements: */
/* radius-sm: 1px — hamburger icon bar */
/* radius-md: 2px — cookie/consent buttons (third-party UI, not brand) */
/* radius-lg: 4px — badge, flyout tags */
/* radius-full: 8px — product UI cards (css-ov220g component) */
/* PILL: 50px–146px — buttons, pill tabs, input fields ONLY */
/* ── Z-Index Scale ── */
--zIndex-1: 100; /* Sticky header helpers */
--zIndex-2: 200; /* Tooltips */
--zIndex-3: 300; /* Dropdowns, flyouts */
--zIndex-4: 400; /* Sticky header */
--zIndex-5: 500; /* Popovers */
--zIndex-6: 600; /* Drawers */
--zIndex-7: 700; /* Modals backdrop */
--zIndex-8: 800; /* Modals */
--zIndex-9: 900; /* Toasts / alerts */
--zIndex-10: 1000; /* Dev overlays, critical UI */
/* ── Border Tokens ── */
/* Light mode: */
border: 1px solid var(--lightmode-border); /* --black-200 #cfcfcf */
border: 1px solid var(--lightmode-border-hover); /* --black-400 #9e9e9e on hover */
/* Dark mode: */
border: 1px solid var(--darkmode-border); /* rgba(217,217,217,0.3) */
border: 1px solid var(--darkmode-border-hover); /* rgba(217,217,217,0.4) on hover */
border: 1px solid var(--darkmode-border-active); /* rgba(217,217,217,0.7) on active */
```
### Elevation Principles
- **Flat is the default.** Cards and panels have no shadow at rest.
- **Shadow reveals interactivity.** `--shadow-10` on hover tells users an element is clickable.
- **Never use `box-shadow` for decorative spacing.** Shadow communicates elevation, not margin.
- **The glow effects (`--glow-coral`, `--glow-purple`) are CSS filter or gradient values** — they are not `box-shadow` tokens. Use them only in hero illustration contexts.
---
## 8. Motion
```css
/* ── Duration Scale ── */
--transition-duration-short: 75ms; /* Micro-interactions: tab switch, toggle */
--transition-duration: 150ms; /* Default: button hover, icon colour */
--transition-duration-med: 225ms; /* Nav open, dropdown reveal */
--transition-duration-long: 300ms; /* Link hover, card hover, modal open */
--transition-duration-xlong: 450ms; /* Page transitions, large reveals */
/* ── Easing ── */
--ease-default: ease; /* Universal default */
/* Loader dots use custom cubic-bezier: cubic-bezier(0.4, 0.155, 0.355, 1) — bounce in */
/* Loader dots exit: cubic-bezier(0.475, 0.005, 0.695, 0.83) — ease out */
```
### Key Named Animations
| Keyframe | Purpose | Typical duration |
|---|---|---|
| `fadeIn` | Opacity 0→1 | 300ms |
| `moveUp` | translateY(175px→0) entry reveal | 450ms |
| `moveUpMed` | translateY(100px→0) | 300ms |
| `moveUpSmall` | translateY(50px→0) | 225ms |
| `moveDown` | translateY(-15px→0) dropdown reveal | 150ms |
| `pulse` / `pop` | scale3d(1→1.05→1) attention pulse | 300ms |
| `floating-timeline` | translateY(0→-8px→0) looping float | 2s infinite |
| `dialog-open` | scale3d(1→1.02→1) modal open confirm | 150ms |
| `commReferralFadeIn` | opacity + translateY(27px→0) | 300ms |
| `loader-rotation` | rotate(0→360deg) | 800ms linear infinite |
| `loader-topDot` / `leftDot` / `rightDot` | Asana 3-dot brand loader | 1.6s staggered |
| `hamburger-menu-open/close` | Menu icon → X morph | 300ms |
| `progress-bar-load` | translateX(-100%→0) | variable |
| `animation-1wlok8` | background-position gradient pan | loop (brand gradient) |
| `animation-hqsdup` | horizontal translate scrolling logo | loop |
### Motion Principles
- **Scroll-triggered reveals:** Use `moveUp` / `moveUpSmall` + `fadeIn` for content entering viewport.
- **State confirmation:** `dialog-open` and `pop` provide micro-scale feedback on user actions.
- **Navigation transitions:** `moveDown` (75ms) for dropdowns opening; hamburger uses dedicated `open/close` keyframe pairs.
- **Looping animations are rare.** Only `floating-timeline` (decorative float) and brand gradient (`animation-1wlok8`) loop indefinitely. Stop loops for users with `prefers-reduced-motion`.
- **The Asana loader** (`loader-topDot`, `leftDot`, `rightDot`) is the brand loading state — never substitute a generic spinner on Asana-branded surfaces.
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}
```
---
## 9. Anti-Patterns & Constraints
**1. Hardcoded hex colours instead of CSS variables**
**Rule:** Never write `color: #f06a6a` directly.
**Why it fails:** AI agents frequently inline the first convenient hex from the palette rather than referencing the semantic token. This creates values that don't update with theme switches and cannot be traced to intent. `#f06a6a` hardcoded looks identical to `var(--darkmode-button-primary-hover)` visually — but the semantic version documents *why* it's coral (button hover) and makes future changes safe.
**What to do instead:** Always use `var(--darkmode-button-primary-hover)` for button hover states, `var(--glow-coral)` for brand marks. If a token doesn't exist for a value, create one before using it.
---
**2. Using Inter, Roboto, or Arial as the default font**
**Rule:** Never use `font-family: 'Inter', sans-serif` or any system font as the primary stack.
**Why it fails:** AI agents default to Inter or Roboto when no font is specified, because these are the most common design-system fonts in training data. On Asana, this produces text that looks completely wrong — wrong weight granularity (Inter has no 250/350 weights), wrong letter-spacing conventions, and wrong optical feel.
**What to do instead:** Always open with `font-family: "TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif`. The font is self-hosted at `/fonts/lausanne/`. If TWK Lausanne is unavailable in a preview environment, "Helvetica Neue" is the correct visual fallback, not Inter.
---
**3. Using rounded card corners (border-radius > 8px on surfaces)**
**Rule:** Never apply `border-radius: 12px`, `16px`, `24px` or higher to cards, panels, modals, or containers.
**Why it fails:** AI agents trained on modern SaaS design defaults assume rounded cards (12–24px) are universal. On Asana, the brand aesthetic is deliberate sharp geometry — surface elements use 0–8px at most. Rounded surfaces make Asana components look like Notion or Linear, not Asana.
**What to do instead:** Cards → `border-radius: var(--border-radius)` (`3px`) or `8px` at most. Reserve pill shapes (`border-radius: 100px+`) exclusively for `<button>`, `<input>`, and pill tab elements.
---
**4. Constructing Tailwind class names dynamically**
**Rule:** Never write `className={\`bg-[${colorToken}]\`}` or `bg-${variant}-500`.
**Why it fails:** Tailwind's JIT compiler purges classes that don't exist as literal strings at build time. Dynamic class construction produces invisible styling (classes not included in the CSS bundle) and is impossible to audit. AI agents do this frequently when mapping token names to Tailwind utility strings.
**What to do instead:** Use `style={{ backgroundColor: 'var(--darkmode-bg)' }}` for token-based values, or define complete class strings as constants: `const cls = variant === 'primary' ? 'bg-[var(--darkmode-button-primary)]' : 'bg-transparent'`.
---
**5. Omitting interaction states (hover, focus, disabled)**
**Rule:** Every interactive element must implement all five states: default, hover, focus-visible, active, disabled.
**Why it fails:** AI agents generate the default state only, then stop. The resulting component passes visual inspection but fails keyboard navigation (no focus ring), accessibility audits (no disabled affordance), and usability testing (no hover feedback). On Asana, hover = coral (`#f06a6a`) is a brand-level convention — omitting it breaks brand consistency across the entire site.
**What to do instead:** For every `<button>`, `<a>`, `<input>`, and `<select>`, write all five CSS rule blocks before moving on. Use the state tables in Section 6 as the checklist.
---
**6. Applying `!important` to override design tokens**
**Rule:** Never use `!important` in component styles.
**Why it fails:** AI agents use `!important` as a shortcut when they can't resolve specificity conflicts. This creates styles that cannot be overridden by theme tokens, dark/light mode switches, or component composition — breaking the entire cascading token system. Asana's CSS architecture uses specificity-managed class names (BEM-like), not `!important`.
**What to do instead:** Resolve specificity by scoping to the correct component class. If a third-party style requires override, use a CSS layer (`@layer overrides`) — not `!important`.
---
**7. Using off-grid spacing values**
**Rule:** Never use spacing values not in the `--spacing-*` scale. Never use `109.33px`, `17px`, `22px`, or other arbitrary values.
**Why it fails:** AI agents frequently calculate layout spacing by eyeballing screenshots and producing literal pixel values (e.g. 17px, 22px, 45px). These break the 8px rhythm that underpins Asana's entire spatial system and create subtle visual inconsistencies that are hard to audit.
**What to do instead:** Map every spacing need to the nearest `--spacing-*` token: need ~16px? Use `--spacing-1`. Need ~24px? Use `--spacing-1plus`. Need ~48px? Use `--spacing-3`. The `109.33px` curated value in the token map is an extraction artifact — it is not a valid spacing value; use `--spacing-7` (112px) instead.
---
**8. Using `position: absolute` for layout structure**
**Rule:** Never use `position: absolute` to position sibling elements or create column layouts.
**Why it fails:** AI agents fall back to absolute positioning when they can't resolve a flex or grid layout. This creates components that collapse on resize, overflow on mobile, and fail to respect the 12-column grid. Asana's layout is grid + flex, not positioned.
**What to do instead:** Use `display: grid` with `--columns: 12` and `gap: var(--gutter)` for multi-column layout. Use `display: flex` with `gap` for linear arrangements. Reserve `position: absolute` for truly overlapping elements (tooltips, dropdowns, badges).
---
**9. Using the full 149-colour primitive palette as UI colours**
**Rule:** Never pick illustration colours (`--bright-coral-1`, `--teal-3`, `--purple-4`, etc.) for UI surfaces, text, or borders.
**Why it fails:** AI agents see a large colour palette and assume all values are available for UI use. Applying `--teal-3: #32c1ff` as a button background or `--gold-3: #ffe01b` as a text colour produces off-brand, jarring results. These are decorative/illustration palette values.
**What to do instead:** For UI surfaces → use `--darkmode-bg-*` or `--lightmode-bg-*` tokens. For text → use `--darkmode-text` / `--lightmode-text` tokens. For accents → use `--glow-coral` / `--darkmode-button-primary-hover`. Refer to the semantic token tier in Section 2.
---
**10. Using placeholder/lorem ipsum content in examples**
**Rule:** Never generate `"Lorem ipsum"` body copy, `"Button"` labels, or `"Heading"` text in component examples.
**Why it fails:** Placeholder text causes agents and reviewers to accept visually wrong type scales (it passes the eye test without conveying real hierarchy). Asana's body copy is 13px / 300 weight — short, punchy sentences. A lorem ipsum paragraph at 13px weight 300 looks unreadably small and would immediately prompt someone to "fix the font size" — destroying the design intent.
**What to do instead:** Use realistic Asana-like copy: headlines like `"Work smarter with AI"`, button labels like `"Get started"` or `"Contact sales"`, body text like `"Coordinate your team's work in one place."` This validates the type scale against real content.
---
## Appendix A: Complete Token Reference
Every token extracted from the source. §0 CORE TOKENS is the primary AI signal; this appendix is reference material an AI can cross-check against when a curated role is missing.
```css
/* Colours (189) */
--color: hsla(204, 80%, 72%, .25);
--background-width: calc(100% + var(--gutter));
--background-columns: repeating-linear-gradient(
to right,
var(--color),
var(--color) var(--column-width),
transparent var(--column-width),
transparent var(--repeating-width)
);
--white: #ffffff;
--gray-1a: #f4f6f6;
--green-1: #e2fffa;
--green-2: #b0fff1;
--green-3: #58f5d9;
--green-4: #25e8c8;
--green-5: #00bf9c;
--teal-1: #edf8ff;
--teal-2: #95e0ff;
--teal-3: #32c1ff;
--teal-4: #14aaf5;
--teal-5: #008ce3;
--purple-1: #f0f0ff;
--purple-2: #aba3ff;
--purple-3: #9287ff;
--purple-4: #796eff;
--purple-5: #635ac7;
--gold-1: #fffede;
--gold-2: #fff78f;
--gold-3: #ffe01b;
--gold-4: #fcbd01;
--gold-5: #f2a100;
--orange-1: #f9dc00;
--orange-2: #ffba00;
--orange-3: #ff7f00;
--coral-1: #ffedef;
--coral-2: #ff9ca5;
--coral-3: #ff7381;
--coral-4: #ff5263;
--coral-5: #ed4758;
--shadow-color: rgb(36, 50, 66);
--lightmode-bg: #ffffff;
--lightmode-bg-hover: var(--black-20);
--lightmode-bg-strong: var(--black-50);
--lightmode-bg-med-hover: var(--black-100);
--lightmode-bg-strong-hover: var(--black-300);
--darkmode-text-weak: rgba(255,255,255,0.55);
--darkmode-text: rgba(255,255,255,0.85);
--darkmode-bg: #1e1f21;
--darkmode-bg-med: #252628;
--darkmode-bg-strong: #2a2b2d;
--darkmode-bg-hover: #38383a;
--darkmode-bg-med-hover: #3e3f41;
--darkmode-bg-strong-hover: #434445;
--darkmode-bg-active: #4c4d4e;
--darkmode-bg-med-active: #525254;
--darkmode-bg-strong-active: #565658;
--darkmode-border-weak: rgba(217,217,217,0.2);
--darkmode-border: rgba(217,217,217,0.3);
--darkmode-border-hover: rgba(217,217,217,0.4);
--darkmode-border-active: rgba(217,217,217,0.7);
--darkmode-icon: rgba(245,244,243,0.6);
--darkmode-button-text-secondary: #f5f4f3;
--darkmode-icon-disabled: rgba(245,244,243,0.15);
--darkmode-button-primary-hover: #f06a6a;
--darkmode-button-disabled: #38393d;
--darkmode-button-text-primary-hover: #0d0e10;
--darkmode-button-text-disabled: #6a696a;
--darkmode-link-text: #dbdbdb;
--darkmode-link-text-weak: #989898;
--blue-0: #cbefff;
--blue-20: #c8ebfc;
--blue-50: #c3e5f8;
--blue-100: #badbf1;
--blue-200: #a9c7e3;
--blue-300: #98b3d6;
--blue-400: #879fc8;
--blue-500: #778bba;
--blue-600: #6678ac;
--blue-700: #55649e;
--blue-800: #445091;
--blue-900: #333c83;
--blue-950: #2a327c;
--blue-980: #252c78;
--blue-1000: #222875;
--coral-0: #ffeaec;
--coral-20: #fce5e8;
--coral-50: #f7dee3;
--coral-100: #f0d3d9;
--coral-200: #e1bbc7;
--coral-300: #d2a4b4;
--coral-400: #c38ca1;
--coral-500: #b4758e;
--coral-600: #a55e7c;
--coral-700: #964669;
--coral-800: #872f56;
--coral-900: #781744;
--coral-950: #710c3a;
--coral-980: #6c0535;
--coral-1000: #690031;
--green-0: #c9fcdb;
--green-20: #c5f8d8;
--green-50: #bff3d3;
--green-100: #b5e9ca;
--green-200: #a1d7b9;
--green-300: #8dc4a8;
--green-400: #79b297;
--green-500: #659f86;
--green-600: #508c76;
--green-700: #3c7a65;
--green-800: #286754;
--green-900: #145543;
--green-950: #0a4b3a;
--green-1000: #004232;
--purple-0: #ffdcff;
--purple-20: #fcd8fd;
--purple-50: #f8d1f9;
--purple-100: #f0c6f3;
--purple-200: #e1b0e7;
--purple-300: #d29ada;
--purple-400: #c384ce;
--purple-500: #b46ec2;
--purple-600: #a658b6;
--purple-700: #9742aa;
--purple-800: #882c9d;
--purple-900: #791691;
--purple-950: #710b8b;
--purple-980: #6d0487;
--purple-1000: #6a0085;
--glow-blue: #4786ff;
--glow-coral: #ff584a;
--glow-green: #22b455;
--glow-purple: #df5dff;
--glow-white: #0d0d0d;
--red: #a82424;
--amber: #7a4b00;
--green: #0c6410;
--google-sign-up-blue: #174ea6;
--google-sign-up-blue-light: #2568d4;
--bright-blue-1: #b8e8fe;
--bright-blue-2: #ade6ff;
--bright-blue-3: #3c45b1;
--bright-blue-4: #2b328e;
--bright-coral-1: #ffdde1;
--bright-coral-2: #ffd7db;
--bright-coral-3: #911c51;
--bright-coral-4: #7e1444;
--bright-green-1: #b9f4ce;
--bright-green-2: #a7f4c2;
--bright-green-3: #09614b;
--bright-green-4: #0a4d3d;
--bright-purple-1: #fbccfb;
--bright-purple-2: #ffc4ff;
--bright-purple-3: #8a16a8;
--bright-purple-4: #780a94;
--brand-mark-1: rgb(255, 88, 74); /* Brand mark fill on 1 svg shape — e.g. "svg" /* mined from svg fill */ */
--black-1000: #0d0d0d;
--darkmode-button-primary: #f5f4f3;
--black-20: #fafafa;
--black-50: #f3f3f3;
--black-100: #e7e7e7;
--black-200: #cfcfcf;
--black-300: #b6b6b6;
--black-400: #9e9e9e;
--black-500: #868686;
--black-600: #6e6e6e;
--black-700: #565656;
--black-800: #3d3d3d;
--black-900: #252525;
--black-950: #191919;
--black-980: #121212;
--black-1100: #000000;
--lightmode-bg-inverted: var(--black-20);
--lightmode-bg-med: var(--black-50);
--lightmode-bg-active: var(--black-20);
--lightmode-bg-med-active: var(--black-100);
--lightmode-bg-strong-active: var(--black-300);
--lightmode-border-weak: var(--black-50);
--lightmode-border: var(--black-200);
--lightmode-border-strong: var(--black-200);
--lightmode-border-hover: var(--black-400);
--lightmode-border-active: var(--black-200);
--darkmode-bg-inverted: var(--white);
--darkmode-border-strong: var(--white);
--darkmode-icon-hover: #f5f4f3;
--darkmode-button-secondary: #0d0e10;
--darkmode-button-secondary-hover: #f06a6a;
--darkmode-button-text-primary: #0d0e10;
--darkmode-button-text-secondary-hover: #0d0e10;
--darkmode-link-text-weak-disabled: #6a696a;
--darkmode-inline-inline-link-text: #dbdbdb;
--darkmode-inline-link-hover: #f06a6a;
--darkmode-inline-link-disabled: #6a696a;
--darkmode-link-hover: #f06a6a;
--darkmode-link-disabled: #6a696a;
--glow-gray: #0d0d0d;
/* Typography (47) */
--shadow-10: 0 3px 5px 0 rgba(36, 50, 66, 0.2);
--shadow-20: 0 11px 12px 0 rgba(36, 50, 66, 0.12);
--shadow-30: 0 20px 30px 0 rgba(36, 50, 66, 0.14);
--shadow-40: 0 50px 60px 0 rgba(36, 50, 66, 0.15);
--fontFamily-print: "Georgia", "Times New Roman", "Times", serif;
--fontFamily-mono: "Consolas", monospace;
--fontSize-base: 16px;
--fontSize-larger: 3.2rem;
--fontSize-large: 2.2rem;
--fontSize-medium: 2rem;
--fontSize-regular: 1.6rem;
--fontSize-small: 1.4rem;
--fontSize-smaller: 1.2rem;
--fontSize-xl: 82;
--fontSize-h1: 56;
--fontSize-h3: 45;
--fontSize-h4: 31;
--fontSize-h5: 22;
--fontSize-h6: 16;
--fontWeight-thin: 300;
--fontWeight-normal: normal;
--fontWeight-medium: 400;
--fontWeight-semibold: 500;
--lightmode-link-text-weak-hover: var(--black-600);
--lightmode-text: var(--black-800);
--lightmode-button-text-secondary: var(--black-1000);
--lightmode-button-text-primary-hover: var(--white);
--lightmode-link-text-weak-disabled: var(--black-400);
--font-size-xs: 13px; /* 12 elements — e.g. h6 "Skills", h6 "Skills", h6 "Skills" /* mined from computed styles */ */
--font-size-sm: 14px; /* 12 elements — e.g. span "Contact sales", span "Log in", a "Pricing" /* mined from computed styles */ */
--font-size-lg: 20px; /* 18 elements — e.g. p "Turns project goals ", p "Finds bottlenecks in", p "Reviews project docs" /* mined from computed styles */ */
--font-size-xl: 24px; /* 5 elements — e.g. h5 "Launch Planner", h5 "Workflow Optimizer", h5 "Compliance Specialis" /* mined from computed styles */ */
--font-size-2xl: 30px; /* 8 elements — e.g. h4 "85% of Fortune 100 c", h4 "Asana Work Graph®", h4 "Multiplayer" /* mined from computed styles */ */
--font-size-3xl: 54px; /* 6 elements — e.g. h2 "AI that works the wa", h2 "Your team just got b", h2 "Deliver real product" /* mined from computed styles */ */
--line-height-tight: 19.5px; /* 12 elements — e.g. h6 "Skills", h6 "Skills", h6 "Skills" /* mined from computed styles */ */
--line-height-loose: 36px; /* 14 elements — e.g. h4 "85% of Fortune 100 c", h4 "Asana Work Graph®", h4 "Multiplayer" /* mined from computed styles */ */
--darkmode-text-strong: var(--white);
--lightmode-text-weak: var(--black-600);
--lightmode-text-strong: var(--black-1000);
--lightmode-button-text-primary: var(--white);
--lightmode-button-text-disabled: var(--white);
--lightmode-button-text-secondary-hover: var(--white);
--lightmode-link-text: var(--black-1000);
--lightmode-link-text-weak: var(--black-600);
--lightmode-inline-link-text: var(--black-1000);
--darkmode-link-text-weak-hover: var(--white);
--fontSize-h2: 56;
/* Spacing (23) */
--max_width: 1920px;
--gutter: 32px;
--spacing-0: 0;
--spacing-1half: 8px;
--spacing-1: 16px;
--spacing-1plus: 24px;
--spacing-3: 48px;
--spacing-4: 64px;
--spacing-5: 80px;
--spacing-6: 96px;
--spacing-7: 112px;
--spacing-8: 128px;
--spacing-9: 144px;
--spacing-10: 160px;
--lightmode-border-weak: var(--black-50);
--lightmode-border-strong: var(--black-200);
--lightmode-border-hover: var(--black-400);
--darkmode-border-strong: var(--white);
--space-xs: 4px; /* 4 elements — e.g. div .siteFooter__secondary__col-wrapper, div .siteFooter__secondary__col-wrapper, div .siteFooter__secondary__col-wrapper /* mined from computed styles */ */
--space-lg: 40px; /* 2 elements — e.g. section .css-1goa3i0, section .css-pb8rhz /* mined from computed styles */ */
--space-2xl: 109.33px; /* 4 elements — e.g. div .siteFooter__secondary__col-wrapper, div .siteFooter__secondary__col-wrapper, div .siteFooter__secondary__col-wrapper /* mined from computed styles */ */
--spacing-2: 32px;
--grid-gutter-width: 32px;
/* Radius (5) */
--border-radius: 3px;
--radius-sm: 1px; /* 1 element — e.g. div .styles_hamburger-button__icon__qeiIr /* mined from computed styles */ */
--radius-md: 2px; /* 6 elements — e.g. button .ot-link-btn "Cookies Details", button .ot-link-btn "Cookies Details", button .ot-link-btn "Cookies Details" /* mined from computed styles */ */
--radius-lg: 4px; /* 3 elements — e.g. span .styles_flyout-link__tag__xQgTA "Coming soon", span .styles_flyout-link__tag__xQgTA "Coming soon", span .styles_flyout-link__tag__xQgTA "Coming soon" /* mined from computed styles */ */
--radius-full: 8px; /* 6 elements — e.g. button .css-ov220g "SkillsRoadmap Syncin", button .css-ov220g "SkillsVoice Governan", button .css-ov220g "SkillsRoadmap Syncin" /* mined from computed styles */ */
/* Effects (61) */
--columns: 12;
--repeating-width: calc(100% / var(--columns));
--column-width: calc((100% / var(--columns)) - var(--gutter));
--zIndex-1: 100;
--zIndex-2: 200;
--zIndex-3: 300;
--zIndex-4: 400;
--zIndex-5: 500;
--zIndex-6: 600;
--zIndex-7: 700;
--zIndex-8: 800;
--zIndex-9: 900;
--zIndex-10: 1000;
--media-xsmall: 480;
--media-small: 768;
--media-medium: 960;
--media-large: 1120;
--media-xlarge: 1280;
--media-xxlarge: 1440;
--media-xxxlarge: 1920;
--media-xsmall-min: cacl(var(--media-xsmall) / var(--fontSize-base));
--media-small-min: cacl(var(--media-small) / var(--fontSize-base));
--media-medium-min: cacl(var(--media-medium) / var(--fontSize-base));
--media-large-min: cacl(var(--media-large) / var(--fontSize-base));
--media-xlarge-min: cacl(var(--media-xlarge) / var(--fontSize-base));
--media-xxlarge-min: cacl(var(--media-xxlarge) / var(--fontSize-base));
--media-xxxlarge-min: cacl(var(--media-xxxlarge) / var(--fontSize-base));
--media-xsmall-max: cacl((var(--media-small) - 1) / var(--fontSize-base));
--media-small-max: cacl((var(--media-medium) - 1) / var(--fontSize-base));
--media-medium-max: cacl((var(--media-large) - 1) / var(--fontSize-base));
--media-large-max: cacl((var(--media-xlarge) - 1) / var(--fontSize-base));
--media-xlarge-max: cacl((var(--media-xxlarge) - 1) / var(--fontSize-base));
--transition-duration-short: 75ms;
--transition-duration: 150ms;
--transition-duration-med: 225ms;
--transition-duration-long: 300ms;
--transition-duration-xlong: 450ms;
--lightmode-button-primary: var(--black-1000);
--lightmode-button-primary-hover: var(--black-800);
--lightmode-icon-disabled: var(--black-400);
--lightmode-button-secondary: var(--white);
--lightmode-button-secondary-hover: var(--black-600);
--lightmode-error: var(--red);
--lightmode-warning: var(--amber);
--lightmode-success: var(--green);
--lightmode-accent-red: var(--coral-100);
--lightmode-accent-red-light: var(--coral-50);
--lightmode-accent-purple: var(--purple-100);
--lightmode-accent-blue: var(--blue-100);
--lightmode-accent-blue-light: var(--blue-0);
--lightmode-accent-green: var(--green-100);
--lightmode-accent-gray: var(--black-50);
--lightmode-a11y-focus-primary: var(--blue-400);
--darkmode-accent-red: var(--coral-1000);
--darkmode-accent-purple: var(--purple-1000);
--darkmode-accent-blue: var(--blue-1000);
--darkmode-accent-green: var(--green-1000);
--shadow-20: 0 11px 12px 0 rgba(36,50,66,0.12);
--shadow-30: 0 20px 30px 0 rgba(36,50,66,0.14);
--shadow-40: 0 50px 60px 0 rgba(36,50,66,0.15);
--shadow-10: 0 3px 5px 0 rgba(36,50,66,0.2);
/* Motion (38) */
----motion-fadeIn: @keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}; /* @keyframes fadeIn */
----motion-moveUp: @keyframes moveUp {
0% { transform: translateY(15px); }
100% { transform: translateY(0px); }
}; /* @keyframes moveUp */
----motion-moveUpMed: @keyframes moveUpMed {
0% { transform: translateY(100px); }
100% { transform: translateY(0px); }
}; /* @keyframes moveUpMed */
----motion-moveUpSmall: @keyframes moveUpSmall {
0% { transform: translateY(50px); }
100% { transform: translateY(0px); }
}; /* @keyframes moveUpSmall */
----motion-arrow: @keyframes arrow {
100% { transform: translateX(0px); }
}; /* @keyframes arrow */
----motion-moveDown: @keyframes moveDown {
0% { transform: translateY(-15px); }
100% { transform: translateY(0px); }
}; /* @keyframes moveDown */
----motion-pulse: @keyframes pulse {
0% { transform: scale3d(1, 1, 1); }
50% { transform: scale3d(1.05, 1.05, 1.05); }
100% { transform: scale3d(1, 1, 1); }
}; /* @keyframes pulse */
----motion-loader-rotation: @keyframes loader-rotation {
0% { transform: rotate(0deg); }
50% { transform: rotate(180deg); }
100% { transform: rotate(360deg); }
}; /* @keyframes loader-rotation */
----motion-loader-topDot: @keyframes loader-topDot {
0% { animation-timing-function: cubic-bezier(0.4,… <0.3KB elided>; /* @keyframes loader-topDot */
----motion-loader-leftDot: @keyframes loader-leftDot {
0% { animation-timing-function: cubic-bezier(0.4,… <0.3KB elided>; /* @keyframes loader-leftDot */
----motion-loader-rightDot: @keyframes loader-rightDot {
0% { animation-timing-function: cubic-bezier(0.4… <0.3KB elided>; /* @keyframes loader-rightDot */
----motion-commReferralFadeIn: @keyframes commReferralFadeIn {
0% { opacity: 0; transform: translateY(27px); }
100% { opacity: 1; transform: translateY(0px); }
}; /* @keyframes commReferralFadeIn */
----motion-pop: @keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}; /* @keyframes pop */
----motion-floating-timeline: @keyframes floating-timeline {
0% { transform: translateY(0px); }
50% { transform: translateY(-8px); }
100% { transform: translateY(0px); }
}; /* @keyframes floating-timeline */
----motion-hamburger-menu-open-bottom: @keyframes hamburger-menu-open-bottom {
0% { transform: translateY(6px); }
30% { transform: translateY(0px); }
100% { transform: translateY(0px); }
}; /* @keyframes hamburger-menu-open-bottom */
----motion-hamburger-menu-open-top: @keyframes hamburger-menu-open-top {
0% { transform: translateY(-6px); }
30% { transform: translateY(0px); }
100% { transform: translateY(0px) rotate(90deg); }
}; /* @keyframes hamburger-menu-open-top */
----motion-hamburger-menu-close-bottom: @keyframes hamburger-menu-close-bottom {
0% { transform: translateY(0px); }
30% { transform: translateY(0px); }
100% { transform: translateY(6px); }
}; /* @keyframes hamburger-menu-close-bottom */
----motion-hamburger-menu-close-top: @keyframes hamburger-menu-close-top {
0% { transform: translateY(0px) rotate(90deg); }
30% { transform: translateY(0px); }
100% { transform: translateY(-6px); }
}; /* @keyframes hamburger-menu-close-top */
----motion-dialog-open: @keyframes dialog-open {
0% { transform: scale3d(1, 1, 1); }
50% { transform: scale3d(1.02, 1.02, 1.02); }
100% { transform: scale3d(1, 1, 1); }
}; /* @keyframes dialog-open */
----motion-fadeInLeft: @keyframes fadeInLeft {
0% { opacity: 0.5; transform: translate3d(110%, 0px,… <0.3KB elided>; /* @keyframes fadeInLeft */
----motion-fadeInRight: @keyframes fadeInRight {
0% { opacity: 0.5; transform: translate3d(-110%, 0px… <0.3KB elided>; /* @keyframes fadeInRight */
----motion-stroke: @keyframes stroke {
100% { stroke-dashoffset: 0; }
}; /* @keyframes stroke */
----motion-rotate: @keyframes rotate {
100% { transform: rotate(360deg); }
}; /* @keyframes rotate */
----motion-progress-bar-load: @keyframes progress-bar-load {
0% { transform: translateX(-100%); }
100% { transform: translateX(0px); }
}; /* @keyframes progress-bar-load */
----motion-styles_hamburger-menu-open-bottom__tKK0H: @keyframes styles_hamburger-menu-open-bottom__tKK0H {
0% { transform: translateY(6px); }
30% { transform: translateY(0px); }
100% { transform: translateY(0px); }
}; /* @keyframes styles_hamburger-menu-open-bottom__tKK0H */
----motion-styles_hamburger-menu-open-top__XqEK0: @keyframes styles_hamburger-menu-open-top__XqEK0 {
0% { transform: translateY(-6px); }
30% { transform: translateY(0px); }
100% { transform: translateY(0px) rotate(90deg); }
}; /* @keyframes styles_hamburger-menu-open-top__XqEK0 */
----motion-styles_hamburger-menu-close-bottom__8j8Z8: @keyframes styles_hamburger-menu-close-bottom__8j8Z8 {
0% { transform: translateY(0px); }
30% { transform: translateY(0px); }
100% { transform: translateY(6px); }
}; /* @keyframes styles_hamburger-menu-close-bottom__8j8Z8 */
----motion-styles_hamburger-menu-close-top__ltI4C: @keyframes styles_hamburger-menu-close-top__ltI4C {
0% { transform: translateY(0px) rotate(90deg); }
30% { transform: translateY(0px); }
100% { transform: translateY(-6px); }
}; /* @keyframes styles_hamburger-menu-close-top__ltI4C */
----motion-plyr-progress: @keyframes plyr-progress {
100% { background-position: var(--plyr-progress-loading-size,25px) 0; }
}; /* @keyframes plyr-progress */
----motion-plyr-popup: @keyframes plyr-popup {
0% { opacity: 0.5; transform: translateY(10px); }
100% { opacity: 1; transform: translateY(0px); }
}; /* @keyframes plyr-popup */
----motion-plyr-fade-in: @keyframes plyr-fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}; /* @keyframes plyr-fade-in */
----motion-animation-1wlok8: @keyframes animation-1wlok8 {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}; /* @keyframes animation-1wlok8 */
----motion-animation-hqsdup: @keyframes animation-hqsdup {
100% { translate: var(--destination-x) 0; }
}; /* @keyframes animation-hqsdup */
----motion-slide-down-custom: @-webkit-keyframes slide-down-custom {
0% { }
100% { bottom: 1em; }
}; /* @keyframes slide-down-custom */
--duration-fast: 0.15s; /* 22 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-base: 0.2s; /* 8 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-slow: 0.3s; /* 58 elements — e.g. button, button, button /* mined from computed styles */ */
--ease-default: ease; /* 179 elements — e.g. button, button, button /* mined from computed styles */ */
```
## Appendix B: Token Source Metadata
```
tokenSource: extracted-css-vars
site: asana.com
extractionDate: [date of extraction]
confidence: high — 351 CSS custom properties found, 45 mapped to curated semantic roles
namingConvention: Original CSS variable names preserved exactly as extracted
Curated aliases (--asana-*) provided as supplementary references only
primaryNames: Original extracted names (e.g. --darkmode-button-primary-hover, not --asana-accent-hover)
colourCount: 149 colour tokens extracted; ~30 actively used for UI semantics
curatedMappings: 45 tokens mapped to standard roles (colours, spacing, radius, motion, typography)
unusedTokens: ~104 colour tokens are primitive palette or illustration values — not for UI use
```
### Notes on Specific Tokens
**`--asana-bg-app: calc(100% + var(--gutter))`** — This is not a colour value. It is `--background-width`, a layout calculation. The curated mapping to `bg-app` is incorrect. Use `--background-width` only for full-bleed layout calculations, not as a background colour.
**`--asana-font-sans: 0 3px 5px 0 rgba(36, 50, 66, 0.2)`** — This is `--shadow-10`, not a font-family value. The curated mapping mislabelled it. Use `--shadow-10` for the shadow, and `"TWK Lausanne", "Helvetica Neue", Helvetica, sans-serif` for the primary font stack.
**`--glow-white: #0d0d0d` and `--glow-gray: #0d0d0d`** — Despite their names, both are near-black. This appears to be a dark-mode context value (text colour on glow effects). Do not use either token as a white or gray colour.
**Font weight discrepancy:** The `--fontWeight-bold: 500` token equals `--fontWeight-semibold: 500`. There is no 600 or 700 weight in the TWK Lausanne font face declarations. The badge component uses `font-weight: 700` (extracted from computed styles), which will resolve to TWK Lausanne 550 (the heaviest declared weight) in practice.
**Breakpoint typo:** All `--media-*-min` and `--media-*-max` tokens use `cacl()` instead of `calc()` in the source CSS — these custom properties are non-functional. Use hardcoded px values in media queries until the source is corrected.
**Framework:** Next.js (detected) + Bootstrap (detected, likely for grid only). The 12-column grid and gutter values align with Bootstrap's defaults, but the design token system is entirely custom.More from the gallery
Browse all kits →You may also like

atlassian.com
MITEnterprise design system for Atlassian's marketing site, featuring bold brand colours, custom typography, and structured spacing tokens built for large-scale SaaS platforms
06
boldsaaslanding-pagelight

github.com
MITGitHub's production design system with 568 CSS custom properties, featuring clean typography, accessible colour tokens, and motion primitives built for developer platforms and collaborative workflows
04
darkdeveloper-toolsaas

Customer.io
MITDark SaaS design system with mint-green accents and clean typography, built for product dashboards and developer-focused platforms
011
darksaasdeveloper-tooldashboard