Skip to main content

Theming and Navaid Design System

Navaid uses Work Sans and the 2026 Deep Teal, Light Teal, and Cream palette. The application exposes a generated accent scale to System Admin, but fixed primary-action tokens remain Navaid Deep Teal so tenant theming cannot erase the product identity.

Core Brand

TokenValueUse
Deep Teal#2C615FPrimary identity, fixed CTA, dark chrome
Deep Teal strong#275350Hover/pressed CTA
Light Teal#7DA19CSecondary/accent
Cream#FCF4DEWarm brand surface/accent
Off-white#F9F8F6Neutral surface
Text#1A1A1APrimary body text
TypefaceWork SansUI and documentation

The Navaid palette has no coral primary. Legacy coral and navy variable names remain compatibility aliases in application source, but they map into the Deep Teal family.

Tailwind v4 CSS-first configuration

There is no tailwind.config.ts. frontend/src/index.css defines tokens in @theme and the @tailwindcss/vite plugin compiles utilities.

@theme {
--font-sans: 'Work Sans', system-ui, sans-serif;

--color-brand-teal-50: #F1F6F5;
--color-brand-teal-100: #DEEAE9;
--color-brand-teal-200: #C2D9D6;
--color-brand-teal-300: #9CC0BC;
--color-brand-teal-400: #7DA19C;
--color-brand-teal-500: #2C615F;
--color-brand-teal-600: #275350;
--color-brand-teal-700: #204341;
--color-brand-teal-800: #183230;
--color-brand-teal-900: #112322;

--color-brand-sage: #7DA19C;
--color-brand: #2C615F;
--color-brand-strong: #275350;
--color-cream: #FCF4DE;
--color-offwhite: #F9F8F6;
}

Compatibility aliases

--color-coral: #2C615F;
--color-coral-hover: #275350;
--color-coral-light: #DEEAE9;

--color-navy: #2C615F;
--color-navy-light: #3F726E;
--color-navy-dark: #1E423F;

Do not infer visual coral/navy from a legacy utility name. Prefer new brand-* utilities for new code and remove aliases only through a deliberate repository-wide migration.

Fixed versus Runtime Tokens

Fixed identity tokens

These are not changed by System Admin:

--color-brand: #2C615F;
--color-brand-strong: #275350;

PrimaryButton and primary conversion actions use these tokens.

Runtime accent scale

applyPrimaryColor(hex) generates and assigns only:

--color-brand-teal-50
--color-brand-teal-100
...
--color-brand-teal-900

This affects navigation highlights, rings, focus accents, and UI elements that use brand-teal-*. It does not replace every palette value.

Loading the theme

At application startup:

  1. ThemeLoader reads system_config.theme_config.
  2. primary_color is validated/normalized by the colour utility.
  3. Generated shades are assigned to the root element.
  4. Missing/invalid configuration leaves the CSS Navaid defaults.

The Theme tab's current UI fallback is #2C615F.

Historical seed

The original 20260407150000_add_theme_config.sql migration contains the older #1E6B5C seed. A fresh database that retains that row may override the CSS default until an administrator saves #2C615F or a forward migration updates the configuration. Documentation must not claim the historical migration already contains the new value.

System Admin Theme Flow

components/admin/ThemeTab.tsx:

  1. loads theme_config,
  2. previews a selected colour immediately,
  3. displays generated shades,
  4. saves through updateThemeConfig,
  5. reapplies the saved value.

Only system administrators can update configuration. theme_config is intentionally included in the public configuration read allowlist so the logged-out shell and authenticated UI can render the same accent.

Preview changes are client-side until saved. A failed save should show an error and the next reload returns to stored configuration.

Generating Shades

frontend/src/utils/colorUtils.ts converts the base colour into a 50–900 scale. When changing the algorithm:

  • preserve sufficient foreground/background contrast,
  • clamp valid RGB/HSL values,
  • reject malformed input,
  • keep deterministic output,
  • test very light and very dark bases,
  • verify focus, hover, disabled and selected states.

Do not write arbitrary CSS from server JSON.

Typography

Work Sans is loaded through the frontend's font setup with system-ui fallback.

