Palette · light · minimal · mono

Porcelain

A clean, near-white system with a single confident indigo accent — minimalism that still has a voice.

Copy as

Building with shadcn/ui?

Install Porcelain as a real theme — light and dark, OKLCH tokens, every colour pair measured, DTCG export.

Get the theme →

Usage system

Everything you need to wire this palette into a real project, not just the colors.

CSS variables
:root {
  --color-bg: #FAFAFB;
  --color-surface: #FFFFFF;
  --color-primary: #4338CA;
  --color-accent: #0EA5E9;
  --color-text: #18181B;
  --color-muted: #71717A;
}
Tailwind config
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        bg: '#FAFAFB',
        surface: '#FFFFFF',
        primary: '#4338CA',
        accent: '#0EA5E9',
        text: '#18181B',
        muted: '#71717A',
      },
    },
  },
};
SCSS variables
$color-bg: #FAFAFB;
$color-surface: #FFFFFF;
$color-primary: #4338CA;
$color-accent: #0EA5E9;
$color-text: #18181B;
$color-muted: #71717A;
DESIGN.md for AI agents

Paste into Cursor, Claude Code, Codex, v0 or Lovable before you ask for UI, or fetch it directly from /d/porcelain.md. Colors, contrast ratios and the rules an agent must follow, all computed from this palette.

# DESIGN.md — Porcelain

> Design context for AI coding agents. Paste this file into Cursor, Claude Code, Codex, v0 or Lovable
> before asking for UI, or commit it at your repo root as `DESIGN.md`.

