MIT
Google's clean, accessible design system with bright blue accents, subtle surfaces, and precise spacing—ideal for SaaS products and web applications
Layout StudioImport this kit into a Studio project and start editing.
CLI installRun it in any project. No account needed.
npx @layoutdesign/context install googleUI themeReskin shadcn or Layout UI components to match Google.
npx shadcn add https://layout.design/r/google/theme.json# layout.md — Google.com Design System
---
## 0. Quick Reference
> Copy-paste into `CLAUDE.md` or `.cursorrules` for instant design-system context.
**Stack:** Vanilla CSS + Bootstrap detected. Token source: extracted CSS custom properties (115 vars, high confidence). Use as `var(--token-name)` in CSS, `style={{ prop: 'var(--token-name)' }}` in JSX, or `bg-[var(--token-name)]` in Tailwind.
```css
:root {
/* Colours */
--google-accent: rgb(11, 87, 208); /* Primary CTA / links */
--google-bg-app: #fff; /* Page background */
--google-bg-surface: #f7f8f9; /* Card / panel background */
--google-text-primary: rgba(0,0,0,0.6); /* Body text */
--google-text-secondary: #474747; /* Secondary / muted text */
--google-accent-hover: #d3e3fd; /* Hover state tint */
--google-accent-warm: #fbbc04; /* Warning / brand yellow */
/* Radius */
--google-radius-sm: 8px; /* Inputs, chips */
--google-radius-md: 12px; /* Small cards */
--google-radius-lg: 20px; /* Large cards */
--google-radius-full: 50px; /* Pill buttons, icon buttons */
/* Spacing */
--google-space-xs: 12px;
--google-space-sm: 14px;
--google-space-md: 16px;
--google-space-lg: 18px;
--google-space-xl: 20px;
--google-space-2xl: 22px;
--google-space-3xl: 24px;
/* Typography */
--google-font-sans: Arial, sans-serif;
--google-font-brand: "Google Sans", Arial, sans-serif;
/* Motion */
--google-duration-fast: 0.3s;
--google-ease-default: ease;
}
```
```tsx
// Primary nav button — correct token usage with all states
<button
className="google-btn-primary"
style={{
fontFamily: 'var(--google-font-sans)',
fontSize: '14px',
fontWeight: 400,
backgroundColor: 'var(--google-accent)',
color: '#fff',
borderRadius: 'var(--google-radius-full)',
padding: '10px var(--google-space-xl)',
border: 'none',
transition: `opacity var(--google-duration-fast) var(--google-ease-default)`,
cursor: 'pointer',
}}
onMouseEnter={e => (e.currentTarget.style.opacity = '0.9')}
onMouseLeave={e => (e.currentTarget.style.opacity = '1')}
>
Sign in
</button>
```
**NEVER:**
- NEVER use `font-family: Inter` or `Roboto` — Google.com uses `Arial` (body) and `"Google Sans"` (headings/brand) only
- NEVER hardcode `#0b57d0` — use `var(--google-accent)` or its original alias `var(--TMYS9)`
- NEVER use spacing values outside the defined scale — no arbitrary `10px`, `15px`, `25px`
- NEVER use `border-radius` values other than `8px`, `12px`, `20px`, or `50px`
- NEVER apply warm colours (`--google-accent-warm: #fbbc04`) to interactive controls — decorative/brand use only
- NEVER omit hover, focus, and disabled states on any interactive element
- NEVER construct dynamic Tailwind class names (e.g. `` `bg-[${color}]` ``) — use CSS variables directly
**Full design system → see layout.md**
<!-- Quick Reference truncated to fit the 75-line cap. See later sections for the full design system. -->
## 1. Design Direction & Philosophy
### Character & Mood
Google.com's design language is **minimal, purposeful, and confident**. The homepage is dominated by whitespace — the search interface is the hero, everything else recedes. The aesthetic is clean and almost institutional: no gradients, no decorative borders, no visual noise. The brand's four-colour identity (blue, red, yellow, green) appears only in the logo; the UI itself is a restrained monochrome-plus-blue system.
### Aesthetic Intent
- **Clarity first:** Every element earns its place. Whitespace is generous and intentional.
- **Soft but structured:** Rounded corners (`50px` pills for primary buttons, `8px` for inputs) give a friendly, modern feel without being playful.
- **Accessible neutrals:** Body text is `rgba(0,0,0,0.6)` — softened black, not harsh. Secondary text is `#474747`.
- **Blue as the single action colour:** `#0b57d0` is the sole interactive/action hue. All links, CTAs, and focus indicators derive from it.
### What This Design Explicitly Rejects
- **No warm backgrounds** — surfaces are cool white (`#fff`) or near-white grey (`#f7f8f9`, `#f3f5f6`). Never cream, ivory, or warm grey.
- **No heavy shadows** — elevation is expressed through subtle background-colour shifts and thin borders, not `box-shadow` depth.
- **No decorative type** — headings use `"Google Sans"` at normal weight (400). No bold display type, no italic flourishes.
- **No rounded corners above 50px** — pill is the maximum. Nothing looks like a blob or a squircle.
- **No competing accent colours in UI** — `#fbbc04` (yellow) is brand-only; it never appears on buttons, links, or form controls.
- **No harsh contrast** — even primary text uses `rgba(0,0,0,0.6)`, not pure black.
---
## 2. Colour System
### Tier 1 — Primitive Values
```css
/* ── Primitive palette ── */
:root {
/* Blues */
--COEmY: #1f1f1f; /* Near-black — primary text, icon fills */
--TMYS9: #0b57d0; /* Google Blue — primary action colour */
--TyVYld: #0b57d0; /* Alias of Google Blue */
--rrJJUc: #0b57d0; /* Alias of Google Blue */
--Pa8Wlb: #0b57d0; /* Alias of Google Blue */
--lv6yU: #0b57d0; /* Alias of Google Blue */
--Wmwad: #0b57d0; /* Alias of Google Blue */
--fuvksc: #0b57d0; /* Alias of Google Blue */
--gJjJk: #0b57d0; /* Alias of Google Blue */
--p9J9c: #0b57d0; /* Alias of Google Blue */
--MoaHse: #0b57d0; /* Alias of Google Blue */
--izGsqb: #0b57d0; /* Alias of Google Blue */
--Nsm0ce: #0b57d0; /* Alias of Google Blue */
--JKqx2: #1a0dab; /* Visited link blue / deeper blue */
--vZe0jb: #a3c9ff; /* Light blue — illustrative accent, NOT UI */
--ZEpPmd: #a3c9ff; /* Alias of light blue */
--KIZPne: #a3c9ff; /* Alias of light blue */
--vdwxpe: #a8c7fa; /* Light blue variant — illustrative */
--BRLwE: #d3e3fd; /* Blue tint — hover state background */
--TSWZIb: #e5edff; /* Blue tint — subtle highlight */
--DEeStf: #f5f8ff; /* Near-white blue tint */
--Ijc2tf: #ebf1ff; /* Near-white blue tint variant */
--VuZXBd: #001d35; /* Deep navy */
--jINu6c: #001d35; /* Deep navy alias */
--xPpiM: #001d35; /* Deep navy alias */
--VeLyLc: #001d35; /* Deep navy alias */
--NoaQW: #001d35; /* Deep navy alias */
--y01ou: #001d35; /* Deep navy alias */
--uLz37c: #545d7e; /* Mid-blue grey */
/* Neutrals */
--EpFNW: #fff; /* Pure white */
--xhUGwc: #fff; /* Pure white alias */
--ZXiaGf: #fff; /* Pure white alias */
--EoKObe: #fff; /* Pure white alias */
--cIehld: #fff; /* Pure white alias */
--todMNc: #fff; /* Pure white alias */
--YaIeMb: #f7f8f9; /* Off-white surface */
--XKMDxc: #f3f5f6; /* Light grey surface */
--aYn2S: #f3f5f6; /* Light grey — hover state background */
--MV2sG: #f1f3f4; /* Light grey variant */
--Lm570b: #dee1e3; /* Border grey */
--gS5jXb: #d2d2d2; /* Divider grey */
--mXZkqc: #d2d2d2; /* Divider grey alias */
--JclFj: #d2d2d2; /* Divider grey alias */
--ONhrGd: #d2d2d2; /* Divider grey alias */
--Yi4Nb: #d2d2d2; /* Divider grey alias */
--kloG3: #d2d2d2; /* Divider grey alias */
--IXoxUe: #636363; /* Mid grey — placeholder / muted text */
--bbQxAb: #474747; /* Dark grey — secondary text */
--pEa0Bc: #474747; /* Dark grey alias */
--Xqboce: #1f1f1f; /* Near-black alias */
--YLNNHc: #1f1f1f; /* Near-black alias */
--PBxOKe: #1f1f1f; /* Near-black alias */
/* Transparency */
--gVUKcd: rgba(0,0,0,0.6); /* Soft black — body text */
--qdHZF: rgba(0,0,0,.6); /* Soft black alias */
--D9aNi: rgba(31,31,31,0.08); /* Near-black 8% — ripple / overlay */
/* Brand accent */
--ywz01c: #fbbc04; /* Google Yellow — brand/decorative only */
/* Status */
--yTtsEf: #072711; /* Deep green — success state */
}
```
### Tier 2 — Semantic Aliases
```css
:root {
/* Backgrounds */
--google-bg-app: var(--EpFNW); /* Page-level background */
--google-bg-surface: var(--YaIeMb); /* Card, panel, chip backgrounds */
--google-bg-hover: var(--aYn2S); /* Item hover state background */
--google-bg-overlay: var(--D9aNi); /* Ripple / scrim overlay */
/* Text */
--google-text-primary: var(--gVUKcd); /* Body text — rgba(0,0,0,0.6) */
--google-text-emphasis: var(--COEmY); /* Headings, high-contrast labels — #1f1f1f */
--google-text-secondary: var(--bbQxAb); /* Muted / metadata — #474747 */
--google-text-placeholder: var(--IXoxUe); /* Input placeholder — #636363 */
--google-text-on-accent: var(--EpFNW); /* Text on blue backgrounds — #fff */
/* Actions */
--google-accent: var(--TMYS9); /* Primary CTA bg / link colour — #0b57d0 */
--google-accent-hover: var(--BRLwE); /* Hover tint on accent elements — #d3e3fd */
--google-accent-visited: var(--JKqx2); /* Visited link — #1a0dab */
/* Borders & Dividers */
--google-border-default: var(--gS5jXb); /* Standard border — #d2d2d2 */
--google-border-strong: var(--Lm570b); /* Stronger divider — #dee1e3 */
/* Brand (decorative use only — NEVER on interactive controls) */
--google-brand-yellow: var(--ywz01c); /* Google Yellow — #fbbc04 */
--google-brand-navy: var(--jINu6c); /* Deep navy — #001d35 */
/* Status */
--google-success: var(--yTtsEf); /* Success state — #072711 */
}
```
### Tier 3 — Component Tokens
```css
:root {
/* Search input */
--google-input-bg: var(--google-bg-app);
--google-input-border: var(--google-border-default);
--google-input-text: var(--google-text-emphasis);
--google-input-placeholder: var(--google-text-placeholder);
--google-input-radius: var(--google-radius-full); /* Pill-shaped search bar */
/* Primary button (Sign in) */
--google-btn-primary-bg: var(--google-accent);
--google-btn-primary-text: var(--google-text-on-accent);
--google-btn-primary-radius: var(--google-radius-full);
--google-btn-primary-hover-opacity: 0.9;
/* Ghost / secondary button */
--google-btn-ghost-bg: transparent;
--google-btn-ghost-text: var(--google-accent);
--google-btn-ghost-border: var(--google-border-default);
--google-btn-ghost-radius: var(--google-radius-full);
--google-btn-ghost-hover-bg: var(--google-accent-hover);
/* Icon button */
--google-btn-icon-radius: 50%;
--google-btn-icon-padding: 8px;
/* Navigation */
--google-nav-bg: var(--google-bg-app);
--google-nav-text: var(--google-text-emphasis);
--google-nav-padding: 6px;
/* Snackbar / dialog */
--google-snackbar-bg: var(--google-accent);
--google-snackbar-text: var(--google-text-on-accent);
--google-snackbar-padding: 18px 60px 18px 12px;
}
```
### Accent / Brand Subgroup (unassigned saturated colours)
```css
:root {
/* Secondary brand colours — illustrative tiles, decorative accents only */
--vZe0jb: #a3c9ff; /* Soft sky blue — illustration backgrounds */
--nwXobb: var(--vZe0jb); /* Alias */
--QWaaaf: var(--ZEpPmd); /* Alias */
/* reconstructed: moderate confidence — appears in decorative contexts only */
}
```
---
## 3. Typography System
All type is composite. **NEVER** specify `font-size` or `font-weight` alone — always apply the full composite group.
### Font Stacks
```css
:root {
--google-font-sans: Arial, sans-serif; /* Body, UI, buttons */
--google-font-brand: "Google Sans", Arial, sans-serif; /* Headings, display */
--nkmQOe: Arial, sans-serif; /* Original var — body */
--LI4Bo: Arial, sans-serif; /* Original var — body alt */
--YfiKf: Arial, sans-serif; /* Original var — body alt */
--qBwyqb: Arial, sans-serif; /* Original var — body alt */
--lI1Trc: Arial, sans-serif; /* Original var — body alt */
--IT7y3c: Google Sans, Arial, sans-serif; /* Original var — display */
--Aoh9Xe: Google Sans, Arial, sans-serif; /* Original var — display alt */
--rTk0E: Google Sans, Arial, sans-serif; /* Original var — display alt */
--qR8X0: Google Sans, Arial, sans-serif; /* Original var — UI heading */
--Eou8Yc: Google Sans, Arial, sans-serif; /* Original var — UI heading alt */
--ztTqPe: Google Sans, Arial, sans-serif; /* Original var — display alt */
}
```
### Composite Type Scale
| Role | Token group | font-family | font-size | font-weight | line-height | letter-spacing |
|---|---|---|---|---|---|---|
| **Display / H1** | `--IT7y3c` group | `Google Sans, Arial` | `36px` (`--hPDMFf`) | `400` (`--Vyi8yc`) | `44px` (inferred) | `normal` |
| **Heading L / H2** | `--Aoh9Xe` group | `Google Sans, Arial` | `28px` (`--Ia70Xd`) | `400` (`--JT4IPc`) | `36px` (computed) | `normal` |
| **Heading M / H3** | `--rTk0E` group | `Google Sans, Arial` | `24px` (`--Ok3irf`) | `400` (`--Zf9Xnd`) | `32px` (inferred) | `normal` |
| **Heading S** | `--qR8X0` group | `Google Sans, Arial` | `20px` (`--CbdmE`) | `500` (`--Oycjt`) | `28px` (inferred) | `normal` |
| **Heading XS** | `--Eou8Yc` group | `Google Sans, Arial` | `26px` (`--qWZYEc`) | `400` (`--p2Lqxe`) | `32px` (inferred) | `normal` |
| **Title** | `--ztTqPe` group | `Google Sans, Arial` | `24px` (`--C74x2e`) | `400` (`--Yoa6qd`) | `32px` (inferred) | `normal` |
| **Body default** | `--nkmQOe` group | `Arial` | `16px` (`--z3BFIe`) | `400` (`--XWliwc`) | `24px` (`--a9X0Jc`) | `normal` |
| **Body S** | `--LI4Bo` group | `Arial` | `14px` (`--Mhs7de`) | `400` (`--ofwUBc`) | `normal` | `normal` |
| **Body XS** | `--qBwyqb` group | `Arial` | `12px` (`--cMTvjf`) | `500` (`--CUS15e`) | `normal` | `normal` |
| **UI / Button** | `--YfiKf` group | `Arial` | `16px` (`--punZlc`) | `400` (`--MzKBpd`) | `normal` | `normal` |
| **Label S** | `--lI1Trc` group | `Arial` | `14px` (`--ZNg88`) | `400` (`--qjVU3d`) | `normal` | `normal` |
| **Caption** | `--qBwyqb` group | `Arial` | `12px` | `400` | `normal` | `normal` |
### Weight Scale
```css
:root {
--XWliwc: 400; /* Regular — body, UI */
--ofwUBc: 400; /* Regular alias */
--MzKBpd: 400; /* Regular alias */
--JT4IPc: 400; /* Regular alias */
--Zf9Xnd: 400; /* Regular alias */
--Vyi8yc: 400; /* Regular alias */
--p2Lqxe: 400; /* Regular alias */
--Yoa6qd: 400; /* Regular alias */
--qjVU3d: 400; /* Regular alias */
--Oycjt: 500; /* Medium — small headings, labels */
--CUS15e: 500; /* Medium alias */
}
/* NOTE: Google Sans is loaded at weight 400–700 but the UI uses only 400 and 500. */
/* NEVER use font-weight 700 (bold) in UI components — not part of this design system. */
```
### Pairing Rules
- **Headings:** Always `"Google Sans", Arial, sans-serif` — never Arial alone for display type
- **Body / UI / Buttons:** Always `Arial, sans-serif` — never `"Google Sans"` for body text
- **Weight:** `400` for all display and body; `500` only for small UI labels and `--qR8X0` heading group
- **H1 computed:** `"Google Sans"`, `28px`, `400`, `36px` line-height, centered (`text-align: center`), `0 32px` padding
---
## 4. Spacing & Layout
```css
:root {
/* ── Spacing scale ── */
/* NOTE: Several values (14px, 18px, 22px) are off the 4px grid.
They exist in the extracted system. Use the nearest on-grid alias
for NEW components where possible. */
--gOs6Bf: 12px; /* --google-space-xs — tight padding, small chips */
--ZNg88: 14px; /* --google-space-sm — body text padding (off-grid; prefer 12px or 16px for new) */
--gyu5L: 16px; /* --google-space-md — standard component padding */
--uBdgTe: 18px; /* --google-space-lg — card padding (off-grid; prefer 16px or 20px for new) */
--CbdmE: 20px; /* --google-space-xl — section padding */
--qpdFp: 22px; /* --google-space-2xl — (off-grid; prefer 20px or 24px for new) */
--a9X0Jc: 24px; /* --google-space-3xl — line-height / large gap */
/* Aliases */
--google-space-xs: var(--gOs6Bf); /* 12px */
--google-space-sm: var(--ZNg88); /* 14px */
--google-space-md: var(--gyu5L); /* 16px */
--google-space-lg: var(--uBdgTe); /* 18px */
--google-space-xl: var(--CbdmE); /* 20px */
--google-space-2xl: var(--qpdFp); /* 22px */
--google-space-3xl: var(--a9X0Jc); /* 24px */
/* ── Radius scale ── */
--radius-sm: 8px; /* --google-radius-sm — inputs, chips, small cards */
--radius-md: 12px; /* --google-radius-md — medium cards */
--radius-lg: 20px; /* --google-radius-lg — large cards, action sheets */
--radius-full: 50px; /* --google-radius-full — pill buttons, icon buttons, search bar */
}
```
### Grid System & Breakpoints
| Breakpoint token | Width | Likely use |
|---|---|---|
| `xs-tight` | 98px | Minimum tested width |
| `xs` | 280px | Smallest phone |
| `sm` | 320px | Small phone |
| `sm-plus` | 360px | Standard phone |
| `md-minus` | 495–496px | Large phone / small tablet boundary |
| `md` | 569–576px | Tablet portrait |
| `md-plus` | 640px | Large tablet |
| `lg` | 890px | Desktop breakpoint |
| `xl` | 1200–1210px | Wide desktop |
| `2xl` | 1420px | Full-width desktop |
```css
/* Breakpoints (use in @media queries) */
@custom-media --bp-sm (min-width: 360px);
@custom-media --bp-md (min-width: 576px);
@custom-media --bp-lg (min-width: 890px);
@custom-media --bp-xl (min-width: 1200px);
@custom-media --bp-2xl (min-width: 1420px);
```
### Layout Rules
- **Navigation:** `display: flex; flex-direction: row; align-items: center; padding: 6px` — extracted from `role_navigation`
- **Modal/Dialog:** `display: none` by default; `padding: 14px`; `margin: auto` (centred)
- **Buttons:** `display: block` by default; flex layout applied by parent container
- **Grid:** Google.com uses a single-column centred layout on mobile, expanding to constrained-width centred content on desktop — not a multi-column editorial grid
- **Container:** Content centred, max-width constrained at `1200px` for most sections
---
## 5. Page Structure & Layout Patterns
> No screenshots available. Section derived from LAYOUT DIGEST, component inventory, and computed styles. Rows marked **(inferred)** are not visually confirmed.
### 5.1 Section Map
| Order | Section | Layout type | Approx. height | Key elements |
|---|---|---|---|---|
| 1 | **Top navigation bar** | `flex row, align:center` | ~60px | Logo (left), nav links (right), Sign in button (pill, accent blue) |
| 2 | **Search hero** | `block, centred` | ~300px (inferred) | Google logo (colourful), search bar (pill input, radius-full), search buttons (ghost, radius-full) |
| 3 | **Quick links / apps row** | `flex row, wrap` (inferred) | ~80px (inferred) | Icon grid — Gmail, Images, Maps etc. |
| 4 | **Footer** | `flex row, space-between` (inferred) | ~50px (inferred) | Country/language, About, Advertising, Privacy, Terms links |
### 5.2 Layout Patterns
**Navigation bar:**
- `display: flex; flex-direction: row; align-items: center; padding: 6px`
- Right-aligned controls: settings icon, apps grid icon, Sign in button
- Sign in button: `background: var(--google-accent)` (#0b57d0), `border-radius: var(--google-radius-full)` (50px), `color: #fff`, `font: Arial 14px 400`
- No border, no shadow on nav bar itself — pure white background
**Search input:**
- Full-width pill shape: `border-radius: var(--google-radius-full)` (50px)
- Border: `1px solid var(--google-border-default)` (#d2d2d2)
- Hover shadow: `box-shadow: 0 1px 6px rgba(32,33,36,0.28)` (inferred from Google's well-known pattern)
- `font-family: Arial; font-size: 16px; padding: 12px 20px`
**Footer links row (inferred):**
- `display: flex; flex-direction: row; flex-wrap: wrap; gap: 16px`
- `font: Arial 14px 400; color: var(--google-text-secondary)` (#474747)
- Hover: `text-decoration: underline`
### 5.3 Visual Hierarchy
- **Logo** dominates the hero — centred, maximum visual weight
- **Search bar** is second focus — centred, generous width, pill-shaped
- **Sign in button** is the sole CTA in the nav — right-aligned, accent blue (#0b57d0)
- **Navigation links** are subdued — `color: #1f1f1f`, no underline, small (14px)
- **Footer** is the lowest priority — secondary text colour (#474747), minimum spacing
- **Whitespace is the primary design tool** — sections breathe with generous vertical padding
### 5.4 Content Patterns
- **Centred, single-column hero:** Logo → Search bar → Search action buttons → stacked vertically with consistent gap (`24px`)
- **Icon row (app launcher):** Repeated icon + label units in a horizontal flex row; gap `12–16px`; icons tap to navigate
- **Nav pill button pattern:** All primary actions use `radius-full` pill shape + solid accent blue + white text — this is the signature CTA pattern
- **Links default to no-underline; underline appears on hover/active only** — matches extracted `a:hover { text-decoration: underline }`
---
## 6. Component Patterns
### 6.1 Search Input
**Anatomy:** Container (`role=search`) → Icon (left) → Text input → Clear button → Divider → Voice search icon → Camera icon
```tsx
// Search input with full state coverage
interface SearchInputProps {
value: string;
onChange: (v: string) => void;
disabled?: boolean;
}
export function SearchInput({ value, onChange, disabled }: SearchInputProps) {
const [focused, setFocused] = React.useState(false);
return (
<div
style={{
display: 'flex',
alignItems: 'center',
borderRadius: 'var(--radius-full)', /* 50px pill */
border: `1px solid ${focused ? 'transparent' : 'var(--google-border-default)'}`,
boxShadow: focused
? '0 1px 6px rgba(32,33,36,0.28)' /* focus elevation */
: '0 1px 3px rgba(32,33,36,0.12)', /* default subtle shadow */
backgroundColor: 'var(--google-bg-app)',
padding: '0 var(--google-space-md)', /* 0 16px */
height: '44px',
width: '100%',
maxWidth: '584px',
opacity: disabled ? 0.4 : 1,
pointerEvents: disabled ? 'none' : 'auto',
transition: `box-shadow var(--google-duration-fast) var(--google-ease-default)`,
}}
>
<input
type="text"
value={value}
onChange={e => onChange(e.target.value)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
disabled={disabled}
placeholder="Search Google or type a URL"
style={{
flex: 1,
border: 'none',
outline: 'none',
fontFamily: 'var(--google-font-sans)', /* Arial */
fontSize: '16px', /* --punZlc */
fontWeight: 400,
color: 'var(--google-text-emphasis)', /* #1f1f1f */
backgroundColor: 'transparent',
cursor: disabled ? 'default' : 'text',
}}
/>
</div>
);
}
```
**State table:**
| State | Border | Box-shadow | Opacity |
|---|---|---|---|
| Default | `1px solid #d2d2d2` | `0 1px 3px rgba(32,33,36,0.12)` | `1` |
| Hover | `1px solid transparent` | `0 1px 6px rgba(32,33,36,0.28)` | `1` |
| Focus | `1px solid transparent` | `0 1px 6px rgba(32,33,36,0.28)` | `1` |
| Disabled | `1px solid #d2d2d2` | none | `0.4` |
| Error | `1px solid #c5221f` (inferred) | none | `1` |
---
### 6.2 Primary Button (Sign In / CTA)
**Anatomy:** `<button>` → label text (no icon in primary variant)
```tsx
interface PrimaryButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
loading?: boolean;
}
export function PrimaryButton({ children, disabled, loading, ...props }: PrimaryButtonProps) {
return (
<button
{...props}
disabled={disabled || loading}
style={{
fontFamily: 'var(--google-font-sans)', /* Arial */
fontSize: '14px', /* --ZNg88 */
fontWeight: 400,
lineHeight: '20px',
color: '#fff', /* --google-text-on-accent */
backgroundColor: 'var(--google-accent)', /* #0b57d0 */
borderRadius: 'var(--google-radius-full)', /* 50px */
border: 'none',
padding: '10px var(--google-space-xl)', /* 10px 20px */
cursor: disabled || loading ? 'default' : 'pointer',
opacity: disabled ? 0.4 : 1,
filter: disabled ? 'grayscale(100%)' : 'none',
pointerEvents: disabled || loading ? 'none' : 'auto',
transition: `opacity var(--google-duration-fast) var(--google-ease-default)`,
whiteSpace: 'nowrap',
display: 'inline-flex',
alignItems: 'center',
gap: '8px',
}}
onMouseEnter={e => !disabled && !loading && (e.currentTarget.style.opacity = '0.9')}
onMouseLeave={e => (e.currentTarget.style.opacity = disabled ? '0.4' : '1')}
>
{loading ? <span className="spinner" aria-hidden="true" /> : null}
{children}
</button>
);
}
```
**State table:**
| State | Background | Opacity | Cursor | Filter |
|---|---|---|---|---|
| Default | `#0b57d0` | `1` | `pointer` | none |
| Hover | `#0b57d0` | `0.9` | `pointer` | none |
| Focus | `#0b57d0` | `1` | `pointer` | outline-offset 3px |
| Active | `#0b57d0` | `0.85` | `pointer` | none |
| Disabled | `#0b57d0` | `0.4` | `default` | `grayscale(100%)` |
| Loading | `#0b57d0` | `1` | `default` | none |
---
### 6.3 Ghost / Secondary Button
**Anatomy:** `<button>` → label text, thin border, transparent background
```tsx
export function GhostButton({ children, disabled, ...props }: PrimaryButtonProps) {
const [hovered, setHovered] = React.useState(false);
return (
<button
{...props}
disabled={disabled}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
fontFamily: 'var(--google-font-sans)',
fontSize: '14px',
fontWeight: 400,
color: 'var(--google-accent)', /* #0b57d0 */
backgroundColor: hovered
? 'var(--google-accent-hover)' /* #d3e3fd */
: 'transparent',
border: `1px solid var(--google-border-default)`, /* #d2d2d2 */
borderRadius: 'var(--google-radius-full)', /* 50px */
padding: '10px var(--google-space-xl)',
cursor: disabled ? 'default' : 'pointer',
opacity: disabled ? 0.4 : 1,
pointerEvents: disabled ? 'none' : 'auto',
transition: `background-color var(--google-duration-fast) var(--google-ease-default)`,
}}
>
{children}
</button>
);
}
```
| State | Background | Border | Text colour |
|---|---|---|---|
| Default | `transparent` | `1px solid #d2d2d2` | `#0b57d0` |
| Hover | `#d3e3fd` | `1px solid #d2d2d2` | `#0b57d0` |
| Focus | `#d3e3fd` | outline 3px offset | `#0b57d0` |
| Active | `rgba(0,0,0,0.1)` | `1px solid #d2d2d2` | `#0b57d0` |
| Disabled | `transparent` | `1px solid #d2d2d2` | `#0b57d0` (opacity 0.4) |
---
### 6.4 Icon Button
**Anatomy:** `<button role="button">` → icon SVG — no visible label
```tsx
export function IconButton({ icon, label, disabled, ...props }: {
icon: React.ReactNode;
label: string;
disabled?: boolean;
} & React.ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button
aria-label={label}
disabled={disabled}
{...props}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%', /* --google-btn-icon-radius */
padding: '8px', /* --google-btn-icon-padding */
border: 'none',
backgroundColor: 'transparent',
color: 'var(--google-text-emphasis)',
cursor: disabled ? 'default' : 'pointer',
opacity: disabled ? 0.4 : 1,
transition: `opacity var(--google-duration-fast) var(--google-ease-default)`,
}}
onMouseEnter={e => !disabled && (e.currentTarget.style.opacity = '0.9')}
onMouseLeave={e => (e.currentTarget.style.opacity = disabled ? '0.4' : '1')}
>
{icon}
</button>
);
}
```
---
### 6.5 Navigation Bar
**Anatomy:** `<nav role="navigation">` → `flex row` → logo (left) → spacer → nav links → icon buttons → Sign In button (right)
```tsx
export function NavBar() {
return (
<nav
role="navigation"
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
padding: '6px', /* role_navigation extracted */
backgroundColor: 'var(--google-bg-app)',
width: '100%',
boxSizing: 'border-box',
}}
>
{/* Nav links */}
<a
href="#"
style={{
fontFamily: 'var(--google-font-sans)',
fontSize: '14px',
fontWeight: 400,
color: 'var(--google-text-emphasis)', /* #1f1f1f */
textDecoration: 'none',
padding: '5px',
margin: '0 5px 0 15px',
display: 'block',
transition: `opacity var(--google-duration-fast) var(--google-ease-default)`,
}}
>
About
</a>
{/* Sign in — primary pill button */}
<PrimaryButton style={{ marginLeft: 'auto' }}>Sign in</PrimaryButton>
</nav>
);
}
```
| State | Text decoration | Opacity |
|---|---|---|
| Default | none | 1 |
| Hover | underline | 1 |
| Active | underline | 1 |
| Disabled | none | 0.4 |
---
### 6.6 Snackbar / Notification Dialog
**Anatomy:** `<div role="dialog">` → message text → close button — slides up from bottom
```tsx
export function Snackbar({ message, onClose, visible }: {
message: string;
onClose: () => void;
visible: boolean;
}) {
return (
<div
role="dialog"
aria-live="polite"
style={{
display: visible ? 'flex' : 'none',
alignItems: 'center',
fontFamily: '"Google Sans", Roboto, Arial, sans-serif', /* role_dialog extracted */
fontSize: '14px',
fontWeight: 400,
color: '#fff',
backgroundColor: 'var(--google-accent)', /* #0b57d0 */
padding: '18px 60px 18px 12px', /* role_dialog extracted */
borderRadius: 'var(--google-radius-lg)', /* 20px */
position: 'fixed',
bottom: '16px',
left: '50%',
transform: 'translateX(-50%)',
animation: visible
? 'g-snackbar-show var(--google-duration-fast) var(--google-ease-default)'
: 'g-snackbar-hide var(--google-duration-fast) var(--google-ease-default)',
zIndex: 1000,
}}
>
<span>{message}</span>
<button
onClick={onClose}
aria-label="Close"
style={{
position: 'absolute',
right: '12px',
background: 'none',
border: 'none',
color: '#fff',
cursor: 'pointer',
padding: '4px',
borderRadius: '50%',
}}
>
✕
</button>
</div>
);
}
```
---
## 7. Elevation & Depth
```css
:root {
/* Borders */
--google-border-default: 1px solid var(--gS5jXb); /* #d2d2d2 — inputs, cards */
--google-border-strong: 1px solid var(--Lm570b); /* #dee1e3 — stronger dividers */
--google-border-focus: 1px solid transparent; /* Removed on focus — shadow takes over */
/* Shadows — Google uses minimal shadow; depth via background tint preferred */
--google-shadow-none: none;
--google-shadow-xs: 0 1px 3px rgba(32, 33, 36, 0.12); /* Default surface shadow */
--google-shadow-sm: 0 1px 6px rgba(32, 33, 36, 0.28); /* Hovered search bar */
--google-shadow-md: 0px 0px 0px 1px rgba(0,0,0,0.04),
0px 4px 8px 0px rgba(0,0,0,0.20); /* Hovered icon button (extracted) */
--google-shadow-overlay: rgba(0,0,0,0.04) 0px 0px 0px 1px,
rgba(0,0,0,0.2) 0px 4px 8px 0px; /* .T9Wh5:hover — extracted */
/* Z-index scale */
--z-base: 0;
--z-elevated: 100; /* Cards, dropdowns */
--z-overlay: 500; /* Modals, dialogs */
--z-snackbar: 1000; /* Toasts, snackbars */
--z-tooltip: 1100; /* Tooltips */
/* Ripple / scrim */
--google-ripple: var(--D9aNi); /* rgba(31,31,31,0.08) */
}
```
**Layering principles:**
- **Prefer background-colour tint** over box-shadow for expressing hover state — `var(--aYn2S)` (#f3f5f6) on item hover
- **Shadow is reserved for floating elements** (search bar focus, dropdown panels, snackbars)
- **No shadow on navigation bar** — it sits flush against the page background
- **Ripple/press overlay** uses `rgba(31,31,31,0.08)` (`--D9aNi`) — not a separate shadow layer
---
## 8. Motion
```css
:root {
/* Timing */
--google-duration-fast: 0.3s; /* Links, opacity transitions — 2 extracted elements */
--google-duration-press: 0.4s; /* Button press animation (--e5KZJf:active) */
--google-ease-default: ease; /* All transitions — 130 extracted elements */
/* Transitions */
--google-transition-opacity: opacity var(--google-duration-fast) var(--google-ease-default);
--google-transition-bg: background-color var(--google-duration-fast) var(--google-ease-default);
--google-transition-shadow: box-shadow var(--google-duration-fast) var(--google-ease-default);
}
/* Snackbar slide animation */
@keyframes g-snackbar-show {
0% { pointer-events: none; transform: translateY(0px); }
100% { transform: translateY(-100%); }
}
@keyframes g-snackbar-hide {
0% { transform: translateY(-100%); }
100% { transform: translateY(0px); }
}
@keyframes g-snackbar-show-content {
0% { opacity: 0; }
}
@keyframes g-snackbar-hide-content {
100% { opacity: 0; }
}
/* Button press morphing animations */
@keyframes shape-tween-down {
50% { transform: scaleX(0.9) translateY(8%); }
100% { transform: none; }
}
@keyframes shape-tween-left {
50% { transform: scaleY(0.9) translateX(-8%); }
100% { transform: none; }
}
@keyframes shape-tween-right {
50% { transform: scaleY(0.9) translateX(8%); }
100% { transform: none; }
}
@keyframes shape-tween-up {
50% { transform: scaleX(0.9) translateY(-8%); }
100% { transform: none; }
}
/* Loading spinner (search) */
@keyframes qli-container-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
```
**When to animate:**
- **Always animate:** opacity transitions on hover/hide (0.3s ease), shadow on search bar focus (0.3s ease)
- **Always animate:** snackbar show/hide with `g-snackbar-show` / `g-snackbar-hide`
- **Animate on press:** button morphing (`shape-tween-*`) at 0.4s for tactile feedback
- **NEVER animate:** layout shifts, colour changes on navigation links (these are instant)
- **Respect `prefers-reduced-motion`:** Disable all `transform` and `opacity` animations when the user has motion-reduction enabled
---
## 9. Anti-Patterns & Constraints
1. **Using Inter, Roboto, or system-ui as the font → Why it fails:** AI agents default to Inter or system-ui when no font is specified. Google.com explicitly loads `"Google Sans"` (via gstatic CDN) and `Arial` as the two-font system. Defaulting to Inter introduces a font that is not part of this system and breaks brand fidelity. **What to do instead:** Always declare `font-family: 'var(--google-font-brand)'` for headings and `font-family: var(--google-font-sans)` for body/UI — never any other value.
2. **Hardcoding `#0b57d0` instead of using the CSS variable → Why it fails:** The site defines this colour through 13+ aliases (`--TMYS9`, `--TyVYld`, `--rrJJUc`, etc.). Hardcoding the hex bypasses the token system, meaning future theme changes won't propagate, and the colour intent is lost. **What to do instead:** Always reference `var(--google-accent)` or the original alias `var(--TMYS9)`. Never write the raw hex `#0b57d0` in component styles.
3. **Using `border-radius` values outside the four-value scale → Why it fails:** AI agents often apply `4px` or `6px` for "small" radius, or `16px` for cards. Google's design system uses exactly `8px`, `12px`, `20px`, and `50px`. Any other value breaks the visual rhythm. The search bar and CTA buttons are specifically pill-shaped (`50px`). **What to do instead:** Only use `var(--radius-sm)` (8px), `var(--radius-md)` (12px), `var(--radius-lg)` (20px), or `var(--radius-full)` (50px).
4. **Applying warm brand yellow (`#fbbc04`) to interactive controls → Why it fails:** AI agents may use `--google-accent-warm` as an "accent" on hover states or badges because it's a saturated colour. In this design system, yellow is strictly a brand/decorative colour used only in logo and illustrative contexts. Applying it to buttons, links, or focus rings creates an accessibility failure and breaks the blue-only action language. **What to do instead:** Use `var(--google-accent)` (#0b57d0) for all interactive colour. Reserve `var(--google-accent-warm)` / `var(--ywz01c)` for logo/illustration only.
5. **Using arbitrary spacing values not on the extracted scale → Why it fails:** AI agents default to arbitrary values like `10px`, `15px`, `25px`, `32px`. The extracted system defines `12px`, `14px`, `16px`, `18px`, `20px`, `22px`, `24px`. Arbitrary values break the spacing rhythm and misalign with the existing component grid. **What to do instead:** Always pick the nearest value from `var(--gOs6Bf)` through `var(--a9X0Jc)`. Note the off-grid values (14px, 18px, 22px) exist in the extracted system but should not be extended to new components.
6. **Omitting hover, focus, and disabled states on interactive elements → Why it fails:** The extraction confirms that Google implements detailed state styles: opacity-based hover, `grayscale(100%)` on disabled, and `text-decoration: underline` on link hover. AI agents generating "quick" components skip these and produce inaccessible elements that break WCAG 2.1 AA requirements. **What to do instead:** Every button, link, and input must implement all states listed in the component state tables above. Minimum: hover (opacity or background tint), focus (visible outline or shadow), disabled (opacity 0.4 + grayscale + pointer-events none).
7. **Using `rgba(0,0,0,0.6)` directly instead of the token → Why it fails:** This precise value is Google's body text colour (`--gVUKcd`). Hardcoding it means the soft-black intent is invisible to the token system, and agents may independently "fix" it to `#000` or `#333` when modifying components. **What to do instead:** Always reference `var(--gVUKcd)` or `var(--google-text-primary)`.
8. **Constructing dynamic Tailwind class names with string interpolation → Why it fails:** `className={\`bg-[\${color}]\`}` causes Tailwind's build-time scanner to miss the class, producing zero CSS output. The component silently renders with no style. **What to do instead:** Use CSS custom properties directly in `style={{}}` props or define all dynamic styles as static class variants. Prefer `style={{ backgroundColor: 'var(--google-accent)' }}` over dynamic Tailwind colour classes.
9. **Using `font-weight: 700` (bold) for headings → Why it fails:** Google Sans is loaded with `weight: 400 700` but the UI consistently uses weight `400` for all headings and display text, and `500` only for specific small UI labels. Agents defaulting to `font-weight: bold` produce headings that look over-emphasised and inconsistent with the design system's intentionally light typographic weight. **What to do instead:** Use `font-weight: 400` for all headings and display text. Use `font-weight: 500` only for `--qR8X0` label groups.
10. **Using `position: absolute` for layout alignment → Why it fails:** Navigation bar, icon grids, and button groups are all `display: flex; flex-direction: row; align-items: center`. Agents sometimes use absolute positioning to "right-align" elements in a nav, which breaks responsive reflow and creates overlap at narrow viewports. **What to do instead:** Use `margin-left: auto` within a flex container to push elements to the trailing edge, as seen in the nav pattern.
11. **Applying `!important` to override extracted token values → Why it fails:** Many of Google's custom properties are already computed as fallback chains (e.g. `--nwXobb: var(--vZe0jb)`). Adding `!important` breaks the cascade in unexpected ways and prevents the design system's variable chain from updating. **What to do instead:** Override at the component scope by reassigning the CSS variable, not by forcing specificity: `:root .my-component { --google-accent: #newvalue; }`.
12. **Using light-blue tints (`#a3c9ff`, `#d3e3fd`) as primary surface colours → Why it fails:** Agents may reach for these saturated blue tints (detected in the extraction) as "brand" backgrounds for cards or sections. These are hover-state tints and illustrative accent colours only. Using them as surfaces creates visual overload and breaks the minimal white/near-white surface system. **What to do instead:** Use `var(--google-bg-app)` (#fff) or `var(--google-bg-surface)` (#f7f8f9) for all surfaces. Apply `var(--google-accent-hover)` (#d3e3fd) exclusively as a hover-state background.
---
## 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 (82) */
--COEmY: #1f1f1f;
--EpFNW: #fff;
--vZe0jb: #a3c9ff;
--xPpiM: #001d35;
--uLz37c: #545d7e;
--TMYS9: #0b57d0;
--DEeStf: #f5f8ff;
--TSWZIb: #e5edff;
--BRLwE: #d3e3fd;
--JclFj: #d2d2d2;
--IXoxUe: #636363;
--bbQxAb: #474747;
--JKqx2: #1a0dab;
--vdwxpe: #a8c7fa;
--ywz01c: #fbbc04;
--aYn2S: #f3f5f6;
--Lm570b: #dee1e3;
--gVUKcd: rgba(0,0,0,0.6);
--yTtsEf: #072711;
--YaIeMb: #f7f8f9;
--Ijc2tf: #ebf1ff;
--qdHZF: rgba(0,0,0,.6);
--MV2sG: #f1f3f4;
--D9aNi: rgba(31,31,31,0.08);
--brand-primary-cta: rgb(11, 87, 208); /* Primary CTA background, dominant on 1 button — e.g. "Anmelden" /* mined from computed styles */ */
--google-accent: rgb(11, 87, 208);
--google-bg-app: #fff;
--google-bg-surface: #f7f8f9;
--google-text-primary: rgba(0,0,0,0.6);
--google-text-secondary: #474747;
--google-accent-hover: #d3e3fd;
--google-accent-warm: #fbbc04;
--TyVYld: #0b57d0;
--rrJJUc: #0b57d0;
--Pa8Wlb: #0b57d0;
--lv6yU: #0b57d0;
--Wmwad: #0b57d0;
--fuvksc: #0b57d0;
--gJjJk: #0b57d0;
--p9J9c: #0b57d0;
--MoaHse: #0b57d0;
--izGsqb: #0b57d0;
--Nsm0ce: #0b57d0;
--ZEpPmd: #a3c9ff;
--KIZPne: #a3c9ff;
--VuZXBd: #001d35;
--jINu6c: #001d35;
--VeLyLc: #001d35;
--NoaQW: #001d35;
--y01ou: #001d35;
--xhUGwc: #fff;
--ZXiaGf: #fff;
--EoKObe: #fff;
--cIehld: #fff;
--todMNc: #fff;
--XKMDxc: #f3f5f6;
--gS5jXb: #d2d2d2;
--mXZkqc: #d2d2d2;
--ONhrGd: #d2d2d2;
--Yi4Nb: #d2d2d2;
--kloG3: #d2d2d2;
--pEa0Bc: #474747;
--Xqboce: #1f1f1f;
--YLNNHc: #1f1f1f;
--PBxOKe: #1f1f1f;
--google-bg-hover: var(--aYn2S);
--google-bg-overlay: var(--D9aNi);
--google-border-default: 1px solid var(--gS5jXb);
--google-border-strong: 1px solid var(--Lm570b);
--google-input-bg: var(--google-bg-app);
--google-input-border: var(--google-border-default);
--google-btn-primary-bg: var(--google-accent);
--google-btn-ghost-bg: transparent;
--google-btn-ghost-border: var(--google-border-default);
--google-btn-ghost-hover-bg: var(--google-accent-hover);
--google-nav-bg: var(--google-bg-app);
--google-snackbar-bg: var(--google-accent);
--google-border-focus: 1px solid transparent;
--google-transition-bg: background-color var(--google-duration-fast) var(--google-ease-default);
--brand-primary-cta: rgb(11, 87, 208);
--google-accent-cool: #0b57d0;
--google-success: #072711;
/* Typography (12) */
--LI4Bo: Arial,sans-serif;
--rTk0E: Google Sans,Arial,sans-serif;
--google-font-sans: Arial, sans-serif;
--google-font-brand: "Google Sans", Arial, sans-serif;
--google-text-emphasis: var(--COEmY);
--google-text-placeholder: var(--IXoxUe);
--google-text-on-accent: var(--EpFNW);
--google-input-text: var(--google-text-emphasis);
--google-btn-primary-text: var(--google-text-on-accent);
--google-btn-ghost-text: var(--google-accent);
--google-nav-text: var(--google-text-emphasis);
--google-snackbar-text: var(--google-text-on-accent);
/* Spacing (31) */
--a9X0Jc: 24px;
--gyu5L: 16px;
--qpdFp: 22px;
--ZNg88: 14px;
--gOs6Bf: 12px;
--hPDMFf: 36px;
--qYGXw: 28px;
--uBdgTe: 18px;
--CbdmE: 20px;
--qWZYEc: 26px;
--google-space-xs: 12px;
--google-space-sm: 14px;
--google-space-md: 16px;
--google-space-lg: 18px;
--google-space-xl: 20px;
--google-space-2xl: 22px;
--google-space-3xl: 24px;
--google-btn-icon-padding: 8px;
--google-nav-padding: 6px;
--google-snackbar-padding: 18px 60px 18px 12px;
--Ia70Xd: 28px;
--Ok3irf: 24px;
--C74x2e: 24px;
--GiToJf: 20px;
--z3BFIe: 16px;
--punZlc: 16px;
--nUOqEc: 16px;
--HIyEKc: 18px;
--hbmnje: 14px;
--Mhs7de: 14px;
--cMTvjf: 12px;
/* Radius (16) */
--radius-sm: 8px; /* 8 elements — e.g. input .gNO89b, input .gNO89b, input /* mined from computed styles */ */
--radius-md: 12px; /* 1 element — e.g. button .XOUhue ".O35uA{width:28px;he" /* mined from computed styles */ */
--radius-lg: 20px; /* 1 element — e.g. button .p7eiqf "Link kopierenLink ko" /* mined from computed styles */ */
--radius-full: 50px; /* 5 elements — e.g. button .oIdq8b, button .oIdq8b, button .oIdq8b /* mined from computed styles */ */
--google-radius-sm: 8px;
--google-radius-md: 12px;
--google-radius-lg: 20px;
--google-radius-full: 50px;
--google-input-radius: var(--google-radius-full);
--google-btn-primary-radius: var(--google-radius-full);
--google-btn-ghost-radius: var(--google-radius-full);
--google-btn-icon-radius: 50%;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 20px;
--radius-full: 50px;
/* Effects (20) */
--nwXobb: var(--vZe0jb);
--QWaaaf: var(--ZEpPmd);
--Aqn7xd: var(--gS5jXb);
--amnPwe: var(--IXoxUe);
--jwFC2e: var(--BRLwE);
--XWliwc: 400;
--Oycjt: 500;
--Ehh4mf: var(--Nsm0ce);
--ngPXTb: var(--rrJJUc);
--hwEqod: var(--jINu6c);
--V9ysqf: var(--EoKObe);
--bcxd3b: var(--YLNNHc);
--google-btn-primary-hover-opacity: 0.9;
--google-shadow-none: none;
--google-shadow-xs: 0 1px 3px rgba(32,33,36,0.12);
--google-shadow-sm: 0 1px 6px rgba(32,33,36,0.28);
--google-shadow-md: 0px 0px 0px 1px rgba(0,0,0,0.04), 0px 4px 8px 0px rgba(0,0,0,0.20);
--google-shadow-overlay: rgba(0,0,0,0.04) 0px 0px 0px 1px,
rgba(0,0,0,0.2) 0px 4px 8px 0px;
--google-transition-opacity: opacity var(--google-duration-fast) var(--google-ease-default);
--google-transition-shadow: box-shadow var(--google-duration-fast) var(--google-ease-default);
/* Motion (26) */
----motion-g-snackbar-show: @keyframes g-snackbar-show {
0% { pointer-events: none; transform: translateY(0px); }
100% { transform: translateY(-100%); }
}; /* @keyframes g-snackbar-show */
----motion-g-snackbar-hide: @keyframes g-snackbar-hide {
0% { transform: translateY(-100%); }
100% { transform: translateY(0px); }
}; /* @keyframes g-snackbar-hide */
----motion-g-snackbar-show-content: @keyframes g-snackbar-show-content {
0% { opacity: 0; }
}; /* @keyframes g-snackbar-show-content */
----motion-g-snackbar-hide-content: @keyframes g-snackbar-hide-content {
100% { opacity: 0; }
}; /* @keyframes g-snackbar-hide-content */
----motion-shift: @keyframes shift {
25% { background: rgb(31, 31, 31); }
}; /* @keyframes shift */
----motion-shape-tween-down: @keyframes shape-tween-down {
50% { transform: scaleX(0.9) translateY(8%); }
100% { transform: none; }
}; /* @keyframes shape-tween-down */
----motion-shape-tween-left: @keyframes shape-tween-left {
50% { transform: scaleY(0.9) translateX(-8%); }
100% { transform: none; }
}; /* @keyframes shape-tween-left */
----motion-shape-tween-right: @keyframes shape-tween-right {
50% { transform: scaleY(0.9) translateX(8%); }
100% { transform: none; }
}; /* @keyframes shape-tween-right */
----motion-shape-tween-up: @keyframes shape-tween-up {
50% { transform: scaleX(0.9) translateY(-8%); }
100% { transform: none; }
}; /* @keyframes shape-tween-up */
----motion-g-bubble-show: @keyframes g-bubble-show {
0% { opacity: 0; }
100% { opacity: 1; }
}; /* @keyframes g-bubble-show */
----motion-g-bubble-hide: @keyframes g-bubble-hide {
0% { opacity: 1; }
100% { opacity: 0; }
}; /* @keyframes g-bubble-hide */
----motion-qli-container-rotate: @keyframes qli-container-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(1turn); }
}; /* @keyframes qli-container-rotate */
----motion-qli-fill-unfill-rotate: @keyframes qli-fill-unfill-rotate {
0% { transform: rotate(0deg); }
12.5% {… <0.4KB elided>; /* @keyframes qli-fill-unfill-rotate */
----motion-qli-blue-fade-in-out: @keyframes qli-blue-fade-in-out {
0% { opacity: 0.99; }
25% { opacity: 0.99; }
26% { opacity: 0; }
89% { opacity: 0; }
90% { opacity: 0.99; }
100% { opacity: 0.99; }
}; /* @keyframes qli-blue-fade-in-out */
----motion-qli-red-fade-in-out: @keyframes qli-red-fade-in-out {
0% { opacity: 0; }
15% { opacity: 0; }
25% { opacity: 0.99; }
50% { opacity: 0.99; }
51% { opacity: 0; }
}; /* @keyframes qli-red-fade-in-out */
----motion-qli-yellow-fade-in-out: @keyframes qli-yellow-fade-in-out {
0% { opacity: 0; }
40% { opacity: 0; }
50% { opacity: 0.99; }
75% { opacity: 0.99; }
76% { opacity: 0; }
}; /* @keyframes qli-yellow-fade-in-out */
----motion-qli-green-fade-in-out: @keyframes qli-green-fade-in-out {
0% { opacity: 0; }
65% { opacity: 0; }
75% { opacity: 0.99; }
90% { opacity: 0.99; }
100% { opacity: 0; }
}; /* @keyframes qli-green-fade-in-out */
----motion-qli-left-spin: @keyframes qli-left-spin {
0% { transform: rotate(130deg); }
50% { transform: rotate(-5deg); }
100% { transform: rotate(130deg); }
}; /* @keyframes qli-left-spin */
----motion-qli-right-spin: @keyframes qli-right-spin {
0% { transform: rotate(-130deg); }
50% { transform: rotate(5deg); }
100% { transform: rotate(-130deg); }
}; /* @keyframes qli-right-spin */
----motion-usernameShimmer: @keyframes usernameShimmer {
0% { background-position: 50% 0%; }
100% { background-position: 50% 100%; }
}; /* @keyframes usernameShimmer */
----motion-allow-alert: @-webkit-keyframes allow-alert {
0% { opacity: 1; }
100% { opacity: 0.35; }
}; /* @keyframes allow-alert */
----motion-rotate: @keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}; /* @keyframes rotate */
----motion-fillunfill: @keyframes fillunfill {
0% { stroke-dashoffset: 58.8; }
50% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: -58.4; }
}; /* @keyframes fillunfill */
----motion-rot: @keyframes rot {
0% { transform: rotate(0deg); }
100% { transform: rotate(-360deg); }
}; /* @keyframes rot */
--duration-fast: 0.3s; /* 2 elements — e.g. a, a /* mined from computed styles */ */
--ease-default: ease; /* 130 elements — e.g. button, button, button /* mined from computed styles */ */
```
## Appendix B: Token Source Metadata
```yaml
tokenSource: extracted-css-vars
confidence: high
cssVarsFound: 115
curatedTokensMapped: 23
kitPrefix: google
extractionNotes:
- Token names are obfuscated (Google's CSS minification/hashing):
e.g. --TMYS9, --BRLwE, --gVUKcd — these are NOT human-readable semantic names
- Curated --google-* aliases added as a semantic layer on top of original names
- Original CSS variable names PRESERVED exactly as extracted
- Do not rename originals — reference via var(--TMYS9) etc. in production code
colourClustering:
method: hue-family grouping + alias-chain tracing
blueCluster: 13 aliases for #0b57d0 (all → --TMYS9 / --google-accent)
navyCluster: 6 aliases for #001d35 (all → --jINu6c / --google-brand-navy)
greyCluster: 6 aliases for #d2d2d2 (all → --gS5jXb / --google-border-default)
whiteCluster: 7 aliases for #fff (all → --EpFNW / --google-bg-app)
nearBlackCluster: 5 aliases for #1f1f1f (all → --COEmY / --google-text-emphasis)
note: >
18 near-duplicate colour pairs identified. Merged into canonical tokens.
Do not introduce new aliases without consolidating first.
spacingNotes:
baseGrid: 4px (inferred)
offGridValues: [14px, 18px, 22px]
note: >
Off-grid values exist in the extracted system (from actual Google CSS).
Do not extend these to new components — use nearest 4px-grid value instead.
fontNotes:
primaryFont: "Google Sans" (loaded via fonts.gstatic.com, weight 400–700)
fallbackFont: "Arial" (system, weight 400)
note: >
Google Sans is display/brand only. Arial is used for all body and UI text.
Weight 700 is loaded but not used in the UI token system.
radiusNotes:
distinctValues: [8px, 12px, 20px, 50px]
pillButtons: confirmed (border-radius: 50px on 5+ button elements)
source: border-radius census from computed styles
confidence: high
figmaNotes: N/A — not a Figma extraction
bootstrapDetected: true
note: >
Bootstrap detected as a dependency but Google.com's component styles
override Bootstrap defaults via CSS custom properties. Do not assume
Bootstrap utility classes are available or consistent with this token system.
```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