Ramp
MIT
Clean, modern fintech design system with warm typography and purposeful colour accents for financial software interfaces
Colour (74)
color.black#1a1919
color.blaze#e96516
color.solar#e4f222
color.white#ffffff
color.black0rgba(var(--black-rgb),0)
color.cardbgvar(--grayLight)
color.spring#5683d2
color.white0rgba(var(--white-rgb),0)
color.black25rgba(var(--black-rgb),.025)
color.black50rgba(var(--black-rgb),.05)
color.inputbgtransparent
color.smolder#17332d
color.white25rgba(var(--white-rgb),.025)
color.white50rgba(var(--white-rgb), 0.05)
color.black100rgba(var(--black-rgb),.1)
color.black200rgba(var(--black-rgb),.2)
color.black300rgba(var(--black-rgb),.3)
color.black400rgba(var(--black-rgb),.4)
color.black500rgba(var(--black-rgb),.5)
color.black600rgba(var(--black-rgb),.6)
color.black700rgba(var(--black-rgb),.7)
color.black800rgba(var(--black-rgb),.8)
color.black900rgba(var(--black-rgb),.9)
color.graydark#6e6a68
color.white100rgba(var(--white-rgb),.1)
color.white200rgba(var(--white-rgb),.2)
color.white300rgba(var(--white-rgb),.3)
color.white400rgba(var(--white-rgb),.4)
color.white500rgba(var(--white-rgb),.5)
color.white600rgba(var(--white-rgb),.6)
color.white700rgba(var(--white-rgb),.7)
color.white800rgba(var(--white-rgb),.8)
color.white900rgba(var(--white-rgb),.9)
color.graylight#f4f2f0
color.rampbgapp#ffffff
color.graymedium#d2cecb
color.rampaccent#e96516
color.rampborder#d2cecb
color.solarlight#f5ff78
color.texthushed#0c0a0899
color.borderwidth1px
color.inputbordervar(--border-primary)
color.rampbgblack#1a1919
color.rampbghovervar(--solarLight)
color.rampwarning#e96516
color.springlight#e4ebf6
color.textprimary#0c0a08
color.btnprimarybgvar(--solar)
Spacing (28)
spacing.navbannerheight0px
spacing.elevationinsetglowinset 0 0 2px rgba(255, 255, 255, 0.6)
spacing.space14px
spacing.rampspacexs4px
spacing.space28px
spacing.rampspacesm8px
spacing.space312px
spacing.rampspacemd12px
spacing.space416px
spacing.rampspacelg16px
spacing.inputpaddingx16px
spacing.space520px
spacing.space624px
spacing.cardpadding24px
spacing.rampspacexl24px
spacing.space832px
spacing.rampspace2xl32px
spacing.space1248px
spacing.rampspace3xl48px
spacing.spacerm64px
spacing.rampspacesectionsm64px
spacing.spacerl128px
spacing.rampspacesectionlg128px
spacing.bpsm600px
spacing.bpmd768px
spacing.bplg992px
spacing.bpxl1024px
spacing.bp2xl1100px
Radius (10)
rampradiussm4–6px
radiussm6px
radiusmd10px
inputradius10px
rampradiusmd10px
cardradius12px
rampradiuslg12px
radiuslg16px
rampradiusxl16px
rampradiusfull16px
Shadow (14)
effect.zmodal500
effect.ztoast800
effect.zheader300
effect.blackrgb33,33,33
effect.whitergb255,255,255
effect.znavmenu900
effect.zoverlay400
effect.zpopover600
effect.ztooltip700
effect.navheightcalc(62px + var(--nav-banner-height))
effect.pagethemevar(--white)
effect.lausanneascent1866
effect.lausannedescent-410
effect.lausanneunitsperem2048
# layout.md — Ramp Design System
Design system extracted from ramp.com. Token source: `extracted-css-vars` (high confidence, 60 CSS custom properties). Preserve original CSS variable names exactly. Use `--ramp-*` curated aliases for standard roles when working in app code.
---
## 0. Quick Reference
**Stack:** React + Tailwind (v3-style utilities detected) + CSS custom properties. Font: `lausanne` (custom, weights 300/350/400/700) with Arial fallback.
**Token source:** `extracted-css-vars` — use original names where available, curated `--ramp-*` aliases for semantic roles.
**How to apply:** Use as `var(--token-name)` in CSS, `style={{ color: 'var(--ramp-text-primary)' }}` in JSX, or `bg-[var(--ramp-bg-surface)]` in Tailwind.
```css
/* ── CORE TOKENS ── */
:root {
/* Colours */
--ramp-accent: #e96516;
--ramp-bg-app: #ffffff;
--ramp-border: #d2cecb;
--ramp-warning: #e96516;
--ramp-bg-hover: var(--solarLight);
--ramp-bg-overlay: rgba(0, 0, 0, 0.25);
--ramp-bg-surface: #f4f2f0;
--ramp-text-muted: #ffffff99;
--ramp-accent-hover: #d2cecb;
--ramp-border-focus: #e4ebf6;
--ramp-text-primary: #0c0a08;
--ramp-accent-subtle: #f5ff78;
--ramp-text-secondary: #0c0a0899;
--ramp-accent-foreground: #0c0a08;
/* Other */
--ramp-space-lg: 16px;
--ramp-space-md: 12px;
--ramp-space-sm: 8px;
--ramp-space-xl: 24px;
--ramp-space-xs: 4px;
--ramp-font-sans: "lausanne", "lausanne Fallback", Arial, sans-serif;
--ramp-radius-lg: 12px;
--ramp-radius-md: 10px;
--ramp-radius-sm: 4–6px;
--ramp-space-2xl: 32px;
--ramp-space-3xl: 48px;
--ramp-radius-full: 16px;
}
```
```tsx
// Canonical primary CTA — the yellow "Solar" button
<button
className="inline-flex items-center justify-center h-11 px-5
font-[var(--ramp-font-sans)] text-[16px] font-normal text-[var(--ramp-text-primary)]
bg-[var(--ramp-accent-solar)] rounded-[var(--ramp-radius-sm)]
transition-colors duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]
hover:bg-[var(--ramp-accent-solar-light)]
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ramp-border-focus)]
disabled:opacity-50 disabled:cursor-not-allowed">
Get started
</button>
```
**NEVER rules:**
1. **NEVER** use `Inter`, `Roboto`, or system-ui as the primary font — this site is `lausanne` only. Without it the brand identity collapses.
2. **NEVER** invent colour tokens outside the extracted palette. Consolidate duplicates (e.g. `--grayMedium` and `--border-primary` are the same `#d2cecb`).
3. **NEVER** use border-radius > 16px on cards/buttons. Ramp is geometric, not pill-shaped.
4. **NEVER** use raw hex values in components — always reference a token.
5. **NEVER** use font-weight 500/600. The lausanne scale is 300 / 350 / 400 / 700 only.
6. **NEVER** animate with default `ease` — use `cubic-bezier(0.4, 0, 0.2, 1)` at 300ms.
7. **NEVER** place the yellow `--solar` CTA on a yellow/warm background — it requires white or dark surfaces for contrast.
8. **NEVER** use Tailwind arbitrary spacing like `p-[23px]`. Stick to 4px grid multiples.
Full design system → see sections below.
---
## 1. Design Direction & Philosophy
**Character:** Editorial-fintech. Clean, confident, slightly warm. The typography does the heavy lifting — large, lightweight `lausanne` headings carry the page, with restrained chrome.
**Aesthetic:**
- **Neutral-first palette** (off-white `#fff`, warm-gray `#f4f2f0`, near-black `#0c0a08`) punctuated by bold accent moments — the electric yellow `--solar #e4f222` and the orange `--blaze #e96516`.
- **Generous whitespace:** section rhythm uses 64px and 128px vertical spacers — not 48/96.
- **Geometric, not soft:** radius scale tops out at 16px for feature cards. Buttons are 6px. No pills.
- **Gradients are narrative, not decorative:** `--dusk`, `--midnight`, `--daylight` are full-bleed section backgrounds representing time of day — never on buttons or small elements.
- **Marquee motion:** horizontal scrolling accolades/logos appear in multiple sections. Use `HomeAccolades` keyframe pattern.
**Explicitly rejects:**
- Rounded pill buttons (radius ≥ 50px).
- Drop shadows for elevation — Ramp uses flat colour blocks and subtle inner glows (`inset 0 0 2px rgba(255,255,255,0.6)`) instead.
- Cool blues as primary CTA. The primary CTA is always `--solar` yellow.
- Warm/dusty corporate photography overlays. Ramp uses product screenshots and clean iconography.
- Multiple accent colours on a single screen — pick `--solar` OR `--blaze`, never both in the same hero.
---
## 2. Colour System
### Tier 1 — Primitives (original names preserved)
```css
/* Neutrals */
--white: #ffffff;
--black: #1a1919;
--grayLight: #f4f2f0; /* warm off-white, card bg */
--grayMedium: #d2cecb; /* border default */
--grayDark: #6e6a68; /* subdued text */
/* Text primitives */
--text-primary: #0c0a08; /* not pure black — warm near-black */
--text-primaryReverse: #ffffff;
--text-hushed: #0c0a0899; /* 60% alpha on dark warm */
--text-hushedReverse: #ffffff99;
/* Border */
--border-primary: #d2cecb; /* identical to --grayMedium — consolidate */
/* Brand accents */
--blaze: #e96516; /* orange — secondary accent, links */
--solar: #e4f222; /* electric yellow — primary CTA */
--solarLight: #f5ff78; /* solar hover */
--smolder: #17332d; /* deep forest green — rare accent */
--spring: #5683d2; /* cornflower blue */
--springLight: #e4ebf6; /* pale blue — focus ring */
/* Gradients (full-bleed sections only) */
--dusk: linear-gradient(in oklab to top, #e4ebf6 8%, #c3d3ef 20%, #5683d2 48%, #001b4a 100%);
--midnight: linear-gradient(in oklab to bottom, #000 0%, #112d5b 100%);
--daylight: linear-gradient(in oklab to bottom, #d2dff3 22%, #f2f5f9 93%);
/* Alpha scales — available as --white-0..900 and --black-0..900 */
--white-rgb: 255, 255, 255;
--black-rgb: 33, 33, 33;
/* e.g. --white-50: rgba(var(--white-rgb), 0.05); */
```
### Tier 2 — Semantic Aliases (curated `--ramp-*`)
| Token | Value | Usage |
|---|---|---|
| `--ramp-text-primary` | `#0c0a08` | body copy, headings on light |
| `--ramp-text-secondary` | `#0c0a0899` | captions, meta, hushed labels |
| `--ramp-text-on-dark` | `#ffffff` | text on `--black`/`--midnight` |
| `--ramp-text-muted-on-dark` | `#ffffff99` | hushed text on dark |
| `--ramp-bg-app` | `#ffffff` | page background |
| `--ramp-bg-surface` | `#f4f2f0` | card/panel bg |
| `--ramp-bg-inverse` | `#1a1919` | dark hero/section bg |
| `--ramp-border` | `#d2cecb` | default borders, dividers |
| `--ramp-border-focus` | `#e4ebf6` | focus ring colour |
| `--ramp-accent-primary` | `#e4f222` | primary CTA bg (solar) |
| `--ramp-accent-primary-hover` | `#f5ff78` | primary CTA hover |
| `--ramp-accent-link` | `#e96516` | inline links, underlines (blaze) |
### Tier 3 — Component Tokens
```css
/* Button primary */
--btn-primary-bg: var(--solar);
--btn-primary-bg-hover: var(--solarLight);
--btn-primary-fg: var(--text-primary);
/* Button secondary (dark) */
--btn-secondary-bg: var(--black);
--btn-secondary-fg: var(--white);
/* Card */
--card-bg: var(--grayLight);
--card-fg: var(--text-primary);
--card-radius: 12px;
--card-padding: 24px;
/* Input */
--input-bg: transparent;
--input-border: var(--border-primary);
--input-radius: 10px;
--input-padding-x: 16px;
/* Nav */
--nav-height: calc(62px + var(--nav-banner-height));
--nav-banner-height: 0px;
```
---
## 3. Typography System
**Font stack:**
```css
--ramp-font-sans: "lausanne", "lausanne Fallback", Arial, sans-serif;
/* Weights available: 300, 350, 400, 700. Italic at 300/350/400/700. */
/* Metric overrides from extraction:
--lausanne-ascent: 1866;
--lausanne-descent: -410;
--lausanne-units-per-em: 2048; */
```
**Composite type scales** (font-family is `--ramp-font-sans` for all):
```css
/* Display / H1 — hero headline */
--type-h1: {
font-size: 48px;
font-weight: 400;
line-height: 50px; /* tight: ratio 1.04 */
letter-spacing: -0.01px;
}
/* H2 — section heading */
--type-h2: {
font-size: 40px;
font-weight: 400;
line-height: 42px; /* ratio 1.05 */
letter-spacing: -0.005px;
}
/* H3 — subsection */
--type-h3: {
font-size: 24px;
font-weight: 400;
line-height: 28px;
letter-spacing: normal;
}
/* Body — default paragraph */
--type-body: {
font-size: 16px;
font-weight: 400;
line-height: 24px; /* ratio 1.5 */
letter-spacing: normal;
}
/* Small / meta */
--type-small: {
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: normal;
}
/* Eyebrow / tab / uppercase label */
--type-eyebrow: {
font-size: 10px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0.18px;
text-transform: uppercase;
}
/* Button label */
--type-button: {
font-size: 16px;
font-weight: 400;
line-height: 1;
letter-spacing: normal;
}
```
**Pairing rules:**
- Headings (H1/H2) are **always weight 400**. Never bold them — the visual weight comes from size, not weight.
- Body and buttons share the same 16px/400 rhythm — differentiation is colour and background, not type.
- Use weight **700** only for strong emphasis inline within body copy, never for headings.
- Eyebrows always uppercase with +0.18px tracking.
---
## 4. Spacing & Layout
**Base grid:** 4px. All spacing is a multiple of 4.
```css
/* Component-scale spacing */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px; /* card gap unit, input padding-x */
--space-5: 20px; /* button padding-x */
--space-6: 24px; /* card padding, H3 margin */
--space-8: 32px; /* card internal gap */
--space-12: 48px;
/* Section-scale spacing (preserved originals) */
--spacer-m: 64px; /* alias: --ramp-space-section-sm */
--spacer-l: 128px; /* alias: --ramp-space-section-lg */
/* Navigation */
--nav-height: calc(62px + var(--nav-banner-height));
--nav-banner-height: 0px;
```
**Breakpoints (from extracted media queries):**
```css
--bp-sm: 600px; /* mobile landscape */
--bp-md: 768px; /* tablet */
--bp-lg: 992px; /* small desktop */
--bp-xl: 1024px; /* desktop */
--bp-2xl: 1100px; /* large desktop */
```
**Containers:**
- Max content width: ~1200–1280px, centred with `margin-inline: auto`.
- Side gutters: 16px (mobile) → 24px (tablet) → 48px (desktop).
**Flex vs Grid decision:**
- **Flex** for 1-axis layouts: nav bars, button rows, card internals (`display: flex; flex-direction: column; justify-content: space-between;` is the standard card layout from extraction).
- **Grid** for 2-axis feature card layouts (3-col and 4-col logo/card walls).
- Section content containers: flex column with explicit `gap`. Never use margin-top on children.
---
## 6. Component Patterns
### 6.1 Button — Primary (Solar CTA)
**Anatomy:** container + label (+ optional leading/trailing icon).
| State | Background | Foreground | Border | Notes |
|---|---|---|---|---|
| default | `--solar` `#e4f222` | `--text-primary` | none | radius 6px |
| hover | `--solarLight` `#f5ff78` | `--text-primary` | none | 300ms ease |
| focus-visible | `--solar` | `--text-primary` | ring `--springLight` 2px offset 2px | |
| active | `--solar` | `--text-primary` | none | translateY(1px) optional |
| disabled | `--solar` at 50% opacity | `--text-primary` | none | `cursor: not-allowed` |
| loading | `--solar` | transparent label | none | spinner overlay, `--motion-spin` |
| error | `--blaze` | `--white` | none | error-flash 2s |
```tsx
import { Loader2 } from "lucide-react";
type ButtonProps = {
variant?: "primary" | "secondary";
isLoading?: boolean;
hasError?: boolean;
disabled?: boolean;
children: React.ReactNode;
onClick?: () => void;
};
export function Button({
variant = "primary", isLoading, hasError, disabled, children, onClick,
}: ButtonProps) {
const base =
"inline-flex items-center justify-center h-11 px-5 " +
"font-[var(--ramp-font-sans)] text-[16px] leading-none font-normal " +
"rounded-[6px] transition-colors duration-300 " +
"ease-[cubic-bezier(0.4,0,0.2,1)] " +
"focus-visible:outline-none focus-visible:ring-2 " +
"focus-visible:ring-offset-2 focus-visible:ring-[var(--ramp-border-focus)] " +
"disabled:opacity-50 disabled:cursor-not-allowed";
const variants = {
primary:
"bg-[var(--ramp-accent-primary)] text-[var(--ramp-text-primary)] " +
"hover:bg-[var(--ramp-accent-primary-hover)]",
secondary:
"bg-[var(--ramp-bg-inverse)] text-[var(--ramp-text-on-dark)] " +
"hover:bg-black/80",
};
const error = hasError ? "!bg-[#e96516] !text-white" : "";
return (
<button
onClick={onClick}
disabled={disabled || isLoading}
className={`${base} ${variants[variant]} ${error}`}
>
{isLoading ? (
<Loader2 className="h-4 w-4 animate-spin" aria-label="Loading" />
) : children}
</button>
);
}
```
### 6.2 Card (Platform / Feature)
**Anatomy:** container, optional eyebrow, title, body, CTA/link, optional media/icon.
| State | Style |
|---|---|
| default | `bg: --grayLight #f4f2f0`, `radius: 12px`, `padding: 24px`, `gap: 32px`, flex-column space-between |
| hover | arrow-icon animates via `--motion-PlatformCard-module__MXf3fW__platform-card-arrow-out-45` (0.5s ease-in-out) |
| focus-within | outline 2px `--springLight`, offset 2px |
| disabled | opacity 0.5, pointer-events none |
```tsx
import { ArrowUpRight } from "lucide-react";
export function FeatureCard({ eyebrow, title, body, href }: {
eyebrow?: string; title: string; body: string; href: string;
}) {
return (
<a
href={href}
className="group flex flex-col justify-between gap-8
bg-[var(--ramp-bg-surface)] rounded-[12px] p-6
text-[var(--ramp-text-primary)]
transition-colors duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]
hover:bg-[#ece9e6]
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-[var(--ramp-border-focus)] focus-visible:ring-offset-2"
>
<div className="flex flex-col gap-3">
{eyebrow && (
<span className="text-[10px] leading-[22px] tracking-[0.18px] uppercase
text-[var(--ramp-text-secondary)]">
{eyebrow}
</span>
)}
<h3 className="text-[24px] leading-[28px] font-normal">{title}</h3>
<p className="text-[16px] leading-[24px] text-[var(--ramp-text-secondary)]">
{body}
</p>
</div>
<ArrowUpRight
className="h-5 w-5 transition-transform duration-500
group-hover:translate-x-2 group-hover:-translate-y-2"
aria-hidden
/>
</a>
);
}
```
### 6.3 Input
| State | Style |
|---|---|
| default | `bg: transparent`, `border: 1px solid --border-primary`, `radius: 10px`, `padding: 0 16px 0 24px`, `height: 48px`, `font-size: 16px`, `line-height: 22px` |
| hover | `border-color: --grayDark #6e6a68` |
| focus | `border-color: --spring #5683d2`, ring 2px `--springLight` |
| disabled | opacity 0.5, `bg: --grayLight` |
| error | `border-color: --blaze #e96516`, helper text `--blaze` |
```tsx
export function TextInput({
label, error, id, ...props
}: React.InputHTMLAttributes<HTMLInputElement> & { label: string; error?: string }) {
return (
<div className="flex flex-col gap-2">
<label
htmlFor={id}
className="text-[16px] leading-6 text-[var(--ramp-text-primary)]"
>
{label}
</label>
<input
id={id}
{...props}
aria-invalid={!!error}
className={`h-12 w-full rounded-[10px] border bg-transparent
pl-6 pr-4 text-[16px] leading-[22px]
text-[var(--ramp-text-primary)]
placeholder:text-[var(--ramp-text-secondary)]
transition-colors duration-150
ease-[cubic-bezier(0.4,0,0.2,1)]
focus:outline-none focus:border-[#5683d2]
focus:ring-2 focus:ring-[var(--ramp-border-focus)]
disabled:opacity-50 disabled:bg-[var(--ramp-bg-surface)]
${error ? "border-[#e96516]" : "border-[var(--ramp-border)]"}`}
/>
{error && (
<span className="text-[14px] text-[#e96516]">{error}</span>
)}
</div>
);
}
```
### 6.4 Nav Item
| State | Style |
|---|---|
| default | `font-size: 16px`, `color: --text-primary`, no underline |
| hover | `color: --blaze`, optional slide-down mega-menu (`--motion-slideDownAndFade`) |
| active/current | `color: --text-primary` with 2px bottom border `--blaze` |
| focus-visible | ring 2px `--springLight` offset 2px |
```tsx
export function NavItem({ href, active, children }: {
href: string; active?: boolean; children: React.ReactNode;
}) {
return (
<a
href={href}
className={`inline-flex items-center h-[62px] px-4 text-[16px]
transition-colors duration-150
ease-[cubic-bezier(0.4,0,0.2,1)]
hover:text-[#e96516]
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-[var(--ramp-border-focus)]
${active
? "text-[var(--ramp-text-primary)] border-b-2 border-[#e96516]"
: "text-[var(--ramp-text-primary)]"}`}
>
{children}
</a>
);
}
```
---
## 7. Elevation & Depth
Ramp **does not use drop shadows** for card elevation. Depth is expressed through:
```css
/* Inner highlight — used on tag/pill elements */
--elevation-inset-glow: inset 0 0 2px rgba(255, 255, 255, 0.6);
/* Focus ring — the ONLY outer shadow pattern */
--elevation-focus-ring: 0 0 0 2px var(--springLight);
/* Toast (Sonner) */
--elevation-toast: 0 4px 12px rgba(0, 0, 0, 0.1);
--elevation-toast-focus: 0 4px 12px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(0, 0, 0, 0.2);
/* Dialog/lightbox overlay scrim (fade-in to 0.25 opacity) */
--elevation-scrim: rgba(0, 0, 0, 0.25);
```
**Borders and z-index scale:**
```css
--border-width: 1px;
--border-color-default: var(--border-primary);
--z-header: 300;
--z-overlay: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;
--z-toast: 800;
--z-navmenu: 900;
```
**Layering principles:**
- Cards sit flat on the page — distinguish them via `--grayLight` fill, not shadow.
- Modals use a 25%-opacity black scrim (animated in via `PopupLightbox overlayShow`).
- Nav dropdowns use `--z-navmenu: 900`, above toasts — a deliberate choice so mega-menus are never occluded.
---
## 8. Motion
```css
/* Easing */
--ramp-ease-standard: cubic-bezier(0.4, 0, 0.2, 1); /* Tailwind default — used everywhere */
/* Durations */
--ramp-duration-fast: 150ms; /* inputs, hover colour shifts */
--ramp-duration-base: 300ms; /* buttons, card hover */
--ramp-duration-slow: 500ms; /* card arrow slide animations */
/* Named keyframes (see extracted motion tokens for full definitions) */
/* - arrow-slide / platform-card-arrow-out-45: 500ms ease-in-out, card hover arrows
- slideDownAndFade / slideUpAndFade: dropdowns, popovers (200ms)
- nav-in / nav-out: sticky nav show/hide on scroll
- HomeAccolades__marquee / KbLogoWall__marquee: infinite horizontal scroll, 40–60s linear
- spin / ping / pulse: loading states
- sonner-fade-in / sonner-fade-out: toast notifications
- accordion-down / accordion-up: FAQ-style collapsibles */
```
**When to animate:**
- All interactive colour state changes: **300ms `ease-standard`** on `color, background-color, border-color`.
- Input focus: faster, **150ms**.
- Card arrow icons on hover: custom 500ms `ease-in-out` with translate choreography.
- Marquees are infinite linear loops — never pause on hover (Ramp treats them as ambient decoration).
**When NOT to animate:**
- Never animate `width`, `height`, `top`, `left` — use `transform` instead.
- Never animate on page load — content appears statically (only nav & marquees have entrance motion).
- Respect `prefers-reduced-motion: reduce` — disable marquees and keep only opacity fades.
---
## 9. Anti-Patterns & Constraints
1. **Never hardcode hex values in components → Why it fails:** You lose the ability to re-theme and you fracture the palette (the extraction already detected 5 near-duplicate colour pairs). Designers lose trust when `#0c0a08` appears as `#0c0a08`, `#0a0a08`, and `#0c0908` across files. **Do instead:** always `var(--ramp-text-primary)` / `var(--text-primary)`.
2. **Never use arbitrary spacing like `p-[23px]` or `mt-[37px]` → Why it fails:** Ramp's rhythm is a strict 4px grid with 64/128 section spacers. A 23px pad creates misalignment with neighbouring components that use 24px, producing a 1px jitter that reads as "off" without being diagnosable. **Do instead:** snap to `--space-*` tokens (4, 8, 12, 16, 20, 24, 32, 48) or `--spacer-m/l` for sections.
3. **Never fall back to Inter, Roboto, Arial, or system-ui for body/headings → Why it fails:** `lausanne` is the brand voice — its low x-height, generous apertures, and specific italic construction *are* the Ramp identity. With Inter, headings look like a generic SaaS landing page; the hero immediately reads as "template." **Do instead:** load the lausanne webfont with `font-display: swap` and the "lausanne Fallback" metric-adjusted Arial.
4. **Never construct Tailwind classes dynamically (`bg-${color}-500`) → Why it fails:** Tailwind's JIT compiler scans source files statically and will not emit classes it cannot see as literal strings, so the class never exists in the CSS bundle and the element renders unstyled. **Do instead:** map a discriminated union to full literal classnames, or use `style={{ background: 'var(--ramp-accent-primary)' }}`.
5. **Never ship a button/link without all seven states → Why it fails:** Default + hover is only 28% of the keyboard and assistive-tech journey. Missing `focus-visible` makes Ramp fail WCAG 2.4.7; missing `disabled` lets submitting twice double-charge. **Do instead:** every interactive component declares default / hover / focus-visible / active / disabled / loading / error — copy the Button example in §6.1 as the template.
6. **Never use inline styles for tokenisable properties → Why it fails:** Inline styles win over classes, breaking any `:hover` or `:focus-visible` state declared via utility classes and forcing future overrides to escalate to `!important`. **Do instead:** keep `style` for exactly one thing — runtime-computed values (`style={{ transform: \`translateX(${x}px)\` }}`). Everything else goes through classes.
7. **Never use `!important` to win a specificity fight → Why it fails:** Every `!important` you add creates a second `!important` somewhere else to override it, and within three iterations the cascade is unrecoverable. The root cause is always a specificity structure problem. **Do instead:** refactor the selector, move the declaration to a more specific layer, or use a CSS layer (`@layer components`).
8. **Never use `position: absolute` as a layout primitive → Why it fails:** Absolute positioning removes elements from flow, so responsive breakpoints (600/768/992/1024/1100) require hand-tuned `top`/`left` values at each — they will drift. **Do instead:** use flexbox or grid for layout; reserve absolute for genuine overlays (badges on cards, close buttons on modals).
9. **Never leave `lorem ipsum`, `TODO`, or placeholder copy in committed components → Why it fails:** Ramp's design relies on editorial voice ("Cards & Expenses that handle themselves"). Lorem text in a 48px heading immediately reveals the work is unfinished, and placeholder copy routinely ships to production when review cycles compress. **Do instead:** use realistic-length real content or mark with an obvious `<ContentTBD />` component that errors in production builds.
10. **Never mix styling approaches in one component (Tailwind + CSS Modules + inline) → Why it fails:** Source-order dependency becomes nondeterministic across bundlers; a class defined in a module can win or lose against a Tailwind utility depending on PostCSS plugin ordering, and refactors silently change visual output. **Do instead:** Tailwind utilities + CSS variables for tokens is the canonical Ramp pattern — stick to it.
11. **Never consolidate the `--blaze` orange and `--solar` yellow into "accent" → Why it fails:** They have mutually exclusive roles. `--solar` is *only* for CTA backgrounds (requires dark text); `--blaze` is *only* for inline links, eyebrow highlights, and error borders. Swapping them breaks contrast requirements (blaze on white is only 3.5:1 for normal text) and floods the page with orange. **Do instead:** treat them as two distinct semantic tokens: `--ramp-accent-primary` (solar) and `--ramp-accent-link` (blaze).
12. **Never apply gradients (`--dusk`, `--midnight`, `--daylight`) to buttons or cards → Why it fails:** These are atmospheric full-bleed section backgrounds meant to evoke time-of-day transitions in the product narrative. Shrunk to button size the oklab stops compress into mud; on small cards they fight text contrast. **Do instead:** reserve gradients for `<section>` backgrounds at full viewport width.
---
## Brand Assets
Uploaded brand marks for this project. When generating UI that references the brand (headers, login screens, loading states) prefer these over stock placeholder imagery. Reference by slot and variant via the `data-brand-logo` convention the Studio injects into previews.
### primary
- **Logo.svg.svg** — /api/storage/branding/6961d90d-4214-408c-9199-475575e8650b/ca7ef113-df78-4e51-b1a7-60dfcee2bbda/primary-Sbm99Z5UpLPLmW-Sy_mvZ.svg
- **ramp_logo.svg-1.svg** — /api/storage/branding/6961d90d-4214-408c-9199-475575e8650b/ca7ef113-df78-4e51-b1a7-60dfcee2bbda/primary-7P_h3Lwl8gz2XWnCg2l6b.svg
- **ramp_logo.svg.svg** — /api/storage/branding/6961d90d-4214-408c-9199-475575e8650b/ca7ef113-df78-4e51-b1a7-60dfcee2bbda/primary-DlseBj-WnW9FwpU4mxL7T.svg
### mark
- **ramp_symbol.svg.svg** — /api/storage/branding/6961d90d-4214-408c-9199-475575e8650b/ca7ef113-df78-4e51-b1a7-60dfcee2bbda/primary-yBHtyWEn9WCA103ANQpO-.svg
## 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 (66) */
--white-0: rgba(var(--white-rgb),0);
--white-25: rgba(var(--white-rgb),.025);
--white-50: rgba(var(--white-rgb), 0.05);
--white-100: rgba(var(--white-rgb),.1);
--white-200: rgba(var(--white-rgb),.2);
--white-300: rgba(var(--white-rgb),.3);
--white-400: rgba(var(--white-rgb),.4);
--white-500: rgba(var(--white-rgb),.5);
--white-600: rgba(var(--white-rgb),.6);
--white-700: rgba(var(--white-rgb),.7);
--white-800: rgba(var(--white-rgb),.8);
--white-900: rgba(var(--white-rgb),.9);
--black-0: rgba(var(--black-rgb),0);
--black-25: rgba(var(--black-rgb),.025);
--black-50: rgba(var(--black-rgb),.05);
--black-100: rgba(var(--black-rgb),.1);
--black-200: rgba(var(--black-rgb),.2);
--black-300: rgba(var(--black-rgb),.3);
--black-400: rgba(var(--black-rgb),.4);
--black-500: rgba(var(--black-rgb),.5);
--black-600: rgba(var(--black-rgb),.6);
--black-700: rgba(var(--black-rgb),.7);
--black-800: rgba(var(--black-rgb),.8);
--black-900: rgba(var(--black-rgb),.9);
--black: #1a1919;
--smolder: #17332d;
--grayLight: #f4f2f0;
--grayMedium: #d2cecb;
--grayDark: #6e6a68;
--white: #ffffff;
--text-primary: #0c0a08;
--text-primaryReverse: #ffffff;
--text-hushed: #0c0a0899;
--text-hushedReverse: #ffffff99;
--border-primary: #d2cecb;
--springLight: #e4ebf6;
--spring: #5683d2;
--ramp-text-primary: #0c0a08;
--ramp-text-secondary: #0c0a0899;
--ramp-text-on-dark: #ffffff;
--ramp-text-muted-on-dark: #ffffff99;
--ramp-bg-app: #ffffff;
--ramp-bg-surface: #f4f2f0;
--ramp-bg-black: #1a1919;
--ramp-accent-blaze: #e96516;
--ramp-accent-solar: #e4f222;
--ramp-accent-solar-light: #f5ff78;
--ramp-accent-spring: #5683d2;
--ramp-border: #d2cecb;
--ramp-border-focus: #e4ebf6;
--btn-primary-bg: var(--solar);
--btn-primary-bg-hover: var(--solarLight);
--btn-secondary-bg: var(--black);
--card-bg: var(--grayLight);
--input-bg: transparent;
--input-border: var(--border-primary);
--elevation-scrim: rgba(0, 0, 0, 0.25);
--border-width: 1px;
--border-color-default: var(--border-primary);
--blaze: #e96516;
--solar: #e4f222;
--solarLight: #f5ff78;
--ramp-bg-inverse: #1a1919;
--ramp-accent-primary: #e4f222;
--ramp-accent-primary-hover: #f5ff78;
--ramp-accent-link: #e96516;
/* Typography (4) */
--dusk: linear-gradient(in oklab to top,#e4ebf6 8%,#c3d3ef 20%,#5683d2 48%,#001b4a 100%);
--midnight: linear-gradient(in oklab to bottom,#000 0%,#112d5b 100%);
--daylight: linear-gradient(in oklab to bottom,#d2dff3 22%,#f2f5f9 93%);
--ramp-font-sans: "lausanne", "lausanne Fallback", Arial, sans-serif;
/* Spacing (21) */
--spacer-m: 64px;
--spacer-l: 128px;
--nav-banner-height: 0px;
--ramp-space-section-sm: 64px;
--ramp-space-section-lg: 128px;
--card-padding: 24px;
--input-padding-x: 16px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-12: 48px;
--bp-sm: 600px;
--bp-md: 768px;
--bp-lg: 992px;
--bp-xl: 1024px;
--bp-2xl: 1100px;
--elevation-inset-glow: inset 0 0 2px rgba(255, 255, 255, 0.6);
/* Radius (9) */
----radius-sm: 6px; /* 15 elements (e.g. button "Products", button "Solutions", button "Resources") /* reconstructed */ */
----radius-lg: 16px; /* 14 elements (e.g. div "Set user access on autopilot.Provision c", div "One platform for all your global spend.I", article "Shannon McCormickInternational Workplace") /* reconstructed */ */
----radius-md: 10px; /* 4 elements (e.g. input, input, input) /* reconstructed */ */
--ramp-radius-sm: 4–6px;
--ramp-radius-md: 10px;
--ramp-radius-lg: 12px;
--ramp-radius-xl: 16px;
--card-radius: 12px;
--input-radius: 10px;
/* Effects (14) */
--lausanne-ascent: 1866;
--lausanne-descent: -410;
--lausanne-units-per-em: 2048;
--white-rgb: 255,255,255;
--black-rgb: 33,33,33;
--z-header: 300;
--z-overlay: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;
--z-toast: 800;
--z-navmenu: 900;
--page-theme: var(--white);
--nav-height: calc(62px + var(--nav-banner-height));
/* Motion (27) */
----motion-arrow-slide: @keyframes arrow-slide {
0% { opacity: 1; transform: translate(0px); }
20%… <0.3KB elided>; /* @keyframes arrow-slide */
----motion-accordion-down: @keyframes accordion-down {
0% { height: 0px; }
100% { height: var(--radix-… <0.3KB elided>; /* @keyframes accordion-down */
----motion-accordion-up: @keyframes accordion-up {
0% { height: var(--radix-accordion-content-height,v… <0.3KB elided>; /* @keyframes accordion-up */
----motion-slideDownAndFade: @keyframes slideDownAndFade {
0% { opacity: 0; transform: translateY(-2px); }
100% { opacity: 1; transform: translateY(0px); }
}; /* @keyframes slideDownAndFade */
----motion-slideLeftAndFade: @keyframes slideLeftAndFade {
0% { opacity: 0; transform: translate(2px); }
100% { opacity: 1; transform: translate(0px); }
}; /* @keyframes slideLeftAndFade */
----motion-slideUpAndFade: @keyframes slideUpAndFade {
0% { opacity: 0; transform: translateY(2px); }
100% { opacity: 1; transform: translateY(0px); }
}; /* @keyframes slideUpAndFade */
----motion-slideRightAndFade: @keyframes slideRightAndFade {
0% { opacity: 0; transform: translate(-2px); }
100% { opacity: 1; transform: translate(0px); }
}; /* @keyframes slideRightAndFade */
----motion-spin: @keyframes spin {
100% { transform: rotate(360deg); }
}; /* @keyframes spin */
----motion-ping: @keyframes ping {
75%, 100% { opacity: 0; transform: scale(2); }
}; /* @keyframes ping */
----motion-pulse: @keyframes pulse {
50% { opacity: 0.5; }
}; /* @keyframes pulse */
----motion-enter: @keyframes enter {
0% { opacity: var(--tw-enter-opacity,1); transform: transl… <0.3KB elided>; /* @keyframes enter */
----motion-exit: @keyframes exit {
100% { opacity: var(--tw-exit-opacity,1); transform: transl… <0.3KB elided>; /* @keyframes exit */
----motion-nav-out: @keyframes nav-out {
0% { transform: translateY(0px); }
100% { transform: translateY(calc(-100% - 56px)); }
}; /* @keyframes nav-out */
----motion-nav-in: @keyframes nav-in {
0% { transform: translateY(calc(-100% - 56px)); }
100% { transform: translateY(0px); }
}; /* @keyframes nav-in */
----motion-WistiaDialogClient-module__8XEj-W__contentFadeIn: @keyframes WistiaDialogClient-module__8XEj-W__contentFadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}; /* @keyframes WistiaDialogClient-module__8XEj-W__contentFadeIn */
----motion-PlatformCard-module__MXf3fW__platform-card-arrow-out-45: @keyframes PlatformCard-module__MXf3fW__platform-card-arrow-out-45 {
0% { opa… <0.3KB elided>; /* @keyframes PlatformCard-module__MXf3fW__platform-card-arrow-out-45 */
----motion-HomeAccolades-module__s5B_4W__scroll: @keyframes HomeAccolades-module__s5B_4W__scroll {
0% { transform: translate(0px); }
100% { transform: translate(-100%); }
}; /* @keyframes HomeAccolades-module__s5B_4W__scroll */
----motion-HomeAccolades-module__s5B_4W__marquee: @keyframes HomeAccolades-module__s5B_4W__marquee {
0% { transform: translate3d(var(--move-initial), 0, 0); }
100% { transform: translate3d(var(--move-final), 0, 0); }
}; /* @keyframes HomeAccolades-module__s5B_4W__marquee */
----motion-KbLogoWall-module__3RH7Jq__scroll: @keyframes KbLogoWall-module__3RH7Jq__scroll {
0% { transform: translate(0px); }
100% { transform: translate(-100%); }
}; /* @keyframes KbLogoWall-module__3RH7Jq__scroll */
----motion-KbLogoWall-module__3RH7Jq__marquee: @keyframes KbLogoWall-module__3RH7Jq__marquee {
0% { transform: translate(0px, 0px); }
100% { transform: translate(-100%); }
}; /* @keyframes KbLogoWall-module__3RH7Jq__marquee */
----motion-PopupLightbox-module__sYoECq__overlayShow: @keyframes PopupLightbox-module__sYoECq__overlayShow {
0% { opacity: 0; }
100% { opacity: 0.25; }
}; /* @keyframes PopupLightbox-module__sYoECq__overlayShow */
----motion-PopupLightbox-module__sYoECq__contentShow: @keyframes PopupLightbox-module__sYoECq__contentShow {
0% { opacity: 0; transform: translate(-50%, -48%) scale(0.96); }
100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}; /* @keyframes PopupLightbox-module__sYoECq__contentShow */
----motion-CustomersStatsWall-module__4zqLAG__scroll: @keyframes CustomersStatsWall-module__4zqLAG__scroll {
0% { transform: translate(0px); }
100% { transform: translate(-210vw); }
}; /* @keyframes CustomersStatsWall-module__4zqLAG__scroll */
----motion-swipe-out: @keyframes swipe-out {
0% { transform: translateY(calc(var(--lift) * var(--of… <0.2KB elided>; /* @keyframes swipe-out */
----motion-sonner-fade-in: @keyframes sonner-fade-in {
0% { opacity: 0; transform: scale(0.8); }
100% { opacity: 1; transform: scale(1); }
}; /* @keyframes sonner-fade-in */
----motion-sonner-fade-out: @keyframes sonner-fade-out {
0% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(0.8); }
}; /* @keyframes sonner-fade-out */
----motion-sonner-spin: @keyframes sonner-spin {
0% { opacity: 1; }
100% { opacity: 0.15; }
}; /* @keyframes sonner-spin */
```
## Appendix B: Token Source Metadata
```yaml
tokenSource: extracted-css-vars
confidence: high
cssCustomPropertiesFound: 60
curatedTokensMapped: 16
fontsDetected: ["lausanne (300/350/400/700 + italic)", "Material Icons", "Material Icons Outlined"]
librariesDetected: ["tailwind", "bootstrap", "lucide"]
breakpointsExtracted: [98, 600, 767, 768, 860, 991, 992, 1024, 1100]
reconstructionNotes:
- Radius scale was partially reconstructed from 53 elements. 6px dominates buttons (15 elements); 12px dominates cards (19 elements); 16px for large feature cards (14 elements); 10px for form inputs (4 elements).
- Near-duplicate colours detected: --grayMedium and --border-primary are both #d2cecb — consolidated in semantic tier as --ramp-border.
- Computed H1 font-size 48px / line-height 50px is unusually tight (ratio 1.04). Confirmed across extraction — preserve as-is.
- Tailwind v3 assumed (no @theme block detected; utility classes present).
authoritativeTier: semantic
rationale: >
Original CSS variable names (--blaze, --solar, --dusk) are poetic/brand-specific and MUST be preserved
for 1:1 site fidelity. Curated --ramp-* aliases are provided for cross-team semantic clarity but do NOT
replace the originals. When in doubt, use the original name — it exists in the production bundle.
antiPatternsAutoDetected:
- "Consolidate near-duplicate colours: 5 pairs nearly identical. Fix: merge into single canonical token per role."
```
## Branding Assets
Uploaded brand assets resolved at runtime via the `data-brand-logo` attribute. Reference these slots in generated code — never hardcode logo URLs, and never use `data-generate-image` for the project's own brand logo.
| Slot | File | Type | Usage |
|------|------|------|-------|
| `primary` | Logo.svg.svg | image/svg+xml | `data-brand-logo="primary"` |
| `primary` | ramp_logo.svg-1.svg | image/svg+xml | `data-brand-logo="primary"` |
| `primary` | ramp_logo.svg.svg | image/svg+xml | `data-brand-logo="primary"` |
| `mark` | ramp_symbol.svg.svg | image/svg+xml | `data-brand-logo="mark"` |
More from the gallery
Browse all kits →You may also like

Vercel
MITClean, minimal developer-focused design system built on Geist typography with light surfaces, precise spacing, and fast interactions for modern web applications
00
lightminimaldeveloper-toolsaas

Sentry
MITDark, minimal design system for developer tools with a sophisticated purple palette and refined typography, tailored for error monitoring and performance platforms
00
darkminimaldeveloper-toolsaas

OpenAI
MITClean, minimal design system for AI-native products built on Next.js and Tailwind, featuring a light monochromatic palette with precise token-based spacing and typography
02
lightminimalsaasdeveloper-tool