Revolut
MIT
Modern fintech design system built on Next.js with carefully orchestrated typography, spacing, and motion tokens optimised for responsive financial products
Layout StudioImport this kit into a Studio project and start editing.
CLI installRun it in any project. No account needed.
npx @layoutdesign/context install revolut# layout.md — Revolut Design System
---
## 0. Quick Reference
**Stack:** Next.js · CSS custom properties (135 vars extracted, high confidence) · Custom fonts: Aeonik Pro (marketing) + Inter (UI)
**Token source:** `extracted-css-vars` — use original `--rui-*` names exactly as written.
**How to apply:** Use as `var(--rui-font-brand)` in CSS, `style={{ fontFamily: 'var(--rui-font-brand)' }}` in JSX, or `font-[family-name:var(--rui-font-brand)]` in Tailwind.
```css
/* ── Core Tokens ── */
--rui-font-marketing: "Aeonik Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--rui-font-brand: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
/* Typography scale */
--rui-font-size-marketing-display1: 3.5rem; /* Hero headlines */
--rui-font-size-heading1: 2rem; /* Section titles */
--rui-font-size-body1: 1rem; /* Body copy */
--rui-font-size-body2: 0.875rem; /* Secondary text */
/* Spacing scale */
--rui-space-s8: 0.5rem; --rui-space-s16: 1rem;
--rui-space-s24: 1.5rem; --rui-space-s32: 2rem;
--rui-space-s48: 3rem; --rui-space-s64: 4rem;
/* Radius */
--rui-radius-r8: 0.5rem; /* Cards, inputs */
--rui-radius-r16: 1rem; /* Widgets */
--rui-radius-r24: 1.5rem; /* Popups, modals */
--rui-radius-round: 9999px; /* Pills, tabs, avatars */
/* Motion */
--rui-duration-sm: 200ms;
--rui-duration-md: 300ms;
--rui-easing-default: cubic-bezier(0.15, 0.5, 0.5, 1);
--rui-timing-sm: var(--rui-duration-sm) var(--rui-easing-default);
/* Elevation */
--rui-shadow-level1: 0 0.125rem 0.1875rem rgb(var(--rui-color-channel-black)/0.05);
--rui-shadow-level2: 0 0.1875rem 0.5rem rgb(var(--rui-color-channel-black)/0.1);
/* Focus */
--rui-focus-outline: 0.125rem solid rgb(var(--rui-color-channel-accent)/0.5);
--rui-focus-outline-offset: 0.125rem;
/* Layout */
--rui-size-layout: 94.375rem; /* Max container width ~1510px */
```
```tsx
// Primary button — correct token usage with all states
const PrimaryButton = ({ children, disabled, loading }: ButtonProps) => (
<button
disabled={disabled || loading}
aria-disabled={disabled || loading}
style={{
fontFamily: 'var(--rui-font-brand)',
fontSize: 'var(--rui-font-size-emphasis1)',
fontWeight: 500,
height: 'var(--rui-size-button-md)', // 3.25rem
borderRadius: 'var(--rui-radius-round)', // pill shape
padding: '0 var(--rui-space-s24)',
transition: 'var(--rui-timing-sm)',
}}
>
{children}
</button>
);
```
**NEVER rules:**
- **NEVER** use `border-radius: 8px` on buttons — Revolut primary buttons use `--rui-radius-round` (9999px pill shape)
- **NEVER** use Inter for marketing headlines — use `--rui-font-marketing` ("Aeonik Pro") at weight 900
- **NEVER** hardcode `#8d969e` or any colour literal — all colours must reference `--rui-color-*` tokens
- **NEVER** use spacing values not on the `--rui-space-s*` scale (multiples: 2,4,6,8,12,16,20,24,32,40,48,56,64)
- **NEVER** omit `pointer-events: none` on disabled buttons — Revolut uses this pattern explicitly
- **NEVER** use `transition: all` — scope transitions to specific properties using `--rui-timing-sm/md/lg`
- **NEVER** use font-weight 600 (semibold) for display text — Aeonik Pro display uses weight 900; Inter UI labels use 400/500/700
<!-- Quick Reference truncated to fit the 75-line cap. See later sections for the full design system. -->
## 1. Design Direction & Philosophy
### Character & Aesthetic Intent
Revolut is a **premium fintech brand** projecting confidence, modernity, and global scale. The visual language is clean, high-contrast, and bold — built around oversized Aeonik Pro headlines, precise spacing, and a near-monochromatic palette punctuated by specific accent colours.
The design is **unapologetically dark-on-hero**: the homepage deploys white text on dark/rich backgrounds for top-of-funnel moments, then pivots to a light-surface UI for feature detail sections. This contrast creates rhythm and signals transition from "brand statement" to "product utility."
### Personality
- **Bold but not aggressive** — massive type at 89px for the hero, but tightly tracked and precisely spaced
- **Premium and controlled** — no decorative flourishes, no gradients for their own sake
- **Motion-aware but restrained** — every transition has an assigned duration and easing; `transition: all` is a bug, not a feature
### What This Design Explicitly Rejects
- Rounded cards with >24px radius (the design system caps at `--rui-radius-r32`; most UI uses r16/r24)
- Warm colour palettes — the brand is cool grey (#8d969e), near-black (#191c1f), and white
- Decorative serifs or expressive typefaces — only "Aeonik Pro" (marketing) and "Inter" (UI)
- Generic system spacing (e.g. Tailwind default 4/8/12…) — use the `--rui-space-s*` scale exclusively
- Flat, shadowless UI — elevation is intentional and tiered across four shadow levels
- Heavy borders — focus rings use 0.125rem; dividers are implied by spacing and shadow, not thick lines
---
## 2. Colour System
### Tier 1 — Primitive Channels
Revolut uses an `rgb()` channel system for compositing. Primitive channels are defined as raw RGB triplets referenced with `/opacity` syntax. The channel names below are referenced throughout the codebase but the raw values are not fully exposed in the extracted CSS vars; they are documented here from computed styles.
```css
/* ── Primitive Colour Channels (reconstructed from computed styles: moderate confidence) ── */
:root {
/* These are referenced as rgb(var(--rui-color-channel-*)/opacity) */
/* --rui-color-channel-black → 0 0 0 (confirmed via shadow tokens) */
/* --rui-color-channel-white → 255 255 255 (confirmed via bWhzwu hover) */
/* --rui-color-channel-blue → inferred ~0 122 255 (used in focus ring) */
/* --rui-color-channel-accent → inferred ~0 122 255 (used in --rui-focus-outline) */
/* --rui-color-channel-action-background-neutral → inferred, used in disabled states */
}
```
### Tier 2 — Semantic Aliases
```css
/* ── Semantic Colour Tokens (extracted + reconstructed) ── */
:root {
/* Surfaces */
--rui-color-background: rgb(255, 255, 255); /* Page/component background (light mode) */
--rui-color-foreground: rgb(25, 28, 31); /* Primary foreground / near-black text */
/* Text */
/* extracted: high confidence — from h2 and modal computed styles */
--color-text-primary: rgb(25, 28, 31); /* Primary text on light surfaces */
--color-text-secondary: rgb(80, 90, 99); /* Secondary body text, captions */
--color-text-inverse: rgb(255, 255, 255); /* Text on dark/hero backgrounds */
/* Brand / CTA */
/* reconstructed: moderate confidence — brand-primary-cta mined from tab bg */
--rui-color-grey-tone-2: rgb(141, 150, 158); /* Tab backgrounds, neutral fills */
/* Semantic state colours (referenced in interactive states) */
--rui-color-grey-50: [TBD - extract manually]; /* Disabled text colour */
--rui-color-disabled: [TBD - extract manually]; /* Disabled overlay / state layer */
/* Focus */
--rui-focus-outline: 0.125rem solid rgb(var(--rui-color-channel-accent)/0.5);
--rui-focus-outline-offset: 0.125rem;
/* Action backgrounds */
--rui-color-action-background-neutral: [TBD - extract manually]; /* DropZone focus bg */
/* Modal overlay */
--color-modal-overlay: rgba(0, 0, 0, 0.4); /* Confirmed from modal computed style */
}
```
### Tier 3 — Component Tokens
```css
/* ── Component Colour Applications ── */
:root {
/* State overlay system (Revolut uses CSS var injection on :hover/:active) */
--rui-color-state-overlay: transparent; /* Default; overridden on interaction */
/* hover → rgb(var(--rui-color-channel-black) / 0.08) on filled buttons */
/* active → rgb(var(--rui-color-channel-black) / 0.15) on filled buttons */
/* hover → rgb(var(--rui-color-channel-white) / 0.05) on dark/inverted buttons */
/* active → rgb(var(--rui-color-channel-white) / 0.08) on dark/inverted buttons */
/* Button disabled opacity */
/* disabled children → opacity: 0.7 (via > * selector) */
/* state layer on disabled → opacity: 0.6 */
}
```
### Colour Census Summary
| Role | Value | Source |
|------|-------|--------|
| Near-black (text/fg) | `rgb(25, 28, 31)` | Computed h2, modal — **high confidence** |
| Secondary text | `rgb(80, 90, 99)` | Computed body, button — **high confidence** |
| White (inverse text) | `rgb(255, 255, 255)` | Computed h1, link — **high confidence** |
| Neutral grey fill | `rgb(141, 150, 158)` | Tab bg, brand-primary-cta — **medium confidence** |
| Modal overlay | `rgba(0, 0, 0, 0.4)` | Computed modal — **high confidence** |
| Focus ring | `rgb(channel-blue / 0.5)` | `--rui-focus-outline` — **high confidence** |
---
## 3. Typography System
### Font Families
```css
:root {
--rui-font-default: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Helvetica Neue", Helvetica, Arial, Arimo, sans-serif;
--rui-font-system: var(--rui-font-default);
--rui-font-brand: "Inter", var(--rui-font-system); /* UI, app, body copy */
--rui-font-marketing:"Aeonik Pro", var(--rui-font-system); /* Hero, display, marketing sections */
--rui-font-emoji: "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
}
```
**Rule:** Use `--rui-font-marketing` for all h1/h2 display text on marketing pages. Use `--rui-font-brand` for all UI components, forms, nav, and body text.
### Composite Type Scale
| Token Group | font-family | font-size | font-weight | line-height | letter-spacing | Usage |
|---|---|---|---|---|---|---|
| `marketing-display1` | `--rui-font-marketing` | `3.5rem` | `900` | `1` (tight) | `calc(0.005em / 56)` | Hero section primary headline |
| `marketing-display2` | `--rui-font-marketing` | `2.5rem` | `900` | `1` | `calc(0.005em / 40)` | Sub-hero marketing headline |
| `marketing-display3` | `--rui-font-marketing` | `2rem` | `900` | `1` | `calc(0.005em / 32)` | Section intro statements |
| `display1` | `--rui-font-marketing` | `3.5rem` | `700` | `1.182` | `calc(-0.56em / 56)` | Large product headlines |
| `display2` | `--rui-font-marketing` | `2.75rem` | `700` | `1` | `calc(-0.56em / 44)` | Product feature titles |
| `heading1` | `--rui-font-marketing` | `2rem` | `700` | `1.1875` | `calc(-0.35em / 32)` | Page section headings |
| `heading2` | `--rui-font-marketing` | `1.5rem` | `700` | `1.25` | `calc(-0.31em / 24)` | Card/panel headings |
| `heading3` | `--rui-font-marketing` | `1.25rem` | `700` | `1.4` | `calc(-0.27em / 20)` | Sub-section headings |
| `emphasis1` | `--rui-font-brand` | `1rem` | `500` | `1.375` | `calc(-0.18em / 16)` | Labels, emphasized UI text |
| `emphasis2` | `--rui-font-brand` | `0.875rem` | `500` | `1.429` | `calc(-0.1em / 14)` | Secondary labels |
| `emphasis3` | `--rui-font-brand` | `0.75rem` | `500` | `1.5` | `0` | Tertiary labels, badges |
| `emphasis4` | `--rui-font-brand` | `0.6875rem` | `500` | `1.273` | `calc(0.16em / 11)` | Micro labels, legal |
| `body1` | `--rui-font-brand` | `1rem` | `400` | `1.375` | `calc(-0.18em / 16)` | Standard body copy |
| `body2` | `--rui-font-brand` | `0.875rem` | `400` | `1.429` | `calc(-0.1em / 14)` | Secondary body, descriptions |
| `body3` | `--rui-font-brand` | `0.75rem` | `400` | `1.5` | `0` | Fine print, captions |
### Real-world Computed Values (verified)
```css
/* ── Verified from page elements ── */
/* h1 "Banking & Beyond" — extracted: high confidence */
h1 {
font-family: "Aeonik Pro", sans-serif;
font-size: 89.216px; /* --font-size-3xl computed value */
font-weight: 500; /* Note: computed as 500, token says 900 — may be Aeonik Pro Medium variant */
line-height: 89.216px; /* tight 1:1 ratio */
letter-spacing: -2.08px;
color: rgb(255, 255, 255);
}
/* h2 section titles — extracted: high confidence */
h2 {
font-family: "Aeonik Pro", sans-serif;
font-size: 52.608px; /* --font-size-2xl computed value */
font-weight: 500;
line-height: 52.608px;
letter-spacing: -0.6px;
color: rgb(25, 28, 31);
text-align: center;
}
/* h3 subheadings — extracted: high confidence */
h3 {
font-family: "Aeonik Pro", sans-serif;
font-size: 18px;
font-weight: 400;
line-height: 24px;
letter-spacing: normal;
color: rgb(25, 28, 31);
}
/* body / default — extracted: high confidence */
body {
font-family: Inter, sans-serif;
font-size: 12px; /* --font-size-xs */
font-weight: 400;
line-height: 18px;
letter-spacing: 0.18px;
color: rgb(80, 90, 99);
}
```
### Font Loading
Fonts are served from `https://assets.revolut.com/assets/fonts/` with `font-display: swap`. Aeonik Pro Black Capitalised is a special variant for uppercase display usage.
---
## 4. Spacing & Layout
### Base Unit & Scale
Revolut's spacing uses a **rem-based scale with 4px granularity** at small sizes, stepping up to 8px increments. All spacing must come from `--rui-space-s*` tokens.
```css
:root {
/* ── Primitive Spacing Scale (extracted: high confidence) ── */
--rui-space-none: 0;
--rui-space-s2: 0.125rem; /* 2px — hairline separators */
--rui-space-s4: 0.25rem; /* 4px — icon internal padding */
--rui-space-s6: 0.375rem; /* 6px — tight inline gaps */
--rui-space-s8: 0.5rem; /* 8px — compact component padding */
--rui-space-s12: 0.75rem; /* 12px — small component padding */
--rui-space-s16: 1rem; /* 16px — standard component padding */
--rui-space-s20: 1.25rem; /* 20px — medium internal gaps */
--rui-space-s24: 1.5rem; /* 24px — card padding, button h-padding */
--rui-space-s32: 2rem; /* 32px — section sub-spacing */
--rui-space-s40: 2.5rem; /* 40px — component group gaps */
--rui-space-s48: 3rem; /* 48px — section spacing */
--rui-space-s56: 3.5rem; /* 56px — large component gaps */
--rui-space-s64: 4rem; /* 64px — section vertical rhythm */
--rui-space-s72: 4.5rem; /* 72px — generous section padding */
}
```
### Semantic Spacing (from curated tokens)
```css
:root {
/* ── Semantic Spacing Aliases ── */
--revolut-space-xs: 24px; /* Component internal padding minimum */
--revolut-space-sm: 40px; /* Component group separation */
--revolut-space-md: 48px; /* Standard section padding */
--revolut-space-lg: 120px; /* Large section vertical gap */
--revolut-space-xl: 220px; /* Hero section vertical breathing room */
}
```
### Layout Dimensions
```css
:root {
/* ── Container & Layout Sizes (extracted: high confidence) ── */
--rui-size-layout: 94.375rem; /* 1510px — max container width */
--rui-size-layout-menu-md: 5.5rem; /* 88px — compact side nav */
--rui-size-layout-menu-lg: 6.5rem; /* 104px — standard side nav */
--rui-size-layout-menu-xl: 15.5rem; /* 248px — expanded side nav */
--rui-size-layout-menu-xxl: 25rem; /* 400px — wide side nav/drawer */
--rui-size-layout-side-medium:25rem; /* 400px — medium side panel */
--rui-size-layout-side-wide: 36.5rem; /* 584px — wide side panel */
}
```
### Border Radius Scale
```css
:root {
/* ── Radius Scale (extracted: high confidence) ── */
--rui-radius-none: unset;
--rui-radius-r2: 0.125rem; /* 2px — barely rounded */
--rui-radius-r4: 0.25rem; /* 4px — subtle rounding */
--rui-radius-r6: 0.375rem; /* 6px — inputs, small elements */
--rui-radius-r8: 0.5rem; /* 8px — tags, chips */
--rui-radius-r12: 0.75rem; /* 12px — buttons (from computed styles) */
--rui-radius-r16: 1rem; /* 16px — widget containers */
--rui-radius-r24: 1.5rem; /* 24px — popups, modals */
--rui-radius-r32: 2rem; /* 32px — large cards */
--rui-radius-round: 9999px; /* pill — tabs, avatar badges, CTAs */
/* ── Semantic Radius Aliases ── */
--rui-radius-widget: var(--rui-radius-r16); /* App widget containers */
--rui-radius-popup: var(--rui-radius-r24); /* Popups, tooltips */
--rui-radius-status-popup: 2.75rem; /* Status/toast popups */
}
```
### Breakpoints
```css
/* ── Responsive Breakpoints (extracted from media queries) ── */
/* xs: 320px — small mobile */
/* sm: 400px — standard mobile */
/* md-: 719px — mobile max / below tablet */
/* md: 720px — tablet start */
/* lg-: 1023px — tablet max / below desktop */
/* lg: 1024px — desktop start */
/* xl: 1280px — wide desktop */
/* 2xl: 1920px — ultra-wide */
```
### Grid & Flex Patterns
```css
/* ── Layout Patterns ── */
.layout-container {
max-width: var(--rui-size-layout); /* 94.375rem */
margin-inline: auto;
padding-inline: var(--rui-space-s24);
}
/* Section vertical rhythm */
.section-padding {
padding-block: var(--revolut-space-md); /* 48px default */
}
/* Hero section */
.section-hero {
padding-block: var(--revolut-space-xl); /* 220px vertical breathing room */
}
```
### Button Size Scale
```css
:root {
--rui-size-button-xs: 1.875rem; /* 30px — compact/icon buttons */
--rui-size-button-sm: 2.5rem; /* 40px — small buttons */
--rui-size-button-md: 3.25rem; /* 52px — standard CTA buttons */
}
```
---
## 5. Page Structure & Layout Patterns
> **Note:** No page screenshots were provided. All section structures are inferred from the layout digest, component inventory, computed styles, and typographic hierarchy. Rows marked **(inferred)** are probabilistic reconstructions.
### 5.1 Section Map
| # | Section | Layout Type | Approx. Height | Key Elements | Confidence |
|---|---------|-------------|----------------|--------------|------------|
| 1 | **Navigation / Header** | Flex row, sticky | `--rui-size-layout-menu-lg` (6.5rem / 104px) | Logo, nav items (6 found), CTAs (buttons), language selector (tab pill) | Confirmed — component inventory |
| 2 | **Hero** | Full-width, dark bg, centered | ~100vh | h1 at 89.216px (white, Aeonik Pro), subtitle, primary CTA button | Inferred — from h1 computed style + `--revolut-space-xl` |
| 3 | **Product Feature Highlight** | 2-col flex or grid | ~600–800px | h2 at 52.608px, body text, product image/visual | Inferred — from h2 computed style + section spacing |
| 4 | **Pricing / Plans** | 3-col grid | ~500–700px | h2 "Standard/Plus/Premium" (24px), h3 pricing labels (18px), feature lists, CTA buttons | Confirmed — typography census mentions these exact strings |
| 5 | **Feature Sections (repeating)** | Alternating 2-col | ~400–600px each | h2 (52.608px, centred), h3 subheadings (18px), body copy, inline CTAs | Inferred — pattern from h2 center-align computed style |
| 6 | **App Download / CTA Band** | Centred single-col or split | ~300–400px | Marketing headline, app store badge buttons, QR code (inferred) | Inferred — standard fintech pattern |
| 7 | **Footer** | Multi-col grid | ~300–500px | Nav groups, legal text (12px body), social icons, language/region selector | Inferred — footer pattern consistent with typography scale |
### 5.2 Layout Patterns
**Navigation:**
- Flex row, `justify-content: space-between`, `align-items: center`
- Height: `var(--rui-size-layout-menu-lg)` (6.5rem)
- Nav items rendered as pill tabs (`border-radius: 9999px`, background `rgb(141, 150, 158)`)
- Container constrained to `--rui-size-layout` (94.375rem)
**Hero Section:**
- Full-width dark background
- Content centred or left-aligned within `--rui-size-layout` container
- Vertical padding: `--revolut-space-xl` (220px) top and bottom
- h1 white, Aeonik Pro, 89.216px, weight 500, tight line-height (1:1)
- CTA button: `--rui-radius-round` pill, `--rui-size-button-md` height (3.25rem)
**Pricing Grid (3-col):**
- CSS grid: `grid-template-columns: repeat(3, 1fr)`
- Gap: `--rui-space-s24` or `--rui-space-s32` (inferred)
- Card radius: `--rui-radius-r24` (1.5rem popup radius semantic alias)
- Elevation: `--rui-shadow-level2` on active/highlighted tier
**Repeating Feature Sections (2-col alternating):**
- `display: grid`, `grid-template-columns: 1fr 1fr`
- Gap: `--rui-space-s48` (inferred)
- h2 centred on light backgrounds (`text-align: center` confirmed from computed style)
- Section vertical separation: `--revolut-space-lg` (120px)
**Mobile (<720px):**
- All multi-col grids collapse to single column
- Horizontal padding: `--rui-space-s16` (1rem)
- Navigation collapses to hamburger/drawer (`--rui-size-layout-menu-xxl: 25rem` drawer width)
### 5.3 Visual Hierarchy
**Hero:** The h1 at 89.216px white on dark background is the single most dominant visual element. CTA buttons appear immediately below — pill-shaped (`--rui-radius-round`), contrasting fill colour.
**Section headers:** h2 at 52.608px, centred, near-black `rgb(25, 28, 31)`, tight line-height. These anchor each content section visually.
**Pricing:** h2 plan names at 24px (`--rui-font-size-xl`), price labels as h3 at 18px. The highlighted plan (Premium/Metal) likely uses `--rui-shadow-level2` or `--rui-shadow-level3` for elevation differentiation.
**CTAs:** Primary CTAs use pill radius (`--rui-radius-round`), height `--rui-size-button-md`. The brand-primary-cta background is `rgb(141, 150, 158)` as extracted; however, **this value originates from a tab/pill element and may represent a neutral selector rather than a primary CTA fill** — primary action buttons likely use foreground (`rgb(25, 28, 31)`) or a blue accent. **Treat primary CTA fill as `[TBD - extract manually]`.**
**Whitespace rhythm:** Section-to-section separation uses `--revolut-space-lg` (120px). Within sections, elements are separated by `--rui-space-s32`–`--rui-space-s48`.
### 5.4 Content Patterns
**Headline + Subtext + CTA (hero pattern):**
```
[h1 — Aeonik Pro, 89px, white]
[subtitle — Inter, 16–18px, white/muted]
[pill CTA button]
```
**Section header pattern:**
```
[h2 — Aeonik Pro, 52.608px, near-black, centred]
[body1 descriptor — Inter, 16px, secondary text, centred]
```
**Pricing card pattern:**
```
[h2 — plan name, 24px]
[h3 — price, 18px]
[feature list — body2, 14px]
[CTA button — pill, full-width on card]
```
**Feature block (2-col):**
```
[visual/image — 50% col]
[h2 + body + CTA — 50% col, vertically centred]
```
---
## 6. Component Patterns
### 6.1 Button
**Anatomy:**
- Wrapper (`<button>`) → State Layer (overlay div) → Content (icon? + label)
- State layer uses `--rui-color-state-overlay` injected via CSS var
**Token Mappings:**
| State | Background | Overlay | Text | Opacity | Border |
|-------|-----------|---------|------|---------|--------|
| Default | [CTA fill — TBD] | `transparent` | depends on variant | 1 | none |
| Hover | same | `rgb(black/0.08)` | — | 1 | none |
| Active | same | `rgb(black/0.15)` | — | 1 | none |
| Focus-visible | same | — | — | 1 | `--rui-focus-outline` inset |
| Disabled | `--rui-color-disabled` | `--rui-color-disabled` | `--rui-color-grey-50` | children: 0.7 | none |
| Inverted hover | same | `rgb(white/0.05)` | — | 1 | — |
| Inverted active | same | `rgb(white/0.08)` | — | 1 | — |
| Inverted disabled | — | — | — | 0.5 | — |
```tsx
// Button — production-ready with all states
// Token source: extracted-css-vars (high confidence for structure, states confirmed from CSS rules)
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost' | 'inverted';
size?: 'xs' | 'sm' | 'md';
disabled?: boolean;
loading?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
const sizeMap = {
xs: 'var(--rui-size-button-xs)', // 1.875rem
sm: 'var(--rui-size-button-sm)', // 2.5rem
md: 'var(--rui-size-button-md)', // 3.25rem
};
export const Button = ({
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
children,
onClick,
}: ButtonProps) => {
const isDisabled = disabled || loading;
return (
<button
onClick={onClick}
disabled={isDisabled}
aria-disabled={isDisabled}
data-variant={variant}
style={{
// Layout
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
gap: 'var(--rui-space-s8)',
height: sizeMap[size],
padding: '0 var(--rui-space-s24)',
position: 'relative',
overflow: 'hidden',
// Typography
fontFamily: 'var(--rui-font-brand)',
fontSize: 'var(--rui-font-size-emphasis1)', // 1rem
fontWeight: Number(500),
lineHeight: String(1),
letterSpacing: 'calc(-0.18em / 16)',
// Shape
borderRadius: 'var(--rui-radius-round)', // 9999px pill
border: 'none',
cursor: isDisabled ? 'not-allowed' : 'pointer',
// Motion
transition: 'var(--rui-timing-sm)', // 200ms easing-default
// Disabled
pointerEvents: isDisabled ? 'none' : 'auto',
}}
>
{/* State overlay layer — colour injected via CSS */}
<span
data-rui="state-layer"
aria-hidden="true"
style={{
position: 'absolute',
inset: 0,
backgroundColor: 'var(--rui-color-state-overlay, transparent)',
borderRadius: 'inherit',
pointerEvents: 'none',
transition: 'var(--rui-timing-sm)',
}}
/>
<span style={{ position: 'relative', opacity: isDisabled ? 0.7 : 1 }}>
{loading ? <span aria-label="Loading…">…</span> : children}
</span>
</button>
);
};
```
```css
/* Button state CSS — add to global stylesheet */
button[data-variant="primary"]:hover { --rui-color-state-overlay: rgb(0 0 0 / 0.08); }
button[data-variant="primary"]:active { --rui-color-state-overlay: rgb(0 0 0 / 0.15); }
button[data-variant="primary"]:focus-visible {
outline: none;
box-shadow: 0 0 0 0.125rem rgb(var(--rui-color-channel-blue, 0 122 255) / 0.5) inset,
0 0 0 0.225rem var(--rui-color-background) inset;
}
button[data-variant="primary"]:is(:disabled, [aria-disabled="true"]) {
--rui-color-state-overlay: var(--rui-color-disabled);
}
button[data-variant="inverted"]:hover { --rui-color-state-overlay: rgb(255 255 255 / 0.05); }
button[data-variant="inverted"]:active { --rui-color-state-overlay: rgb(255 255 255 / 0.08); }
button[data-variant="inverted"]:is(:disabled, [aria-disabled="true"]) { opacity: 0.5; }
```
---
### 6.2 Tab / Pill Selector
**Anatomy:** Container (pill-shaped) → individual tab items (pill-shaped, `--rui-radius-round`)
```css
/* Tab component */
.tab-container {
display: inline-flex;
border-radius: var(--rui-radius-round);
background-color: rgb(141, 150, 158); /* --rui-color-grey-tone-2 */
overflow: hidden;
}
.tab-item {
font-family: Arial, sans-serif; /* Note: computed style shows Arial — system fallback */
font-size: 13.333px; /* Browser default for form elements */
border-radius: var(--rui-radius-round);
text-align: center;
padding: var(--rui-space-s8) var(--rui-space-s16);
transition: var(--rui-timing-sm);
cursor: pointer;
}
.tab-item[aria-selected="true"] {
background-color: var(--rui-color-background); /* White selected state */
}
```
---
### 6.3 Modal / Overlay
**Anatomy:** Backdrop → Dialog container → Content
| State | Property |
|-------|----------|
| Backdrop | `rgba(0,0,0,0.4)` confirmed from computed |
| Container radius | `--rui-radius-popup` (`--rui-radius-r24`, 1.5rem) |
| Container shadow | `--rui-shadow-level4` (deepest elevation) |
| Font | `--rui-font-brand` (Inter), 16px, `rgb(25,28,31)` |
| Transition in | `--rui-timing-lg` (450ms) |
```tsx
export const Modal = ({ isOpen, onClose, children }: ModalProps) => (
isOpen ? (
<div
role="dialog"
aria-modal="true"
style={{
position: 'fixed', inset: 0, zIndex: 1000,
backgroundColor: 'rgba(0, 0, 0, 0.4)', /* extracted from computed style */
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
onClick={onClose}
>
<div
onClick={e => e.stopPropagation()}
style={{
fontFamily: 'var(--rui-font-brand)',
fontSize: 'var(--rui-font-size-body1)',
color: 'rgb(25, 28, 31)',
backgroundColor: 'var(--rui-color-background)',
borderRadius: 'var(--rui-radius-popup)', /* 1.5rem */
boxShadow: 'var(--rui-shadow-level4)',
padding: 'var(--rui-space-s32)',
maxWidth: 'var(--rui-size-layout-side-medium)', /* 25rem */
width: '100%',
}}
>
{children}
</div>
</div>
) : null
);
```
---
### 6.4 Navigation Item
**Anatomy:** `<a>` or `<button>` → optional icon → label text
```tsx
export const NavItem = ({ label, href, active }: NavItemProps) => (
<a
href={href}
aria-current={active ? 'page' : undefined}
style={{
fontFamily: 'var(--rui-font-brand)',
fontSize: 'var(--rui-font-size-emphasis1)', /* 1rem */
fontWeight: active ? 500 : 400,
color: active ? 'rgb(25, 28, 31)' : 'rgb(80, 90, 99)',
textDecoration: 'none',
padding: 'var(--rui-space-s8) var(--rui-space-s12)',
borderRadius: 'var(--rui-radius-r8)',
transition: 'var(--rui-timing-sm)',
outline: 'none',
}}
>
{label}
</a>
);
```
```css
/* Nav item states */
a[href]:hover { color: rgb(25, 28, 31); background-color: var(--rui-color-grey-tone-2, rgb(141 150 158 / 0.2)); }
a[href]:focus-visible { outline: var(--rui-focus-outline); outline-offset: var(--rui-focus-outline-offset); }
```
---
### 6.5 DropZone / Interactive Surface
```css
/* Extracted directly from CSS rules */
.drop-zone {
transition: background-color var(--rui-timing-sm);
}
.drop-zone:hover { background-color: var(--rui-color-grey-tone-2); }
.drop-zone:focus-within { background-color: var(--rui-color-action-background-neutral); }
.drop-zone:active,
.drop-zone[data-active] {
background-color: rgb(var(--rui-color-channel-action-background-neutral) / 0.7);
}
```
---
## 7. Elevation & Depth
```css
:root {
/* ── Shadow Scale (extracted: high confidence) ── */
--rui-shadow-none: none;
--rui-shadow-level1: 0 0.125rem 0.1875rem
rgb(var(--rui-color-channel-black) / 0.05);
/* 2px 3px — barely lifted, cards at rest */
--rui-shadow-level2: 0 0.1875rem 0.5rem
rgb(var(--rui-color-channel-black) / 0.1);
/* 3px 8px — active cards, focused widgets */
--rui-shadow-level3: 0 0.1875rem 1.875rem
rgb(var(--rui-color-channel-black) / 0.08);
/* 3px 30px — floating panels, popovers */
--rui-shadow-level4: 0 1rem 4rem
rgb(var(--rui-color-channel-black) / 0.12);
/* 16px 64px — modals, deep drawers */
--rui-shadow-side: 0 0.125rem 0.25rem rgb(var(--rui-color-channel-black) / 0.05),
0 0.1875rem 1rem rgb(var(--rui-color-channel-black) / 0.1);
/* Lateral panels, side drawers */
/* ── Shadow Transition ── */
--rui-easing-shadow: cubic-bezier(0.4, 0.3, 0.8, 0.6);
--rui-timing-shadow: var(--rui-duration-sm) var(--rui-easing-shadow);
/* Use for box-shadow transitions specifically — distinct from default easing */
}
```
### Z-index Layering (inferred — TBD extract from CSS)
| Layer | Usage | z-index |
|-------|-------|---------|
| Base content | Page sections | 0 |
| Sticky nav | Navigation bar | [TBD] |
| Dropdowns | Nav flyouts | [TBD] |
| Modals | Overlays | ~1000 |
| Toasts | Notifications | ~1100 |
### Elevation Usage Rules
- **Level 1:** Cards at rest in grids
- **Level 2:** Cards on hover, pricing highlighted tier
- **Level 3:** Floating popovers, tooltips
- **Level 4:** Modals, sheet drawers
- **Side:** Lateral navigation panels
---
## 8. Motion
```css
:root {
/* ── Duration Scale (extracted: high confidence) ── */
--rui-duration-xs: 100ms; /* Instant feedback — toggles, checkboxes */
--rui-duration-sm: 200ms; /* Quick interactions — buttons, hover states */
--rui-duration-md: 300ms; /* Standard — panels, dropdowns opening */
--rui-duration-lg: 450ms; /* Deliberate — modals, page transitions */
--rui-duration-xl: 900ms; /* Slow — hero animations, onboarding sequences */
--rui-duration-skeleton: 1500ms; /* Loading skeleton pulse cycle */
/* ── Easing Functions (extracted: high confidence) ── */
--rui-easing-default: cubic-bezier(0.15, 0.5, 0.5, 1);
/* Snappy with soft landing — standard for interactive elements */
--rui-easing-shadow: cubic-bezier(0.4, 0.3, 0.8, 0.6);
/* Shadow-specific — slower out, for elevation transitions */
--rui-easing-toast: cubic-bezier(0.175, 0.885, 0.21, 1.65);
/* Overshoot spring — toast/snackbar entrance */
--rui-easing-linear: linear; /* Progress bars, skeleton shimmer */
/* ── Composite Timing Shorthands (extracted: high confidence) ── */
--rui-timing-sm: var(--rui-duration-sm) var(--rui-easing-default); /* 200ms — button hover */
--rui-timing-md: var(--rui-duration-md) var(--rui-easing-default); /* 300ms — panel open */
--rui-timing-lg: var(--rui-duration-lg) var(--rui-easing-default); /* 450ms — modal in */
--rui-timing-xl: var(--rui-duration-xl) var(--rui-easing-default); /* 900ms — hero anim */
--rui-timing-shadow: var(--rui-duration-sm) var(--rui-easing-shadow); /* shadow-specific */
}
```
### Motion Rules
**When to animate:**
- All interactive state changes (hover, active, focus) → `--rui-timing-sm`
- Panel/dropdown open/close → `--rui-timing-md`
- Modal entrance/exit → `--rui-timing-lg`
- Shadow elevation changes → `--rui-timing-shadow` (different easing curve)
- Loading skeleton shimmer → `--rui-duration-skeleton` with `--rui-easing-linear`
**When NOT to animate:**
- `prefers-reduced-motion: reduce` media query — always respect it
- Colour value changes on disabled states — apply immediately
- Layout reflow (avoid animating width/height — use transform/opacity)
**NEVER use `transition: all`** — always specify the property being transitioned. Button components use `transition: box-shadow var(--rui-timing-shadow)`.
---
## 9. Anti-Patterns & Constraints
1. **Hardcoded button radius → Why it fails → Use `--rui-radius-round`**
An AI agent defaulting to `border-radius: 8px` or `border-radius: 0.5rem` will produce squared-off buttons that contradict Revolut's pill-shaped CTA language. The brand's button elements have `border-radius: 9999px` in computed styles. **Always use `var(--rui-radius-round)` for primary and secondary action buttons.**
2. **Using Inter for marketing headlines → Why it fails → Use `--rui-font-marketing`**
AI agents default to Inter because it's the UI font and commonly used. But Revolut's h1/h2 marketing headings use "Aeonik Pro" (`--rui-font-marketing`) — a custom typeface not present in standard stacks. Rendering these headings in Inter produces generic-looking output that completely breaks the brand character. **All `h1` and large `h2` elements on marketing pages must use `var(--rui-font-marketing)`.**
3. **Hardcoding colour hex values → Why it fails → Always use `--rui-color-*` tokens**
AI agents may pluck `#191c1f` or `rgb(25, 28, 31)` from computed styles and inline them. This breaks dark mode compatibility and makes colour changes impossible to propagate. The entire colour system uses channel tokens for alpha compositing (`rgb(var(--rui-color-channel-black)/0.08)`). **Reference only named colour tokens; never write literal hex or rgb() values in component code.**
4. **Arbitrary spacing values → Why it fails → Use only `--rui-space-s*` tokens**
AI agents generate spacing like `padding: 20px` or `margin: 1.2rem` that sits outside the design system scale. Revolut's scale has specific increments (2, 4, 6, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64, 72px). Off-scale values create visual inconsistency that accumulates across a page. **Every padding, margin, and gap value must map to a `--rui-space-s*` token.**
5. **Using `transition: all` → Why it fails → Scope to specific properties**
`transition: all` triggers a repaint on every animatable property change, including layout-affecting properties (width, height, padding), causing janky animations. Revolut uses scoped transitions: `box-shadow var(--rui-timing-shadow)` for elevation, `background-color var(--rui-timing-sm)` for state changes. **Always specify the exact CSS property in the transition shorthand.**
6. **Missing `pointer-events: none` on disabled buttons → Why it fails → Required by system**
Revolut's design system explicitly sets `pointer-events: none` on disabled button states (confirmed in extracted CSS). Omitting this means disabled buttons still respond to cursor events, showing hover effects that confuse users and break the intended disabled UX. **Disabled buttons must have `pointer-events: none`.**
7. **Wrong font weight for display text → Why it fails → Weight depends on font/context**
The Aeonik Pro marketing display uses weight 900 (Black) per CSS tokens, but computed styles show weight 500 on the live h1. This is because Aeonik Pro has a "Black Capitalised" variant that maps 900 → a specific optical variant. Defaulting to `font-weight: 700` (Bold, common AI default) produces the wrong character. **Use `font-weight: 900` for `--rui-font-weight-marketing-display*` tokens, 700 for headings, 500/400 for body.**
8. **Building responsive layout with fixed pixel widths → Why it fails → Use layout tokens**
AI agents often write `max-width: 1200px` (a generic default). Revolut's layout max-width is `--rui-size-layout: 94.375rem` (1510px). Using 1200px creates a narrower, non-authentic layout that wastes space on wide screens and misaligns with the actual grid. **Always use `var(--rui-size-layout)` for page container max-width.**
9. **Omitting focus-visible styles → Why it fails → Critical for accessibility and brand**
Revolut has a specifically designed focus system: an inset double-ring — `0 0 0 0.125rem rgb(blue/0.5) inset, 0 0 0 0.225rem var(--rui-color-background) inset`. AI agents either omit focus styles entirely (accessibility failure) or use a simple outline (inconsistent with the brand's inset ring pattern). **Every interactive element must implement the extracted focus style using the inset double box-shadow pattern.**
10. **Dynamic Tailwind class construction → Why it fails → Classes are purged**
Tailwind purges classes that aren't statically detectable in source files. Code like `` `bg-${colorVar}` `` or `` `text-${sizeVar}` `` generates classes at runtime that Tailwind has already removed from the CSS bundle. This silently removes all styling with no error. **Never construct Tailwind class names with string interpolation. Use static class strings or `style={}` with CSS variables for dynamic values.**
11. **Using `!important` for state overrides → Why it fails → Breaks the state layer system**
Revolut's state system works by injecting `--rui-color-state-overlay` as a CSS variable that cascade updates on hover/active. Adding `!important` to background or colour properties prevents this cascade from reaching the component, breaking all interactive states silently. **Never use `!important`. Use CSS specificity and the `--rui-color-state-overlay` pattern for interactive states.**
---
## 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 (12) */
--brand-primary-cta: rgb(141, 150, 158); /* Primary CTA background, dominant on 3 buttons — e.g. "button" /* mined from computed styles */ */
--rui-color-background: rgb(255, 255, 255);
--rui-color-foreground: rgb(25, 28, 31);
--color-text-primary: rgb(25, 28, 31);
--color-text-secondary: rgb(80, 90, 99);
--color-text-inverse: rgb(255, 255, 255);
--rui-color-grey-tone-2: rgb(141, 150, 158);
--rui-color-grey-50: [TBD];
--rui-color-disabled: [TBD];
--rui-color-action-background-neutral: [TBD];
--color-modal-overlay: rgba(0, 0, 0, 0.4);
--rui-color-state-overlay: rgb(255 255 255 / 0.08);
/* Typography (96) */
--rui-easing-default: cubic-bezier(0.15,0.5,0.5,1);
--rui-easing-shadow: cubic-bezier(0.4,0.3,0.8,0.6);
--rui-easing-toast: cubic-bezier(0.175,0.885,0.21,1.65);
--rui-font-default: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Helvetica, Arial, Arimo, sans-serif;
--rui-font-system: var(--rui-font-default);
--rui-font-brand: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--rui-font-emoji: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--rui-font-marketing: "Aeonik Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--rui-font-size-display1: 3.5rem;
--rui-size-button-sm: 2.5rem;
--rui-font-size-heading1: 2rem;
--rui-font-size-display2: 2.75rem;
--rui-font-size-heading2: 1.5rem;
--rui-font-size-heading3: 1.25rem;
--rui-font-size-body1: 1rem;
--rui-font-size-body2: 0.875rem;
--rui-font-size-body3: 0.75rem;
--rui-font-size-emphasis4: 0.6875rem;
--rui-font-size-sm-h6: 1.375rem;
--rui-font-weight-marketing-display1: 900;
--rui-font-weight-display1: 700;
--rui-font-weight-sm-h6: 500;
--rui-font-weight-body1: 400;
--rui-line-height-marketing-display1: 1;
--rui-line-height-sm-h6: 1.182;
--rui-line-height-heading1: 1.1875;
--rui-line-height-heading2: 1.25;
--rui-line-height-heading3: 1.4;
--rui-line-height-body1: 1.375;
--rui-line-height-body2: 1.429;
--rui-line-height-body3: 1.5;
--rui-line-height-emphasis4: 1.273;
--rui-letter-spacing-normal: normal;
--rui-letter-spacing-marketing-display1: calc(0.005em / 56);
--rui-letter-spacing-marketing-display2: calc(0.005em / 40);
--rui-letter-spacing-marketing-display3: calc(0.005em / 32);
--rui-letter-spacing-display1: calc(-0.56em / 56);
--rui-letter-spacing-display2: calc(-0.56em / 44);
--rui-letter-spacing-heading1: calc(-0.35em / 32);
--rui-letter-spacing-heading2: calc(-0.31em / 24);
--rui-letter-spacing-heading3: calc(-0.27em / 20);
--rui-letter-spacing-emphasis1: calc(-0.18em / 16);
--rui-letter-spacing-emphasis2: calc(-0.1em / 14);
--rui-letter-spacing-emphasis3: 0;
--rui-letter-spacing-emphasis4: calc(0.16em / 11);
--rui-letter-spacing-sm-h6: calc(-0.01em / 22);
--rui-letter-spacing-sm-subtitle-2: calc(1em / 16);
--rui-size-button-xs: 1.875rem;
--rui-size-button-md: 3.25rem;
--rui-size-layout: 94.375rem;
--rui-size-layout-menu-md: 5.5rem;
--rui-size-layout-menu-lg: 6.5rem;
--rui-size-layout-menu-xl: 15.5rem;
--rui-size-layout-menu-xxl: 25rem;
--rui-size-layout-side-wide: 36.5rem;
--rui-shadow-side: 0 0.125rem 0.25rem rgb(var(--rui-color-channel-black)/0.05),0 0.1875rem 1rem rgb(var(--rui-color-channel-black)/0.1);
--font-size-xs: 12px; /* 65 elements — e.g. h2 "Global Finances", h2 "Investments", h2 "Help" /* mined from computed styles */ */
--font-size-sm: 14px; /* 6 elements — e.g. p "For the financial ba", p "For the smart spende", p "For elevating every " /* mined from computed styles */ */
--font-size-md: 16px; /* 74 elements — e.g. span "Germany", span "Personal", span "Business" /* mined from computed styles */ */
--font-size-lg: 18px; /* 12 elements — e.g. h3 "Free", h3 "£3.99/month", h3 "£7.99/month" /* mined from computed styles */ */
--font-size-xl: 24px; /* 5 elements — e.g. h2 "Standard", h2 "Plus", h2 "Premium" /* mined from computed styles */ */
--font-size-2xl: 52.608px; /* 9 elements — e.g. h2 "Your salary, reimagi", h2 "Elevate your spend", h2 "Go virtual" /* mined from computed styles */ */
--font-size-3xl: 89.216px; /* 1 element — e.g. h1 "Banking & Beyond" /* mined from computed styles */ */
--font-weight-semibold: 600; /* 11 elements — e.g. h2 "Global Finances", h2 "Investments", h2 "Help" /* mined from computed styles */ */
--line-height-normal: 22px; /* 40 elements — e.g. p "For the financial ba", p "For the smart spende", p "For elevating every " /* mined from computed styles */ */
--rui-font-size-marketing-display1: 3.5rem;
--rui-font-size-marketing-display2: 2.5rem;
--rui-font-size-marketing-display3: 2rem;
--rui-font-size-emphasis1: 1rem;
--rui-font-size-emphasis2: 0.875rem;
--rui-font-size-emphasis3: 0.75rem;
--rui-font-size-sm-subtitle-2: 1rem;
--rui-font-weight-marketing-display2: 900;
--rui-font-weight-marketing-display3: 900;
--rui-font-weight-display2: 700;
--rui-font-weight-heading1: 700;
--rui-font-weight-heading2: 700;
--rui-font-weight-heading3: 700;
--rui-font-weight-emphasis1: 500;
--rui-font-weight-emphasis2: 500;
--rui-font-weight-emphasis3: 500;
--rui-font-weight-emphasis4: 500;
--rui-font-weight-body2: 400;
--rui-font-weight-body3: 400;
--rui-font-weight-subheader: 500;
--rui-font-weight-sm-subtitle-2: 500;
--rui-line-height-marketing-display2: 1;
--rui-line-height-marketing-display3: 1;
--rui-line-height-display1: 1.182;
--rui-line-height-emphasis1: 1.375;
--rui-line-height-emphasis2: 1.429;
--rui-line-height-emphasis3: 1.5;
--rui-line-height-sm-subtitle-2: 1.375;
--rui-letter-spacing-body1: calc(-0.18em / 16);
--rui-letter-spacing-body2: calc(-0.1em / 14);
--rui-letter-spacing-body3: 0;
/* Spacing (36) */
--rui-space-none: 0;
--rui-focus-outline-offset: 0.125rem;
--rui-space-s4: 0.25rem;
--rui-space-s6: 0.375rem;
--rui-space-s8: 0.5rem;
--rui-space-s12: 0.75rem;
--rui-space-s16: 1rem;
--rui-space-s20: 1.25rem;
--rui-space-s24: 1.5rem;
--rui-space-s32: 2rem;
--rui-space-s40: 2.5rem;
--rui-space-s48: 3rem;
--rui-space-s56: 3.5rem;
--rui-space-s64: 4rem;
--rui-space-s72: 4.5rem;
--space-xs: 24px; /* 4 elements — e.g. section .Box-rui__sc-1g1k12l-0, section .Box-rui__sc-1g1k12l-0, section .Box-rui__sc-1g1k12l-0 /* mined from computed styles */ */
--space-sm: 40px; /* 2 elements — e.g. footer .Box-rui__sc-1g1k12l-0, footer .Box-rui__sc-1g1k12l-0 /* mined from computed styles */ */
--space-md: 48px; /* 1 element — e.g. section .Box-rui__sc-1g1k12l-0 /* mined from computed styles */ */
--space-lg: 120px; /* 1 element — e.g. section .Box-rui__sc-1g1k12l-0 /* mined from computed styles */ */
--space-xl: 220px; /* 2 elements — e.g. footer .Box-rui__sc-1g1k12l-0, footer .Box-rui__sc-1g1k12l-0 /* mined from computed styles */ */
--rui-size-layout: 94.375rem;
--rui-space-s2: 0.125rem;
--revolut-space-xs: 24px;
--revolut-space-sm: 40px;
--revolut-space-md: 48px;
--revolut-space-lg: 120px;
--revolut-space-xl: 220px;
--rui-size-layout-menu-md: 5.5rem;
--rui-size-layout-menu-lg: 6.5rem;
--rui-size-layout-menu-xl: 15.5rem;
--rui-size-layout-menu-xxl: 25rem;
--rui-size-layout-side-medium: 25rem;
--rui-size-layout-side-wide: 36.5rem;
--rui-size-button-xs: 1.875rem;
--rui-size-button-sm: 2.5rem;
--rui-size-button-md: 3.25rem;
/* Radius (14) */
--rui-radius-none: unset;
--rui-radius-r2: 0.125rem;
--rui-radius-r4: 0.25rem;
--rui-radius-r6: 0.375rem;
--rui-radius-r8: 0.5rem;
--rui-radius-r12: 0.75rem;
--rui-radius-r16: 1rem;
--rui-radius-r24: 1.5rem;
--rui-radius-r32: 2rem;
--rui-radius-round: 9999px;
--rui-radius-widget: var(--rui-radius-r16);
--rui-radius-popup: var(--rui-radius-r24);
--rui-radius-status-popup: 2.75rem;
--radius-sm: 12px; /* 10 elements — e.g. button .ButtonBase__ButtonBaseWrapper-rui__sc-1aqanxw-1, button .ButtonBase__ButtonBaseWrapper-rui__sc-1aqanxw-1 "Germany", button .ButtonBase__ButtonBaseWrapper-rui__sc-1aqanxw-1 /* mined from computed styles */ */
/* Effects (20) */
--rui-duration-xs: 100ms;
--rui-duration-sm: 200ms;
--rui-duration-md: 300ms;
--rui-duration-lg: 450ms;
--rui-duration-xl: 900ms;
--rui-duration-skeleton: 1500ms;
--rui-easing-linear: "linear";
--rui-timing-sm: var(--rui-duration-sm) var(--rui-easing-default);
--rui-timing-md: var(--rui-duration-md) var(--rui-easing-default);
--rui-timing-lg: var(--rui-duration-lg) var(--rui-easing-default);
--rui-timing-xl: var(--rui-duration-xl) var(--rui-easing-default);
--rui-timing-shadow: var(--rui-duration-sm) var(--rui-easing-shadow);
--rui-shadow-none: none;
--rui-shadow-level1: 0 0.125rem 0.1875rem
rgb(var(--rui-color-channel-black) / 0.05);
--rui-shadow-level2: 0 0.1875rem 0.5rem
rgb(var(--rui-color-channel-black) / 0.1);
--rui-shadow-level3: 0 0.1875rem 1.875rem
rgb(var(--rui-color-channel-black) / 0.08);
--rui-shadow-level4: 0 1rem 4rem
rgb(var(--rui-color-channel-black) / 0.12);
--rui-focus-outline: 0.125rem solid rgb(var(--rui-color-channel-accent)/0.5);
--rui-shadow-side: 0 0.125rem 0.25rem rgb(var(--rui-color-channel-black) / 0.05),
0 0.1875rem 1rem rgb(var(--rui-color-channel-black) / 0.1);
--rui-easing-shadow: cubic-bezier(0.4, 0.3, 0.8, 0.6);
/* Motion (3) */
--duration-fast: 0.2s; /* 53 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-base: 0.3s; /* 53 elements — e.g. button, button, button /* mined from computed styles */ */
--ease-default: 0.5; /* 106 elements — e.g. button, button, button /* mined from computed styles */ */
```
More from the gallery
Browse all kits →You may also like

Contra
MITPlayful yet professional design system with teal and purple accents, bold typography, and vibrant accent surfaces—built for modern SaaS platforms and landing pages
03
lightboldsaaslanding-page

DoorDash
MITClean, accessible food-delivery system with DoorDash's signature red accent, light neutral palette, and system-font typography—built for rapid product development
03
lightecommsaasmobile

Mercedes-Benz
MITLuxe, minimalist design system with sharp black-and-white contrast and serif typography, crafted for premium automotive and lifestyle brands
05
darkminimalboldecomm