@gitbutler/design-core
v10.0.0
Published
Design tokens for GitButler applications
Readme
@gitbutler/design-core
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.
Installation
npm install @gitbutler/design-coreUsage
Single Import
The 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";Design Tokens (CSS)
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.
Fonts
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";Styles
Import individual style sheets:
@import "@gitbutler/design-core/styles/reset"; /* CSS reset */
@import "@gitbutler/design-core/styles/text"; /* Text utilities */CSS Reset
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 Utilities
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>Available Exports
@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
Included Fonts
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)
Development
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.cssNothing 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.
Building
# 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. 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.
Token files
| 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 |
Pulling tokens from Figma
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 createdAtNo output means only timestamps moved — revert and skip the release.
Releasing
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.
Color Tool
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.
