/// guides
Adopting PDS in an existing app
You rarely rewrite a live app in a weekend, and you should not. PDS is built to move in — coexisting with your current styles while you replace ad-hoc UI page by page, with a working product at every step.
The shape of a migration
- [ 01 ]Install & isolate
- [ 02 ]Map your variables
- [ 03 ]Adopt the contract
- [ 04 ]Migrate page by page
Each step is independently shippable. You never need a big-bang cutover, and you can stop at any point with a coherent result.
1. Install and isolate
Follow Installation to add the package, stylesheet, and fonts. The stylesheet only defines --pds-* variables and .pds-* classes, so it cannot collide with your existing class names. Import it before your own global CSS so your rules keep winning until you retire them.
2. Map your variables to the semantic layer
If your app already themes with CSS custom properties, the fastest way to make new PDS components match your look is to point the PDS semantic tokens at your existing variables. Do this once, at :root:
:root {
--pds-color-canvas: var(--app-bg);
--pds-color-surface: var(--app-surface);
--pds-color-ink: var(--app-text);
--pds-color-secondary: var(--app-text-muted);
--pds-accent: var(--app-brand);
}Now every PDS component inherits your palette without any per-component overrides. As you migrate, you can flip the direction — let PDS tokens be the source of truth and retire the app variables. Map at the semantic layer only; never reach for the private --pds-p-* primitives.
3. Adopt the component contract
The highest-leverage change is replacing ad-hoc elements with the right PDS component. The system draws hard lines between four look-alikes that most codebases blur — get these right and the interface immediately reads as intentional. Case and family encode interactivity: uppercase mono is actionable, sentence-case sans tint is status, as-typed muted mono is metadata.
| Replace this | With | When it… |
|---|---|---|
<button>, styled <a>-as-button | Button | Does something in place — submits, deploys, opens. Use Link when it navigates. |
| Coloured status pill | Badge | Shows a state (success, warning…). Static, sentence-case, always labelled — never colour alone. |
| Category / label pill | Tag | Is inert metadata — an id, a version, a label shown as typed. Optionally copy-on-click. |
| Toggle / filter pill | Chip | Holds a selected state that filters or toggles a view. It is a pressed toggle, not a one-shot action. |
4. Migrate page by page
Pick one screen — ideally a dense, control-heavy one where the system pays off fastest — and convert it end to end: chrome, controls, tables, states. Ship it. The converted page becomes the reference the rest of the team copies from, and the contrast with the un-migrated screens makes the case for the next one.
- Start with a real screen, not a component gallery — you want the patterns (AppShell, data tables, forms) exercised together.
- Convert whole regions, not stray elements, so a half-migrated screen never mixes two button styles side by side.
- Delete the old CSS as you go. Leaving dead rules behind is how a migration stalls at 80%.
Coexisting with Tailwind
PDS and Tailwind live together fine — they occupy different layers. PDS owns component internals (you do not style a Button’s guts with utilities); Tailwind is handy for one-off page layout around PDS components. A few notes keep them from fighting:
| Concern | What to do |
|---|---|
| Preflight reset | Tailwind’s Preflight is gentle and does not target .pds-* classes; no special handling needed. Keep it enabled. |
| Layout utilities | Fine to use flex, grid, and spacing utilities around components. Prefer the PDS layout components (Stack, Grid, Container) for brand rhythm. |
| Restyling internals | Don’t. Never add utilities to a component’s inner markup or override its radius/shadow. Theme through tokens instead. |
| Colour utilities | Point Tailwind’s theme colours at the PDS tokens (e.g. bg-[var(--pds-color-surface)]) so both systems draw from one palette. |
The same rule applies to any utility framework: use it for layout scaffolding, never to restyle a component’s internals or bypass the token layer.