Design tokens and fonts for GitButler applications. This package provides CSS custom properties, design tokens in JSON format, and web fonts used across GitButler products.
npm install @gitbutler/design-coreThe easiest way to get everything — fonts, CSS reset, text utilities, and design tokens:
@import "@gitbutler/design-core/core";Or in JavaScript/TypeScript:
import "@gitbutler/design-core/core";Import only the design tokens CSS file:
@import "@gitbutler/design-core/tokens";Or in JavaScript/TypeScript:
import "@gitbutler/design-core/tokens";This provides CSS custom properties for colors, spacing, typography, and other design tokens with automatic light/dark mode support.
Import all font declarations:
@import "@gitbutler/design-core/fonts";Or reference individual font files directly:
@import "@gitbutler/design-core/fonts/inter/Inter-Variable.woff2";
@import "@gitbutler/design-core/fonts/geist-mono/GeistMono-Variable.woff2";
@import "@gitbutler/design-core/fonts/but-head/But-Head-Regular.woff2";Import individual style sheets:
@import "@gitbutler/design-core/styles/reset"; /* CSS reset */
@import "@gitbutler/design-core/styles/text"; /* Text utilities */The reset is scoped to @layer reset to avoid specificity conflicts with application styles. It applies the following normalizations:
- Box sizing —
box-sizing: border-boxon all elements and pseudo-elements. - Font rendering —
-webkit-font-smoothing: antialiased,-moz-osx-font-smoothing: grayscale,text-rendering: optimizeLegibility, and font size inflation prevention onhtml. - Body defaults — zero padding and margin,
overflow-x: hidden, and base values forcolor,font-family,font-weight, andline-heightpulled from design tokens. - Form elements —
input,textarea,select, andbuttonhave their padding, border, and background stripped, and inheritcolorandfontfrom the parent. - Headings —
h1–h6havemargin-blockremoved. - Code & pre —
codeandpreusevar(--fontfamily-mono). - Lists & paragraphs —
ulandolhavepadding-leftandlist-styleremoved;ul,pre, andphavemargin-blockremoved.
Text classes are scoped to @layer text. The layer also sets three font-family custom properties on :root:
| Property | Value |
|---|---|
--fontfamily-base |
var(--font-family-base), sans-serif |
--fontfamily-mono |
var(--font-family-mono), monospace |
--fontfamily-accent |
var(--font-family-accent), serif |
Size classes set font-size, font-weight: regular, line-height, and font-family: base as a self-contained unit:
| Class | Font size |
|---|---|
.text-11 |
11px (0.6875rem) |
.text-12 |
12px (0.75rem) |
.text-13 |
13px (0.8125rem) |
.text-14 |
14px (0.875rem) |
.text-15 |
15px (0.938rem) |
.text-16 |
16px (1rem) |
.text-accent-head |
62px (3.875rem) — display / brand heading |
Modifier classes are designed to be composed on top of a size class:
| Class | Effect |
|---|---|
.text-semibold |
font-weight: var(--font-weight-semibold) |
.text-bold |
font-weight: var(--font-weight-bold) |
.text-italic |
font-style: italic |
.text-monospace |
Switches to var(--fontfamily-mono) |
.text-body |
Switches to the looser var(--font-line-height-body) line height |
.text-balance |
text-wrap: balance — ideal for short headings |
.text-pre |
white-space: pre-wrap + word-break: break-word — preserves whitespace without overflow |
Example usage:
<p class="text-13 text-semibold">Label</p>
<code class="text-12 text-monospace">git commit</code>
<h1 class="text-accent-head text-italic">GitButler</h1>@gitbutler/design-core/core- Single import: fonts + reset + text utilities + tokens@gitbutler/design-core/tokens- CSS custom properties (design tokens)@gitbutler/design-core/fonts- All font CSS declarations@gitbutler/design-core/fonts/*- Individual font files@gitbutler/design-core/styles/reset- CSS reset@gitbutler/design-core/styles/text- Text utility classes
All fonts are variable fonts unless noted.
- Inter — Primary UI font (
Inter-Variable.woff2,Inter-Variable-Italic.woff2) - Geist Mono — Monospace font for code (
GeistMono-Variable.woff2) - But Head — Brand display font (
But-Head-Regular.woff2,But-Head-Italic.woff2)
Design tokens live in Figma and flow into this repo through a fixed pipeline:
Figma variables ──tokens-bruecke──▶ tokens/json/*.tokens.json ──terrazzo──▶ tokens/tokens.css
Nothing in tokens/ is edited by hand. tokens/json/*.tokens.json is generated by the
export, and tokens/tokens.css is generated from it by the build.
The repo pins pnpm (packageManager) and Node (devEngines.runtime) in package.json.
pnpm install downloads that Node, so no separate Node version manager is needed.
# Install dependencies and the pinned Node
pnpm install
# Build tokens.css from tokens/json
pnpm run build
# Rebuild on every token change
pnpm run dev:tokenspnpm run build runs scripts/postprocess-light-dark.mjs, which:
- runs Terrazzo (
tz build) overcore.tokens.jsonandsemantic.tokens.json— seeterrazzo.config.js— to writetokens/tokens.css. A color that aliases another and adds its own opacity (Figma's "opacity at scale") isn't valid DTCG, and Terrazzo rejects it, soscripts/composed-colors.mjsfirst lifts those out of copies in.terrazzo/, and they come back in step 2 ascolor-mix(in srgb, var(--base) var(--opacity), transparent), keeping both links; - merges the
:rootand:root.darkblocks into singlelight-dark(…)declarations, so light and dark mode need no class switching, and adds the lifted colors; - appends box-shadow custom properties generated from
fx.tokens.jsonbyscripts/generate-shadow-vars.mjs. - writes each token's Figma description as a comment above its declaration, so
tokens.csssays when to use a token.
Aliases stay aliases: a variable that points at another in Figma comes out as var(--target), for sizes as well as colors.
| File | Source | Consumed by |
|---|---|---|
tokens/json/core.tokens.json |
core variable collection — raw palette, scales |
Terrazzo |
tokens/json/semantic.tokens.json |
semantic variable collection — aliases onto core, with light/dark modes |
Terrazzo |
tokens/json/fx.tokens.json |
Figma effect styles — shadows | generate-shadow-vars.mjs |
Exports use the tokens-bruecke CLI, the same tool as the Figma plugin, in DTCG 2025.10 format. It has to be 3.12 or later: older versions skip colors that alias another with their own opacity.
Credentials go in a gitignored .env at the repo root:
FIGMA_API_KEY=figd_… # personal access token, must include the file_variables:read scope
FIGMA_FILE_KEY=… # the design tokens Figma fileset -a && . ./.env && set +a
npx tokens-bruecke \
-a "$FIGMA_API_KEY" \
-f "$FIGMA_FILE_KEY" \
-c .claude/skills/release-tokens/figma-export.config.json \
-o tokens/json \
--split-by-collectionThe config file pins the export settings the committed tokens were generated with — hex
colors, no scopes, no Figma metadata, effect styles as fx. Changing it rewrites every
token file, so leave it alone unless that is the intent.
Two gotchas:
-
The Figma variables REST API requires an Enterprise plan and a token carrying the
file_variables:readscope. Without it the export fails with403. Personal access tokens also expire every 90 days. -
Every export rewrites a
createdAtstamp in each file, so a diff alone does not mean the tokens changed. Check with:git diff -U0 tokens/json | grep '^[+-]' | grep -v '^[+-][+-]' | grep -v createdAt
No output means only timestamps moved — revert and skip the release.
Version bumps follow the impact on the generated CSS custom properties, since those are the public API:
| Change | Bump |
|---|---|
| Token values tweaked | patch |
| Tokens or collections added | minor |
| Tokens removed or renamed (CSS var names change) | major |
House style: the version bump lives in the same commit as the token changes, tagged
with a bare X.Y.Z annotated tag (no v prefix).
npm version <patch|minor|major> --no-git-tag-version
# commit package.json + tokens/ together
git tag -a "$(node -p "require('./package.json').version")" -m "…"
npm publish --dry-run
npm publish # prompts for a 2FA one-time code
git push origin main && git push --tagsprepublishOnly rebuilds the CSS, so a published package always matches its committed
JSON. Pushing tokens/tokens.css also redeploys the hue-dini site via
.github/workflows/deploy-hue-dini.yml.
The whole sequence — export, no-op check, build, bump, commit, tag — is automated as a
Claude Code skill in .claude/skills/release-tokens/.
Run it with /release-tokens; it stops before npm publish, which stays manual because
npm 2FA requires a one-time code.
hue-dini is the tool used to preview and export the GitButler color palette. It applies a single shared luminance scale across all hues — gray, accent, and semantic — so shades at the same step feel visually equivalent regardless of hue.
Live: https://gitbutlerapp.github.io/design-core/
cd tools/hue-dini
pnpm install
pnpm devSee tools/hue-dini/README.md for more details.