Design systems
Three token tiers, the semantic contract every pack satisfies, the Material 3, Carbon and Ant Design packs, and how to build your own.
A UI document never contains a colour, a font size or a radius. Every visual value comes from a design-system pack: a set of design tokens in the W3C DTCG 2025.10 format. Swap the pack and the same document renders in a different design system, with no change to the JSON.
Three token tiers#
| Tier | What it holds | Example |
|---|---|---|
| Primitive (reference) | Raw palettes, type families, spacing steps | md.ref.palette.primary.40, carbon.color.blue.60 |
| System | The design system's own named roles, often per mode | md.sys.color.on-surface, carbon.theme.text-primary, antd.token.colorText |
| Semantic | The Polyxd contract: the only tier renderers and documents see | color.text.default, type.body.default, size.target.min |
Each pack keeps its design system's own names in the primitive and system tiers, so every semantic token can be traced back to its source. Semantic tokens are {alias} references to system tokens wherever an equivalent exists.
The semantic contract#
packages/spec/tokens/semantic-contract.json (contract version 0.1.0) lists what every pack must provide, in every mode it declares:
- 86 tokens: colour (43: surfaces, text, borders, actions, selection, status, data), type (8), space (10), motion (7), radius (4), opacity (4), size (3), border (2), focus (2), shadow (2) and measure (1).
- 34 contrast pairs: 18 text pairs at WCAG 2.2 1.4.3 (4.5:1) and 16 non-text pairs at WCAG 2.2 1.4.11 (3:1).
- 4 constraints:
| Token | Constraint | Why |
|---|---|---|
type.body.default |
font size at least 16px | Readable body text |
size.target.min |
at least 24px | WCAG 2.2 2.5.8 target size (minimum) |
measure.max |
45–75 | Comfortable line length |
opacity.state.disabled |
0.3–0.6 | Disabled content stays perceivable but clearly inactive |
The contract checker resolves aliases, checks each token's type, measures every contrast pair (compositing translucent colours before measuring), and checks the constraints.
The packs#
| Pack | Modes | Built from |
|---|---|---|
@polyxd/ds-material3 |
light, dark | @material/material-color-utilities 0.4.0 (seed #6750A4, tonal spot) and material-web v0_192 token sources |
@polyxd/ds-carbon |
light (Carbon White), dark (Carbon Gray 100) | @carbon/colors, themes, layout, type, motion and charts, at pinned versions |
@polyxd/ds-antd |
light (defaultAlgorithm), dark (darkAlgorithm) |
antd 6.6.4 and @ant-design/colors 8.0.1 |
Each pack is generated by a script from vendored, version-pinned sources, gives the same output on every run, and records provenance in its manifest. None of the values are scraped from design-system websites.
What building the packs found#
Mapping three real design systems onto one contract surfaced real accessibility gaps. The packs fix them by moving to the nearest passing value from the same design system, and leave the original tokens in place.
- Material 3 passes every contrast pair with its raw roles in both modes. No tones were adjusted.
- Ant Design's defaults fail WCAG in several places. White on the primary blue
#1677ffis 4.10:1, below 4.5:1 for normal text. Warning text on the warning background is 1.83:1. The default input border#d9d9d9is 1.41:1 against the 3:1 non-text minimum. The pack moves each failing role to the nearest passing step of the same Ant palette; for example, the primary background becomes step 7#0958d9(6.16:1). - Carbon's light warning colour (
$support-warning, yellow 30) is 1.68:1 on white. The pack uses Carbon's own$status-yellow-outline(yellow 60, 4.99:1) instead. - Disabled opacity is below the contract's 0.3 minimum in both Carbon (0.25, raised to 0.3) and Ant (0.25, raised to 0.45, the next step of Ant's text-alpha ladder).
- Body text is 16px, not 14px. Both Carbon and Ant default to 14px body text. The contract requires
type.body.defaultof at least 16px, so both packs map it to their 16px style (body-02in Carbon,fontSizeLGin Ant) and keep 14px fortype.body.smalland labels. - Translucent colours (Ant's neutrals are alpha colours) are composited on the real background before contrast is measured.
Each pack's README lists every mapping and adjustment with before and after ratios.
Theme CSS#
@polyxd/react compiles each pack into one CSS file, themes/<pack>.css. Only semantic contract tokens are emitted; renderers never see primitives.
[data-pxd-theme="material3"]:not([data-pxd-mode]),
[data-pxd-theme="material3"][data-pxd-mode="light"] {
--pxd-color-surface-default: #fdf7ff;
--pxd-color-text-default: #1d1b20;
--pxd-color-action-primary-background: #65558f;
/* … every contract token … */
/* shadcn/ui variable names, so shadcn components follow this pack too */
--background: var(--pxd-color-surface-default);
--primary: var(--pxd-color-action-primary-background);
--radius: var(--pxd-radius-default);
color-scheme: light;
}
Token names become variables by replacing dots with dashes and adding --pxd-: color.text.default is --pxd-color-text-default. Typography tokens expand into -family, -size, -weight, -line-height and -letter-spacing.
Each theme also sets shadcn/ui's variable names (--background, --foreground, --card, --popover, --primary, --secondary, --muted, --accent, --destructive, --border, --input, --ring, --radius, --chart-1 to --chart-5), so an existing shadcn app picks up the same pack.
The PolyxdSurface theme and mode props set data-pxd-theme and data-pxd-mode on the surface element.
Make your own pack#
A pack is a manifest plus DTCG token files.
1. Write the manifest (manifest.json, validated by schema/design-system.schema.json). Files listed for a mode are merged in order, later files overriding earlier ones.
{
"$schema": "../spec/schema/design-system.schema.json",
"name": "acme",
"displayName": "Acme",
"version": "0.1.0",
"contractVersion": "0.1.0",
"license": "Apache-2.0",
"modes": {
"light": ["tokens/primitive.json", "tokens/system.json", "tokens/system.light.json", "tokens/semantic.json"],
"dark": ["tokens/primitive.json", "tokens/system.json", "tokens/system.dark.json", "tokens/semantic.json"]
},
"defaultMode": "light",
"provenance": [{ "source": "Acme brand tokens", "version": "2026.1" }]
}
2. Write DTCG token files. Keep your own names in the primitive and system tiers, and alias them from a semantic file that defines all 86 contract tokens. A $type on a group is inherited by its tokens.
{
"color": {
"$type": "color",
"text": {
"default": { "$value": "{acme.color.ink}" },
"muted": { "$value": "{acme.color.ink-soft}" }
}
}
}
3. Check it against the contract.
npm run check-ds -w @polyxd/spec -- ../ds-acme/manifest.json
[light] color.text.muted: contrast 3.92:1 against color.surface.default is below 4.5:1 (WCAG 2.2 1.4.3)
1 issue(s)
It exits with 0 and prints "Design system satisfies the semantic token contract." when every mode passes. From code, use checkDesignSystem(manifestPath) from @polyxd/spec.
4. Compile it to CSS. Put the pack in packages/ds-<name>/ and run npm run build:themes -w @polyxd/react, or pass manifest paths to scripts/build-themes.ts directly.
An importer for Figma variables and Tokens Studio is planned but does not exist yet.
Polyxd is an early preview. Found something unclear? It will get better with your feedback.