Guidelines:

  • use weight/size/spacing before introducing another family,
  • keep body text at readable line height,
  • use tabular numerals for data tables,
  • avoid light-weight text on tinted backgrounds,
  • retain 200% zoom and narrow-layout usability.

If the browser font origin changes, update the production CSP.

Layout and Spacing

The app uses Tailwind utilities plus shared primitives such as PageHeader.

Breakpoints

An extra xs breakpoint at 360 px supports narrow phones. Route layouts should still work below 360 px without horizontal document overflow.

Padding precedence

Tailwind responsive shorthand can override a base side-specific utility. Do not combine a shorthand such as:

p-4 sm:p-6 pt-16

when top padding must survive the sm rule. Decompose it:

px-4 sm:px-6 pb-4 sm:pb-6 pt-16 lg:pt-8

Use the same pattern for pl-* plus responsive p-*.

Surfaces and Components

Primary actions

  • fixed Deep Teal background
  • strong hover token
  • white text
  • visible focus ring and offset
  • disabled state with more than colour alone

The sidebar uses Deep Teal-family dark chrome. Active items need both contrast and a structural/typographic cue.

Cards and data

  • neutral white/off-white surfaces
  • subtle grey borders
  • restrained radius and shadow
  • tabular numerals for usage/budget tables
  • semantic status colours only where status, not brand, is being conveyed

Cream

Cream is an accent surface. Avoid using it as low-contrast body text or as the only indicator of selected/error state.

Maturity Colours

Maturity is semantic data and does not use the runtime brand scale. MATURITY_LEVEL_COLORS in frontend/src/types/maturity.ts is:

LevelBackgroundText
1#E5E7EB#4B5563
2#FEE2B0#92400E
3#FDE68A#854D0E
4#BBF7D0#166534
5#86EFAC#14532D

Legacy readiness colours remain in types/readiness.ts for historical sessions:

  • strong #BBF7D0
  • developing #FEF08A
  • fragile #FECDD3
  • unknown #E5E5E5

Do not describe the legacy map as the active five-level product.

Motion

Motion appears in:

  • loading indicators
  • maturity transition cues
  • voice/listening visualization
  • Aida activity and the optional seasonal theme
  • dialog/menu transitions

Respect prefers-reduced-motion. Decorative animation must not block a task or be required to understand status.

Accessibility and Contrast

Every themeable component must be checked against:

  • normal text contrast
  • large text/icon contrast
  • focus indicator visibility
  • hover/selected/disabled differentiation
  • high-contrast and forced-colour behaviour where practical
  • maturity/status meaning without colour alone

Runtime accent controls can generate unsafe combinations. Keep fixed CTA tokens for critical actions and validate preview examples across the scale.

Documentation Site Branding

The Docusaurus site has its own implementation under:

docs/docusaurus.config.ts
docs/src/css/custom.css
docs/src/pages/
docs/static/img/

It should reuse Navaid name, logo, Work Sans and palette, but it does not load system_config.theme_config. Documentation branding is build-time/static and must remain readable in both light and dark Docusaurus modes.

Do not restore Docusaurus sample logos, mountain imagery, or the retired UK Market Readiness Advisor/Newfound navy-and-coral copy.

Extension Guidance

When adding a visual token:

  1. decide whether it is fixed identity, runtime accent, neutral, or semantic,
  2. define it once in CSS-first theme tokens,
  3. provide hover/focus/disabled companions,
  4. test contrast and reduced motion,
  5. avoid naming a semantic token after one current hex value,
  6. document the intended ownership and runtime behaviour.

When adding a component:

  • use shared primitives before introducing a local variant,
  • avoid hard-coded brand hex values in JSX,
  • do not use colour as the only state signal,
  • test mobile, keyboard, dark docs mode where applicable, and 200% zoom.

Troubleshooting

Saved theme does not affect a primary button

Expected: primary action tokens are fixed. The runtime setting changes only the brand-teal-* scale.

Fresh local database looks like the old green

The historical seed may be overriding CSS defaults. Save #2C615F in System Admin or add/apply an authorized forward migration.

A responsive page loses top padding at sm

Decompose responsive padding shorthands into axis/side utilities.

Theme works in app but not docs

Docusaurus uses its own CSS/config/static assets and does not read Supabase configuration.