@estebanruano/design-tokens
v1.0.38
Published
Design system tokens — single source of truth for all platforms
Maintainers
Readme
Design System Tokens
Single source of truth for all design tokens — DESIGN.md (the google-labs DESIGN.md format: YAML frontmatter + human-readable body) is the source of truth, generated into tokens/, platform dist/ outputs, and figma/tokens.json (now a generated artifact for Figma / Tokens Studio import).
Documentation
| Guide | Audience |
|-------|----------|
| DESIGN.md SSOT | DESIGN.md as source — pnpm run sync |
| Workflow & production | Releases (markdown-sync sections are historical) |
| General next steps | Platform leads — adopting tokens across web, mobile, Flutter |
| Android + Material 3 | Android / Compose — theme mapping |
| DESIGN.md | The source of truth — token values + design rationale |
Quick Start
pnpm install
# Edit token values in DESIGN.md (YAML frontmatter — the SSOT), then:
pnpm run sync| Command | Source | Generates |
|---------|--------|-----------|
| pnpm run sync | DESIGN.md | tokens/, dist/**, figma/tokens.json, package.json ← VERSION |
pnpm run sync is an alias for sync:spec. The legacy pnpm run sync:figma (import figma/tokens.json → tokens/) is kept for migration only.
Pipeline
DESIGN.md → everything
DESIGN.md → pnpm run sync → tokens/ + dist/ + figma/tokens.jsonVersioning (releases)
Release numbers for npm (@estebanruano/design-tokens), Android Maven (tokensVersion), and the version field in package.json all come from the VERSION file at the repo root (single line, semver). pnpm run version:set -- --version x.y.z writes it (and mirrors it into the foundations md and package.json); pnpm run sync copies VERSION into package.json; Gradle reads VERSION when -PtokensVersion / TOKENS_VERSION are unset. Bump it for each release (npm and GitHub Packages reject duplicate versions). DESIGN.md's version: alpha is the format version and is ignored for releases.
Further reading: Workflow & production · General next steps · Android + Material 3
Using tokens on the web
Web artifacts are dist/web/tokens.css (CSS custom properties on :root) and dist/web/tokens.js (named ES module exports). dist/figma/tokens.json is for Figma Variables / Tokens Studio import. dist/json/tokens.json is a flat Style Dictionary dump for scripts.
Figma
After pnpm run figma or pnpm run sync, import dist/figma/tokens.json in Tokens Studio for Figma (or your Variables sync plugin). Token names match Oter CSS variables (primary-color, text-primary, type-h1, …). Override the collection name with FIGMA_COLLECTION="My Set" if needed.
In this monorepo / locally
Point your app at the folder (or run pnpm run sync after token edits):
pnpm add "design-tokens@file:../design-system"
# or: npm install file:../path/to/design-systemThen import CSS once (global variables) and/or use JS constants:
import '@estebanruano/design-tokens/css';
import { ColorPrimary500, Spacing4 } from '@estebanruano/design-tokens';/* Bundlers that resolve package exports */
@import '@estebanruano/design-tokens/css';
.my-button {
background: var(--color-brand-primary);
padding: var(--spacing-4);
}Published npm package (recommended for apps)
The package name is @estebanruano/design-tokens. The published version is the **Version:** line in design-system-foundations.md (copied into package.json when you run pnpm run sync before Publish web tokens (npm)). Install from the public npm registry:
pnpm add @estebanruano/design-tokens
# or: npm install @estebanruano/design-tokensUse the same import '@estebanruano/design-tokens/css' and import { … } from '@estebanruano/design-tokens' paths; @estebanruano/design-tokens/json resolves to the flat tokens.json if you need it in Node or build scripts.
npm release checklist (maintainers)
- Bump the version locally:
pnpm run version:set -- --version 1.0.10(writesVERSIONand mirrors it into the foundations md +package.json), runpnpm run sync:figma, commit, push. - GitHub → Actions → Publish web tokens (npm) or Publish Android library → Run workflow on the branch to release (no inputs — the version is read from the
VERSIONfile). Both sync fromfigma/tokens.jsonand builddist/android/*.xmlfor the AAR. - First time only (npm): bootstrap with
npm publish --access public, then configure Trusted publishing forpublish-web.yml(see First publish on npm (bootstrap)).
Fetching without a package manager (CDN)
After a version is on npm, CDNs mirror tarballs, for example:
https://cdn.jsdelivr.net/npm/@estebanruano/[email protected]/dist/web/tokens.csshttps://cdn.jsdelivr.net/npm/@estebanruano/[email protected]/dist/web/tokens.js(ES module; usetype="module"in a script tag only if your page setup supports it)
Pin the version in the URL for reproducible builds. For production SPAs, prefer installing the package so your bundler fingerprints assets and you stay on supported import semantics.
Token Structure
tokens/
├── color/
│ ├── brand.json # Indigo brand (primary, hover, tints)
│ ├── surface.json # Slate surfaces + borders
│ ├── text.json # Text ramp
│ ├── semantic.json # Success, warning, danger, info
│ ├── eisenhower.json # Tasks matrix accents
│ └── gradient.json # Auth hero gradient
├── typography/
│ ├── family.json # Geist, Geist Mono, Lexend
│ ├── weight.json
│ └── scale.json # Semantic type scale (h1–mono)
├── spacing/
│ └── spacing.json # xs → xxl (4px base)
├── radius/
│ └── radius.json # sm → full
├── shadow/
│ └── shadow.json # sm → xl
├── motion/
│ ├── duration.json
│ └── easing.json
└── z-index/
└── z-index.json # Stacking ladderHow to Update Tokens
For engineers (Claude Code)
claude "Update colors.brand.primary to #4F46E5 in DESIGN.md, run pnpm run sync, commit and push"For designers (GitHub Web UI)
- Open
DESIGN.mdon GitHub → Edit → change the value in the YAML frontmatter - Create branch + open PR → CI validates → reviewer merges
For non-technical team (Cloud automation)
- Edit
DESIGN.mdin the shared repo / drive folder - n8n automation validates and opens a PR automatically
Adding a New Token
- Add the value under the matching group in
DESIGN.mdfrontmatter (e.g. a newcolors.brand.*entry):
Thecolors: brand: accent: "#F97316"$typeis inferred by token path inspecValueToDtcg(token-name-map.mjs); add a rule there only for a genuinely new category/shape. The reverse Figma name is derived bytokenPathToFigmaName— extend it (orFIGMA_TO_TOKEN_PATH) only if the default naming is wrong. - Run
pnpm run syncto verify all platforms generate correctly (never edittokens/,dist/, orfigma/tokens.jsonby hand) - Commit and push — CI validates; merge to
main, then run Publish Android library and/or Publish web tokens (npm) manually when you want a Maven or npm release (see below)
Automation (GitHub Actions)
Full setup, branch flows, Figma in prod, release checklists, and troubleshooting: docs/workflow-and-production.md.
| Workflow | When | What it does |
|----------|------|----------------|
| Sync tokens from DESIGN.md | Push to DESIGN.md (or manual) | pnpm run sync → commit tokens/, dist/, figma/tokens.json, package.json |
| CI | PR to main / belcorp | sync → fail on drift → assemble Android |
| Publish web tokens (npm) | Manual | sync → npm publish |
| Publish Android library | Manual | sync → Gradle publish |
Merging to main does not publish npm or Maven — run publish workflows when consumers need a new version.
npm: Trusted publishing setup
Web publishes use npm Trusted Publishing from GitHub Actions (no long-lived NPM_TOKEN). Requirements from npm: Node ≥ 22.14, npm CLI ≥ 11.5.1 (the workflow upgrades npm before publish).
First publish on npm (bootstrap)
The public registry has no @estebanruano/design-tokens until the first successful npm publish. Do this before opening Trusted publishing in the npm UI (that screen needs an existing package). Run as an npm user (or org) that is allowed to publish under the @estebanruano scope. Use Node ≥ 18.12 for pnpm:
cd /path/to/design-system
nvm use 22 # or another Node ≥ 18.12 (pnpm); ≥ 22.14 to match CI
pnpm install --frozen-lockfile
pnpm run sync
npm login # browser login, or use a granular publish token (see npm docs)
npm publish --access public # creates the package; version = package.json (from **Version:** in the MD)Check with npm view @estebanruano/design-tokens version. If publish fails with 403, your npm user does not own the estebanruano scope — create an npm org or change package.json → name to a scope you control. If you see 404 Scope not found, the @estebanruano scope does not exist on npm yet: create an organization named estebanruano at npmjs.com/org/create (and add your user), or rename the package to a scope you already have (for example @<your-npm-username>/design-tokens) and update imports in apps + Trusted publishing after the first publish.
Connect GitHub Actions (Trusted publishing)
After the package exists on npm:
- On npmjs.com → package
@estebanruano/design-tokens→ Settings → Trusted publishing → choose GitHub Actions. - Set the publisher so values match exactly (npm does not validate until publish):
- Repository:
esteban505r/design-system(or your fork’sowner/name— then setpackage.json→repository.urlto that repo’s HTTPS URL, required by npm). - Workflow filename:
publish-web.yml(filename only, including.yml).
- Repository:
- Run Actions → Publish web tokens (npm) on
mainto confirm OIDC works; then you can revoke any bootstrap publish token you no longer need. - Optional hardening: under package Publishing access, npm recommends restricting token-based publishes (docs).
If Publish web tokens (npm) fails with ENEEDAUTH or trusted-publisher errors, re-check the workflow filename, repository name, and repository.url in package.json (https://github.com/esteban505r/design-system.git for this upstream repo).
Android apps add the GitHub Packages Maven URL and dependency (replace OWNER/REPO):
repositories {
maven {
url = uri("https://maven.pkg.github.com/OWNER/REPO")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
}
dependencies {
implementation("com.estebanruano:tokens-android:1.0.2")
}Use the same mavenGroupId, mavenArtifactId, and release version (**Version:** / package.json) as in this design-system repo’s gradle.properties / design-system-foundations.md (e.g. com.estebanruano:tokens-android).
Authenticate for GitHub Packages (local machine): add to ~/.gradle/gradle.properties (do not commit):
gpr.user=YOUR_GITHUB_USERNAME
gpr.key=YOUR_PAT_WITH_read:packagesIn CI for the consuming app, inject the same values (e.g. repository secrets mapped to env vars or ORG_GRADLE_PROJECT_gpr.* so Gradle picks them up).
Material 3 and multi-project structure: see docs/android-material3-next-steps.md for mapping tokens to M3 (Compose + Views), shared theme libraries vs apps, and flavors / multiple products.
Using tokens in Android app code
The tokens-android artifact is a normal com.android.library: it ships resource XML only. After implementation(...), those resources are merged into your app module, so you reference them like any other library resource.
Local repo vs published AAR: this repo keeps four files under dist/android/ (colors.xml, dimens.xml, integers.xml, strings.xml). Android Studio often shows them as one combined <resources> block when you inspect the library dependency — that is normal. If colors or font_size_* differ from your local dist/android/, the app is almost certainly on an older Maven version; bump the dependency and re-run Publish Android library with a new version after pnpm run sync.
Resource names match the generated files in dist/android/: colors.xml, dimens.xml (spacing, radius, and font sizes in sp), integers.xml, strings.xml. There is no R.font_dimens type — font sizes are normal R.dimen entries (e.g. R.dimen.font_size_h1, @dimen/font_size_h1 in XML).
Font sizes in Compose: dimensionResource() returns Dp, but Text.fontSize needs TextUnit (sp). Use:
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
Text(
fontSize = with(LocalDensity.current) {
dimensionResource(R.dimen.font_size_h1).toSp()
},
)XML layouts
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/color_brand_primary"
android:textSize="@dimen/font_size_body"
android:padding="@dimen/spacing_md" />styles.xml / Material theme
<style name="Theme.MyApp" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/color_primary_500</item>
<item name="colorOnPrimary">@color/color_neutral_0</item>
</style>Kotlin (Views, no Compose) — use your application module R (it includes merged library resources):
import androidx.core.content.ContextCompat
import com.yourapp.R
val color = ContextCompat.getColor(context, R.color.color_primary_500)
view.setBackgroundColor(color)
val paddingPx = resources.getDimensionPixelSize(R.dimen.spacing_4)Jetpack Compose
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import com.yourapp.R
@Composable
fun BrandSurface() {
Surface(color = colorResource(R.color.color_primary_500)) {
// …
}
}dimensionResource(R.dimen.…) follows normal Android dimen semantics; check the AndroidX Compose docs for your BOM to see how values map to Dp in composables.
R class / non-transitive R
With android.nonTransitiveRClass=true, you still normally use com.yourapp.R in the app module for merged resources from dependencies. The library’s own namespace (tokensAndroidNamespace in gradle.properties) is mainly for the AAR’s internal R / manifest, not something you must import in app code unless you choose to.
Name clashes
If your app defines the same resource name (e.g. color_primary_500) in res/values/, the app resource overrides the library. To avoid collisions long-term, add a stable prefix in the token build (e.g. ds_color_primary_500) in Style Dictionary / naming convention.
Local Gradle in this repo copies dist/android/*.xml into design-tokens-android on each preBuild — run pnpm run sync before ./gradlew if dist/android is missing.
Adding a New Platform
Edit sd.config.mjs and add a new platform entry. See Style Dictionary docs for available formats and transform groups.
Semver guidelines (token changes)
When you bump **Version:** in the foundations doc for a release, align the bump with the kind of token change (same ideas as semver):
- Major (2.0.0): Breaking change — token renamed or removed
- Minor (1.1.0): New tokens added
- Patch (1.0.1): Token value changed
