@servicetitan/hammer-token
v3.0.2
Published
Design token system for the Hammer design system, built with [Style Dictionary v5](https://styledictionary.com/) and the [Design Tokens Community Group (DTCG) format](https://www.designtokens.org/tr/2025.10/format/).
Downloads
55,091
Keywords
Readme
@servicetitan/hammer-token
Design token system for the Hammer design system, built with Style Dictionary v5 and the Design Tokens Community Group (DTCG) format.
Overview
This package transforms design tokens from JSON source files into multiple output formats for use across web applications. It generates JavaScript modules, SCSS variables, CSS utility classes, and TypeScript definitions to support both runtime and build-time token consumption.
Token Structure
Tokens follow the Design Tokens Community Group (DTCG) format.
Primitive Color Token
Primitive tokens have a static value and declare their Figma variable scope via $extensions["com.figma.scopes"]. An empty array means no scope restriction (the variable appears in all pickers).
{
"$type": "color",
"$value": "#ffffff",
"$extensions": {
"com.figma.scopes": []
}
}Semantic Color Token (with dark mode)
Semantic tokens reference primitives and include light/dark appearance variants under $extensions.appearance. Both light and dark repeat the $type and use $value.
{
"$type": "color",
"$value": "{status.color.info}",
"$extensions": {
"appearance": {
"light": {
"$type": "color",
"$value": "{status.color.info}"
},
"dark": {
"$type": "color",
"$value": "{status.color.info}"
}
},
"com.figma.scopes": ["STROKE_COLOR"]
}
}Composite Color Token (color + alpha)
Used for colors that need an alpha channel. The color field is a reference to a primitive token; the build system emits color-mix() in CSS output to preserve the live primitive CSS variable.
{
"$type": "color",
"$value": {
"color": "{color.neutral.900}",
"alpha": 0.08
},
"$extensions": {
"appearance": {
"light": {
"$type": "color",
"$value": { "color": "{color.neutral.900}", "alpha": 0.08 }
},
"dark": {
"$type": "color",
"$value": { "color": "{color.neutral.0}", "alpha": 0.08 }
}
},
"com.figma.scopes": ["EFFECT_COLOR"]
}
}Dimension Token
Dimension tokens (sizes, radii, breakpoints) use a nested object for $value with value and unit fields.
{
"$type": "dimension",
"$value": {
"value": 0.375,
"unit": "rem"
},
"$extensions": {
"com.figma.scopes": ["CORNER_RADIUS"]
}
}The $root Pattern
The $root pattern allows a token group to have a default value while still containing sub-tokens (e.g. interactive states). A node with $value cannot normally also have children; $root bridges this by placing the default value one level deeper.
"primary": {
"$root": {
"$type": "color",
"$value": "{color.blue.500}"
},
"hover": {
"$type": "color",
"$value": "{color.blue.600}"
},
"active": {
"$type": "color",
"$value": "{color.blue.700}"
}
}You can reference the group directly as {background.color.primary} — the build system resolves it to the .$root token automatically. The $root segment is stripped from all generated names (e.g. --a2-background-color-primary, not --a2-background-color-primary-root).
Token Naming Conventions
- Path to name: JSON object paths become hyphen-separated names (CSS/SCSS) or PascalCase (JS exports).
$rootsegments: Stripped from the path in all outputs.- State suffixes: State names (
hover,active) are included as-is in the hyphenated name and capitalized in PascalCase exports.
Build Output (/build/web)
All generated CSS variable names use the a2- prefix by default (e.g. --a2-background-color-primary).
The build process executes three sequential Style Dictionary builds — Primitive → Theme (semantic) → Component — and writes output to /build/web:
build/web/
├── index.js # Main entry point
├── index.d.ts # TypeScript definitions
├── types.d.ts # Core types (TokenObj, Token)
└── core/
├── primitive.js # Primitive tokens (JS)
├── primitive.scss # Primitive tokens (SCSS)
├── primitive-variables.scss # Primitive tokens map (SCSS)
├── primitive.d.ts # Primitive tokens (TypeScript)
├── semantic.js # Semantic tokens (JS)
├── semantic.scss # Semantic tokens (SCSS)
├── semantic-variables.scss # Semantic tokens map (SCSS)
├── semantic.d.ts # Semantic tokens (TypeScript)
├── component.js # Component tokens (JS)
├── component.scss # Component tokens (SCSS)
├── component-variables.scss # Component tokens map (SCSS)
├── component.d.ts # Component tokens (TypeScript)
├── index.js # Core index
├── index.d.ts # Core TypeScript definitions
└── css-utils/ # CSS utility classesFile Types
*.js Files
ES6 named exports with static resolved values. Tokens with dark variants include an extensions.appearance.dark.value property.
import { BackgroundColorPrimary } from "@servicetitan/hammer-token/build/web/core/semantic";
// { value: "#0265dc", extensions: { appearance: { dark: { value: "#78bbfa" } } } }Caution: Values are resolved at build time and do not respond to CSS variable changes at runtime. Use
*.scssfiles when dynamic theming is needed.
*.scss Files
SCSS variables with var(--a2-name, fallback) syntax, supporting recursive reference chains and light-dark() for dark mode.
*-variables.scss Files
SCSS maps ($light, $dark, $nonColor) used by ThemeProvider.module.scss.
.d.ts Files
Auto-generated TypeScript definitions using the unified TokenObj type.
import { TokenObj } from "../types";
export declare const ButtonPrimaryBackgroundColor: TokenObj;Utility Files (src/utils/)
token-helpers.js
Core helpers for token value extraction, reference resolution, and CSS fallback building. Key points:
- Smart
$rootresolution:resolveReferenceautomatically retries with.$rootappended when a reference points to a group rather than a leaf token. - Memoized token map:
buildTokenMapcaches an O(1) name→tokenMapper dictionary instance viaWeakMap. - Composite colors:
{ color, alpha }tokens emitcolor-mix(in srgb, var(--a2-name, #hex) N%, transparent)in CSS contexts, preserving the live primitive CSS variable reference. Static contexts (JS exports, SCSS maps) fall back to hex8. buildFallbackWithRefs: Main entry point used by all formats. Builds recursivevar(--name, ...)chains and insertslight-dark()at the level where light and dark values first diverge.
sd-transforms.js
Registers DTCG transforms and the dtcg transform group:
dtcg/set-token-names(preprocessor): Pre-setstoken.nameto the full hyphenated path before transforms run. Prevents false name-collision warnings for tokens with unresolvable references that skip the transform pipeline.dtcg/name,dtcg/value,dtcg/cubic-bezier,dtcg/color-opacity: Path normalization, dimension formatting, cubicBezier→CSS, and composite color pass-through.
css-utils-format-utils.js
Pure functions that generate CSS utility class strings — generateBorderClasses, generateColorClasses, generateFontClasses, generateSpacingClasses. Each accepts a pre-resolved value (which may already contain a light-dark() expression built by buildFallbackWithRefs) and an optional prefix.
sd-formats.js
Registers all custom Style Dictionary output formats:
custom/scss-variables— SCSS variables withvar()+light-dark()fallbacks.custom/scss-variables-map— SCSS maps ($light,$dark,$nonColor/$token) forThemeProvider.module.scss.custom/es6-variable— ES6 named exports with static resolved values.custom/CSSVariables—:root {}block with CSS custom properties.custom/CSSUtils/{prefix}All|Borders|Colors|Fonts|Spacing— CSS utility class files (registered for both""and"a2-"prefixes).
The All format uses a single-pass loop over allTokens (one buildFallbackWithRefs call per token for the light value, a second only when a dark variant exists) feeding three output buckets. The dedicated Borders, Colors, Fonts, and Spacing formats use separate filter+map pipelines.
Figma Sync (src/utils/figma/)
Script to sync design tokens to Figma variables using the Figma REST API.
Features
- Syncs all tokens (primitives, semantic, component) to a single Figma variable collection
- Creates Light and Dark modes for appearance variants
- Resolves primitive token references to actual values
- Creates variable aliases for semantic/component tokens that reference other tokens
- Handles
$rootpattern by creating separate variables for root and state variants - Uses path-based naming (e.g.,
color/blue/500,background/color/primary) - Applies Figma variable scopes so variables only appear in the relevant UI pickers
- Only updates variables when values change; still sends scope-only updates when scopes need to be applied
- Handles rate limiting with automatic retries
Directory
auth.js- Authentication and configurationconstants.js- Figma file and collection constantserrors.js- Custom error classesget-token.js- OAuth2 token helper scripttoken-parsing.js- Token file loading, parsing, and flatteningtoken-resolution.js- Reference resolution and dependency sortingtoken-conversion.js- Converting tokens to Figma formatfigma-api.js- Figma API requests, collections, and modessync-primitives.js- Primitive token sync logicsync-semantic.js- Semantic token sync logicsync-components.js- Component token sync logicsync-orchestration.js- Theme sync orchestrationsync-main.js- Main entry point and CLIutils.js- Shared utility functions
Variable Scopes
Figma variables can be scoped so they only show in certain property pickers (e.g. a color variable only in stroke color, or a dimension only in corner radius).
Scopes are defined directly on each token in $extensions["com.figma.scopes"] — an array of Figma scope names (e.g. SHAPE_FILL, STROKE_COLOR, CORNER_RADIUS, FONT_SIZE). The sync reads this field when creating or updating variables; scopes are deduplicated before sending.
To change which pickers a token appears in, edit $extensions["com.figma.scopes"] in the token file.
Commands
pnpm figma:sync- Sync all tokens to Figma (default file)pnpm figma:sync:file <fileKey>- Sync tokens to a specific Figma filepnpm figma:test- Test Figma API access (default file)pnpm figma:test:file <fileKey>- Test access to a specific Figma filepnpm figma:validate- Validate token files without syncingpnpm figma:get-token- Interactive OAuth2 token helper script
CLI Options: --file-key/-f, --dry-run, --verbose/-v, --full, --help/-h
Authentication
Supports Personal Access Tokens (PAT) and OAuth2 refresh tokens. Configure via environment variables or .figma-config.json (repo root, gitignored).
Environment variables:
| Variable | Description |
| --------------------- | -------------------------- |
| FIGMA_ACCESS_TOKEN | PAT |
| FIGMA_CLIENT_ID | OAuth2 client ID |
| FIGMA_CLIENT_SECRET | OAuth2 client secret |
| FIGMA_REFRESH_TOKEN | OAuth2 refresh token |
| FIGMA_FILE_KEY | Optional file key override |
.figma-config.json:
{
"accessToken": "figd_...",
"clientId": "your-oauth-client-id",
"clientSecret": "your-oauth-client-secret",
"refreshToken": "your-oauth-refresh-token"
}Environment variables take priority over the config file. OAuth2 access tokens are cached in memory and refreshed automatically on expiry (~1 hour).
GitHub Actions:
- name: Sync tokens to Figma
env:
FIGMA_CLIENT_ID: ${{ secrets.FIGMA_CLIENT_ID }}
FIGMA_CLIENT_SECRET: ${{ secrets.FIGMA_CLIENT_SECRET }}
FIGMA_REFRESH_TOKEN: ${{ secrets.FIGMA_REFRESH_TOKEN }}
run: pnpm figma:syncDry-run mode (--dry-run): validates and reads existing variables without writing. Reports counts of what would be created/updated/skipped.
Troubleshooting:
- Auth errors: Verify credentials. For OAuth2, re-authenticate if refresh token has expired.
- "Authentication Failed - Please Re-Login": OAuth2 refresh token expired or revoked.
- 403 Forbidden: Token may lack organization-level access.
- File not found: Check the file key and access permissions.
- Rate limiting: Retried automatically with exponential backoff.
- Refresh token rotation: Update credentials with the new token logged to stdout.
- Run
pnpm figma:testto diagnose issues before syncing;pnpm figma:validateto check token structure without API calls.
Development
Adding New Tokens
- Add token JSON files to
src/global/primitive/(primitives) orsrc/theme/core/(semantic/component) - Run
pnpm buildto regenerate output files - Import tokens in your code using the generated files
Modifying Build Output
- Transforms: Edit
src/utils/sd-transforms.js - Formats: Edit
src/utils/sd-formats.js - Build Configs: Edit
src/utils/sd-build-configs.js - CSS Utils: Edit
src/utils/css-utils-format-utils.js
