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(--text-fontfamily-base), sans-serif |
--fontfamily-mono |
var(--text-fontfamily-mono), monospace |
--fontfamily-accent |
var(--text-fontfamily-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-10 |
10px (0.625rem) |
.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(--text-weight-semibold) |
.text-bold |
font-weight: var(--text-weight-bold) |
.text-italic |
font-style: italic |
.text-monospace |
Switches to var(--fontfamily-mono) |
.text-body |
Switches to the looser var(--text-lineheight-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.
# Install dependencies
npm install
# Build tokens.css from tokens/json
npm run build
# Rebuild on every token change
npm run dev:tokensnpm 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; - merges the
:rootand:root.darkblocks into singlelight-dark(…)declarations, so light and dark mode need no class switching; - appends box-shadow custom properties generated from
fx.tokens.jsonbyscripts/generate-shadow-vars.mjs.
| 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.
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.