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.
10 KiB
@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>/brandfrom itsThemesConfig(which generally lives inbrand.json) -
generateTwThemePalettesCss(config?): Produces--color-{theme}-{1..12}for all 7 themes/palettes in:rootand[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 afterbrand-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.