SDAIA UI ships with two brand themes: Public (the default) and Private. A brand is more than a coat of paint — it controls the visual rules every component follows, including typography, border radii, padding and spacing, icon sizing, and the brand color palette. You opt into a brand by applying a single CSS class to an ancestor element in your app.
At a glance
Same components, same behavior, same accessibility — different visual freedom.
Public — strict DGA brand
Locked typography, radii, spacing, icon sizes, and colors. Guaranteed visual consistency across every government service.
Private — flexible brand
Same defaults, but every brand-level token can be overridden. Tailor the look to your product without forking components.
Default brand
If you do nothing, your app renders with the Public brand. The Private brand is only applied when the brand-private class is present on an ancestor of the rendered tree.
Public vs Private
Both brands ship with the same defaults — including IBM Plex Sans Arabic as the typeface — and the same component behavior. They differ in which of those defaults a designer or developer is allowed to bend. Public follows the DGA design standard strictly; Private is a relaxed brand intended for products that need more visual flexibility.
Public — strict DGA brand
The Public brand enforces the DGA visual rules and these cannot be bypassed. IBM Plex Sans Arabic is the official typeface and must not be replaced. Components ship with fixed radii, fixed padding and spacing scales, fixed icon sizes, and a locked brand color palette. This guarantees that every product using the Public brand is visually consistent with every other government service. Use Public for any service that must conform to DGA's design standard.
Private — flexible brand
The Private brand relaxes the DGA constraints. The typeface, radii, padding and spacing, icon sizes, and the brand color palette can all be customized through tokens — IBM Plex Sans Arabic remains the default, but you can swap it for any font your product needs. This gives product teams room to express a distinct identity while still inheriting the same component behavior, accessibility, and RTL support. Use Private for products that are not bound by the DGA visual standard, or for internal tools and bespoke brand experiences.
Side-by-side: what changes between Public and Private
These comparisons illustrate how the same component tokens behave under each brand. Values shown for Private are examples — the brand exposes the tokens so each product can ship its own scale.
Brand colors
The brand color palette is the most visible difference. Public uses the official DGA palette; Private exposes the same tokens for override. The values below are illustrative — your Private theme can ship any palette.
The default action color used across buttons, links, and active states.
Border radius
Public locks every component to the DGA radius scale. Private exposes the same tokens so a brand can ship a sharper or softer look.
Padding & spacing
The spacing scale governs density. Public uses the fixed DGA rhythm; Private can tune the scale to suit a denser or roomier interface.
Icon sizes
Public enforces the DGA icon scale. Private exposes the size tokens for products that prefer a larger or smaller iconography.
Typography
Both brands ship with the same default typeface. In Public, that typeface is locked. In Private, you can swap it for any font your product needs by overriding the font tokens.
Static activation
Use static activation when your entire application always renders in one brand. Apply the brand class once on the document root and every descendant inherits it. This is the simplest setup and the easiest to render correctly during server-side rendering.
Recommended — on the <html> element
Placing the class on <html> matches how dark mode and document direction are typically set. It also avoids a flash of unstyled brand during hydration because the class is present before any React code runs.
<!DOCTYPE html><html lang="en" class="brand-private"> <body> <!-- All SDAIA UI components inside render with the Private brand --> </body></html>Alternative — on the <body> element
If your stack does not let you control the <html> tag (some CMS templates, embeddable widgets, micro-frontends), the class also works on <body> or on any wrapping container. The brand resolves from the nearest ancestor that carries the class.
<body class="brand-private"> <!-- Components below render with the Private brand --></body>In Next.js (App Router)
Set the class in your root layout so it ships in the initial HTML payload. This guarantees the correct brand renders on the server and prevents a visual swap when the React tree hydrates.
// app/layout.tsxexport default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en" className="brand-private"> <body>{children}</body> </html> );}Dynamic activation (toggleable)
Use dynamic activation when users should be able to switch brands at runtime — for example a brand picker in settings, or a preview tool. SDAIA UI ships a BrandModeProvider that manages the class on the document and persists the choice in localStorage.
1. Wrap your app in the provider
Mount BrandModeProvider once, as high in the tree as possible. It applies the brand class to the document root and keeps it in sync with state.
// app/providers.tsx'use client'; import { BrandModeProvider } from 'sdaia-ui'; export function Providers({ children }: { children: React.ReactNode }) { return <BrandModeProvider>{children}</BrandModeProvider>;}2. Read and update the brand from any component
Use the useBrandMode hook to read the current brand and switch it. The hook returns { brand, setBrand } — brand is either 'public' or 'private'.
'use client'; import { useBrandMode } from 'sdaia-ui'; export function BrandSwitch() { const { brand, setBrand } = useBrandMode(); return ( <button onClick={() => setBrand(brand === 'public' ? 'private' : 'public')}> Switch to {brand === 'public' ? 'Private' : 'Public'} </button> );}Combining static and dynamic
It is safe to use both at once. Set a static class on <html> for the initial render, then mount BrandModeProvider on top — the provider will take over and reconcile state on the first client render. This gives you a correct SSR render and a runtime toggle.
Scoping a brand to a subtree
Because the brand class is resolved from the nearest ancestor, you can apply Private to one section of a page while the rest stays on Public. This is useful for preview tools, embedded brand showcases, or mixed-tenant dashboards.
<main> {/* Public brand here (inherited from <html>) */} <Hero /> <section className="brand-private"> {/* Private brand applies to everything in this section */} <PrivatePreview /> </section> <Footer /></main>Portals and overlays
Components that render into a portal — Tooltip, Popover, Menu, Select, DatePicker — live outside the DOM tree of the trigger. SDAIA UI already walks the trigger's ancestors and re-applies the brand class on the portal root, so you do not need to do anything extra. Just make sure your trigger is inside the scoped subtree.
Avoiding a flash of unstyled brand
If you set the brand only from client-side code, users may briefly see the Public brand before your script runs and swaps it to Private. To avoid this, either render the class statically on the server (recommended), or use a small inline script in the document head that reads the persisted brand from localStorage and applies the class before the React tree mounts.
<!-- Place in <head>, before any other script --><script> (function () { try { var b = localStorage.getItem('brand-mode'); if (b === 'private') document.documentElement.classList.add('brand-private'); } catch (e) {} })();</script>What changes between brands
The brand class swaps the design-rule tokens that govern shape, density, and color. Typography, component behavior, accessibility, and RTL support are identical across brands.
Locked in Public, customizable in Private:
- Typography — Public is locked to IBM Plex Sans Arabic; Private exposes the font tokens so you can ship a custom typeface.
- Border radius scale — Public uses the fixed DGA radii; Private exposes the radius tokens for override.
- Padding and spacing scale — Public locks the DGA spacing rhythm; Private lets you adjust spacing tokens to suit a denser or roomier layout.
- Icon sizes — Public enforces the DGA icon size scale; Private allows alternative icon sizing.
- Brand color palette — Public uses the official DGA colors only; Private exposes the brand color tokens so you can ship a custom palette.
Same across both brands:
- Default typeface — both brands start with IBM Plex Sans Arabic; only Private can change it.
- Component behavior — interaction patterns, keyboard handling, and focus management are identical.
- Accessibility — WCAG-aligned semantics, ARIA, and screen reader behavior do not change between brands.
- RTL support — directional mirroring works the same way in both brands.
Overriding Private brand tokens
Because Private exposes the brand tokens, you can override them anywhere the brand-private class applies. Define your values inside a CSS rule scoped to .brand-private — the cascade will pick them up automatically.
/* app/brand-private.css */.brand-private { /* Brand colors */ --ds-color-brand-primary-600: #6E56CF; --ds-color-brand-primary-500: #7C66D9; --ds-color-brand-primary-300: #B7A4F0; --ds-color-brand-primary-50: #F5F2FF; /* Radii */ --ds-radius-md: 14px; --ds-radius-lg: 20px; /* Spacing */ --ds-spacing-md: 12px; --ds-spacing-lg: 16px; /* Typography (optional — defaults to IBM Plex Sans Arabic) */ --ds-brand-font-display: 'Inter', system-ui, sans-serif; --ds-brand-font-text: 'Inter', system-ui, sans-serif;}Public tokens are locked on purpose
Do not override brand tokens inside an unscoped selector or inside :root — that would also affect the Public brand, which must stay aligned with the DGA standard. Always scope overrides to .brand-private (or to a more specific descendant if you want to scope further).
Troubleshooting
Common pitfalls when brand styles do not appear as expected.
- The class is on a sibling, not an ancestor — brand styles only inherit downward. Move the class up to a common parent of the components you want themed.
- A nested ancestor also carries brand-private — the nearest ancestor wins. Remove the inner class if you want to reset to Public.
- Brand fonts are not loading — confirm Inter and IBM Plex Sans Arabic are included by your app shell (e.g. next/font in Next.js).
- Brand flashes on first paint — set the class statically on <html> at SSR time, or use the inline-script snippet above.