PLW Component Library Foundation · 01 · Token Contract Vol. 1 · Rev. 3 · June 2026

One contract.
Composed skins.

A skin is not one thing, it is a stack: a palette, a type pairing, and a shape personality, each an independent layer. Mix any combination, then drop in a customer's real logo color. Components read the same tokens and never change. Same skeleton, their brand.

01

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.

Presetslayer combos
1 · Palette--color-*
Customer brand
Accent

Paste the logo hex. --color-accent-fg auto-picks a readable on-color.

2 · Type--font-*
3 · Shape--radius · --shadow

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.

Retained est. 1994 AV rated

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.

02

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.

03

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.

04

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.

05

Constant across every layer

The shared skeleton

These never change between customers or layers. The type ramp holds while fonts swap; spacing rhythm, line-heights, and border widths are identical everywhere. That constancy is what lets one component serve every brand.

Type scale · --text-xs → --text-7xl

Space · 4px base · --space-1 → --space-32

Line-height tokens

--leading-tight
1.15
Headlines and display type want air pulled tight so the eye reads the shape of the words first, the line second.
--leading-snug
1.3
Subheads and short stacked phrases sit at snug, holding together without crowding their neighbours.
--leading-normal
1.5
Body copy lives at normal. It is the default for any paragraph a visitor is expected to read end to end.
--leading-relaxed
1.7
Long-form editorial and legal prose loosen to relaxed, where extended reading earns the extra breathing room.

The slots · filled per shape

Radius and border are slot names. The Brutalist shape fills --radius-md with 0; Soft fills it with 12px. The component only ever asks for --radius-md.

Radius · none / sm / md / lg / pill

none0
sm0–6px
md0–12px
lg0–20px
pill9999

Border · thin / default / heavy

thin1px
default2px
heavy3px
06

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>
  );
}
07

Every variable a component may read

Token contract reference

TokenRole · layer
Color · layer 1 (palette)
--color-fgPrimary text and iconography on the page background.
--color-bgPage / canvas background.
--color-surfaceRaised surfaces: cards, inputs, wells.
--color-mutedSecondary text, captions, placeholders. AA against bg.
--color-accentBrand action color. The one line a customer overrides.
--color-accent-fgText / icons ON accent. Tuned AA per palette.
--color-borderHairlines, dividers, input and card outlines.
--color-ringFocus ring. Visible against bg in every palette.
--color-success / -warning / -dangerStatus: validation, toasts, badges.
Type · layer 2 (pairing) + shared ramp
--font-display / -body / -monoSet by the type layer. Mono is constant.
--text-xs … --text-7xlShared 11-step ramp. The font swaps, the ramp holds.
--leading-none … --leading-relaxedFive shared line-height steps.
Shape · layer 3 + shared
--radius-none / -sm / -md / -lg / -pillCorner slots, filled by the shape layer.
--shadow-sm / -md / -lg / -insetElevation slots. Reference color tokens, so they adapt.
--border-thin / -default / -heavy1 / 2 / 3px. Shared widths.
Space · shared
--space-1 … --space-324px base scale. All padding, gaps, rhythm.
08

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.

Tokens drive everything Mobile-first 360px WCAG 2.1 AA Editorial weight No em-dashes