Files
zeekay 386e2aef60 the face is Zen, and it ships from @hanzo/font
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.

Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.

Comments naming a face we no longer use are deleted, not reworded.
2026-09-01 08:57:07 -07:00

10 KiB
Raw Permalink Blame History

@hanzo/branding

A theming and component toolkit for building white-label apps on @hanzo/gui. Provides generation of Tailwind tokens expected by shadcn for compatibility with previous systems. Provide seed colors, get a complete design system.

How it works

Every color in the system — backgrounds, text, borders, button states — is derived from a 12-step palette, which can be configured with a simple one-color "seed." This generates both light and dark versions of the palette. The system calls these palettes "themes," and our system uses 7 of them: neutral, primary, secondary, info, success, warning, and danger — any or all of which can just be the default. These themes are used by all @hanzo/gui components. They are also translated into a Tailwind CSS theme that can be used with systems based on shadcn.

Org-specific brand packages (@my-org/brand) just provide seeds (or 12-step palettes) and assets; all the generation logic lives here.

Install

pnpm add @hanzo/branding

Quick start

@hanzo/gui config and org's brand module

In the org's brand module — assemble and export a @hanzo/gui config from your seeds:

// @my-org/brand/src/gui-config.ts
import { createGuiConfig } from '@hanzo/branding/tg-config'
import brandJson from './brand.json'

export const guiConfig = createGuiConfig({
  themes: brandJson.themes,
  // omitted fields (fonts, size, space) fall back to defaults
})

In the org's apps — wrap the app with GuiProvider:

// @my-org/<app>/src/main.tsx
import { guiConfig } from '@my-org/brand'
import { GuiProvider } from '@hanzo/gui'

<GuiProvider config={guiConfig}>
  <App />
</GuiProvider>

Tailwind / shadcn

For use with shadcn-based code, token mappings from @hanzo/gui 'themes' (palettes) are provided by @hanzo/branding. Only the palettes are org-specific, and they are generated at build time by the generate-palettes CLI (see For org brand packages).

For convenience, an org's brand package should likely export a single CSS file that assembles the pieces in the right order.

// App.tsx
@import '@my-org/brand/tw-my-org.css';

This file would contain...

@import 'tailwindcss';
@import './my-org-tw-additions.css';            /* (optional) org-specific non-color tokens */
@import './brand-palettes.css';                 /* --color-{theme}-{1..12}, generated by prebuild*/
@import '@hanzo/branding/shadcn-semantic-tw-colors.css';  /* shadcn semantic refs derived from the above file */

NOTE: Order matters here. brand-palettes.css define the vars, shadcn-semantic-tw-colors.css references and maps them for shadcn.

Palette system

A single hex seed produces a 12-step palette for both light and dark schemes:

Steps Role Examples
1–3 Backgrounds base, raised, subtle
4–6 Borders, separators subtle, medium, strong
7–9 Interactive states, quiet text muted, secondary, quiet
10–12 Foreground text primary, strong, darkest

Light palettes run lightest (1) to darkest (12). Dark palettes run darkest (1) to lightest (12). Saturation tapers at the extremes for a natural feel.

Theme seeds

Seven named themes, all optional. Omitted themes get these defaults:

Theme Default Purpose
neutral #808080 Greyscale canvas — backgrounds, text, borders
primary #3B82F6 Primary actions — buttons, links, focus rings
secondary #721be4 Secondary accent
info #eab308 Informational callouts, tips
success #16a34a Success states, confirmations
warning #fb923c Warning states, caution
danger #dc2626 Errors, destructive actions

Each theme can be specified as:

// A seed — generates both light and dark palettes
{ seed: '#1a2744' }

// An explicit 12-step palette — used for both schemes (dark is reversed)
['#f0f3f8', '#dfe5f0', ..., '#0c1322']

// Per-scheme — mix seeds and explicit palettes
{ light: { seed: '#1a2744' }, dark: ['#0c1322', '#131d34', ..., '#f0f3f8'] }

@hanzo/gui config

createGuiConfig() produces a complete @hanzo/gui config from your supplied values:

import { createGuiConfig } from '@hanzo/branding/tg-config'

const config = createGuiConfig({
  themes: {
    primary: { seed: '#1a2744' },
    // neutral, secondary, info, success, warning, danger — pick up defaults
  },
  fonts: {
    body:    { family: '"Zen", sans-serif', size: { ... }, ... },
    heading: { family: '"Zen", sans-serif', size: { ... }, ... },
    mono:    { family: '"JetBrains Mono", monospace', size: { ... }, ... },
  },
  // pick up defaults for 'size' and 'space'
})
Option Type Default
themes ThemesConfig Default color seeds for the 7 themes specified above
fonts.body FontDef System sans-serif stack
fonts.heading FontDef System sans-serif stack
fonts.mono FontDef System monospace stack
size Record<string, number> Non-linear component size scale (20–144px)
space Record<string, number> Linear 4px grid (0–96px)

Each theme seed becomes a @hanzo/gui children theme. Components use standard theme wrapping:

