hashgraph / hashgraph/guardian

Theming token system for the Guardian frontend

Open
#6,091 2 comments 1 reaction 1 assignee Claimed by @Pyatakov View on GitHub
Dominant language
TypeScript
Stars
146
Forks
186
Avg merge
2d 20h
Merged PRs (30d)
126

Description

## Summary

Sharing a design for a token-driven theming layer in the Guardian Angular frontend that lets deployers white-label Guardian under their own brand without forking component SCSS. Filing this ahead of a PR so we can align on naming and scope before code review starts. A reference branch is linked below for direct preview.

## Why

Three problems we've all seen across the codebase:

1. **White-label deployers carry permanent fork divergence.** Anyone shipping Guardian under their own brand (Climission and others) maintains a patched fork that touches dozens of component files. Each UI change risks breaking those patches.
2. **Runtime tenant branding only partially propagates.** `BrandingService` writes `--color-primary` onto `document.body`, but many components ignore that variable and reference hard-coded hex values, so per-tenant brand overrides apply inconsistently.
3. **The upcoming UI refresh will re-fragment branding** if it inherits hard-coded values in component styles. Landing the token abstraction first means the refresh gets built on top of a shared theming layer rather than around it.

A token system solves all three at once and gives Guardian a first-class white-label story for new commercial deployers.

## Design

**Single source of truth.** A new file `frontend/src/styles/guardian-tokens.scss` declares every brand value (colors, surfaces, text, borders, gray scale, semantic colors, typography scale + weights + tracking, radii, spacing, timings + easings, shadows, gradients, layout dimensions) as `--guardian-*` CSS custom properties.

**Aliasing layer.** `frontend/src/variables.scss` is rewritten so every legacy variable (`--color-*`, `--primary-color`, `--linear-gradient`, `--header-*`, `--button-*`) resolves to a token. Default token values match the values currently hard-coded in `variables.scss`, so the visual diff on the day this lands is zero.

**Token-driven style layer.** Seven new files under `frontend/src/styles/`:

| File | What it controls |
|------|------------------|
| `guardian.fonts.scss` | `.mat-typography` sizing and weight ramp via tokens |
| `guardian.inputs.scss` | Form inputs, opt-in via `.guardian` scope |
| `guardian.banner.scss` | Alert banner variants (important / warning / low / info / star) |
| `guardian.dialog.scss` | Material and PrimeNG dialog treatments (opt-in via class) |
| `guardian.feedback.scss` | Unified 3-tier loader system (action arc, skeleton, large ring), PrimeNG progress bar with shimmer, shared `.loading` wrapper |
| `guardian.prime.scss` | PrimeNG component overrides through tokens (inputs, dropdowns, multi-selects, checkboxes, buttons, tabs, tables, tree-tables, paginators). Also fixes the "box in a box" double-border PrimeNG renders for nested `.p-inputtext` inside `.p-dropdown` / `.p-multiselect`. |
| `guardian.patterns.scss` | Reusable admin layout primitives (page hero, stats grid, cards, badges, filter rows, stages with stagger animations, scroll-hint, user picker). All opt-in. |

**BrandingService refactor** (`frontend/src/app/services/branding.service.ts`):

- Adds `PLATFORM_CHROME_ROUTE_PREFIXES` (route allowlist for "platform-owned" surfaces like `/login`, `/admin`, `/branding`) and `PLATFORM_CHROME_ROLES` (operator-role allowlist) as module-level exports that deployers can extend or replace.
- `applyBrandingForRoute(url, role)` decides whether to apply tenant branding or platform-default branding based on both signals. Solves the case where an admin user navigates to a tenant route and shouldn't see the tenant's brand leak into operator chrome.
- `applyPrimaryColor` and `applyHeaderGradient` extracted as public helpers, so the Branding Settings "preview" flow and the production apply flow share one implementation and cannot drift.
- Existing API (`loadBrandingData`, `saveBrandingData`, `applyBranding`) is unchanged. Backwards compatible.

**`DefaultBrandings`** in `@guardian/interfaces`. A small constant exposing the platform-default brand payload (`headerColor`, `headerColor1`, `primaryColor`, `companyName`, `companyLogoUrl`, `loginBannerUrl`, `faviconUrl`) so frontend and backend agree on the fallback applied when no tenant branding is loaded.

**Documentation.** A new `frontend/THEMING-GUIDE.md` covering the token chain, how to white-label (compile-time defaults + runtime fallback), the platform-chrome vs tenant-workspace distinction, preview / production parity, and author guidelines for new components.

## Out of scope for this change

- **Brand-specific visual treatments.** No deployer logos, custom fonts, brand-specific imagery, or cinematic surfaces. The defaults are Guardian's existing palette and fonts.
- **App-shell wiring for `applyBrandingForRoute`.** The method is in place but no `NavigationEnd` / auth-change subscriptions are added. Easy follow-up if useful.
- **New PrimeNG features.** This is theming only. No new components.

## Backwards compatibility

The token layer is additive:

- Every component that hard-codes `--color-primary`, `--primary-color`, `--linear-gradient`, `--header-*`, `--button-*` continues to work unchanged. Those names are aliased to tokens in `variables.scss`.
- The runtime branding override path (BrandingService writing onto `document.body`) targets the same legacy variable names. Tenants who have set custom brand colors see no behavior change.
- All new style files are opt-in. No existing page is restyled by this change. Components migrate to the new classes incrementally if and when they're touched.

## Reference branch

- Branch: https://github.com/Climission/guardian/tree/feat/theming-tokens
- Diff vs `develop`: https://github.com/Climission/guardian/compare/develop...feat/theming-tokens

Four commits, broken roughly along the natural scope splits below.

## Open questions

1. **Timing relative to the UI refresh.** Ideally tokens land before the refresh branch cuts, so the new UI is built on the abstraction rather than around it. If the refresh branch is imminent, flag here so we can fast-track.
2. **Naming.** Chose `--guardian-*` for the token prefix and `guardian.*.scss` for the style files. If a different convention fits the project better (`--gx-*`, `--theme-*`, `frontend/src/theme/`), call it now and we'll rename across the branch.
3. **Scope split.** Can land as one PR or split into (a) tokens + variables.scss aliasing, (b) style layer, (c) BrandingService refactor + DefaultBrandings, (d) docs. (a) alone delivers most of the value. Reviewer preference?
4. **Patterns library.** `guardian.patterns.scss` is the most opinionated piece. Happy to drop it from the first PR if we'd rather settle the tokens + Prime overrides first and bring patterns in as a follow-up.
5. **PrimeNG trajectory.** If the refresh is moving off PrimeNG, `guardian.prime.scss` is dead weight. Worth confirming before we polish the PR.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.