Klarna
MIT
Clean, modern fintech interface with Klarna's signature pink accents and dark eggplant typography, designed for seamless payment and shopping experiences
Layout StudioImport this kit into a Studio project and start editing.
CLI installRun it in any project. No account needed.
npx @layoutdesign/context install klarna# layout.md — Klarna Design System
---
## 0. Quick Reference
**Stack:** Extracted CSS custom properties (538 vars, high confidence) · Bootstrap detected · Custom font: Klarna Title / Klarna Text with system fallbacks · Token source: `extracted-css-vars` (1:1 fidelity).
**How to apply:** Use as `var(--token-name)` in CSS, `style={{ prop: 'var(--token-name)' }}` in JSX, or `bg-[var(--token-name)]` in Tailwind.
```css
:root {
/* Backgrounds */
--colors-bg-page: #FFFFFF;
--colors-bg-subtle: #F3F3F5;
--colors-bg-inverse: #0B051D;
--colors-bg-brand: #FFA8CD; /* Klarna pink — hero/CTA backgrounds */
/* Text */
--colors-text-default: #0B051D; /* Primary text, near-black eggplant */
--colors-text-body: #282636; /* Body copy */
--colors-text-subtle: #504F5F; /* Secondary/supporting text */
--colors-text-inverse: #F9F8F5; /* Text on dark surfaces */
--colors-text-link: #582FB4; /* Links */
/* Actions */
--colors-btn-primary: #0B051D; /* Primary button bg */
--colors-btn-brand: #FFA8CD; /* Brand/CTA button bg */
--colors-btn-secondary: #F3F3F5; /* Secondary button bg */
--primary-action-hover: #28272E; /* Primary button hover bg */
--button-background-secondary-hover: #E2E2E7;
/* Borders & Focus */
--colors-border-default: #E2E2E7;
--headerBorder: #ebeff5;
--focus: #7B57D8; /* Focus ring — purple */
/* Status */
--colors-bg-positive: #046234;
--colors-bg-warning: #FBC64D;
--colors-bg-negative: #AE1D1D;
/* Radius — pill-shaped brand buttons */
--radius-sm: 8px; /* Cards, chips */
--radius-lg: 100px; /* Primary pill buttons */
--radiuses-radius-round: 99999px; /* Circular elements */
/* Spacing (4px grid) */
--spaces-space-4: 4px;
--spaces-space-8: 8px;
--spaces-space-16: 16px;
--spaces-space-24: 24px;
--spaces-space-48: 48px;
--spaces-space-64: 64px;
/* Motion */
--duration-fast: 0.2s;
--duration-base: 0.3s;
--ease-default: ease;
/* Typography */
--fonts-font-brand-title: "Klarna Title", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
--fonts-font-brand-text: "Klarna Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
}
```
```tsx
// Primary button — correct token usage
<button className="btn-primary" style={{
backgroundColor: 'var(--colors-btn-primary)',
color: 'var(--colors-text-inverse)',
borderRadius: 'var(--radius-lg)',
fontFamily: 'var(--fonts-font-brand-text)',
transition: 'background var(--duration-base) var(--ease-default)',
}}>Pay Now</button>
```
**NEVER rules:**
- NEVER use `border-radius` values other than `--radius-sm` (8px), `--radius-lg` (100px), or `--radiuses-radius-round` (99999px) on buttons.
- NEVER hardcode `#FFA8CD` — use `var(--colors-bg-brand)` or `var(--colors-btn-brand)`.
<!-- Quick Reference truncated to fit the 75-line cap. See later sections for the full design system. -->
## 1. Design Direction & Philosophy
### Character & Aesthetic Intent
Klarna's design is **confident, modern fintech** with a distinctive brand personality: dark near-black eggplant (`#0B051D`) paired with signature Klarna pink (`#FFA8CD`) creates a premium-yet-playful contrast. The aesthetic is clean and minimal with generous whitespace, large display typography, and smooth rounded pill-shaped CTAs. The brand communicates trust through restraint — no gradients, no decorative noise on core UI — while injecting personality through brand color accents.
**Typefaces define the hierarchy:** `Klarna Title` (a custom heading face) carries authority and brand distinctiveness; `Klarna Text` and the system font stack handle body copy readability. Display type scales dramatically large (90px h1) to create impactful hero moments.
### What This Design Explicitly Rejects
- **No sharp-cornered buttons.** All interactive pill elements use `border-radius: 100px` or `99999px`. A rectangular button is never correct for primary or brand CTAs.
- **No warm neutral palettes.** Klarna's neutrals are cool-gray (`#F3F3F5`, `#E2E2E7`) with a purple-eggplant undertone, not warm beige.
- **No decorative drop shadows on cards by default.** Cards use subtle inset borders on hover (`inset 0 0 0 1px var(--border100)`), not floating box shadows.
- **No arbitrary purple.** The purple accent (`#7039E2`, `#582FB4`) is reserved for accent text, badge accents, and focus rings — never as a primary button color.
- **No dark mode switching** (single light mode is the canonical surface for the brand marketing site).
---
## 2. Colour System
### Tier 1 — Primitives (from extracted CSS vars)
```css
/* Brand Scale */
--brand10: #EFECFF;
--brand20: #E4E0F7;
--brand30: #D9C2FB;
--brand40: #AA89F2;
--brand50: #7B57D8;
--brand60: #7039E2;
--brand70: #5C32B8;
--brand80: #3D2A70;
--brand90: #2C2242;
--brand100: #0B051D;
/* Klarna Named Palette */
--colors-klarna-pink: #FFA8CD; /* Signature pink */
--colors-klarna-eggplant: #2F1F5A; /* Deep eggplant */
--colors-klarna-black: #0B051D; /* Near-black */
--colors-klarna-balloon: #B798BE; /* Muted mauve */
--colors-klarna-off-white: #F9F8F5; /* Warm off-white */
--colors-klarna-herring: #E4E3DF; /* Warm light gray */
--colors-klarna-sticky-note: #E6FFA9; /* Lime accent */
/* CTA Pink Scale */
--cta10: #FFD0E2;
--cta20: #FFD0E2;
--cta30: #FFA8CD; /* Base brand CTA */
--cta60: #FFA8CD;
--cta80: #E27EAC; /* Darker hover state */
--cta100: #E27EAC;
/* Border Scale */
--border10: #F3F3F5;
--border20: #E2E2E7;
--border40: #C4C3CA;
--border60: #615F6D;
--border95: #1D192A;
--border100: #0B051D;
/* Status — Positive */
--positive10: #CCF9D4;
--positive20: #A8F3B7;
--colors-bg-positive: #046234;
--colors-bg-positive-subtle: #E6FCEB;
--colors-text-positive: #046234;
--colors-text-positive-heading: #06884A;
/* Status — Warning */
--warning10: #F9F0AA;
--colors-bg-warning: #FBC64D;
--colors-bg-warning-subtle: #FFF5E4;
--colors-text-warning: #664600;
/* Status — Negative */
--colors-bg-negative: #AE1D1D;
--colors-bg-negative-subtle: #FFE6E6;
--colors-text-negative: #931414;
--colors-text-negative-heading: #DC2B2B;
--colors-border-negative: #AE1D1D;
/* Sale */
--saleText: #e61919;
--saleBackground: #ffcfcf;
```
### Tier 2 — Semantic Aliases
```css
/* Surface */
--colors-bg-page: #FFFFFF; /* Page background */
--colors-bg-plain: #FFFFFF; /* Plain white surface */
--colors-bg-container: #F8F7FA; /* Container/section background */
--colors-bg-subtle: #F3F3F5; /* Subtle surface, secondary button bg */
--colors-bg-neutral: #E2E2E7; /* Neutral dividers and fills */
--colors-bg-inverse: #0B051D; /* Dark inverse (footer, hero dark bg) */
--colors-bg-accent: #7039E2; /* Accent surface (purple) */
--colors-bg-brand: #FFA8CD; /* Brand pink surface */
--colors-bg-accent-subtle: #EFECFF; /* Subtle accent surface */
--headerBackground: #fefefe; /* Header background */
--footerBackground: #0B051D; /* Footer background — inverse black */
/* Text */
--colors-text-default: #0B051D; /* Primary text — max contrast */
--colors-text-body: #282636; /* Body copy */
--colors-text-subtle: #504F5F; /* Subtle/secondary text */
--colors-text-disabled: #96959F; /* Disabled state text */
--colors-text-placeholder: #96959F; /* Input placeholder */
--colors-text-inverse: #F9F8F5; /* On dark surfaces */
--colors-text-accent: #582FB4; /* Accent/link text */
--colors-text-accent-heading: #7039E2; /* Accent headings */
--colors-text-link: #582FB4; /* Hyperlinks */
/* Borders */
--colors-border-default: #E2E2E7; /* Default border */
--colors-border-neutral: #C4C3CA; /* Neutral border (stronger) */
--colors-border-active: #0B051D; /* Active/selected border */
--headerBorder: #ebeff5; /* Header bottom border */
/* Focus */
--focus: #7B57D8; /* Focus ring — purple brand */
/* Overlays */
--colors-overlay-hover-default: rgba(0, 0, 0, 0.04);
--colors-overlay-press-default: rgba(0, 0, 0, 0.08);
--colors-overlay-dialog: rgba(0, 0, 0, 0.16);
--colors-overlay-fadeout: rgba(255, 255, 255, 0.56);
--colors-overlay-border-subtle: rgba(11, 5, 29, 0.12);
```
### Tier 3 — Component Tokens
```css
/* Buttons */
--colors-btn-primary: #0B051D; /* Primary button background */
--colors-btn-secondary: #F3F3F5; /* Secondary button background */
--colors-btn-tertiary: #FFFFFF; /* Tertiary button background */
--colors-btn-ghost: rgba(255, 255, 255, 0); /* Ghost button */
--colors-btn-brand: #FFA8CD; /* Brand/CTA pink button */
--colors-btn-danger: #AE1D1D; /* Danger/destructive button */
--colors-btn-disabled: #F3F3F5; /* Disabled button background */
--colors-btn-idle: #C4C3CA; /* Idle button state */
--primary-action-base: #0B051D; /* Primary action base */
--primary-action-hover: #28272E; /* Primary action hover */
--primary-action-active: #0B051D; /* Primary action active */
--primary-action-disabled: #E2E2E7; /* Primary action disabled */
--button-background-secondary: #F3F3F5;
--button-background-secondary-hover: #E2E2E7;
/* Badges */
--colors-badge-strong: #000000;
--colors-badge-pop: #CFF066; /* Lime pop badge */
--colors-badge-brand: #FFA8CD; /* Pink brand badge */
--colors-badge-accent: #E4E0F7; /* Subtle accent badge */
--colors-badge-accent-inverse: #7B57D8; /* Inverse accent badge */
--colors-badge-positive: #C1F4D1;
--colors-badge-positive-inverse: #06884A;
--colors-badge-negative: #FFB8B8;
--colors-badge-negative-inverse: #AE1D1D;
--colors-badge-warning: #FFE8B7;
--colors-badge-warning-inverse: #AD7C00;
/* Inputs */
--inputBackground: #FFFFFF;
--input-background: #FFFFFF;
--inputBackgroundDisabled: #E2E2E7;
--input-background-disabled: #E2E2E7;
/* Ribbons */
--ribbon-default-background: #E4E3DF;
--ribbon-default-text: #0B051D;
--ribbon-sale-background: #FCD3D3;
--ribbon-sale-text: #B62454;
--ribbon-watched-background: #E6FFA9;
--ribbon-watched-text: #0B051D;
--ribbon-in-stock-background: #A8F3B7;
--ribbon-in-stock-text: #00331D;
--ribbon-out-of-stock-background: #FDE2E2;
--ribbon-out-of-stock-text: #500D22;
--ribbon-rank-background: #E4E0F7;
--ribbon-rank-text: #0B051D;
--ribbon-popular-background: #E4E0F7;
--ribbon-popular-text: #0B051D;
--ribbon-unknown-stock-background: #F3F3F5;
--ribbon-unknown-stock-text: #37363F;
```
### Accent / Brand Subgroup (decorative, illustration, data viz)
```css
/* Unassigned brand accents — decorative tiles, illustration, charts */
--saleText: #e61919; /* Sale price text — red */
--colors-badge-warning-inverse: #AD7C00; /* Warning badge inverse */
--colors-text-accent-heading: #7039E2; /* Purple accent heading */
--stickynote30: #AAD336; /* Sticky-note lime */
--colors-data-6: #3F7FDC; /* Data series 6 — blue */
--colors-data-positive: #0EAA5D; /* Data positive — green */
--colors-data-1: #E57DAF; /* Chart series 1 */
--colors-data-2: #7039E2; /* Chart series 2 */
--colors-data-3: #379FAA; /* Chart series 3 */
--colors-data-4: #CF7F3E; /* Chart series 4 */
--colors-data-5: #A03DB3; /* Chart series 5 */
--colors-data-7: #1A6773; /* Chart series 7 */
--colors-data-8: #AC4A85; /* Chart series 8 */
```
| Role | Token | Value |
|------|-------|-------|
| Page bg | `--colors-bg-page` | `#FFFFFF` |
| Section bg | `--colors-bg-container` | `#F8F7FA` |
| Subtle bg | `--colors-bg-subtle` | `#F3F3F5` |
| Inverse bg | `--colors-bg-inverse` | `#0B051D` |
| Brand pink | `--colors-bg-brand` | `#FFA8CD` |
| Primary text | `--colors-text-default` | `#0B051D` |
| Body text | `--colors-text-body` | `#282636` |
| Subtle text | `--colors-text-subtle` | `#504F5F` |
| Inverse text | `--colors-text-inverse` | `#F9F8F5` |
| Link | `--colors-text-link` | `#582FB4` |
| Primary CTA bg | `--colors-btn-primary` | `#0B051D` |
| Brand CTA bg | `--colors-btn-brand` | `#FFA8CD` |
| Focus ring | `--focus` | `#7B57D8` |
---
## 3. Typography System
### Font Stacks
```css
--fonts-font-brand-title: "Klarna Title", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
--fonts-font-brand-text: "Klarna Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
--fonts-font-system: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
--fonts-font-monospace: monospace, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
```
### Weight Scale
```css
--weights-normal: 400;
--weights-medium: 500;
--weights-bold: 700;
```
### Composite Typography Groups
**Rule:** ALWAYS use heading composites together — never set `font-size` without also setting `font-family`, `font-weight`, and `line-height`.
```css
/* ── DISPLAY SCALE (Klarna Title, fluid/responsive) ── */
/* Display S — Mobile: ~32px → Desktop: ~40px */
.type-display-s {
font-family: var(--fonts-font-brand-title);
font-size: var(--textSizes-display-mobile-s); /* calc(32px + (1rem - 16px) * 0.4472) */
font-weight: var(--weights-bold); /* 700 */
line-height: var(--lineHeights-display-mobile-s); /* calc(32px + (1rem - 16px) * 0.7647) */
letter-spacing: normal;
}
/* Display M — Mobile: ~40px → Desktop: ~52px */
.type-display-m {
font-family: var(--fonts-font-brand-title);
font-size: var(--textSizes-display-mobile-m); /* calc(40px + (1rem - 16px) * 0.2709) */
font-weight: var(--weights-bold); /* 700 */
line-height: var(--lineHeights-display-mobile-m); /* calc(40px + (1rem - 16px) * 0.6471) */
letter-spacing: normal;
}
/* Display L — Mobile: ~52px → Desktop: ~64px */
.type-display-l {
font-family: var(--fonts-font-brand-title);
font-size: var(--textSizes-display-mobile-l); /* calc(52px + (1rem - 16px) * 0.1042) */
font-weight: var(--weights-bold); /* 700 */
line-height: var(--lineHeights-display-mobile-l); /* calc(50px + (1rem - 16px) * 0.5) */
letter-spacing: normal;
}
/* Display XL — Mobile: ~68px → Desktop: 84px */
.type-display-xl {
font-family: var(--fonts-font-brand-title);
font-size: var(--textSizes-display-desktop-xl); /* 84px — desktop max */
font-weight: var(--weights-bold); /* 700 — confirmed from h1 computed style */
line-height: var(--lineHeights-display-desktop-xl); /* calc(80px + ...) ≈ 99px on h1 */
letter-spacing: normal;
}
/* ── HEADING SCALE (Klarna Title, fluid) ── */
/* Heading S — ~20–24px */
.type-heading-s {
font-family: var(--fonts-font-brand-title);
font-size: var(--textSizes-heading-mobile-s); /* calc(20px + (1rem - 16px) * 0.8337) */
font-weight: var(--weights-medium); /* 500 */
line-height: var(--lineHeights-heading-mobile-s); /* calc(24px + ...) */
letter-spacing: normal;
}
/* Heading M — ~28–32px */
.type-heading-m {
font-family: var(--fonts-font-brand-title);
font-size: var(--textSizes-heading-mobile-m); /* calc(28px + (1rem - 16px) * 0.5585) */
font-weight: var(--weights-medium); /* 500 */
line-height: var(--lineHeights-heading-mobile-m); /* calc(32px + ...) */
letter-spacing: normal;
}
/* Heading L — ~40–44px */
.type-heading-l {
font-family: var(--fonts-font-brand-title);
font-size: var(--textSizes-heading-mobile-l); /* calc(40px + (1rem - 16px) * 0.2709) */
font-weight: var(--weights-medium); /* 500 — confirmed h2 computed */
line-height: var(--lineHeights-heading-mobile-l); /* calc(40px + ...) */
letter-spacing: normal;
}
/* Heading XL — ~44–52px */
.type-heading-xl {
font-family: var(--fonts-font-brand-title);
font-size: var(--textSizes-heading-mobile-xl); /* calc(44px + (1rem - 16px) * 0.2035) */
font-weight: var(--weights-medium); /* 500 */
line-height: var(--lineHeights-heading-mobile-xl); /* calc(48px + ...) */
letter-spacing: normal;
}
/* ── BODY / TEXT SCALE (Klarna Text / system font) ── */
/* Body XS — 12px / 1rem */
.type-body-xs {
font-family: var(--fonts-font-brand-text);
font-size: var(--textSizes-text-mobile-xs); /* min(0.75rem, ...) ≈ 12px */
font-weight: var(--weights-normal); /* 400 */
line-height: var(--lineHeights-body-mobile-xs); /* min(1rem, ...) ≈ 16px */
letter-spacing: normal;
}
/* Body S — 14px / 20px */
.type-body-s {
font-family: var(--fonts-font-brand-text);
font-size: var(--textSizes-text-mobile-s); /* min(0.875rem, ...) ≈ 14px */
font-weight: var(--weights-normal); /* 400 — confirmed body computed */
line-height: var(--lineHeights-body-mobile-s); /* calc(20px + ...) ≈ 20px */
letter-spacing: normal;
}
/* Body M — 16px / 24px */
.type-body-m {
font-family: var(--fonts-font-brand-text);
font-size: var(--textSizes-text-mobile-m); /* min(1rem, ...) ≈ 16px */
font-weight: var(--weights-normal); /* 400 */
line-height: var(--lineHeights-body-mobile-m); /* calc(24px + ...) ≈ 24px */
letter-spacing: normal;
}
/* Body L — 18–20px / 24-28px */
.type-body-l {
font-family: var(--fonts-font-brand-text);
font-size: var(--textSizes-text-mobile-l); /* calc(18px + ...) */
font-weight: var(--weights-normal); /* 400 */
line-height: var(--lineHeights-body-mobile-l); /* calc(24px + ...) */
letter-spacing: normal;
}
/* ── LABEL SCALE ── */
/* Label M — 16px / 20px (confirmed: label computed style) */
.type-label-m {
font-family: var(--fonts-font-brand-text);
font-size: var(--text-size-m); /* 1rem = 16px */
font-weight: var(--weights-medium); /* 500 */
line-height: var(--lineHeights-label-mobile-m); /* calc(20px + ...) ≈ 20px */
letter-spacing: normal;
}
/* Label S */
.type-label-s {
font-family: var(--fonts-font-brand-text);
font-size: var(--text-size-s); /* 0.875rem = 14px */
font-weight: var(--weights-medium); /* 500 */
line-height: var(--lineHeights-label-mobile-s); /* min(1rem, ...) ≈ 16px */
letter-spacing: normal;
}
```
### Typographic Pairing Rules
| Role | Font | Weight | Token |
|------|------|--------|-------|
| Hero / Display | Klarna Title | 700 | `--fonts-font-brand-title` + `--weights-bold` |
| Section Heading (h2) | Klarna Title | 500 | `--fonts-font-brand-title` + `--weights-medium` |
| Sub-heading (h3) | Klarna Title | 700 | `--fonts-font-brand-title` + `--weights-bold` |
| Body copy | Klarna Text / system | 400 | `--fonts-font-brand-text` + `--weights-normal` |
| Labels / UI text | Klarna Text / system | 500 | `--fonts-font-brand-text` + `--weights-medium` |
| Monospace | system mono | 400 | `--fonts-font-monospace` |
---
## 4. Spacing & Layout
### Base Unit: **4px**
All spacing values are multiples of 4px. NEVER use off-grid values.
```css
/* ── Complete Spacing Scale ── */
--spaces-space-4: 4px; /* Micro — icon gap, tight internal padding */
--spaces-space-8: 8px; /* XS — tag padding, inline gap */
--spaces-space-12: 12px; /* SM — compact component padding */
--spaces-space-16: 16px; /* Base — standard component padding */
--spaces-space-20: 20px; /* MD — button vertical padding approx */
--spaces-space-24: 24px; /* LG — card internal gap, form spacing */
--spaces-space-32: 32px; /* XL — section inner padding */
--spaces-space-40: 40px; /* 2XL — component vertical gap */
--spaces-space-48: 48px; /* 3XL — card grid gap (confirmed: card computed gap:48px) */
--spaces-space-64: 64px; /* 4XL — section vertical padding */
--spaces-space-80: 80px; /* 5XL — large section gap */
```
### Radius Scale
```css
--radius-xs: 2px; /* Micro radius */
--radius-s: 4px; /* Small chip/tag */
--radius-sm: 8px; /* Cards, modals, badges */
--radius-m: 12px; /* Medium panels */
--radius-l: 16px; /* Large panels, card surfaces (confirmed: card computed 16px) */
--radius-xl: 24px; /* XL panels */
--radius-xxl: 32px; /* XXL panels */
--radiuses-radius-round: 99999px; /* Fully pill/circular */
/* CANONICAL BUTTON RADII */
/* Small buttons: 20px (9 elements, confirmed mined) */
/* Standard pill buttons: 100px (7 elements, confirmed mined) — use for ALL primary/brand CTAs */
/* Circular icon buttons: 100% (2 elements, confirmed mined) */
```
### Grid System & Breakpoints
```css
/* ── Breakpoints ── */
/* Mobile base: < 640px */
/* Tablet: 640px */
/* Tablet+: 768px */
/* Small desktop: 1024px */
/* Large desktop: 1536px */
/* Additional intermediate breakpoints extracted: */
/* 400px, 425px, 426px, 476px, 480px, 530px, 550px, 600px, */
/* 639px, 849px, 850px, 896px, 1023px */
/* ── Navigation Layout (from computed: role_navigation) ── */
/* display: flex; flex-direction: row; gap: 48px; align-items: center */
/* ── Card Grid Layout (from computed: card) ── */
/* display: grid; gap: 48px; border-radius: 16px; */
/* ── Primary Button Layout (from computed: button_primary) ── */
/* display: flex; flex-direction: row; justify-content: center; align-items: center; */
/* border-radius: 100px; */
```
---
## 5. Page Structure & Layout Patterns
*No screenshots supplied. Sections inferred from layout digest, component inventory, and computed styles. Rows marked "(inferred)" are not visually confirmed.*
### 5.1 Section Map
| # | Section | Layout Type | Approx Height | Key Elements |
|---|---------|-------------|---------------|--------------|
| 1 | Header / Nav | `flex row`, `gap: 48px` | ~64–80px | Logo, nav links, Login CTA (`--colors-btn-primary`), Shopping link |
| 2 | Hero | Full-width dark (`--colors-bg-inverse: #0B051D`) | ~560–720px (inferred) | H1 @ 90px/700, brand pink accent text, pink CTA button (`--colors-btn-brand`) |
| 3 | Feature Grid / Cards | `display: grid`, `gap: 48px` | ~480–640px (inferred) | Card components with 16px radius, H2 @ 52px, body text, secondary CTAs |
| 4 | App / Product Highlights | Alternating content-image (inferred) | ~400px per row (inferred) | H2 heading, body paragraph, `--colors-btn-primary` CTA, product image |
| 5 | Testimonials / Ratings | Card or list layout (inferred) | ~320px (inferred) | Star rating, quote text, reviewer name, `--ratingBackground: #0B051D` |
| 6 | Badge / Ribbon Showcase | Horizontal scroll or grid (inferred) | ~200px (inferred) | Badge components, ribbon variants |
| 7 | CTA Banner | Full-width inverse or brand-pink bg (inferred) | ~240px (inferred) | H2, single primary CTA pill button |
| 8 | Footer | `display: flex` or grid, dark bg | ~320px (inferred) | `--footerBackground: #0B051D`, `--footer-top-content-text: #FFFFFF`, nav links, legal |
### 5.2 Layout Patterns
**Navigation:**
```
display: flex;
flex-direction: row;
align-items: center;
gap: 48px; /* confirmed: computed role_navigation */
border-bottom: 1px solid var(--headerBorder); /* #ebeff5 */
background: var(--headerBackground); /* #fefefe */
```
**Card Grid:**
```
display: grid;
gap: 48px; /* confirmed: computed card gap */
border-radius: 16px; /* confirmed: computed card border-radius */
background: var(--colors-klarna-off-white); /* #F9F8F5 */
```
**Hero Section (inferred from H1 computed style + bg-inverse token):**
```
background: var(--colors-bg-inverse); /* #0B051D */
color: var(--colors-text-inverse); /* #F9F8F5 */
/* H1: font-size: 90px; font-weight: 700; line-height: 99px; */
/* Primary CTA: background: var(--colors-btn-brand); border-radius: 100px; */
```
**Section containers (inferred from bg-container token):**
```
background: var(--colors-bg-container); /* #F8F7FA */
padding: var(--spaces-space-64) var(--spaces-space-48);
```
### 5.3 Visual Hierarchy
- **H1 at 90px / 700 weight (Klarna Title)** dominates the hero — the largest typographic element on the page.
- **H2 at 52px / 500 weight** heads feature sections; sometimes 41px for tighter sections.
- **Primary CTAs** are pill-shaped (`border-radius: 100px`), dark (`--colors-btn-primary: #0B051D`) or brand-pink (`--colors-btn-brand: #FFA8CD`). The login button is the primary dark CTA; brand/marketing CTAs use pink.
- **Cards** use `gap: 48px` internally (grid) — generous whitespace signals premium brand.
- **Footer** is always full-width inverse dark (`--footerBackground: #0B051D`) with white text.
- **Focus states** use purple ring: `box-shadow: 0 0 0 3px var(--focus)` (`#7B57D8`).
### 5.4 Content Patterns
**Card repeating pattern (inferred from grid + component inventory):**
```
[Icon or image]
[H3 heading — Klarna Title, 52px / 700]
[Body paragraph — Klarna Text, 16px / 400]
[Optional CTA link or button]
```
**Feature alternating row (inferred):**
```
[Left: text block → H2 + body + pill CTA]
[Right: product image or phone mockup]
Alternates L/R on each row.
```
**Badge/Ribbon labeling pattern (confirmed from ribbon token set):**
```
Ribbons appear on product cards: sale (pink/red), trending, popular, watched, rank, in-stock, out-of-stock.
Each ribbon uses a paired text + background token (e.g. --ribbon-sale-text + --ribbon-sale-background).
```
---
## 6. Component Patterns
### 6.1 Button
**Anatomy:** `[optional icon] [label text]`
**Token mappings:**
| State | Background | Text | Border/Shadow | Transform |
|-------|-----------|------|---------------|-----------|
| Default (primary) | `--colors-btn-primary` (#0B051D) | `--colors-text-inverse` (#F9F8F5) | none | — |
| Hover (primary) | `--primary-action-hover` (#28272E) | `--colors-text-inverse` | none | — |
| Active (primary) | `--primary-action-active` (#0B051D) | `--colors-text-inverse` | none | `scale(0.95)` |
| Focus-visible | — | — | `box-shadow: 0 0 0 3px var(--focus)` | — |
| Disabled | `--primary-action-disabled` (#E2E2E7) | `--colors-text-disabled` (#96959F) | none | — |
| Default (brand) | `--colors-btn-brand` (#FFA8CD) | `--colors-text-default` (#0B051D) | none | — |
| Hover (brand) | `--cta80` (#E27EAC) | `--colors-text-default` | none | — |
| Default (secondary) | `--colors-btn-secondary` (#F3F3F5) | `--colors-text-default` | none | — |
| Hover (secondary) | `--button-background-secondary-hover` (#E2E2E7) | `--colors-text-default` | none | — |
**Border radius:** ALWAYS `--radius-lg` (100px) for standard CTAs, `20px` for compact variants.
**Transition:** `box-shadow var(--duration-base) var(--ease-default), background var(--duration-base) var(--ease-default), transform var(--duration-base) var(--ease-default)`
```tsx
// Button.tsx — production-ready with full state handling
import React from 'react';
type ButtonVariant = 'primary' | 'brand' | 'secondary' | 'ghost';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
isLoading?: boolean;
children: React.ReactNode;
}
const variantStyles: Record<ButtonVariant, React.CSSProperties> = {
primary: {
backgroundColor: 'var(--colors-btn-primary)',
color: 'var(--colors-text-inverse)',
},
brand: {
backgroundColor: 'var(--colors-btn-brand)',
color: 'var(--colors-text-default)',
},
secondary: {
backgroundColor: 'var(--colors-btn-secondary)',
color: 'var(--colors-text-default)',
},
ghost: {
backgroundColor: 'var(--colors-btn-ghost)',
color: 'var(--colors-text-default)',
border: '1px solid var(--colors-border-default)',
},
};
export function Button({
variant = 'primary',
isLoading = false,
disabled,
children,
...props
}: ButtonProps) {
const isDisabled = disabled || isLoading;
return (
<button
{...props}
disabled={isDisabled}
style={{
/* Layout */
display: 'inline-flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 'var(--spaces-space-8)',
/* Sizing */
padding: '14px var(--spaces-space-24)',
/* Typography */
fontFamily: 'var(--fonts-font-brand-text)',
fontSize: 'var(--text-size-m)', /* 1rem / 16px */
fontWeight: 'var(--weights-normal)',
lineHeight: '1',
/* Appearance */
borderRadius: '100px', /* --radius-lg canonical value */
border: 'none',
cursor: isDisabled ? 'not-allowed' : 'pointer',
transition: [
`background var(--duration-base) var(--ease-default)`,
`box-shadow var(--duration-base) var(--ease-default)`,
`transform var(--duration-base) var(--ease-default)`,
`color var(--duration-base) var(--ease-default)`,
].join(', '),
/* Disabled override */
...(isDisabled ? {
backgroundColor: 'var(--colors-btn-disabled)',
color: 'var(--colors-text-disabled)',
} : variantStyles[variant]),
...props.style,
}}
onMouseEnter={(e) => {
if (isDisabled) return;
const el = e.currentTarget;
if (variant === 'primary') el.style.backgroundColor = 'var(--primary-action-hover)';
if (variant === 'brand') el.style.backgroundColor = 'var(--cta80)';
if (variant === 'secondary') el.style.backgroundColor = 'var(--button-background-secondary-hover)';
props.onMouseEnter?.(e);
}}
onMouseLeave={(e) => {
if (isDisabled) return;
const el = e.currentTarget;
const bg = variantStyles[variant].backgroundColor as string;
el.style.backgroundColor = bg;
props.onMouseLeave?.(e);
}}
onMouseDown={(e) => {
if (isDisabled) return;
e.currentTarget.style.transform = 'scale(0.95)';
props.onMouseDown?.(e);
}}
onMouseUp={(e) => {
e.currentTarget.style.transform = 'scale(1)';
props.onMouseUp?.(e);
}}
>
{isLoading ? (
<span aria-label="Loading" style={{ opacity: 0.6 }}>···</span>
) : children}
</button>
);
}
```
---
### 6.2 Card
**Anatomy:** `[image/media] [content: heading + body + optional CTA]`
| State | Background | Border/Shadow | Transition |
|-------|-----------|---------------|-----------|
| Default | `--colors-klarna-off-white` (#F9F8F5) | none | — |
| Hover | `--colors-klarna-off-white` | `inset 0 0 0 1px var(--border100)` | `0.6s ease-out` |
| Focus-visible | — | `box-shadow: 0 0 0 3px var(--focus)` | — |
```tsx
// Card.tsx
export function Card({ heading, body, children }: {
heading: string;
body: string;
children?: React.ReactNode;
}) {
const [isHovered, setIsHovered] = React.useState(false);
return (
<div
style={{
display: 'grid',
gap: 'var(--spaces-space-48)', /* confirmed: 48px from computed */
borderRadius: 'var(--radius-l)', /* 16px — confirmed computed */
backgroundColor: 'var(--colors-klarna-off-white)',
boxShadow: isHovered ? 'inset 0 0 0 1px var(--border100)' : 'none',
transition: '0.6s ease-out', /* confirmed: card computed transition */
overflow: 'hidden',
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{children}
<div style={{ padding: 'var(--spaces-space-32)' }}>
<h3 style={{
fontFamily: 'var(--fonts-font-brand-title)',
fontSize: '52px',
fontWeight: 'var(--weights-bold)',
lineHeight: '57.2px',
color: 'var(--colors-text-default)',
margin: '0 0 var(--spaces-space-16)',
}}>
{heading}
</h3>
<p style={{
fontFamily: 'var(--fonts-font-brand-text)',
fontSize: 'var(--text-size-m)',
fontWeight: 'var(--weights-normal)',
lineHeight: 'var(--text-lh-m)',
color: 'var(--colors-text-body)',
margin: 0,
}}>
{body}
</p>
</div>
</div>
);
}
```
---
### 6.3 Input
**Anatomy:** `[optional label] [input field] [optional helper/error text]`
| State | Background | Border | Shadow |
|-------|-----------|--------|--------|
| Default | `--inputBackground` (#FFF) | `1px solid var(--colors-border-neutral)` | none |
| Hover | `--inputBackground` | `1px solid var(--colors-border-active)` | none |
| Focus | `--inputBackground` | transparent | `box-shadow: 0 0 0 3px var(--focus)` |
| Disabled | `--inputBackgroundDisabled` (#E2E2E7) | `1px solid var(--colors-border-default)` | none |
| Error | `--inputBackground` | `1px solid var(--colors-border-negative)` | none |
**Border-radius:** `50px` — pill input (confirmed: computed input `border-radius: 50px`)
```tsx
// Input.tsx
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
helperText?: string;
}
export function Input({ label, error, helperText, disabled, id, ...props }: InputProps) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spaces-space-8)' }}>
{label && (
<label
htmlFor={id}
style={{
fontFamily: 'var(--fonts-font-brand-text)',
fontSize: 'var(--text-size-m)',
fontWeight: 'var(--weights-medium)',
lineHeight: 'var(--text-lh-s)',
color: 'var(--colors-text-default)',
}}
>
{label}
</label>
)}
<input
{...props}
id={id}
disabled={disabled}
style={{
fontFamily: 'var(--fonts-font-brand-text)',
fontSize: 'var(--text-size-s)',
fontWeight: 'var(--weights-normal)',
color: disabled ? 'var(--colors-text-disabled)' : 'var(--colors-text-default)',
backgroundColor: disabled ? 'var(--inputBackgroundDisabled)' : 'var(--inputBackground)',
border: `1px solid ${error ? 'var(--colors-border-negative)' : 'var(--colors-border-neutral)'}`,
borderRadius: '50px', /* confirmed: input computed */
padding: '6px 35px 6px 15px', /* confirmed: input computed */
outline: 'none',
transition: `box-shadow var(--duration-fast) var(--ease-default),
border-color var(--duration-fast) var(--ease-default)`,
cursor: disabled ? 'not-allowed' : 'text',
width: '100%',
boxSizing: 'border-box',
}}
onFocus={(e) => {
e.currentTarget.style.boxShadow = '0 0 0 3px var(--focus)';
props.onFocus?.(e);
}}
onBlur={(e) => {
e.currentTarget.style.boxShadow = 'none';
props.onBlur?.(e);
}}
/>
{(error || helperText) && (
<span style={{
fontFamily: 'var(--fonts-font-brand-text)',
fontSize: 'var(--text-size-xs)',
color: error ? 'var(--colors-text-negative)' : 'var(--colors-text-subtle)',
}}>
{error ?? helperText}
</span>
)}
</div>
);
}
```
---
### 6.4 Badge
**Anatomy:** `[optional icon] [label text]`
| Variant | Background | Text |
|---------|-----------|------|
| Strong | `--colors-badge-strong` (#000) | `--colors-text-inverse` |
| Pop | `--colors-badge-pop` (#CFF066) | `--colors-text-default` |
| Brand | `--colors-badge-brand` (#FFA8CD) | `--colors-text-default` |
| Accent | `--colors-badge-accent` (#E4E0F7) | `--colors-text-accent` (#582FB4) |
| Positive | `--colors-badge-positive` (#C1F4D1) | `--colors-text-positive` (#046234) |
| Negative | `--colors-badge-negative` (#FFB8B8) | `--colors-text-negative` (#931414) |
| Warning | `--colors-badge-warning` (#FFE8B7) | `--colors-text-warning` (#664600) |
```tsx
// Badge.tsx
type BadgeVariant = 'strong' | 'pop' | 'brand' | 'accent' | 'positive' | 'negative' | 'warning';
const badgeTokens: Record<BadgeVariant, { bg: string; text: string }> = {
strong: { bg: 'var(--colors-badge-strong)', text: 'var(--colors-text-inverse)' },
pop: { bg: 'var(--colors-badge-pop)', text: 'var(--colors-text-default)' },
brand: { bg: 'var(--colors-badge-brand)', text: 'var(--colors-text-default)' },
accent: { bg: 'var(--colors-badge-accent)', text: 'var(--colors-text-accent)' },
positive: { bg: 'var(--colors-badge-positive)', text: 'var(--colors-text-positive)' },
negative: { bg: 'var(--colors-badge-negative)', text: 'var(--colors-text-negative)' },
warning: { bg: 'var(--colors-badge-warning)', text: 'var(--colors-text-warning)' },
};
export function Badge({ variant = 'accent', children }: {
variant?: BadgeVariant;
children: React.ReactNode;
}) {
const { bg, text } = badgeTokens[variant];
return (
<span style={{
display: 'inline-flex',
alignItems: 'center',
gap: 'var(--spaces-space-4)',
padding: '2px var(--spaces-space-8)',
borderRadius: 'var(--radiuses-radius-round)', /* pill badge */
backgroundColor: bg,
color: text,
fontFamily: 'var(--fonts-font-brand-text)',
fontSize: 'var(--text-size-xs)',
fontWeight: 'var(--weights-medium)',
lineHeight: 'var(--text-lh-xs)',
whiteSpace: 'nowrap',
}}>
{children}
</span>
);
}
```
---
### 6.5 Nav Item
**Anatomy:** `[label]` (optional icon suffix)
| State | Color | Decoration | Shadow |
|-------|-------|-----------|--------|
| Default | `--colors-text-default` (#0B051D) | none | none |
| Hover | `--colors-text-default` | underline | none |
| Focus-visible | `--colors-text-default` | none | `box-shadow: 0 0 0 3px var(--focus)` |
| Active | `--colors-text-default` | none | none |
**Layout:** Nav container `display: flex; flex-direction: row; gap: 48px; align-items: center` — confirmed from computed `role_navigation`.
```tsx
// NavItem.tsx
export function NavItem({ href, children }: { href: string; children: React.ReactNode }) {
return (
<a
href={href}
style={{
fontFamily: 'var(--fonts-font-brand-text)',
fontSize: 'var(--text-size-m)',
fontWeight: 'var(--weights-normal)',
lineHeight: 'var(--text-lh-m)',
color: 'var(--colors-text-default)',
textDecoration: 'none',
outline: 'none',
borderRadius: 'var(--radius-s)',
padding: '2px var(--spaces-space-4)',
transition: `color var(--duration-fast) var(--ease-default)`,
}}
onMouseEnter={(e) => { e.currentTarget.style.textDecoration = 'underline'; }}
onMouseLeave={(e) => { e.currentTarget.style.textDecoration = 'none'; }}
onFocus={(e) => { e.currentTarget.style.boxShadow = '0 0 0 3px var(--focus)'; }}
onBlur={(e) => { e.currentTarget.style.boxShadow = 'none'; }}
>
{children}
</a>
);
}
```
---
## 7. Elevation & Depth
```css
/* ── Shadow Tokens ── */
--shadows-shadow-s: 0px 2px 4px 0px rgba(0, 0, 0, 0.1); /* Small: input, chip */
--shadows-shadow-m: 0px 6px 12px 0px rgba(0, 0, 0, 0.1); /* Medium: dropdown, tooltip */
--shadows-shadow-l: 0px 12px 24px 0px rgba(0, 0, 0, 0.1); /* Large: modal, dialog */
/* ── Inset Shadow Tokens (from extracted vars) ── */
--shadow-sm: rgb(228, 227, 223) 0px 0px 0px 1px inset; /* Subtle inset border */
--shadow-md: rgba(0, 0, 0, 0) 0px 0px 0px 1px inset; /* Transparent inset (reset) */
--shadow-lg: rgb(226, 226, 231) 0px 2px 6px 0px; /* Light outer glow */
/* ── Card Hover State ── */
/* Cards use inset border on hover — NOT floating shadow */
/* hover: box-shadow: inset 0 0 0 1px var(--border100); */
/* ── Dialog Overlay ── */
--colors-overlay-dialog: rgba(0, 0, 0, 0.16);
/* ── Z-Index Scale (inferred — not extracted from CSS vars) ── */
/* Base content: z-index: 0 */
/* Sticky header: z-index: 100 */
/* Dropdown/menu: z-index: 200 */
/* Modal overlay: z-index: 300 */
/* Modal dialog: z-index: 400 */
/* Toast/alert: z-index: 500 */
```
**Layering principles:**
- Cards NEVER use `box-shadow` at rest — only on hover as an inset border.
- Modal dialogs use `--shadows-shadow-l` + `--colors-overlay-dialog` backdrop.
- The header uses `border-bottom: 1px solid var(--headerBorder)` for elevation separation, not a shadow.
---
## 8. Motion
```css
/* ── Duration Tokens ── */
--duration-fast: 0.2s; /* Micro-interactions: hover color, focus ring */
--duration-base: 0.3s; /* Standard transitions: button bg, nav items */
/* 0.6s ease-out: Card expand transitions — hardcoded in card component */
/* ── Easing Tokens ── */
--ease-default: ease; /* Used on 130+ elements — universal default */
/* ── Button Transition (extracted from computed) ── */
/* transition: box-shadow 0.3s, background 0.3s, color 0.3s, transform 0.3s; */
/* ── Button Active Press (extracted from interactive states) ── */
/* :active → transform: scale(0.95); transition-duration: 0s (immediate snap), 300ms (release) */
/* ── Card Transition (extracted from computed) ── */
/* transition: 0.6s ease-out; */
/* ── Focus Ring Animation ── */
/* focus-visible → box-shadow: 0 0 0 3px var(--focus) */
/* Transition: box-shadow var(--duration-fast) var(--ease-default) */
```
**When to animate:**
- Hover state color/background changes: always, `var(--duration-fast)`.
- Button active press: `scale(0.95)` snap (0s in), release 300ms.
- Card hover border: `0.6s ease-out` — intentionally slow for premium feel.
- Focus rings: `var(--duration-fast)` snap in, no transition out needed.
**When NOT to animate:**
- Disabled state changes (no transition on disabled — state is instant).
- Page-level background color changes.
- Text content changes.
- Decorative data-viz elements (use `prefers-reduced-motion` media query to disable).
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
```
---
## 9. Anti-Patterns & Constraints
1. **Hardcoded hex colours → AI confabulates off-brand values → use CSS var tokens.**
AI agents default to hardcoding `#FF69B4` or `#1A1A2E` when told "use Klarna pink" or "near-black". These hex values have no semantic meaning and bypass the token system. This causes inconsistency when the design updates. **Fix:** Always write `var(--colors-btn-brand)` for the Klarna pink and `var(--colors-btn-primary)` for the dark action button. Never write raw hex in component code.
2. **Rectangular buttons → AI uses 8px border-radius by default → Klarna requires pill-shaped buttons.**
When AI agents generate button components without explicit radius guidance, they default to `border-radius: 4px` or `8px` — which is completely wrong for Klarna's pill-shaped CTA system. This is the most common failure mode. **Fix:** ALWAYS specify `border-radius: 100px` (or `var(--radius-lg)`) for primary and brand CTAs. Only use `20px` for compact/chip-style buttons.
3. **Wrong font-family → AI defaults to Inter, Roboto, or system-ui → Klarna uses custom fonts.**
AI coding agents frequently output `font-family: 'Inter', sans-serif` or `font-family: system-ui` because these are the most common fonts in their training data. This completely loses the Klarna brand character. **Fix:** Always set `font-family: var(--fonts-font-brand-title)` for headings and `var(--fonts-font-brand-text)` for body copy. Both include system fallbacks automatically.
4. **Arbitrary spacing → AI generates `margin: 15px` or `padding: 18px` → breaks 4px grid.**
AI agents generate psychologically "reasonable" spacing values like 15px, 18px, 22px that feel visually close but are off-grid. The extraction found off-grid values (18px, 22px, 26px, 42px) in the source — do not replicate these. **Fix:** Use only `--spaces-space-*` tokens (4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80). If the closest value is `--spaces-space-16`, use it even if your instinct says 15px.
5. **Missing focus states → AI omits `:focus-visible` → WCAG 2.1 AA failure.**
AI agents generate the default, hover, and active states but routinely omit `:focus-visible`. On Klarna, focus is always `box-shadow: 0 0 0 3px var(--focus)` — the purple ring (`#7B57D8`) — never `outline: none` alone. **Fix:** Every interactive element MUST include a `focus-visible` handler that applies `box-shadow: 0 0 0 3px var(--focus)`. Outline is set to `none` only when the box-shadow focus ring is present.
6. **Floating card shadows → AI adds `box-shadow: 0 4px 12px rgba(0,0,0,0.1)` to cards → wrong brand pattern.**
Klarna cards do NOT float. At rest they have no shadow. On hover they use an inset border (`inset 0 0 0 1px var(--border100)`) not an outer shadow. AI agents trained on generic design patterns add drop shadows by default because most design systems do. **Fix:** Cards: `box-shadow: none` at rest. On hover: `box-shadow: inset 0 0 0 1px var(--border100)`. Use `--shadows-shadow-*` tokens only for modals and dropdowns.
7. **Purple as the primary brand color → AI assigns purple as the primary action → incorrect.**
The purple brand scale (`--brand60: #7039E2`, `--colors-text-accent-heading: #7039E2`) is highly saturated and visible. AI agents confuse "prominent brand color" with "primary action color." Purple is ONLY for: accent text, badge-accent, focus rings, and decorative illustration. **Fix:** Primary action buttons are `--colors-btn-primary: #0B051D` (near-black). Brand CTAs are `--colors-btn-brand: #FFA8CD` (pink). Purple is NEVER a button background color.
8. **Dynamic Tailwind class construction → AI writes `bg-[${variant}]` → PurgeCSS removes it.**
If using Tailwind alongside these tokens, AI agents frequently construct class names dynamically: `className={`bg-[var(--${variant})]`}`. PurgeCSS / Tailwind's content scanner cannot detect these and strips them in production builds. **Fix:** Use inline `style={{ backgroundColor: 'var(--colors-btn-primary)' }}` for dynamic token application, or map variants to complete pre-declared class names in a lookup object.
9. **`!important` to override states → AI uses !important for disabled/error states → breaks cascade.**
Disabled and error state overrides applied with `!important` prevent later valid state transitions (e.g. re-enabling a button). AI agents add `!important` when specificity conflicts arise during state handling. **Fix:** Use a single style object computed from props (as shown in the Button component example) that resolves state priority explicitly — no `!important` needed.
10. **Mixing inline styles and Tailwind arbitrarily → AI combines both without a consistent strategy → unmaintainable.**
AI agents will mix `style={{ color: '#0B051D' }}` with `className="text-[#0B051D]"` and `className="text-gray-900"` in the same component. This makes token updates require changes in multiple syntax systems. **Fix:** Pick ONE styling approach per project. For token fidelity, prefer CSS custom properties via `style={{ prop: 'var(--token)' }}` in JSX or CSS Modules with `var(--token)` values.
11. **Using generic carousel/modal component border-radius values as brand tokens → AI extracts wrong radius.**
Third-party plugin components (Bootstrap, cookie banners) may have large or unusual border-radius values that do not represent the Klarna design system. AI agents may see `border-radius: 8px` from an unrelated component and use it on primary buttons. **Fix:** Use only the four canonical radius values: `8px` (cards/panels), `100px` (pill CTAs), `99999px` (circular icons), `16px` (large card surfaces).
---
## 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 (198) */
--text-on-background: #FFFFFF;
--colors-bg-container: #F8F7FA;
--button-background-secondary: #F3F3F5;
--button-background-secondary-hover: #E2E2E7;
--primary-action-active: #0B051D;
--colors-text-accent-heading: #7039E2;
--colors-border-negative: #AE1D1D;
--colors-bg-negative-subtle: #FFE6E6;
--colors-bg-warning: #FBC64D;
--colors-bg-warning-subtle: #FFF5E4;
--colors-text-positive: #046234;
--colors-bg-positive-subtle: #E6FCEB;
--colors-bg-accent-subtle: #EFECFF;
--colors-bg-brand: #FFA8CD;
--colors-text-body: #282636;
--colors-text-subtle: #504F5F;
--textDisabled: #96959F;
--colors-text-inverse: #F9F8F5;
--colors-text-accent: #582FB4;
--colors-text-negative: #931414;
--colors-text-negative-heading: #DC2B2B;
--colors-text-warning: #664600;
--colors-border-warning: #885F00;
--colors-text-positive-heading: #06884A;
--input-decoration-secondary-active: #C4C3CA;
--colors-btn-ghost: rgba(255,255,255,0);
--colors-badge-strong: #000000;
--stickynote20: #CFF066;
--ribbon-rank-background: #E4E0F7;
--focus: #7B57D8;
--colors-badge-positive: #C1F4D1;
--colors-badge-negative: #FFB8B8;
--colors-badge-warning: #FFE8B7;
--colors-badge-warning-inverse: #AD7C00;
--colors-overlay-hover-default: rgba(0,0,0,0.04);
--colors-overlay-hover-inverse: rgba(255, 255, 255, 0.12);
--colors-overlay-press-default: rgba(0,0,0,0.08);
--colors-overlay-press-inverse: rgba(255, 255, 255, 0.16);
--colors-overlay-fadeout: rgba(255,255,255,0.56);
--colors-overlay-dialog: rgba(0,0,0,0.16);
--colors-overlay-image-subtle: rgba(102, 102, 153, 0.05);
--colors-overlay-image-darken: rgba(0, 0, 0, 0.24);
--colors-overlay-border-subtle: rgba(11,5,29,0.12);
--stickynote30: #AAD336;
--colors-data-positive: #0EAA5D;
--colors-data-warning: #DFA200;
--colors-data-1: #E57DAF;
--colors-data-3: #379FAA;
--colors-data-4: #CF7F3E;
--colors-data-5: #A03DB3;
--colors-data-6: #3F7FDC;
--colors-data-7: #1A6773;
--colors-data-8: #AC4A85;
--brand80: #3D2A70;
--colors-data-10: #1F4DA3;
--colors-data-11: #984E22;
--colors-data-12: #65117D;
--colors-klarna-eggplant: #2F1F5A;
--balloon35: #B798BE;
--ribbon-default-background: #E4E3DF;
--ribbon-watched-background: #E6FFA9;
--brand30: #D9C2FB;
--brand40: #AA89F2;
--brand70: #5C32B8;
--brand90: #2C2242;
--primary-action-hover: #28272E;
--cta10: #FFD0E2;
--cta80: #E27EAC;
--content10: #feede2;
--content20: #f3d5d0;
--content40: #e2beb7;
--content60: #bc627b;
--content80: #910737;
--content90: #3d0f1f;
--textContentPrimary: #282b30;
--supportSecondary10: #CCF9D4;
--ribbon-in-stock-background: #A8F3B7;
--supportSecondary30: #88DBA5;
--supportSecondary40: #48A77B;
--positive50: #287E59;
--supportSecondary60: #136E43;
--positive70: #1A5B33;
--supportSecondary80: #014822;
--ribbon-in-stock-text: #00331D;
--warning10: #F9F0AA;
--warning20: #F0E788;
--warning30: #DBCA6E;
--warning40: #A79542;
--warning50: #7C7027;
--warning60: #6D621D;
--warning70: #554C14;
--warning80: #423900;
--negative10: #FFE9E9;
--ribbon-out-of-stock-background: #FDE2E2;
--negative30: #F5BECA;
--negative40: #E47B93;
--negative50: #B94966;
--ribbon-sale-text: #B62454;
--negative70: #942546;
--negative80: #6A152F;
--ribbon-out-of-stock-text: #500D22;
--grayscale50: #706E7B;
--border60: #615F6D;
--textSecondary: #37363F;
--saleText: #e61919;
--saleBackground: #ffcfcf;
--sponsored10: #f5f6f5;
--sponsored20: #d0dad4;
--sponsored60: #2e5e40;
--sponsored80: #2e5d40;
--border95: #1D192A;
--ribbon-sale-background: #FCD3D3;
--linkPrimaryHover: inherit;
--textTertiary: #a1a8b3;
--textContentSecondary: #5b6370;
--overlayPrimary: rgba(52, 52, 52, 0.7);
--overlaySecondary: rgba(255, 255, 255, 0.5);
--overlayContent: rgba(51, 53, 54, 0.04);
--overlayWatched: rgba(77, 150, 105, 0.05);
--overlaySale: rgba(215, 24, 24, 0.05);
--overlayTrending: rgba(19, 121, 212, 0.05);
--overlayContentImage: #52031e;
--ratingText: #fefefe;
--headerBorder: #ebeff5;
--header-background: var(--grayscale0);
--frameBackgroundHover: #f1f1f1;
--frameBackgroundPressed: #e2e2e2;
--brand-primary-cta: rgb(11, 5, 29); /* Primary CTA background, dominant on 1 button — e.g. "Login" /* mined from computed styles */ */
--brand-secondary-cta: rgb(255, 168, 205); /* Secondary CTA background, dominant on 2 buttons — e.g. "button" /* mined from computed styles */ */
--brand-surface-2: rgb(255, 208, 226); /* Brand surface, dominant on 1 element — e.g. "div" /* mined from computed styles */ */
--brand-surface-3: rgb(230, 255, 169); /* Brand surface, dominant on 1 element — e.g. ""Sehr schnelles und einfaches " /* mined from computed styles */ */
--colors-bg-page: #FFFFFF;
--colors-bg-subtle: #F3F3F5;
--colors-bg-inverse: #0B051D;
--colors-text-default: #0B051D;
--colors-text-link: #582FB4;
--colors-btn-primary: #0B051D;
--colors-btn-brand: #FFA8CD;
--colors-btn-secondary: #F3F3F5;
--colors-border-default: #E2E2E7;
--colors-bg-positive: #046234;
--colors-bg-negative: #AE1D1D;
--brand10: #EFECFF;
--brand20: #E4E0F7;
--brand50: #7B57D8;
--brand60: #7039E2;
--brand100: #0B051D;
--colors-klarna-pink: #FFA8CD;
--colors-klarna-black: #0B051D;
--colors-klarna-balloon: #B798BE;
--colors-klarna-off-white: #F9F8F5;
--colors-klarna-herring: #E4E3DF;
--colors-klarna-sticky-note: #E6FFA9;
--cta20: #FFD0E2;
--cta30: #FFA8CD;
--cta60: #FFA8CD;
--cta100: #E27EAC;
--border10: #F3F3F5;
--border20: #E2E2E7;
--border40: #C4C3CA;
--border100: #0B051D;
--positive10: #CCF9D4;
--positive20: #A8F3B7;
--colors-bg-plain: #FFFFFF;
--colors-bg-neutral: #E2E2E7;
--colors-bg-accent: #7039E2;
--headerBackground: #fefefe;
--footerBackground: #0B051D;
--colors-text-disabled: #96959F;
--colors-text-placeholder: #96959F;
--colors-border-neutral: #C4C3CA;
--colors-border-active: #0B051D;
--colors-btn-tertiary: #FFFFFF;
--colors-btn-danger: #AE1D1D;
--colors-btn-disabled: #F3F3F5;
--colors-btn-idle: #C4C3CA;
--primary-action-base: #0B051D;
--primary-action-disabled: #E2E2E7;
--colors-badge-pop: #CFF066;
--colors-badge-brand: #FFA8CD;
--colors-badge-accent: #E4E0F7;
--colors-badge-accent-inverse: #7B57D8;
--colors-badge-positive-inverse: #06884A;
--colors-badge-negative-inverse: #AE1D1D;
--inputBackground: #FFFFFF;
--input-background: #FFFFFF;
--inputBackgroundDisabled: #E2E2E7;
--input-background-disabled: #E2E2E7;
--ribbon-default-text: #0B051D;
--ribbon-watched-text: #0B051D;
--ribbon-rank-text: #0B051D;
--ribbon-popular-background: #E4E0F7;
--ribbon-popular-text: #0B051D;
--ribbon-unknown-stock-background: #F3F3F5;
--ribbon-unknown-stock-text: #37363F;
--colors-data-2: #7039E2;
--colors-text-warning-heading: #885F00;
--colors-border-positive: #06884A;
/* Typography (52) */
--fonts-font-system: -apple-system, ...;
--fonts-font-monospace: monospace, -apple-system, ...;
--fonts-font-brand-title: "Klarna Title", -apple-system, ...;
--fonts-font-brand-text: "Klarna Text", -apple-system, ...;
--textSizes-display-mobile-s: calc(32px + (1rem - 16px) * 0.4472);
--textSizes-display-desktop-s: calc(40px + (1rem - 16px) * 0.2709);
--textSizes-display-desktop-m: calc(52px + (1rem - 16px) * 0.1042);
--textSizes-display-desktop-l: calc(64px + (1rem - 16px) * 0.0254);
--textSizes-display-mobile-xl: calc(68px + (1rem - 16px) * 0.013);
--textSizes-display-desktop-xl: 84px;
--textSizes-heading-mobile-s: calc(20px + (1rem - 16px) * 0.8337);
--textSizes-heading-desktop-s: calc(24px + (1rem - 16px) * 0.687);
--textSizes-heading-mobile-m: calc(28px + (1rem - 16px) * 0.5585);
--textSizes-heading-desktop-l: calc(44px + (1rem - 16px) * 0.2035);
--textSizes-text-mobile-xs: min(0.75rem, calc(24px + (1rem - 32px) * 0.3435));
--textSizes-text-mobile-s: min(0.875rem, calc(28px + (1rem - 32px) * 0.2793));
--textSizes-text-mobile-m: min(1rem, calc(32px + (1rem - 32px) * 0.2236));
--textSizes-text-mobile-l: calc(18px + (1rem - 16px) * 0.9143);
--lineHeights-body-mobile-xs: min(1rem, calc(32px + (1rem - 32px) * 0.3824));
--lineHeights-label-mobile-xs: min(0.75rem, calc(24px + (1rem - 32px) * 0.4412));
--shadows-shadow-s: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
--shadows-shadow-m: 0px 6px 12px 0px rgba(0, 0, 0, 0.1);
--shadows-shadow-l: 0px 12px 24px 0px rgba(0, 0, 0, 0.1);
--text-tertiary: var(--grayscale60);
--text-content-primary: var(--text-primary);
--text-content-secondary: var(--text-secondary);
--rating-text: var(--text-on-background);
--text-size-xs: 0.75rem;
--text-size-s: 0.875rem;
--text-size-m: 1rem;
--text-size-l: 1.25rem;
--text-size-xl: 1.625rem;
--text-lh-xxs: 14px;
--text-lh-xs: 18px;
--text-lh-s: 20px;
--text-lh-m: 24px;
--text-lh-l: 32px;
--text-lh-xl: 36px;
--heading-size-l: 2rem;
--heading-size-xl: 2.5625rem;
--heading-size-xxl: 3.25rem;
--font-normal: Klarna Text;
--heading-font-normal: Klarna Title;
--font-size-xs: 12px; /* 14 elements — e.g. p "Kundenbewertungen sp", p "Sieh dir unsere 5-St", p "¹ Verdiene Cashback " /* mined from computed styles */ */
--font-size-md: 16px; /* 38 elements — e.g. p "4.8/5 im App Store", p "Bleib mit Klarnas Za", p "Shoppe mit Vertrauen" /* mined from computed styles */ */
--font-size-xl: 41px; /* 6 elements — e.g. h2 "Flexible Zahlungsmet", h2 "Keine Gebühren bei p", h2 "Reibungsloser Checko" /* mined from computed styles */ */
--font-size-2xl: 52px; /* 4 elements — e.g. h2 "Nutze Klarna überall", h2 "6 smarte Gründe für ", h2 "Finde großartige Dea" /* mined from computed styles */ */
--font-size-3xl: 90px; /* 4 elements — e.g. h1 "Bezahlesicher mitKla", span "Bezahle", span "sicher mit" /* mined from computed styles */ */
--font-weight-regular: 400; /* 38 elements — e.g. p "Vorsicht! Du könntes", p "Shoppe sicher und en", p "Erlebe sicheres Shop" /* mined from computed styles */ */
--font-weight-medium: 500; /* 43 elements — e.g. h2 "Nutze Klarna überall", h2 "6 smarte Gründe für ", h2 "Flexible Zahlungsmet" /* mined from computed styles */ */
--font-weight-semibold: 700; /* 9 elements — e.g. h1 "Bezahlesicher mitKla", h2 "Finde großartige Dea", h3 "Alle lieben Klarna" /* mined from computed styles */ */
--line-height-tight: 12.6px; /* 14 elements — e.g. p "Kundenbewertungen sp", p "Sieh dir unsere 5-St", p "¹ Verdiene Cashback " /* mined from computed styles */ */
/* Spacing (26) */
--spaces-space-4: 4px;
--spaces-space-8: 8px;
--spaces-space-12: 12px;
--spaces-space-16: 16px;
--spaces-space-20: 20px;
--spaces-space-24: 24px;
--heading-lh-m: 32px;
--spaces-space-40: 40px;
--spaces-space-48: 48px;
--spaces-space-64: 64px;
--spaces-space-80: 80px;
--header-border: var(--border20);
--heading-lh-xxs: 18px;
--heading-lh-xs: 22px;
--heading-lh-s: 26px;
--heading-lh-l: 36px;
--heading-lh-xl: 42px;
--heading-lh-xxl: 52px;
--spaces-space-32: 32px;
--heading-size-xxs: 0.875rem;
--heading-size-xs: 1rem;
--heading-size-s: 1.25rem;
--heading-size-m: 1.625rem;
--heading-size-l: 2rem;
--heading-size-xl: 2.5625rem;
--heading-size-xxl: 3.25rem;
/* Radius (13) */
--radius-s: 4px;
--radius-sm: 8px;
--radius-m: 12px;
--radius-l: 16px;
--radius-xl: 24px;
--radius-xxl: 32px;
--radiuses-radius-round: 99999px;
--radius-xs: 2px;
--radius-sm: 20px; /* 9 elements — e.g. button .sc-bb78919f-7, button .sc-221f0a72-0 "Entdecke Klarna", button .sc-221f0a72-0 "Shopping" /* mined from computed styles */ */
--radius-md: 50px; /* 1 element — e.g. input /* mined from computed styles */ */
--radius-lg: 100px; /* 7 elements — e.g. button .bOVWr0jn4Q, button .sc-221f0a72-0, button .sc-221f0a72-0 /* mined from computed styles */ */
--radius-full: 100%; /* 2 elements — e.g. button .pr-1a81ze9-Carousel-arrowButton-Carousel-arrowLeft, button .pr-1tgi3pt-Carousel-arrowButton-Carousel-arrowRight /* mined from computed styles */ */
--radius-lg: 100px;
/* Effects (26) */
--lineHeights-display-mobile-s: calc(32px + (1rem - 16px) * 0.7647);
--lineHeights-display-desktop-s: calc(40px + (1rem - 16px) * 0.6471);
--lineHeights-display-desktop-m: calc(52px + (1rem - 16px) * 0.4706);
--lineHeights-display-mobile-l: calc(50px + (1rem - 16px) * 0.5);
--lineHeights-display-desktop-l: calc(62px + (1rem - 16px) * 0.3235);
--lineHeights-display-mobile-xl: calc(66px + (1rem - 16px) * 0.2647);
--lineHeights-display-desktop-xl: calc(80px + (1rem - 16px) * 0.0588);
--lineHeights-heading-mobile-s: calc(24px + (1rem - 16px) * 0.8824);
--lineHeights-heading-desktop-l: calc(48px + (1rem - 16px) * 0.5294);
--lineHeights-heading-desktop-xl: calc(56px + (1rem - 16px) * 0.4118);
--lineHeights-body-mobile-s: calc(20px + (1rem - 16px) * 0.9412);
--lineHeights-body-desktop-l: calc(28px + (1rem - 16px) * 0.8235);
--weight-normal: 400;
--weight-medium: 500;
--weights-bold: 700;
--heading-content: var(--grayscale100);
--shadowPrimary: none;
--shadow-sm: rgb(228, 227, 223) 0px 0px 0px 1px inset; /* 2 elements — e.g. button .gY_w21Xq3A, button .OUxE8BVE5e /* mined from computed styles */ */
--shadow-md: rgba(0, 0, 0, 0) 0px 0px 0px 1px inset; /* 1 element — e.g. button .bOVWr0jn4Q /* mined from computed styles */ */
--shadow-lg: rgb(226, 226, 231) 0px 2px 6px 0px; /* 2 elements — e.g. button .pr-1a81ze9-Carousel-arrowButton-Carousel, button .pr-1tgi3pt-Carousel-arrowButton-Carousel /* mined from computed styles */ */
--shadows-shadow-s: 0px 2px 4px 0px rgba(0,0,0,0.1);
--shadows-shadow-m: 0px 6px 12px 0px rgba(0,0,0,0.1);
--shadows-shadow-l: 0px 12px 24px 0px rgba(0,0,0,0.1);
--shadow-sm: rgb(228,227,223) 0px 0px 0px 1px inset;
--shadow-md: rgba(0, 0, 0, 0) 0px 0px 0px 1px inset;
--shadow-lg: rgb(226,226,231) 0px 2px 6px 0px;
/* Motion (3) */
--duration-fast: 0.2s; /* 24 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-base: 0.3s; /* 16 elements — e.g. button, button, button /* mined from computed styles */ */
--ease-default: ease; /* 130 elements — e.g. button, button, button /* mined from computed styles */ */
```
## Appendix B: Token Source Metadata
```
tokenSource: extracted-css-vars
site: klarna.com
confidence: high
totalVarsFound: 538
curatedTokens: 45 (mapped to standard roles with --klarna-* aliases)
preservedNames: yes — all original CSS variable names preserved 1:1
(e.g. --colors-bg-brand, --fonts-font-brand-title, --focus, --headerBorder)
Curated --klarna-* aliases are SUPPLEMENTARY identifiers only;
original names are the canonical references throughout this document.
clusteringMethod:
- Colour grouping: clustered by semantic role (surface, text, border, button, badge, ribbon,
status, data-viz) from extracted var name prefixes
- Radius: mined from 22 page elements; identified pill pattern (100px on 7 CTAs, 50px on input)
- Spacing: confirmed 4px grid from --spaces-space-* token set (11 steps: 4–80px)
- Typography: composite groups assembled from textSizes-*, lineHeights-*, fonts-*, weights-* vars
fontDeclarations:
- "Klarna Title" (weight: 400 declared; 700 computed on h1/h3 — weight applied via font-weight)
- "Klarna Text" (custom brand text face — referenced in CSS vars, not in @font-face extraction)
- System font stack as fallback throughout
bootstrapDetected: true (may introduce conflicting radius/spacing overrides — use CSS var tokens
to override Bootstrap defaults explicitly)
reconstructedTokens: none — all tokens derived from extracted CSS custom properties
(not from computed style clustering)
authorityNote:
This file documents klarna.com's live production CSS as of extraction date.
Tokens are implementation-accurate, not Figma design intent.
For authoritative design intent, cross-reference with the Klarna design system (Brix).
```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