Consumer Integration Guide
This guide is for any project that consumes @verdigristech/design-tokens: Patina, www, the evaluator pipeline, the Cowork skill, internal tooling, and external partners.
If you read top-to-bottom, you will be running with the package in about 5 minutes. The rest is reference.
Quick start (5 minutes)
npm install @verdigristech/design-tokens
The package is published to GitHub Packages under the @verdigristech scope. Authenticate first with a ~/.npmrc entry:
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT
@verdigristech:registry=https://npm.pkg.github.com
Pin to an exact version in package.json. Floats invite drift across consumers:
{
"dependencies": {
"@verdigristech/design-tokens": "0.8.0"
}
}
That is enough to import any of the surfaces below.
What ships in the package
| Surface | Path | Use when |
|---|---|---|
| JS module | @verdigristech/design-tokens |
Reading token values from JS / TS |
| Tailwind preset | @verdigristech/design-tokens/tailwind |
Adding tokens to a Tailwind project |
| CSS variables (OKLch) | @verdigristech/design-tokens/css/oklch |
Patina-style modern stylesheets |
| CSS variables (HSL) | @verdigristech/design-tokens/css/hsl |
Legacy stylesheets without OKLch support |
| Hex JSON | @verdigristech/design-tokens/hex |
Email, print, Figma exports |
| Print stylesheets | @verdigristech/design-tokens/print/<name>.css |
Whitepaper, slides, one-pagers, case studies |
| Token JSON | @verdigristech/design-tokens/tokens/<path> |
Raw W3C DTCG token files |
| Visual rules | @verdigristech/design-tokens/rules/<name> |
Evaluator pipeline rule loading |
| Voice recipes | @verdigristech/design-tokens/voice/<file> |
Content generation systems |
voice/team/* is intentionally not exported. It contains real personal voice profiles and is repo-internal per Loop 5O. The npm run test:package check enforces this at build time.
Logo and brand assets
Logo and banner files are not in the npm package. They are served from the docs site so partners, press, and anyone without repo access can reach them, at design.verdigris.co/brand-assets.
| What | Where |
|---|---|
| Whole kit, one file | /assets/brand-kit/verdigris-brand-kit.zip |
| Logo SVG (6 variants) | /assets/logos/{icon,lockup}-{teal,white,dark}.svg |
| Logo PNG | /assets/logos/png/<variant>-<width>.png |
| Banner | /assets/banners/verdigris-banner.{avif,png} |
Use the SVG unless your tool cannot place vector files. The assets/logos/brand/logo-green.svg and logo-white.svg files are superseded Sketch exports kept only for history; logo-green.svg carries a broken drop-shadow filter. Do not use them.
The PNGs and the zip are generated by npm run build:brand-kit and checked by npm run validate:brand-kit, which fails if a source asset changed without the kit being rebuilt.
Tailwind preset
Use the preset to inherit the full token surface (colors, spacing, font sizes, radii) with one import. Extend it with project-specific overrides.
// tailwind.config.js
import designPreset from '@verdigristech/design-tokens/tailwind';
export default {
presets: [designPreset],
content: ['./src/**/*.{ts,tsx,html}'],
theme: {
extend: {
// project-specific additions go here, not in the preset
colors: {
'patina-feature': 'oklch(0.6 0.18 90)',
},
},
},
};
Failure mode prevented: hand-coding #0fc8c3 across files. When the OKLch reference shifts in v5, your preset picks it up; hand-coded hex stays wrong forever.
Raw JS token consumption
The default export module exposes flat-key dictionaries. Keys are the full token path; values are resolved primitives. Use this pattern for Canvas, Remotion, evaluator code, or any non-Tailwind consumer.
// ESM
import {
hexColors,
typography,
spacing,
elevation,
radius,
viz,
durations,
easings,
} from '@verdigristech/design-tokens';
hexColors['color.brand.verdigris']; // '#0fc8c3'
typography['fontSize.h1']; // '4rem'
spacing['spacing.4']; // '1rem'
spacing['spacing.print.cover.layout.deck-to-byline-min']; // '1.4in' (unit-split groups: .layout / .type)
elevation['shadow.md']; // '0 4px 6px -1px rgb(0 0 0 / 0.1), ...'
elevation['zIndex.overlay']; // 50
radius['radius.base']; // '0.625rem'
viz.trace.primary; // '#0fc8c3'
durations.normal; // 200
easings.revealCubic(0.5); // 0.875
// CommonJS (Node, older toolchains)
const tokens = require('@verdigristech/design-tokens');
console.log(tokens.hexColors['color.brand.verdigris']);
For raw token JSON without the resolved-primitive convenience:
// Modern bundlers (Vite, Webpack 5+, esbuild, Rollup with @rollup/plugin-json)
import baseColor from '@verdigristech/design-tokens/tokens/color/base.json';
// Node.js (no bundler) — works on any LTS without import-attributes flags
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const baseColorPath = require.resolve('@verdigristech/design-tokens/tokens/color/base.json');
const baseColor = JSON.parse(readFileSync(baseColorPath, 'utf8'));
Avoid import ... with { type: 'json' } (ES2025 import attributes) — it works in modern Node + Chromium but trips older bundler toolchains. The two patterns above cover every consumer.
Failure mode prevented: parsing tokens directly from disk in each consumer. Each parser invents its own reference-resolution and dotted-path convention; over a year, three consumers ship three subtly different views of the same token.
CSS reference stylesheets
/* Modern browsers, Patina pattern */
@import '@verdigristech/design-tokens/css/oklch';
.hero { color: var(--color-brand-verdigris); }
/* Legacy fallback. Prefer OKLch when you can. */
@import '@verdigristech/design-tokens/css/hsl';
For paged PDF formats, import the matching print stylesheet:
@import '@verdigristech/design-tokens/print/cover.css'; /* whitepaper covers */
@import '@verdigristech/design-tokens/print/slides.css'; /* sales decks */
@import '@verdigristech/design-tokens/print/one-pager.css'; /* leave-behinds */
@import '@verdigristech/design-tokens/print/case-study.css'; /* customer stories */
When to choose oklch.css vs hsl.css: ship OKLch if your audience is on a recent Chromium, Safari, or Firefox release. Use HSL only as a fallback for legacy SSR pipelines or email clients. Ship both with @supports if you need to bridge.
Failure mode prevented: redefining brand colors in each downstream stylesheet. The CSS variables are the canonical surface; hand-redefining --color-brand-verdigris locally means a brand refresh requires N find-replace passes.
Voice recipes and ingredients
Content generation systems (Cowork skill, AI agents writing on Verdigris’s behalf) consume the public voice surface:
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import yaml from 'js-yaml';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const recipesPath = require.resolve(
'@verdigristech/design-tokens/voice/recipes.yaml'
);
const recipes = yaml.load(readFileSync(recipesPath, 'utf-8'));
// Pick the recipe for the surface you are writing for
const homepage = recipes.recipes.homepage;
// homepage.mix is { strategic_narrative: 35, technical_precision: 25, ... }
Three companion files round out the public voice surface:
| File | Purpose |
|---|---|
voice/recipes.yaml |
Ratio mixes per surface (homepage, case study, slide deck, etc.) |
voice/ingredients.yaml |
The ingredient definitions referenced by every recipe |
voice/feelings.yaml |
Target emotional outcomes per audience |
voice/USE.md |
Read this once before generating any content |
voice/README.md |
Overview of the voice system |
voice/team/*.yaml is not part of the public surface. Those files contain individual voice profiles and stay inside this repo only. Do not vendor them into a downstream codebase even if you have a copy locally.
Failure mode prevented: Cowork or any other skill picking a recipe by guessing. Ground every generation in a recipe lookup; if no recipe fits, file a Linear issue against this repo to add one.
Visual rules YAML
The evaluator pipeline (and any future linter) consumes rules/visual-rules.yml to enforce design constraints across surfaces.
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import yaml from 'js-yaml';
const require = createRequire(import.meta.url);
const rulesPath = require.resolve(
'@verdigristech/design-tokens/rules/visual-rules.yml'
);
const rules = yaml.load(readFileSync(rulesPath, 'utf-8'));
// rules.rules is an array of { id, severity, type, description, test, maturity? }
Schema notes for evaluator authors:
- Every rule has
id,severity,type,description. type: "constraint"rules carry atestblock (regex, min/max, value, values, or llm_eval).type: "reference"entries are informational and have no severity.maturitycontrols enforcement:experimental(warning),convention(warning + justify),rule(default, blocks merge),invariant(axiomatic).- All
llm_evalprompts use the YES = violation convention. Invert if your evaluator expects the opposite.
Failure mode prevented: an evaluator silently passing rules it does not understand. Validate the schema version before loading; the file declares it in the header (Version: 4.x.x).
Worked example: Cowork skill
Cowork generates content on Verdigris’s behalf. It needs three things from this package: a voice recipe, the brand colors that will appear in any rendered preview, and the rules YAML for an evaluator pre-flight check.
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import yaml from 'js-yaml';
import { hexColors } from '@verdigristech/design-tokens';
const require = createRequire(import.meta.url);
function loadYaml(spec) {
return yaml.load(readFileSync(require.resolve(spec), 'utf-8'));
}
const recipes = loadYaml('@verdigristech/design-tokens/voice/recipes.yaml');
const ingredients = loadYaml(
'@verdigristech/design-tokens/voice/ingredients.yaml'
);
const rules = loadYaml(
'@verdigristech/design-tokens/rules/visual-rules.yml'
);
export function buildContext(surface) {
const recipe = recipes.recipes[surface];
if (!recipe) {
throw new Error(`No recipe for surface "${surface}". File a Linear issue.`);
}
return {
recipe,
ingredients: ingredients.ingredients,
rules: rules.rules,
palette: {
primary: hexColors['color.brand.verdigris'],
ink: hexColors['color.neutral.950'],
paper: hexColors['color.neutral.50'],
},
};
}
This pattern keeps Cowork pinned to a known recipe matrix. When this repo adds a new recipe (say, partner-enablement-deck), Cowork picks it up after a npm install of the new version. No Cowork code change required.
Versioning and breaking-change policy
This package follows semver. Releases are automated by the auto-release.yml workflow on merge to main.
| Bump | Triggers | Examples |
|---|---|---|
| Major | PR labeled major, or BREAKING CHANGE in commit body |
Renamed token paths, removed tokens, schema changes that break evaluator pipelines |
| Minor | PR labeled minor, or any feat() commit prefix |
New tokens, new rules, new recipes, new compositions |
| Patch | Default | Value tweaks, doc updates, YAML corrections |
What is part of the contract:
- Every exported member of the JS module (
hexColors,typography,spacing,elevation,radius,viz,durations,easings). - The Tailwind preset’s color, spacing, fontSize, and borderRadius keys.
- Every CSS variable name in
oklch.cssandhsl.css. - The shape of every published JSON token file.
- The IDs of every rule in
visual-rules.yml. - The
voice/recipes.yaml,voice/ingredients.yaml,voice/feelings.yamlschemas. - The
print/*.cssclass names.
Renaming or removing any of the above requires a major bump.
What is not part of the contract:
- Internal token values may shift within a minor bump if the canonical source moves (for example, Patina ships a calibrated brand teal). Consumers reading via tokens or CSS variables get the new value automatically.
- Rule values (min/max, threshold) may tighten within a minor bump if no surface currently violates the new bound.
- Voice recipe mixes may shift within a minor bump.
The npm run test:package script enforces the package surface boundary. Adding a directory to package.json#files requires updating the test’s positive list.
Pinning strategy
| Consumer type | Recommended pin |
|---|---|
| Patina (reference implementation) | Track main via git dependency for fast iteration |
| www site | Pin to the latest minor; bump on release |
| Evaluator pipeline | Pin to an exact version; coordinate bumps with rule authors |
| External partners | Pin to a specific minor; review changelog before bumping |
| AI skills (Cowork, internal tools) | Pin to an exact version; CI bumps via dependabot |
Floating ranges (^0.8.0, ~0.8) work but make change attribution harder. Pin exactly when you can.
Filing integration issues
If anything in this guide is wrong, missing, or unclear, file an issue against VerdigrisTech/design with the label consumer-integration and a one-line description of what you tried to do.
For Linear users: file under team Z2O, project “Design System: VerdigrisTech/design”. Include:
- The consumer (Patina, www, evaluator, Cowork, partner name).
- The version pinned (
@verdigristech/design-tokens@x.y.z). - The pattern that did not work (paste the import or the runtime error).
- What you expected.
Most consumer issues are documentation gaps. Filing the issue closes that gap for the next consumer.
Related reading
README.md: repo overview and quick links.CLAUDE.md: project conventions, build pipeline, release process.AGENTS.md: how AI agents should consume this repo.LEARNINGS.md: production-process patterns the design repo absorbs from real consumer integrations.workflows/pii-review.md: whyvoice/team/*is repo-internal.