Mix and match · live
Compose a skin
Pick one of each layer and watch the same markup re-skin in place. Paste a customer's brand hex into the accent field to simulate the real handoff: their logo color, everything else inherited. Try a preset, then change just the palette, or just the shape.
Paste the logo hex. --color-accent-fg auto-picks a readable on-color.
Client handed you a brand guide? Override the full ground, not just the accent.
Established Counsel
Representation that reads the room.
One component library, every vertical. The same hero you are reading now serves a law firm, a plumber, and a dental practice without a line of markup changing.
Practice area
Commercial Litigation
Discreet, methodical, and relentless when the matter calls for it.
Practice area
Corporate Counsel
From formation to exit, the steady hand on every transaction.
Layer 1 · color only
Palette library
Twenty seeded palettes spanning the verticals PLW sells into, from courthouse champagne to bright retail crimson. Each is purely the --color-* tokens, nothing else. A real customer keeps the closest palette and overrides --color-accent with their exact brand hex, the accent-fg on-color is tuned to clear WCAG AA. Hand over a full brand guide and the playground overrides text, background, and surface too.
Layer 2 · font only
Type pairings
Eleven display-plus-body pairings, weighted toward sans for the workhorse jobs, from a Verdana system stack to geometric Space Grotesk and Plus Jakarta Sans, with serifs kept for the editorial verticals. All Google Fonts (or system) for Lighthouse-friendly loading. The size ramp never changes, only the family. Mono stays JetBrains Mono everywhere.
Layer 3 · radius + shadow
Shape personalities
Five shape sets fill the radius and shadow slots, from zero-radius Brutalist to fully-rounded Pill to shadowless Flat. Shadows reference the color tokens, so a shape adapts to whatever palette it is stacked with: brutalist gets a hard offset in the palette's foreground color, soft gets a diffuse glow tinted by the accent. Each card below is its own live token island.
Drop in, compose, ship
The tokens file
Tailwind v4. The shared skeleton lives in @theme so utilities are generated. The three layers are CSS-variable overrides on attribute scopes, runtime-swappable, so they sit outside @theme. Set a skin with three attributes: <html data-palette="champagne" data-type="playfair" data-shape="refined">.
/* globals.css — shared skeleton. Tailwind also generates utilities. */
@import "tailwindcss";
@theme {
/* Type scale — the ramp never changes, only the font */
--text-xs: 0.75rem; --text-sm: 0.875rem;
--text-base: 1rem; --text-lg: 1.125rem;
--text-xl: 1.25rem; --text-2xl: 1.5rem;
--text-3xl: 1.875rem; --text-4xl: 2.25rem;
--text-5xl: 3rem; --text-6xl: 3.75rem; --text-7xl: 4.5rem;
/* Line heights */
--leading-tight: 1.15; --leading-snug: 1.3;
--leading-normal: 1.5; --leading-relaxed: 1.7;
/* Space — 4px base, --space-1 … --space-32 */
--space-1: 0.25rem; --space-2: 0.5rem;
--space-4: 1rem; --space-8: 2rem; --space-16: 4rem;
/* …3,5,6,7,9–14,20,24,28,32 in tokens.css */
/* Border widths — shared */
--border-thin: 1px; --border-default: 2px; --border-heavy: 3px;
}/* layer 1 — PALETTE. Color tokens only. One scope per palette. */
[data-palette="champagne"] { /* Law · CounselDigital */
--color-fg: #1a1a1a; --color-bg: #f7f3ec;
--color-surface: #fffdf8; --color-muted: #645d54;
--color-accent: #c9a96e; --color-accent-fg: #1a1a1a;
--color-border: #e3dac9; --color-ring: #1a1a1a;
}
[data-palette="yellow"] { /* Trades · yellow-pages */ --color-accent: #ffd400; … }
[data-palette="coral"] { /* Dental */ --color-accent: #e07a5f; … }
[data-palette="forest"], [data-palette="crimson"],
[data-palette="cobalt"], [data-palette="emerald"],
[data-palette="violet"] { /* …17 more seeded, 20 total */ }
/* Customer override: keep the palette, swap one line. */
[data-palette="champagne"] { --color-accent: #b89254; /* their logo */ }/* layer 2 — TYPE. Font tokens only. Mono constant across pairings. */
[data-type="playfair"] { --font-display: "Playfair Display", serif;
--font-body: "Inter", sans-serif; }
[data-type="anton"] { --font-display: "Anton", sans-serif;
--font-body: "Inter", sans-serif; }
[data-type="bricolage"] { --font-display: "Bricolage Grotesque";
--font-body: "Nunito Sans", sans-serif; }
[data-type="dmserif"] { --font-display: "DM Serif Display";
--font-body: "DM Sans", sans-serif; }
[data-type="archivo"] { --font-display: "Archivo Black";
--font-body: "Archivo", sans-serif; }
[data-type="spectral"] { --font-display: "Spectral", serif;
--font-body: "Work Sans", sans-serif; }/* layer 3 — SHAPE. Radius + shadow. Shadows reference color tokens, */
/* so a shape adapts to whatever palette it stacks with. */
[data-shape="brutalist"] {
--radius-md: 0; --radius-lg: 0; --radius-pill: 0;
--shadow-md: 4px 4px 0 var(--color-fg);
--shadow-lg: 7px 7px 0 var(--color-fg);
}
[data-shape="refined"] {
--radius-md: 0.25rem; --radius-lg: 0.375rem;
--shadow-md: 0 6px 18px color-mix(in srgb, var(--color-fg) 10%, transparent);
}
[data-shape="soft"] {
--radius-md: 0.75rem; --radius-lg: 1.25rem;
--shadow-md: 0 10px 28px color-mix(in srgb, var(--color-accent) 18%, transparent);
}// 1 · Compose a skin at the root. Three attributes, done.
<html data-palette="champagne" data-type="playfair" data-shape="refined">
// 2 · Drop in the customer's real brand color. Nothing else moves.
<html ... style="--color-accent:#b89254">
// 3 · Components read tokens, never hardcode. Example Button:
export function Button({ children, ...props }) {
return (
<button
className="font-[var(--font-body)] text-[var(--text-sm)]
px-[var(--space-5)] py-[var(--space-3)]
rounded-[var(--radius-md)] shadow-[var(--shadow-md)]
bg-[var(--color-accent)] text-[var(--color-accent-fg)]
focus-visible:outline-[var(--color-ring)]"
{...props}
>{children}</button>
);
}Every variable a component may read
Token contract reference
| Token | Role · layer |
|---|---|
| Color · layer 1 (palette) | |
| --color-fg | Primary text and iconography on the page background. |
| --color-bg | Page / canvas background. |
| --color-surface | Raised surfaces: cards, inputs, wells. |
| --color-muted | Secondary text, captions, placeholders. AA against bg. |
| --color-accent | Brand action color. The one line a customer overrides. |
| --color-accent-fg | Text / icons ON accent. Tuned AA per palette. |
| --color-border | Hairlines, dividers, input and card outlines. |
| --color-ring | Focus ring. Visible against bg in every palette. |
| --color-success / -warning / -danger | Status: validation, toasts, badges. |
| Type · layer 2 (pairing) + shared ramp | |
| --font-display / -body / -mono | Set by the type layer. Mono is constant. |
| --text-xs … --text-7xl | Shared 11-step ramp. The font swaps, the ramp holds. |
| --leading-none … --leading-relaxed | Five shared line-height steps. |
| Shape · layer 3 + shared | |
| --radius-none / -sm / -md / -lg / -pill | Corner slots, filled by the shape layer. |
| --shadow-sm / -md / -lg / -inset | Elevation slots. Reference color tokens, so they adapt. |
| --border-thin / -default / -heavy | 1 / 2 / 3px. Shared widths. |
| Space · shared | |
| --space-1 … --space-32 | 4px base scale. All padding, gaps, rhythm. |
Built in, not bolted on
Accessibility & rationale
Accessibility notes
- Contrast. Each palette's --color-fg on --color-bg clears WCAG AA for body text; every --color-accent-fg is tuned to its accent (charcoal on gold, dark on coral, black on yellow, bone on oxblood), all AA.
- Pasted accents. When a customer hex is dropped in, --color-accent-fg is auto-chosen by relative luminance so on-accent text stays legible without a manual pass.
- Focus. A dedicated --color-ring guarantees a visible indicator in every palette, never relying on a low-contrast accent.
- Muted floor. --color-muted is held above 4.5:1 against bg so secondary text and placeholders stay legible.
- No build-time color. All color is a token: a brand audit is fixed in one file, every component inherits the correction.
Why three layers
The Trades skin was never really yellow, it was zero-radius, hard-shadow, condensed display. The yellow is just a palette slotted in. Splitting a skin into palette + type + shape means a real customer who already owns a logo keeps the personality they need and swaps only the one color that is theirs.
Twenty palettes, eleven pairings, five shapes is 1,100 named combinations before a single custom hex, all served by one untouched component library. The presets are just convenient starting points on that grid, not a fixed menu, and any client brand guide drops straight in on top.