- **Source:** [Porcelain on CSS Crème](https://csscreme.com/themes/porcelain)
- **Default mode:** light
- **Tags:** light, minimal, mono
- **Install as a real shadcn/ui theme:** `npx shadcn@latest add https://csscreme.com/r/porcelain.json`
- **Fingerprint:** `fnv1a-a988f212` — a hash of the token set below. It changes only when a token changes.
- **Verify this copy:** call the MCP tool `verify_design_md` with `{ "id": "porcelain", "fingerprint": "fnv1a-a988f212" }`
  at `https://csscreme.com/mcp`, or refetch https://csscreme.com/d/porcelain.md. A stale copy is worse than none: an agent will follow it faithfully.
- **Also available as:** [DTCG tokens](https://csscreme.com/d/porcelain.tokens.json) for Tokens Studio / Figma Variables · [agent rules block](https://csscreme.com/d/porcelain.rules.md) for CLAUDE.md, AGENTS.md or .cursor/rules

A clean, near-white system with a single confident indigo accent — minimalism that still has a voice.

---

## Semantic color roles

Six roles carry the identity. Everything else in this file is derived from them.

| Role | Hex | OKLCH | Use it for |
| --- | --- | --- | --- |
| `bg` | `#FAFAFB` | `oklch(98.5% 0.001 286.4)` | page background |
| `surface` | `#FFFFFF` | `oklch(100.0% 0.000 89.9)` | cards, panels, popovers |
| `primary` | `#4338CA` | `oklch(45.7% 0.215 277.0)` | brand actions: primary buttons, links, focus rings |
| `accent` | `#0EA5E9` | `oklch(68.5% 0.148 237.3)` | secondary emphasis only, never a second primary |
| `text` | `#18181B` | `oklch(21.0% 0.006 285.9)` | body and heading text |
| `muted` | `#71717A` | `oklch(55.2% 0.014 285.9)` | secondary text, borders, disabled states |

## Full token set (shadcn/ui, both modes)

Colors are OKLCH (Tailwind v4 / shadcn native). Brand colors are identical in both modes by design:
only the neutrals move, and they keep their hue, so light and dark read as the same design system.

| Token | Light | Dark |
| --- | --- | --- |
| `--background` | `oklch(98.5% 0.001 286.4)` | `oklch(18.3% 0.004 286.0)` |
| `--foreground` | `oklch(21.0% 0.006 285.9)` | `oklch(96.4% 0.001 286.4)` |
| `--card` | `oklch(100.0% 0.000 89.9)` | `oklch(25.2% 0.000 89.9)` |
| `--card-foreground` | `oklch(21.0% 0.006 285.9)` | `oklch(96.4% 0.001 286.4)` |
| `--popover` | `oklch(100.0% 0.000 89.9)` | `oklch(25.2% 0.000 89.9)` |
| `--popover-foreground` | `oklch(21.0% 0.006 285.9)` | `oklch(96.4% 0.001 286.4)` |
| `--primary` | `oklch(45.7% 0.215 277.0)` | `oklch(45.7% 0.215 277.0)` |
| `--primary-foreground` | `oklch(100.0% 0.000 89.9)` | `oklch(100.0% 0.000 89.9)` |
| `--secondary` | `oklch(99.4% 0.001 286.4)` | `oklch(22.7% 0.002 286.2)` |
| `--secondary-foreground` | `oklch(21.0% 0.006 285.9)` | `oklch(96.4% 0.001 286.4)` |
| `--muted` | `oklch(99.4% 0.001 286.4)` | `oklch(22.7% 0.002 286.2)` |
| `--muted-foreground` | `oklch(55.2% 0.014 285.9)` | `oklch(70.6% 0.003 286.3)` |
| `--accent` | `oklch(68.5% 0.148 237.3)` | `oklch(68.5% 0.148 237.3)` |
| `--accent-foreground` | `oklch(15.2% 0.009 285.3)` | `oklch(15.2% 0.009 285.3)` |
| `--destructive` | `oklch(62.6% 0.193 23.0)` | `oklch(62.6% 0.193 23.0)` |
| `--destructive-foreground` | `oklch(15.2% 0.009 285.3)` | `oklch(15.2% 0.009 285.3)` |
| `--border` | `oklch(84.0% 0.006 286.3)` | `oklch(34.9% 0.003 286.2)` |
| `--input` | `oklch(84.0% 0.006 286.3)` | `oklch(34.9% 0.003 286.2)` |
| `--ring` | `oklch(45.7% 0.215 277.0)` | `oklch(45.7% 0.215 277.0)` |
| `--chart-1` | `oklch(45.7% 0.215 277.0)` | `oklch(45.7% 0.215 277.0)` |
| `--chart-2` | `oklch(68.5% 0.148 237.3)` | `oklch(68.5% 0.148 237.3)` |
| `--chart-3` | `oklch(56.0% 0.223 315.7)` | `oklch(56.0% 0.223 315.7)` |
| `--chart-4` | `oklch(62.2% 0.122 242.9)` | `oklch(62.2% 0.122 242.9)` |
| `--chart-5` | `oklch(55.8% 0.178 259.1)` | `oklch(55.8% 0.178 259.1)` |
| `--sidebar` | `oklch(99.4% 0.000 89.9)` | `oklch(21.8% 0.002 286.2)` |
| `--sidebar-foreground` | `oklch(21.0% 0.006 285.9)` | `oklch(96.4% 0.001 286.4)` |
| `--sidebar-primary` | `oklch(45.7% 0.215 277.0)` | `oklch(45.7% 0.215 277.0)` |
| `--sidebar-primary-foreground` | `oklch(100.0% 0.000 89.9)` | `oklch(100.0% 0.000 89.9)` |
| `--sidebar-accent` | `oklch(99.4% 0.001 286.4)` | `oklch(22.7% 0.002 286.2)` |
| `--sidebar-accent-foreground` | `oklch(21.0% 0.006 285.9)` | `oklch(96.4% 0.001 286.4)` |
| `--sidebar-border` | `oklch(84.0% 0.006 286.3)` | `oklch(34.9% 0.003 286.2)` |
| `--sidebar-ring` | `oklch(45.7% 0.215 277.0)` | `oklch(45.7% 0.215 277.0)` |

**What is measured and what is derived.** The six roles are the source. `chart-*` and `sidebar-*` follow
fixed rules over them (chart-1 = primary, chart-2 = accent, chart-3/4 = primary rotated ±40°, chart-5 = the
midpoint of primary and accent; sidebar maps onto surface, border and primary). `shadow-*` is the foreground
colour at a fixed opacity per step. None of those is a measurement, and an agent should treat them as
defaults it may override, unlike the six roles, which it may not.

## Typography

- **Headings:** Geist
- **Body:** Geist

## Shape and rhythm

- **Radius:** `10px` (the `--radius` token; derive smaller/larger from it, do not invent new values)
- **Spacing scale:** 4px base · 4 / 8 / 12 / 16 / 24 / 32 / 48

## Contrast, measured

Computed from the tokens above in light mode, not asserted. Ratios are WCAG 2.1 relative luminance.

| Pair | Foreground | Background | Ratio | Verdict |
| --- | --- | --- | --- | --- |
| Body text on page background | `#18181B` | `#FAFAFB` | 16.98:1 | AAA |
| Body text on card | `#18181B` | `#FFFFFF` | 17.72:1 | AAA |
| Muted text on page background | `#71717A` | `#FAFAFB` | 4.63:1 | AA |
| Button label on primary | `#ffffff` | `#4338CA` | 7.9:1 | AAA |
| Button label on accent | `#0b0b0f` | `#0EA5E9` | 7.09:1 | AAA |

## Rules for the agent

These are constraints, not suggestions. Each one is checkable against the tables above.

1. **Use only the tokens listed here.** Do not introduce a hex value that is not in the token table. If
   you need an in-between shade, mix two existing tokens rather than inventing a new hue.
2. **`#4338CA` (primary) is the only call-to-action color.** One primary action per view. `accent`
   is for secondary emphasis and must never be styled as a second primary button.
3. **Never restyle brand colors between light and dark.** Only neutrals change mode. A theme whose
   primary shifts between modes is a different theme.
4. **Respect the radius token.** Every corner derives from `10px`. Mixed radii across a view is
   the most common tell of generated UI.
5. **Stay on the spacing scale.** 4px base · 4 / 8 / 12 / 16 / 24 / 32 / 48. No arbitrary pixel values.
6. **Do not drop below the contrast ratios above.** If a combination you need is not in the contrast
   table, compute it before shipping it. Text under 4.5:1 fails AA.
7. **Headings in Geist, body in Geist.** Do not add a third family.
8. **Use the semantic role, not the raw color.** Write `var(--primary)`, never `#4338CA`, so the
   theme stays swappable.

## CSS variables

Paste into `globals.css`:

```css
:root {
  --background: oklch(98.5% 0.001 286.4);
  --foreground: oklch(21.0% 0.006 285.9);
  --card: oklch(100.0% 0.000 89.9);
  --card-foreground: oklch(21.0% 0.006 285.9);
  --popover: oklch(100.0% 0.000 89.9);
  --popover-foreground: oklch(21.0% 0.006 285.9);
  --primary: oklch(45.7% 0.215 277.0);
  --primary-foreground: oklch(100.0% 0.000 89.9);
  --secondary: oklch(99.4% 0.001 286.4);
  --secondary-foreground: oklch(21.0% 0.006 285.9);
  --muted: oklch(99.4% 0.001 286.4);
  --muted-foreground: oklch(55.2% 0.014 285.9);
  --accent: oklch(68.5% 0.148 237.3);
  --accent-foreground: oklch(15.2% 0.009 285.3);
  --destructive: oklch(62.6% 0.193 23.0);
  --destructive-foreground: oklch(15.2% 0.009 285.3);
  --border: oklch(84.0% 0.006 286.3);
  --input: oklch(84.0% 0.006 286.3);
  --ring: oklch(45.7% 0.215 277.0);
  --chart-1: oklch(45.7% 0.215 277.0);
  --chart-2: oklch(68.5% 0.148 237.3);
  --chart-3: oklch(56.0% 0.223 315.7);
  --chart-4: oklch(62.2% 0.122 242.9);
  --chart-5: oklch(55.8% 0.178 259.1);
  --sidebar: oklch(99.4% 0.000 89.9);
  --sidebar-foreground: oklch(21.0% 0.006 285.9);
  --sidebar-primary: oklch(45.7% 0.215 277.0);
  --sidebar-primary-foreground: oklch(100.0% 0.000 89.9);
  --sidebar-accent: oklch(99.4% 0.001 286.4);
  --sidebar-accent-foreground: oklch(21.0% 0.006 285.9);
  --sidebar-border: oklch(84.0% 0.006 286.3);
  --sidebar-ring: oklch(45.7% 0.215 277.0);
  --radius: 10px;
  --shadow-2xs: 0 1px 2px 0px rgb(24 24 27 / 0.05);
  --shadow-xs: 0 1px 3px 0px rgb(24 24 27 / 0.06);
  --shadow-sm: 0 1px 3px 0px rgb(24 24 27 / 0.08), 0 1px 2px -1px rgb(24 24 27 / 0.08);
  --shadow-md: 0 4px 6px -1px rgb(24 24 27 / 0.09), 0 2px 4px -2px rgb(24 24 27 / 0.09);
  --shadow-lg: 0 10px 15px -3px rgb(24 24 27 / 0.1), 0 4px 6px -4px rgb(24 24 27 / 0.1);
  --shadow-xl: 0 20px 25px -5px rgb(24 24 27 / 0.12), 0 8px 10px -6px rgb(24 24 27 / 0.12);
  --shadow-2xl: 0 25px 50px -12px rgb(24 24 27 / 0.22);
  --spacing: 4px;
}

.dark {
  --background: oklch(18.3% 0.004 286.0);
  --foreground: oklch(96.4% 0.001 286.4);
  --card: oklch(25.2% 0.000 89.9);
  --card-foreground: oklch(96.4% 0.001 286.4);
  --popover: oklch(25.2% 0.000 89.9);
  --popover-foreground: oklch(96.4% 0.001 286.4);
  --primary: oklch(45.7% 0.215 277.0);
  --primary-foreground: oklch(100.0% 0.000 89.9);
  --secondary: oklch(22.7% 0.002 286.2);
  --secondary-foreground: oklch(96.4% 0.001 286.4);
  --muted: oklch(22.7% 0.002 286.2);
  --muted-foreground: oklch(70.6% 0.003 286.3);
  --accent: oklch(68.5% 0.148 237.3);
  --accent-foreground: oklch(15.2% 0.009 285.3);
  --destructive: oklch(62.6% 0.193 23.0);
  --destructive-foreground: oklch(15.2% 0.009 285.3);
  --border: oklch(34.9% 0.003 286.2);
  --input: oklch(34.9% 0.003 286.2);
  --ring: oklch(45.7% 0.215 277.0);
  --chart-1: oklch(45.7% 0.215 277.0);
  --chart-2: oklch(68.5% 0.148 237.3);
  --chart-3: oklch(56.0% 0.223 315.7);
  --chart-4: oklch(62.2% 0.122 242.9);
  --chart-5: oklch(55.8% 0.178 259.1);
  --sidebar: oklch(21.8% 0.002 286.2);
  --sidebar-foreground: oklch(96.4% 0.001 286.4);
  --sidebar-primary: oklch(45.7% 0.215 277.0);
  --sidebar-primary-foreground: oklch(100.0% 0.000 89.9);
  --sidebar-accent: oklch(22.7% 0.002 286.2);
  --sidebar-accent-foreground: oklch(96.4% 0.001 286.4);
  --sidebar-border: oklch(34.9% 0.003 286.2);
  --sidebar-ring: oklch(45.7% 0.215 277.0);
  --shadow-2xs: 0 1px 2px 0px rgb(243 243 244 / 0.16);
  --shadow-xs: 0 1px 3px 0px rgb(243 243 244 / 0.18);
  --shadow-sm: 0 1px 3px 0px rgb(243 243 244 / 0.22), 0 1px 2px -1px rgb(243 243 244 / 0.22);
  --shadow-md: 0 4px 6px -1px rgb(243 243 244 / 0.26), 0 2px 4px -2px rgb(243 243 244 / 0.26);
  --shadow-lg: 0 10px 15px -3px rgb(243 243 244 / 0.3), 0 4px 6px -4px rgb(243 243 244 / 0.3);
  --shadow-xl: 0 20px 25px -5px rgb(243 243 244 / 0.34), 0 8px 10px -6px rgb(243 243 244 / 0.34);
  --shadow-2xl: 0 25px 50px -12px rgb(243 243 244 / 0.45);
}
```

Tailwind v4 maps them into utilities:

```css
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
  --radius: var(--radius);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  --shadow-2xs: var(--shadow-2xs);
  --shadow-xs: var(--shadow-xs);
  --shadow-sm: var(--shadow-sm);
  --shadow-md: var(--shadow-md);
  --shadow-lg: var(--shadow-lg);
  --shadow-xl: var(--shadow-xl);
  --shadow-2xl: var(--shadow-2xl);
  --spacing: var(--spacing);
}
```

---

Generated by [CSS Crème](https://csscreme.com) — curated web design, decoded so you can build it.
Every value above is computed from the palette, not written by hand.
Font pairing Geist / heading · Geist / body
Corner radius 10px
Spacing scale 4px base · 4 / 8 / 12 / 16 / 24 / 32 / 48

In context

New

Build better, faster

A card, a heading, a button and a hint of text: the palette doing real work.

Every pair, measured

Body text on the page background measures 16.98:1, which passes AAA. Below is every ink on every ground in this palette, computed with the WCAG 2.1 relative-luminance formula and reported against normal body text, which is the strictest case. 7 of 14 usable pairs reach AA or better. The failures are left in: a palette that hides them is less useful than one that tells you which two colours never to put together.

ink on groundBackgroundSurfacePrimaryAccent
Text Aa 16.98 AAA Aa 17.72 AAA Aa 2.24 Fail Aa 6.39 AA
Muted Aa 4.63 AA Aa 4.83 AA Aa 1.64 Fail Aa 1.74 Fail
Primary Aa 7.58 AAA Aa 7.9 AAA — Aa 2.85 Fail
Accent Aa 2.66 Fail Aa 2.77 Fail Aa 2.85 Fail —

Each cell is the ink actually painted on the ground, so you are reading the real pair rather than a swatch of it. Ratios are computed at build time from the hex values on this page; nothing here is asserted. Need the other standard? The contrast checker takes any two colours.

Under colour-vision deficiency

The six roles as they read under each simulated deficiency (Machado 2009, full severity). What to check: do primary and accent still tell apart, and does text still sit off surface? A simulation, not a measurement of anyone's vision.

Typical
Protanopia
Deuteranopia
Tritanopia
Achromatopsia

Found one you like? Pair it with a template, generate a matching background in the gradient generator, or browse all palettes.