Brand in templates
A template gets its brand from BrandKit: colors, fonts, logos, assets and a few conventions, published as brandkit.json. The template keeps one published version of it in a single file and renders from it, so a document looks the same in the editor, the viewer and the PDF. Publishing a new version in BrandKit changes no template: a template takes it only when someone previews it there and activates it (Taking a new brand version).
This page uses the brand runtime in uhuu-components 0.5 or later, where it lives under the BrandKit group at the package root. The version preview and the brand command are new in 0.4.7; on 0.4 the same members are flat names (BrandKitProvider, brandKitSelection, useBrandKit).
Give a template its brand
A template keeps its brand in one file, src/brand/brandkit.json: a published version's brandkit.json (…/versions/vN/brandkit.json), byte for byte, as npx uhuu-components brand activate <version link> writes it. Taking a newer version replaces that file, reviewed like any other dependency change.
To create a client's team brand from a template, import the template's brandkit.json in BrandKit under Settings → Developer → Import.
Render the brand
import { BrandKit } from 'uhuu-components';
import brandKit from './brand/brandkit.json';
export default function Template({ payload }) {
const { src } = BrandKit.selection(payload);
return (
<BrandKit.Provider src={src} brandKit={brandKit}>
{/* pages and editor overlay chrome */}
</BrandKit.Provider>
);
}- The provider puts the brand's CSS variables on a
display: contentselement around the pages, and loads the brand's fonts. Set the body font there:[data-uhuu-brand-kit] { font-family: var(--font-sans); }. - No request is made for the brand unless the payload (or, in development,
?brand=) names one; a fetched brand replaces the template's own once it has loaded. - Pagination waits for the brand's font stylesheets, so a print step needs no separate wait.
Which brand a document uses
BrandKit.selection(payload) applies one order:
| Priority | Source | Notes |
|---|---|---|
| 1 | payload.brand | A per-document override: { kind: 'kit', url: '…/versions/vN/brandkit.json' }. It must name a version; an unpinned URL warns, because a later publish would change the document. |
| 2 | payload.brandKitUrl, payload.brandKitId | Deprecated, with a warning. Refusing them in print is proposed from 2027-03-31. |
| 3 | None | The template's own brand (src/brand/brandkit.json) renders. The normal case. |
The renderer warns when a brand is fetched from an unpinned URL while printing. Never render or store the kit's root brandkit.json or a versions/latest/ URL: both follow the newest version.
Taking a new brand version
Publishing in BrandKit releases a new, immutable version (v9) and changes nothing that renders. Every template keeps the version it holds until someone previews the new one on that template and activates it there. A brand change can suit one layout and break another, so the decision is made per template, by someone who can look at its pages. Staying on an older version is safe, and rolling back is activating the previous version again.
In a code template
npx uhuu-components brand status # Acme v8 · v9 available (1 newer)
pnpm dev # open the template with ?brand=v9
npx uhuu-components brand activate v9 # writes src/brand/brandkit.json, prints what changed
git add src/brand/brandkit.json && git commit -m "Brand: Acme v8 → v9"Status.
brand statusprints the versionsrc/brand/brandkit.jsonholds and the newest published one. It only reports, and exits 0 even when the template is behind or offline.Preview. On the development server,
?brand=renders another version in place of the template's brand (and of anypayload.brand), under a bar: Previewing Acme v9 · template uses v8 · What changed · Stop. The DEV menu offers the same under Brand Kit → Preview a version…. It takes:v9, when the template's file names its version (generator.release, stamped into the versions BrandKit publishes);- a version link,
https://…/versions/v9/brandkit.json(Copy v9 link in BrandKit's publish confirmation, or Settings → Developer → Published files); - a path on the dev server, such as
/candidate.json: an unpublished draft exported from BrandKit (Settings → Developer → Export draft) intopublic/. A draft can be previewed, not activated; do not commit it.
The preview lives only in the page URL. It runs only on development hosts, the PDF renderer ignores it, the bar is not printed, and nothing is written to the payload. Check the pages and the print preview in the same session before activating.
Activate.
brand activate v9(or a version link) downloads that version, checks that it is a current Brand Kit document (brandkit.json1.x carryingruntimeversion 1), writes it byte for byte tosrc/brand/brandkit.jsonand nothing else, and lists what changed (CSS variables, font families, logo files). If the file does not name its version yet, activate once by version link; after thatv9works. Review the diff, run the template's checks, and commit with the message the command prints. Documents get the new version once that commit is built and deployed.
In Template Studio
The template's brand panel shows when a newer version is available (v9 available). Preview renders the template's pages with it; nothing is saved. Activate shows what changed, pins the version and records it in the template's history; publish the template to release it. Studio never publishes a team brand: a change meant for every template is made and published in BrandKit.
Documents with their own brand
A document that overrides its brand sends payload.brand pinned to …/versions/vN/brandkit.json. It changes only when the document is saved with another version link.
Colors
Use the semantic roles, always paired with their foreground:
| Role | Use for |
|---|---|
primary / primary-foreground | Headings, bands, table heads, and text on them |
secondary / secondary-foreground | The second brand color |
accent | Rules, quote bars, emphasis |
background / foreground | Paper and body text |
muted / muted-foreground | Quiet panels and secondary text |
card, border | Panels and rules |
chart-1 … chart-5 | Data series (only when the brand sets them, see Charts) |
Classes such as bg-primary text-primary-foreground, text-foreground and border-border resolve to the brand. Never put text-white on a brand fill.
Colors the roles do not cover are extension colors, published as --brand-ext-<namespace>-<key>. BrandKit edits them on the Brand page under More colors:
| Need | Namespace | Example variable |
|---|---|---|
| Status | status (success, warning, danger, info) | --brand-ext-status-success |
| Categories | category | --brand-ext-category-holiday |
| A pick-list (cover colors) | palette-<name>, ordered | --brand-ext-palette-cover-c1 |
Code that needs the values reads them with readExtensionStatus, readExtensionCategories, readExtensionPalette and readExtensionColors from @uhuu/brandkit.
A color that must never change with the brand (a protected seal color) belongs inside the artwork, not in CSS.
Fonts
Fonts come from the brand by role: --font-display for headings, --font-sans for body text, --font-serif and --font-mono. The provider loads what the brand declares, Google stylesheets and self-hosted files alike. Declare the weights the template uses on the brand's font so the right faces load.
Sizes, line heights and tracking stay in the template: a brand never resizes a layout. When a face lays out differently in the PDF renderer, set its metric overrides (ascentOverride, descentOverride, lineGapOverride) on the font in BrandKit; they are written into every @font-face.
Logos
const { logo } = BrandKit.useBrandKit();
const onPaper = logo({ kind: 'primary', background: 'light' }); // { src, format, alt, kind, background } | null
const onDark = logo({ kind: 'primary', background: 'dark' }); // back covers, dark panels
const mark = logo({ kind: 'mark' }); // headers, folios
const premium = logo({ name: 'Premium', locale: payload.locale }); // a named or language version- Choose the ground from where the logo sits. A missing variant falls back to
any, then the other ground; a missing wordmark falls back to the primary logo. nameandlocalepick a named or language version (exact language, then the language without region, then the unlocalized logo). An unknown name gives the standard logo.- A variant is one file: its SVG when it has one, else its PNG, else its JPG.
formatsays which, so a page can size a bitmap logo differently from a vector one. Without a logo,logo()returnsnull: show nothing or a neutral placeholder. - Minimum size and clear space are the template's own layout decision; the kit does not carry them.
- Partner, association and certification logos are not the brand's logo: keep them in a
graphicscollection with fixed keys such aspartner-…andseal-…, and place them by key.
Images, icons and backgrounds
const { collection } = BrandKit.useBrandKit();
const photos = collection('photos'); // { items: [{ key, src, label }] } from the promoted version
const icons = collection('icons'); // item.key is the meaning, e.g. "kitchen"Brand imagery lives in BrandKit collections (photos, icons, graphics, backgrounds). Listing photos and one-off uploads are document data, not brand assets.
Maps and charts
Maps. BrandKit.useBrandKit().mapStyle('micro' | 'macro') returns the brand's map style URL. Legends and markers use roles, not hex.
Charts
The brand's chart colors reach a template only when the brand set them; otherwise the template keeps its own palette. BrandKit's editor shows this as Template colors. For chart libraries and the chart dialog, which want hex:
const { chartPalette, chartBrand } = BrandKit.useBrandKit();
const { colors } = chartPalette({ fallback: TEMPLATE_CHART_COLORS, count: 8 }); // '#rrggbb'
// One colour set for the printed chart and the dialog's preview, so the two agree.
const brand = chartBrand({ palette: TEMPLATE_CHART_COLORS, ink: '#1a1a1a' });
$uhuu.editDialog({ path: 'sales_chart', type: 'chart', state: salesChart, config: { brand } });chartBrand(defaults) returns palette plus ink, line, muted and surface, all as #rrggbb. The palette is the brand's chart colors when the brand set them, otherwise defaults.palette. For the four roles the template's own value wins, and the kit's foreground, border, muted-foreground and background fill in what the template leaves out. Print the chart with the same object you pass the dialog.
Replaces chartDialogBrand
BrandKit.chartDialogBrand(kit, options) is gone in 0.5.1. Migrate brand: { ...templateColours, ...BrandKit.chartDialogBrand(kit, { fallback }) } to brand: chartBrand(templateColours): the roles the template passes now win, so a template keeps its own chart design instead of the kit silently replacing it.
Corners
A brand's corner choice applies only when the template opts in, because many layouts are measured in millimetres. adopt with radius adds --radius and --radius-sm … --radius-xl:
<BrandKit.Provider src={src} brandKit={brandKit} adopt={{ radius: true }}>Numbers, money, dates and company facts
The brand carries a default locale and currency (extensions.locale); the template formats with them. Read them from the hook and let the document's locale win:
const { locale: brandLocale } = BrandKit.useBrandKit(); // { locale?: 'de-CH', currency?: 'CHF' }
const locale = payload.locale ?? brandLocale.locale;
const currency = brandLocale.currency ?? 'CHF';
new Intl.NumberFormat(locale, { style: 'currency', currency }).format(3600000);
new Intl.NumberFormat(locale).format(1234567);
new Intl.DateTimeFormat(locale, { dateStyle: 'long' }).format(new Date(payload.date));Formatters removed
formatMoney, formatNumber and formatDate were removed from the package root in 0.5.0; no template used them. Format with Intl as above, or keep a helper of your own in the template.
Company facts shared by every document of a brand (name, legal name, web, contact, default disclaimer) can live in the brand as extensions.org and are read with readExtensionOrg from @uhuu/brandkit. Offices and legal texts per language remain template defaults and document data.
What the brand owns and what the template owns
| BrandKit | Template |
|---|---|
| Colors and their pairing, extension colors, chart colors when set | Which role each element uses, the chart fallback palette |
| Font families, faces and metrics | Sizes, line heights, tracking |
| Logos, named and language versions | Which logo each place uses, its size and clear space |
| Image, icon, graphic and background collections | Which item goes where |
| Map styles | Map configuration |
| Corners (opt-in) | Format, margins, bleed, page furniture, pagination |