Code branding
Edit brand.json, components.json, and styles.css, then validate, preview, publish, or remove CSS safely
Use Code branding to edit the complete semantic configuration, add reusable dashboard content, and apply optional CSS overrides as one versioned draft.
Availability
Platform account owners and active platform admins can open Platform dashboard > White-label > Branding > Code.
Code is available when advanced branding is enabled for the account. If the tab says Advanced branding unavailable, editing is temporarily disabled and existing published branding remains unchanged. An operator can also disable reusable dashboard components separately; in that state, the server rejects changes that add or modify components and rejects publishing a non-empty library, but still permits removing components.
Work safely
Code and Design share one semantic branding configuration: the named app, theme, navigation, text, and dashboard choices. Editing brand.json changes the values shown in Design, while Design changes are reflected in the next Code snapshot. components.json and styles.css add layers that Design does not author.
| File | Responsibility |
|---|---|
brand.json | Complete semantic branding configuration shared with Design |
components.json | Reusable dashboard structure and content |
styles.css | Optional presentation overrides loaded after semantic theme styles |
Save a draft before leaving Code. Saving does not change the customer dashboard. Preview the exact saved artifact, then publish it when all three files are correct. See Branding overview for the shared lifecycle.
Add reusable images in Assets before inserting their references. An asset added to the library is not customer-visible until the Branding draft that uses it is published.
Use the editor
Select a file tab to edit it. A neutral dot marks a file with unsaved changes; a red dot marks invalid JSON. The footer reports the active file's line count, byte count, and Invalid JSON, Unsaved changes, or Draft saved state. The built-in Monaco code editor shows JSON and CSS diagnostics inline, but the server parser remains authoritative.
Use the editor controls as follows:
- Select Format, or press Shift+Alt+F, to format the active file. Pasting also runs the formatter. When formatting changes the file, Code saves the formatted three-file bundle. Incomplete or invalid input stays editable if formatting cannot finish.
- Toggle line wrapping with Wrap long lines. The preference is shared by all three files and persists in this browser.
- Select Expand editor for fullscreen editing. Press Escape or select Collapse editor to exit fullscreen.
- Press Command+S on macOS or Ctrl+S on Windows and Linux to run Save draft without leaving the editor.
- In
styles.css, use the asset action to inserturl("assets.your-alias"). Inbrand.json, it inserts the JSON string"assets.your-alias".components.jsonhas no asset picker. - In
styles.css, select CSS reference to search the live styling catalog and copy or insert a selector at the cursor. The control appears only forstyles.css. See Look up a part.
Invalid brand.json keeps both the text you entered and the last good semantic configuration. Design becomes read-only until the JSON is corrected or discarded. Invalid brand.json or components.json blocks Save draft, Preview, and Publish.
Brand JSON
brand.json is a complete schema-version 2 document. Start from this valid document and change only the values you need:
{
"schemaVersion": 2,
"identity": {
"appName": "Acme",
"wordmarkLight": null,
"wordmarkDark": null,
"icon": null
},
"appearance": {
"colorMode": "auto",
"theme": {
"preset": "default"
},
"accent": "#DD7740",
"font": "default",
"iconLibrary": "lucide"
},
"dashboard": {
"layout": "standard",
"background": {
"preset": "default"
},
"sidebar": "left",
"navigation": {
"overrides": {},
"schema": null
},
"surface": null
},
"content": {
"text": {}
},
"iconPack": null
}Every top-level object has one responsibility:
| Key | Responsibility |
|---|---|
schemaVersion | Selects the document schema. The current value is the number 2. |
identity | Sets the app name and optional light wordmark, dark wordmark, and app icon references. appName saves as a string. At display time, markup is removed and the value is trimmed; a result that is empty or over 80 characters falls back to the account or app name. |
appearance | Selects color mode, theme, accent, font, and icon library. |
dashboard | Selects layout, background, sidebar side, navigation, and dashboard surface. |
content | Holds sparse customer-facing text overrides under text. |
iconPack | Holds a custom icon-pack configuration generated by Design, or null. Do not construct this object manually. |
Use these exact values:
appearance.colorMode:auto,light, ordark.appearance.theme.preset:default,dracula,nord,gruvbox,catppuccin,tokyonight,solarized,onedark,rosepine,github,vercel,linear,notion,supabase,ayu,everforest,kanagawa,monokai,nightowl, ormaterial.appearance.font:default,inter,roboto,open-sans,lato,montserrat,poppins,source-sans-3,nunito,work-sans,dm-sans,plus-jakarta-sans, orspace-grotesk.appearance.iconLibrary:lucide,heroicons,phosphor, ortabler.dashboard.layout:standard.dashboard.background.preset:default,dots,grid,diagonal,noise,rings,soft-gradient,ambient-glow,mesh,vignette,aurora-drift,pulse-glow,fluid-noise,fluid-flow,fluid-smoke,fluid-bloom,fluid-cellular,fluid-gyroid,fluid-crystal,fluid-interfere, orfluid-sweep.dashboard.sidebar:leftorright.
appearance.theme is a one-of object: set either preset or custom, never both. A Custom theme has light and dark maps under custom, and each map must supply all 11 core variables listed under Tokens and cascade. Accent and token colors accept validated hexadecimal, rgb()/rgba(), or hsl()/hsla() forms.
dashboard.background is also a one-of object. Set exactly one of these shapes:
presetwith one supported preset ID.gradientwithfromandtocolors.imagewith a singleurlreference. Use a canonicalassets.reference from Assets for a managed image.
dashboard.navigation contains overrides and either a complete schema or null. Use Design navigation for the current catalog IDs, locked entries, custom-link limits, and schema groups.
content.text is sparse: add only the supported keys you want to override. Put them under dashboard, navigation, settings, dialogs, or status, then under a supported subject. Use the editor's completion list to choose subjects and keys instead of inventing names. Omitted keys inherit their current values. Ordinary text overrides are limited to 200 characters; description and error overrides are limited to 1,000 characters. Greeting lists contain at most 12 strings of 80 characters each. Markup is forbidden, and every override must preserve the stock placeholder set exactly. Deleting an override means inherit the current value; to restore stock copy, use Design or enter the verified stock value.
Set dashboard.surface to null to inherit the platform defaults. When you need custom blocks or tab placement, author the surface fields shown under Components JSON. Never author dashboard.surface.components in brand.json; component definitions belong only in components.json.
JSON comments are invalid. Strict brand.json envelope objects reject unknown keys, so do not add notes or application-specific metadata to them. dashboard.surface is fail-soft: it drops unknown IDs and label keys, caps labels, restores missing tabs, and degrades an invalid component library. Custom-theme normalization also drops unknown tokens. Review the saved snapshot to catch values the server removed or repaired.
Components JSON
components.json defines reusable structure and content. An empty library is valid:
{
"version": 1,
"components": []
}This complete library defines one paragraph:
{
"version": 1,
"components": [
{
"type": "p",
"id": "partner-note",
"content": "Welcome to Acme."
}
]
}A definition does not render until dashboard.surface.order places it. Inside dashboard, replace the "surface": null field in brand.json with this complete surface field:
"surface": {
"version": 1,
"hidden": [],
"tabs": [
{ "id": "tab.myProjects" },
{ "id": "tab.recents" },
{ "id": "tab.starred" },
{ "id": "tab.templates" }
],
"order": [
"greeting",
"components.partner-note",
"prompt"
]
}Use Design blocks and tabs for the built-in dashboard block and tab controls. Code adds component references to the order array.
Use only these HTML element names for type: h1, h2, h3, h4, h5, h6, p, blockquote, small, ul, ol, li, div, section, a, and hr. Text elements use content; links use content and href; hr has no content. div and section use children. Lists use li children and also accept an items array of strings as shorthand. Component JSON defines structure and content only; put all presentation in styles.css.
Every top-level component requires an id; nested nodes may have an ID for styling. IDs must start with a lowercase letter or digit and contain only lowercase letters, digits, and hyphens. They must be unique across the entire file and no longer than 40 characters. Do not start an ID with whu-c-, and do not use __proto__, constructor, or prototype.
An a node's href must be either an absolute HTTPS URL or an internal path beginning with exactly one /. Link values may contain at most 2,048 characters. Backslashes, protocol-relative URLs, unsafe decoded values, and other schemes are rejected.
The server normalizes within these limits:
- 12 top-level components.
- 60 nodes across the complete library.
- Depth 3, where a top-level component is depth 1.
- Eight items in each list.
- 280 characters in each text value.
- 40 characters in each ID.
- 2,048 characters in each link URL.
- 24 entries in
dashboard.surface.order.
Text containing markup is rejected. A wrong root object or a version other than 1 is a hard error that blocks the file. Individual unusable nodes are dropped during fail-soft normalization; over-limit text is truncated, over-limit lists and node collections drop later entries, and duplicate IDs keep the first usable occurrence. Inspect the saved result instead of assuming a marked-up or over-limit node survived.
Component references use components.<id>. A reference may appear more than once in the order. A missing component reference renders nothing. If greeting or prompt is missing from the order, the renderer appends the missing anchor in canonical order. Style the example component with the explicit .whu-c-partner-note selector; that namespaced form remains stable.
Styles CSS
styles.css is optional source CSS loaded after the semantic theme styles. Code opens it as free-form text; there is no editor action that seeds or resets it. A ready-to-edit starter stylesheet is published as the starterCss field of GET /api/reseller/theme/contract, and it emits dark-mode values under :root and light-mode values under :root[data-app-theme="light"].
This complete stylesheet changes a core token in each mode, styles a stable part and variant, styles an active navigation state, and styles the example component:
:root {
--brand: #DD7740;
}
:root[data-app-theme="light"] {
--brand: #A84A1B;
}
[data-part="Button"] {
border-radius: 0.5rem;
}
[data-part="Button"][data-variant="secondary"] {
border-color: var(--brand);
}
[data-part="NavItem"][data-active] {
color: var(--brand);
}
.whu-c-partner-note {
margin-block: 1rem;
color: var(--text-secondary);
}Three handles cover most of what you will write: [data-part="Name"] for a catalog part, .whu-c-<id> for a component you defined in components.json, and the core variables for palette values you reuse. Parts and selectors covers the part grammar, the surfaces the catalog reaches, and how to look up an exact selector.
The server sanitizes every saved stylesheet:
- It scopes each top-level selector under
html[data-whu].:rootbecomeshtml[data-whu], and:root[data-app-theme="light"]becomeshtml[data-whu][data-app-theme="light"]. A selector that cannot be scoped safely is removed. - CSS nesting is supported when the nested selector provably inherits its parent scope. Root-anchored nested selectors and ambiguous nesting are removed.
- Group rules
@media,@supports,@container, and@layerare supported, and the rules inside them go through every check on this list.@layeris also accepted in its statement form,@layer base, components;. A@media,@supports, or@containerrule written without a block is removed. - Top-level
@font-face,@property, and standard or vendor-prefixed keyframes are supported. Each holds declarations only, and those declarations face the same value screening as any other rule. All three are invalid inside a style rule and are removed there. @import,@charset, and@namespaceare removed in every context, and so is any at-rule outside the supported set, including@scope,@page, and@starting-style.- Declarations using
expression(),-moz-binding, orbehaviorare removed. Escaped spellings are decoded before inspection. - A resource URL must be an explicit credential-free HTTPS URL or a canonical managed
assets.reference. HTTP,data:, protocol-relative, relative, and dynamically assembled resource URLs are rejected.
Accepted external images and fonts are fetched without credentials, checked by content type and file signature, stored under the platform account, and rewritten to an immutable first-party URL. This ingestion path accepts PNG, JPEG, WebP, AVIF, and GIF images, plus WOFF and WOFF2 fonts. It does not accept SVG images or TTF and OTF fonts. External hosts must resolve to public addresses. One save may ingest at most 16 files and 4 MiB of decoded resources in total. Each image is limited to 2 MiB, each font to 1 MiB, each fetch to five redirects, and each resource fetch to an eight-second wall-clock deadline.
The complete PUT JSON request, including CSS and the serialized brand document, is limited to 400 KiB. Sanitized compiled CSS is limited to 200 KiB; exceeding that limit returns sanitizer text with HTTP 400, not 413. The separate serialized account-theme snapshot ceiling is 65,536 bytes and returns 413 theme_too_large. Code stores the source you wrote separately from the compiled CSS that customers receive, and the save result reports each removed selector, at-rule, or declaration with its source line and reason.
Parts and selectors
A part is a named element in the customer-facing app that carries a data-part attribute for you to select. Target the attribute, then narrow it with a variant or a state:
| Selector | Matches |
|---|---|
[data-part="Button"] | Every instance of the part |
[data-part="Button"][data-variant="secondary"] | One declared variant |
[data-part="SelectTrigger"][data-state="open"] | The usual declared-state form |
[data-part="NavItem"][data-active] | A state that the element spells with its own attribute |
[data-part="Button"]:hover | A universal interaction state |
:hover, :focus-visible, :active, and :disabled work on every part. Declared states vary by part, and their attributes are not uniform, so read the exact fragment from the CSS reference or the contract rather than building it from the state name.
What the catalog covers
The catalog names every white-label surface down to the individual element. More than 1,100 live parts cover the customer dashboard, the project and template grids, the prompt composer, the editor, the sign-in and invite screens, notifications, credits, and customer settings.
Element-level hooks reach inside the containers. A project card exposes its preview frame, thumbnail, title, status chip, star, metadata row, and action cluster as separate parts. The prompt composer exposes its input area, placeholder text, attachment strip, toolbar, and send button. The project and templates grids expose the grid container and each grid cell separately, so you can re-column a grid without touching the cards inside it. The dashboard greeting is its own part, and its typeface has dedicated custom properties.
These are the parts partners reach for most often. Every other published part works the same way.
| Surface | Part | What it styles |
|---|---|---|
| Dashboard | DashboardSurface | The region holding the hero and the projects panel |
| Dashboard | DashboardCanvas | The rounded content card behind the page |
| Dashboard | Greeting | The greeting headline |
| Dashboard | DashboardTab | One project panel tab; state active |
| Dashboard | DashboardProjectGrid | The project grid container; set your columns here |
| Dashboard | DashboardProjectGridItem | One project grid cell; set grid-column and grid-row here |
| Dashboard | DashboardTemplatesGrid | The templates grid container |
| Dashboard | CreditsWidget | The credit balance widget; variants compact and full |
| Project card | ProjectCard | The card itself |
| Project card | ProjectCardPreview | The thumbnail frame |
| Project card | ProjectCardTitle | The project name |
| Project card | ProjectCardStatus | The publish-status chip; variants private, published, unpublished |
| Project card | ProjectCardStar | The favorite star; state active |
| Project card | ProjectCardActions | The action button cluster |
| Prompt box | PromptBox | The composer container; variants dark and light |
| Prompt box | PromptComposerInput | The input area |
| Prompt box | PromptComposerPlaceholder | The placeholder text |
| Prompt box | PromptAttachmentRow | The attachment strip |
| Prompt box | PromptBoxToolbar | The toolbar row |
| Prompt box | PromptSendButton | The send button |
| Editor | EditorHeader | The editor top chrome |
| Editor | EditorViewTab | One view switcher tab; state active |
| Editor | ChatPanel | The chat column |
| Editor | PreviewFrame | The preview frame |
| Editor | PreviewViewport | The preview device box; variants desktop, tablet, mobile |
| Editor | PublishButton | The publish action |
| Sign-in | SigninRequired | The signed-out wall on a partner domain |
| Sign-in | SigninRequiredPanel | That wall's centered panel |
| Sign-in | SigninRequiredTitle | That wall's headline |
| Sign-in | InviteCard | The invite landing card |
| Sign-in | SsoStatusPanel | The single sign-on handoff panel |
Look up a part
Two sources return the current catalog, and both stay in step with the app.
- In Code, with
styles.cssas the active file, select CSS reference. The Parts tab lists each part with its description and its exact selector; choosing a variant or state rewrites that selector in place. Select Copy to take the selector, or Insert to drop a rule scaffold in at the cursor. The Tokens tab lists the core and extended variables with the two mode roots, and the Components tab lists a.whu-c-<id>selector for every ID in yourcomponents.json. The search field filters all three tabs by part name, state, token, or component ID. GET /api/reseller/theme/contractreturns the same catalog as JSON. Each part carriesname,status,description, its declaredvariantsandstates, and aselectorsobject holdingbase,variantSuffixes, andstateSuffixes. Join the base with a suffix to build a selector; that is exactly what the CSS reference does.
Use only parts whose status is implemented. A part marked unavailable is a reserved name with no live element behind it: a rule targeting one saves, compiles, and then matches nothing.
The same response's starterCss field is narrower, and it is not a catalog listing. It is a ready-to-edit stylesheet that seeds an empty rule for a working set of a few dozen live parts: the core primitives, the shell and navigation, and the customer dashboard down to its grid cells, card internals, and composer internals. Treat it as a starting point and look the rest up in parts. The catalog grows, so read it at request time instead of pinning a part list or a catalog size.
Tokens and cascade
The semantic theme defines 11 core variables:
--bg-canvas--bg-base--bg-raised--bg-overlay--bg-inset--text-heading--text-primary--text-secondary--text-tertiary--brand--accent
The semantic theme generated from brand.json sets all 11 in both dark and light modes, and a Custom theme must supply all 11 in each map. The starter stylesheet does not redefine them: its opening block declares a separate six-value working palette — --background, --surface, --text, --muted, --border, and --brand — under the two mode roots, for the seeded rules below it to reuse. --brand is the only core variable among those six, so do not expect the starter to define --bg-canvas, --text-heading, or the rest; write them yourself when you want to override them. A longer extended list exists alongside the core 11: GET /api/reseller/theme/contract returns both under tokens as core and extended, and the CSS reference lists both under Tokens.
Published styles.css loads after the semantic styles generated from brand.json, so a CSS declaration can override a Design value. Keep reusable palette choices on the core variables and refer to them with var(...); this preserves a clear dark/light cascade and reduces duplicate values.
Set the greeting typeface
The dashboard greeting sets its own typeface and letter spacing inline, so an ordinary font-family rule on [data-part="Greeting"] loses to it. Two custom properties exist for this, and setting either one wins without !important:
| Property | Controls | Value when unset |
|---|---|---|
--whu-greeting-font | The greeting's font family list | 'Space Grotesk' |
--whu-greeting-tracking | The greeting's letter spacing | -0.03em on desktop and -0.6px on mobile |
Set them on the scoped root so both the desktop and the mobile greeting inherit them:
:root {
--whu-greeting-font: "Plus Jakarta Sans", "Inter";
--whu-greeting-tracking: -0.01em;
}--whu-greeting-font is substituted into a family list that still ends in sans-serif, so that fallback stays in place and you do not need to repeat it. Quote any family name that contains a space. To use a font that is not already loaded, declare it with @font-face first and follow the resource-URL rules under Styles CSS. Leave either property unset to keep the stock greeting.
Save, preview, and publish
- Edit all three files. Resolve red file dots and inline diagnostics, then check the footer byte count. Remember that server validation can find an error Monaco does not show.
- Select Save draft, or press Command+S or Ctrl+S. The server validates both JSON files, sanitizes the CSS, resolves assets, and stores the three files as one immutable artifact. Review any stripped-CSS report. Code retains the artifact's 16-character lowercase hexadecimal hash internally.
- Select Preview. Code saves unsaved changes first, then opens the customer dashboard preview for that exact hash in a new tab. Allow pop-ups for the Platform dashboard if the tab does not open.
- Return to Code without making another edit, select Publish, then confirm the generic publish prompt. Code saves unsaved changes when needed and automatically sends the resulting hash as both the publish target and its confirmation. The backend rejects a mismatch; you do not copy, compare, or enter the hash. If you edit after Preview, open Preview again before publishing.
- Open a fresh customer dashboard session and verify both light and dark modes, every changed component state, asset loading, navigation, and component placement.
Save and publish use the Branding revision, which identifies the current server state, and the selected draft's base hash, which identifies the artifact the edit started from. If another update changes either value, the server rejects the stale write instead of replacing newer branding.
Select a saved draft
The saved-draft list returns at most the ten most recent artifacts. The account retains the newest 20 drafts.
Select a draft to load its paired brand.json, components.json, and styles.css bundle into the editor. Selection changes the working base only; it is not a rollback or deletion action and does not change live branding. Preview and publish the selected artifact if it should become live.
Unpublish CSS
Select Unpublish CSS, then confirm the action, to remove only the live styles.css. Live semantic branding from brand.json remains in place, and saved three-file drafts remain available.
After confirmation, the status may show Reconciling while Code reloads the paired server snapshot. Wait for the current status before editing again. If the reload fails, select Retry.
Troubleshoot Code branding
Branding changed on the server
The Branding revision or base hash changed after the page loaded. Select Reload server state, review the authoritative three-file snapshot, then reapply the changes you still need. Recovery is complete when Conflict clears and Save draft can produce the Draft saved state.
Preview does not open
The browser blocked the new tab. Allow pop-ups for the Platform dashboard, then select Preview again. Recovery is complete when a new tab opens the customer dashboard preview.
Some CSS is missing
The sanitizer removed an unsafe or unsupported selector, at-rule, declaration, or resource URL. Read the stripped-rule report, correct the reported source line, and save another draft. Recovery is complete when the next report no longer lists that removal and Preview shows the intended style.
brand.json is invalid
The server parser rejected the reported path, which also makes Design read-only and blocks save, preview, and publish. Correct the value in brand.json; to abandon the invalid text and restore the last good configuration, select Discard invalid JSON. Recovery is complete when the invalid alert and red file dot disappear and Design is editable again.
components.json is invalid
The server parser rejected the reported component path, so save, preview, and publish remain blocked. Correct the value, or select Discard invalid JSON to restore the last good component library. Recovery is complete when the invalid alert and red file dot disappear and Save draft is available.
Advanced branding unavailable
Advanced branding was disabled for the account. Existing published branding remains unchanged; ask an operator to re-enable the capability, then reload Branding. Recovery is complete when the three Code file tabs and editor load again.
feature_disabled
A capability was disabled after Code loaded. If the response path is dashboard.surface.components, remove the component library or wait for reusable components to be re-enabled; otherwise ask an operator to restore the named capability and reload. Recovery is complete when Save draft finishes without feature_disabled and the footer shows Draft saved.
A save finishes after another edit
Code ignores a late save response rather than replacing newer editor text. Select Save draft again with the current text, then select Preview. Recovery is complete when that current bundle reaches Draft saved and the preview opens it.
The server rejects a file with no inline error
Monaco supplies editing help, but the server parser defines what can be saved. Follow the returned path and error, remove the unsupported key or value, and select Save draft again. Recovery is complete when the server accepts the bundle and the footer shows Draft saved.
A published change has no visible effect
Confirm that the selected draft was published. Then check whether the selector targets an unavailable contract entry, the light rule uses a root other than :root[data-app-theme="light"], the sanitizer removed the selector or declaration, an asset reference did not resolve, or a component was defined but not placed. Recovery is complete when a fresh customer dashboard session shows the change in the intended mode and state.
Couldn't load code branding
Code could not retrieve the paired server snapshot. Select Retry and do not continue from a partial file bundle. Recovery is complete when all three files and the current publish status load.
Code stays in Reconciling
Code is still refreshing the paired snapshot after Unpublish CSS. Do not edit until reconciliation finishes. If Code also shows Couldn't load code branding, select Retry. Recovery is complete when Reconciling clears and the editor actions are available again.