<Theme name="primary">
  <Button>Submit</Button>     {/* picks up primary palette automatically */}
</Theme>

<Theme name="danger">
  <Card>Error occurred</Card> {/* danger palette for bg, border, text */}
</Theme>

Light/dark is handled structurally — the active scheme is inherited from the root, and each children theme has both variants.

Tailwind / shadcn details

Brand palettes as brand-palettes.css

  • auto-generated within an org's <org>/brand from its ThemesConfig (which generally lives in brand.json)

  • generateTwThemePalettesCss(config?): Produces --color-{theme}-{1..12} for all 7 themes/palettes in :root and [data-color-scheme='dark'].

Shadcn semantic colors are derived in shadcn-semantic-tw-colors.css

  • @hanzo/branding/shadcn-semantic-tw-colors.css — Derives shadcn's expected color tokens (--background, --primary, --destructive...) from the palettes as css vars (var(--color-neutral-1), var(--color-primary-10)...) that were generated from the org's ThemesConfig. Simply maps, so does not vary. Just needs to be included in the app's css file after brand-palettes.css.

The generate-palettes CLI

@hanzo/branding includes a generate-palettes CLI that reads an org's brand.json and writes the palettes CSS file:

# Defaults: reads src/brand.json, writes src/brand-palettes.css
npx generate-palettes

# Custom paths
npx generate-palettes --brandFile src/our-brand.json --outFile src/our-brand-palettes.css

Normally this is not run manually, but as a prebuild step in an org brand package's package.json:

"prebuild": "generate-palettes"

Output: brand-palettes.css

:root {
  --color-neutral-1: #fafafa;
  --color-neutral-2: #f5f5f5;
  /* ... through 12, for all 7 themes */
}
[data-color-scheme='dark'] {
  --color-neutral-1: #0d0d0d;
  /* ... */
}

Components

Themed @hanzo/gui components: ThemedButton, GhostButton, OutlineButton, StatusBox, ToggleSwitch, StyledCard.

See COMPONENTS.md for usage and examples.

Package structure

src/
├── types.ts                              # Palette12, ThemeSeed, ThemeDesc, ThemesConfig
├── palette-utils.ts                      # generatePalette, generatePalettes, resolveThemeDesc
├── tg/                                   # @hanzo/gui-specific
│   ├── types.ts                          # FontDef, GuiConfigOptions
│   ├── create-config.ts                  # createGuiConfig(options?)
│   ├── index.ts                          # (internal barrel)
│   ├── defaults/
│   │   ├── themes.ts                     # default seed colors
│   │   ├── spacing.ts                    # default size + space scales
│   │   ├── fonts.ts                      # default system font definitions
│   │   └── index.ts                      # barrel (seeds, size, space, fonts)
│   └── components/                       # see COMPONENTS.md
└── shadcn-tw/                            # Tailwind / shadcn
    ├── utils.ts                          # generateTwThemePalettesCss
    ├── generate-palettes.ts              # CLI: reads brand.json, writes brand-palettes.css
    ├── index.ts                          # (internal barrel)
    └── shadcn-semantic-colors.css        # static — shadcn semantic color mappings

Export paths

Path What
@hanzo/branding/tg-config createGuiConfig and types
@hanzo/branding/tg-components @hanzo/gui components
@hanzo/branding/tg-config-defaults Defaults (seeds, size, space, fonts)
@hanzo/branding/shadcn-semantic-tw-colors.css Static CSS — maps 12-step palettes to semantic color values expected by shadcn

Example use in @my-org/brand

An org brand package depends on @hanzo/branding and provides just data:

@my-org/brand/
├── src/
│   ├── brand.json                # org identity, URLs, and @hanzo/gui palettes in 'themes' field
│   ├── brand-palettes.css        # generated by prebuild (gitignored)
│   ├── my-org-tw-additions.css   # (optional) org-specific tw tokens
│   ├── tw-my-org.css             # bundle: tailwind + additions + palettes + semantic
│   ├── fonts.ts                  # (optional) org-specific font defs 
│   ├── gui-config.ts         # calls createGuiConfig
│   ├── types.ts                  # BrandIdentity, OrgConfig (define the rest of brand.json)
│   └── index.ts                  # re-exports
└── assets/                       # logos, etc

The gui-config.ts calls createGuiConfig with the org's seeds and fonts:

import { createGuiConfig } from '@hanzo/branding/tg-config'
import brandJson from './brand.json'
import { bodyFont, headingFont, monoFont } from './fonts'

export const guiConfig = createGuiConfig({
  themes: brandJson.themes,
  fonts: { body: bodyFont, heading: headingFont, mono: monoFont },
})

The package.json has a prebuild script that runs the palette generator:

"scripts": {
  "prebuild": "generate-palettes",
  "build": "tsup src/index.ts --format cjs,esm --dts --clean"
}

generate-palettes is included in @hanzo/branding as a bin. By default it reads src/brand.json and writes src/brand-palettes.css.