Customisation Guide
Theme structure — the one-file promise
99% of visual customisation happens in one file: src/app/globals.css. The
three-tier OKLCH token system below is the single source of truth for every
colour, spacing, motion, and font binding in the kit.
Tier 1 — Primitives Tier 2 — Semantic aliases Tier 3 — Component tokens
( --p-*, raw OKLCH ) → ( --brand-accent, --brand- → ( --bg-surface, --text-
glow, --chart-series-*, primary, --border-*,
--severity-*, --model-* ) --glass-*, --radius-* )
Most brand-critical OKLCH values live in primitives. Semantic aliases derive
from primitives where they need to rebrand together; intentionally independent
semantic/component tokens (severity, chart series, glass alpha, overlays,
shadows, and model comparison colours) keep their own OKLCH values so they
remain meaningful across rebrands. Tailwind consumes the final layer through
the @theme inline { ... } block.
Tailwind v4 note — this kit uses Tailwind v4's CSS-first configuration. All theme tokens live in
src/app/globals.cssinside@theme inline { … }. There is notailwind.config.js/tailwind.config.tsto edit. If you're coming from v3, stop looking for the JS config — it's gone by design.
Where to edit
| You want to change… | Edit | Notes |
|---|---|---|
| The warm-amber brand accent | globals.css --p-amber-400/500/600/700 in :root | Adjust hue to rebrand the whole kit. Light mode inherits the same primitives; edit html.light only when you intentionally want independent light-mode surfaces. |
| Light/dark backgrounds + surfaces | globals.css :root and html.light | All surfaces derive from the primitive --p-* tokens. |
| Severity palette (success/warning/error/info) | globals.css --severity-* tokens (registered as --color-severity-* in @theme) | Independent of brand accent — use Tailwind utilities like bg-severity-success, text-severity-error/20. |
| Chart series colours (Recharts) | globals.css --chart-series-* tokens | Referenced by SVG <stop> / <rect> elements via var(--chart-series-N). |
| Model colours (dashboard Model Comparison) | globals.css --model-emerald/violet/blue/amber | Components read the CSS variables directly and use color-mix() for translucent states. |
| Border radius | globals.css --radius-* | Single source for cards, buttons, inputs. |
| Motion timing + easing | globals.css --duration-* / --ease-* | Used by CSS utilities. Framer Motion and GSAP components keep local timing props so interactive effects remain tuned per component. |
| Fonts | src/app/layout.tsx (import) + globals.css @theme inline (binding) | See "Typography" below. |
| Brand identity strings | src/lib/site-config.ts | Name, emails, social, legal entity, API URLs. |
| Logo mark | src/components/layout/navbar.tsx, src/components/layout/footer.tsx, src/app/icon.svg | Replace the default Zap icon in the nav/footer and keep the favicon SVG in the same brand style. |
| Package identity | package.json name | Optional during a full rebrand; rename ui01-aria-ai-saas to your own package/app slug if you publish or embed it in a monorepo. |
Site language (HTML lang attribute) | src/app/layout.tsx <html lang="…"> AND src/app/global-error.tsx <html lang="…"> | Both files must be kept in sync — global-error.tsx runs when the root layout fails so cannot inherit. Update both atomically. |
| OAuth / SSO provider buttons | src/components/auth/login-page-parts.tsx OAuthButtons | Generic "Continue with SSO" placeholder ships behind NEXT_PUBLIC_TE_DEMO=true. To wire real providers (Google, GitHub, Microsoft, etc.), replace the buttons with your provider's branded button per their brand guidelines, then implement the auth handler. The kit ships frontend-only — bring your own auth provider (NextAuth.js, Clerk, Auth0, Supabase Auth, etc.). |
Documented exceptions (raw hex allowed)
A handful of files intentionally hold literal hex values because CSS custom properties are unavailable at that execution point. Each carries a top-of-file comment explaining why. If you retheme the accent or model colours, update these too:
| File | Why it's a raw literal |
|---|---|
src/lib/tokens.ts | JS mirror of the CSS palette — consumed by WebGL / Canvas / Three.js shaders that cannot read CSS variables. light-droplets-bg.tsx imports from here automatically. Edit globals.css first, then sync the matching hex values here. |
src/app/icon.svg | Standalone SVG served as the favicon — no CSS context. Pin the background colour to your --p-amber-600 and the glyph colour to your brand-accent foreground mirror. |
src/app/global-error.tsx | Runs when the root layout crashes — globals.css has not loaded, so CSS vars cannot resolve. Mirror the ERROR_FALLBACK palette from src/lib/tokens.ts here. |
<meta name="theme-color"> in layout.tsx | Browser UI chrome (mobile address bar, PWA theming) requires hex — CSS vars aren't resolved in HTTP metadata. |
Toggle-knob colour comes from the
--toggle-knobCSS variable (defined inglobals.css) — it's near-white in dark mode and near-black in light mode so both satisfy WCAG AA contrast against the alpha-on-ink track. Don't replace with a literalbg-white— that fails light-mode contrast and has been deliberately retired from this kit.
Colours
The brand colour system is defined as OKLCH tokens in src/app/globals.css.
Editing the four --p-amber-* primitive lines alone cascades through every
brand-semantic surface in the kit — buttons, links, badges, section labels,
borders, pricing card auras, shadow glows, orb backgrounds, surface tints.
3 additional files require manual edits for the hex/RGBA fallback (used
by WebGL), the app icon SVG, and the error fallback page. See
Documented exceptions above and
Rebrand workflow below.
Important: If you are shipping under a different brand, complete the full manual checklist before considering the rebrand finished.
Change the brand colour
Edit the Tier 1 primitives in :root {}:
:root {
/* Tier 1 — Primitive palette */
--p-amber-400: oklch(0.85 0.16 75); /* light amber */
--p-amber-500: oklch(0.76 0.175 75); /* mid amber */
--p-amber-600: oklch(0.740 0.111 74.6); /* ← main accent (#D4A055) */
--p-amber-700: oklch(0.52 0.145 75); /* dark amber for light-mode text */
}
Adjust the hue value (75 = warm amber) to shift the entire brand colour:
75— warm amber/gold (default)145— green180— teal255— blue320— pink/rose
The second OKLCH value controls saturation (chroma). Lightness steps are set for contrast — keep the relative differences when changing.
Accent variants
The commented Teal/Rose snippets in globals.css are reference palettes, not
one-step theme switches. For a complete rebrand, re-hue the four
--p-amber-* primitives in :root {} and then update the three non-CSS
surfaces in the workflow below. That keeps glow, orb, surface-tint, light-mode,
and hero-frame colours on the same hue instead of leaving amber-derived tokens
behind.
Light mode colours
Overrides are in html.light {} in globals.css. Edit those values to customise the light theme independently.
Known exceptions
These elements use hardcoded colours that won't update from the CSS tokens:
- Hex/RGBA fallback (
src/lib/tokens.ts) — source of truth for WebGL, Canvas, viewport metadata, and root error fallback contexts that cannot read CSS custom properties.light-droplets-bg.tsximportsBRAND.amber.accent;ambient-glow.tsximportsWEBGL.ambientBrand/WEBGL.darkBackground;src/app/global-error.tsxmirrorsERROR_FALLBACKlocally. - SVG icon (
src/app/icon.svg) — replace#D4A055manually or regenerate in your brand colour, and keep the glyph fill/stroke aligned with your brand-accent foreground mirror. - Recharts chart colours — SVG
<stop>elements insrc/components/dashboard/UsageDailyChart.tsxreferencevar(--chart-series-2). To change the chart-2 hue, edit the--chart-series-2-base: oklch(0.733 0.156 297.2);token insrc/app/globals.css; the exported--chart-series-2token derives from it and light mode automatically mixes the same base for contrast — no component edit needed. - Status/severity colours — operational (green), warning (amber), error (red), info (blue) are driven by four CSS custom properties in
globals.css(--severity-success,--severity-warning,--severity-error,--severity-info) that are independent of the brand colour. These are registered in@themeas--color-severity-*, so Tailwind utilities work with the full modifier syntax:bg-severity-success,text-severity-error,border-severity-info/20,hover:bg-severity-warning/10. Edit the four OKLCH values inglobals.cssto shift the whole status palette. - Shadow glows —
shadow-[0_0_Xpx_var(--brand-glow)]utilities throughout the kit resolve through acolor-mix(in oklch, var(--brand-accent) X%, transparent)cascade, so no manual edit is needed after a primitive hue swap.
Rebrand workflow
Follow these steps in order when changing the brand colour:
Step 1 — CSS tokens (covers most UI)
- Open
src/app/globals.css - Change the four
--p-amber-400/500/600/700primitives to your brand hue (adjust lightness, chroma, and hue) - That's it for the cascade.
--brand-accent,--brand-glow*,--brand-orb-glow,--brand-surface-tint*,.brand-glow-*utilities, and the orb backgrounds all derive from these primitives viavar()orcolor-mix()— no further edit needed. - Convert your OKLCH values to hex at oklch.com when you need a literal for the non-CSS surfaces in Step 2.
Step 2 — Non-CSS surfaces (3 files)
| # | File | What to change |
|---|---|---|
| 1 | src/lib/tokens.ts | Source of truth — update BRAND.amber.accent, .glow, .muted, .rgb fields (search for the BRAND export). If your rebrand changes the page background, browser chrome palette, or root error fallback, update VIEWPORT_THEME.dark, VIEWPORT_THEME.light, and ERROR_FALLBACK in the same file. light-droplets-bg.tsx imports from here automatically. |
| 2 | src/app/icon.svg | Replace #D4A055 with your brand hex and #1f1308 with your brand-accent foreground mirror (cannot import TS — keep in sync with tokens.ts) |
| 3 | src/app/global-error.tsx | Mirror ERROR_FALLBACK from src/lib/tokens.ts — this page renders without the root layout, so it intentionally keeps local fallback constants instead of importing CSS vars or modules |
Informational — token-driven, no edit needed
| File | Notes |
|---|---|
src/components/ui/orb-bg.tsx | Three radial-gradient orbs reference semantic var(--brand-orb-glow), which derives from --brand-accent. Rebrand via the --p-amber-* primitives in Step 1 and all three orbs update automatically. Listed here only so you know where the orb colours come from. |
--brand-glow* shadow tokens | Now composed from --brand-accent via color-mix(in oklch, … X%, transparent) for 4 alpha steps (brand-glow, -strong, -subtle, -whisper). Cascades both in :root (dark) and html.light. |
--brand-surface-tint / --brand-surface-gradient-tint | Derived from --brand-accent + --p-ink-900 / --p-slate-50 via color-mix. Card backgrounds get a subtle warm wash that re-tints with the brand on rebrand. |
--similarity-mid / --similarity-low | Derive from --severity-warning + a fixed orange step — deliberately NOT tied to brand so RAG score traffic-lights (red → amber → green) stay semantic across rebrands. |
Step 3 — Verify
- Run
pnpm devand check the landing page in both dark and light mode - Toggle the theme — all branded elements should match your new colour
- Check the dashboard, pricing, and error pages
Step 4 — Catch leftover brand strings
The kit ships with pnpm rebrand-check (see scripts/rebrand-check.mjs).
It greps the source for hardcoded occurrences of the kit's original brand
name (Aria) outside SITE_CONFIG. Run it after Step 3 — any hits are
strings the cascade can't reach (model labels, status copy, demo data
seeded with the brand name). Replace each with SITE_CONFIG.name or
your new brand string.
pnpm rebrand-check
The script exits 0 if every match is in an allowlisted file (e.g.
src/lib/site-config.ts, where the brand name is the source of truth).
Typography
Fonts are bundled as .woff2 variable font files in src/app/fonts/ and
loaded via next/font/local — no network access is required at build time
(deliberate choice for air-gapped CI / offline-build support). Three files
ship with the kit:
| Role | File | Font | Variable (layout) | @theme inline binding |
|---|---|---|---|---|
| Body / sans | src/app/fonts/geist-sans.woff2 | Geist Sans (100–900) | --font-geist-sans | --font-sans |
| Code / mono | src/app/fonts/geist-mono.woff2 | Geist Mono (100–900) | --font-geist-mono | --font-mono |
| Display headings | src/app/fonts/space-grotesk.woff2 | Space Grotesk (300–700) | --font-space-grotesk | --font-display |
The @theme inline { … } block in globals.css maps these variables to the
role-neutral names Tailwind consumes. Consumers use font-sans, font-mono,
.font-display (or style={{ fontFamily: 'var(--font-display)' }}) — never
reference --font-space-grotesk directly outside the binding.
The display font is preloaded because the homepage H1 uses it as an LCP-critical headline font. Keeping it preloaded avoids layout shift from a late headline font swap on wide viewports.
Change the display font
Replace src/app/fonts/space-grotesk.woff2 with any variable .woff2 and update the localFont config in src/app/layout.tsx:
// src/app/layout.tsx
const spaceGrotesk = localFont({
src: [{ path: './fonts/your-font.woff2', weight: '300 700', style: 'normal' }],
variable: '--font-space-grotesk',
display: 'swap',
preload: true,
})
The --font-space-grotesk CSS variable is referenced throughout the template via the .font-display class.
Change the body font
The body font is Geist Sans. Replace src/app/fonts/geist-sans.woff2 and update the localFont config:
const geistSans = localFont({
src: './fonts/your-body-font.woff2',
variable: '--font-geist-sans',
weight: '100 900',
display: 'swap',
})
Fluid type scale
Font sizes use clamp() in @theme inline {}:
--text-step-3: clamp(1.5rem, 1.2rem + 1.5vi, 2.25rem);
Adjust the min, preferred, and max values to taste. The vi unit scales relative to the viewport inline size.
Content
Site copy
All section content lives in the component files under src/components/sections/. Each section exports a single component; edit the constant arrays and strings directly.
Navigation links
Edit src/components/layout/navbar.tsx — the NAV_LINKS constant.
Brand identity (name, emails, social links, metadata, API URLs)
Shell-level brand identity (name, metadata, contact emails, social links, API URLs) is centralised in src/lib/site-config.ts. Change the SITE_CONFIG object to update these across the site in one place. Demo marketing, legal, blog, and testimonial content is separate and must be replaced manually — see the "Demo content" section below.
The wordmark text comes from SITE_CONFIG.name, but the mark itself is an
icon. Replace the default Zap icon imports/rendering in
src/components/layout/navbar.tsx and src/components/layout/footer.tsx, then
keep src/app/icon.svg in sync so the browser favicon matches the visible logo.
The config object has the following sections:
| Key | Purpose | Used by |
|---|---|---|
name | Brand name (e.g. "Aria") | Navbar logo, footer, forgot-password page |
slug | Safe lowercase identifier for API keys, model IDs, URL fragments, and analytics keys | Dashboard models, scripts, generated IDs |
tagline | Short brand tagline | Footer |
url | Canonical site URL | OpenGraph, noreply email derivation |
legalEntity.companyName | Registered company name (e.g. "Aria, Inc.") | Footer copyright, legal pages |
legalEntity.companyNumber | Registered company / business number | Footer registered-entity disclosure |
legalEntity.placeOfRegistration | Registered jurisdiction text such as "Registered in England and Wales" | Footer registered-entity disclosure |
legalEntity.registeredAddress | Your registered business address | Terms of Service, Privacy Policy |
legalEntity.jurisdiction | State/country of incorporation | Terms of Service (governing law) |
legalEntity.vatNumber | VAT / tax registration number; empty hides the line | Footer registered-entity disclosure |
legalEntity.regulator / regulatorNumber | Regulated-profession disclosure fields; empty hides the line | Footer registered-entity disclosure |
regions.uk / regions.eu / regions.us | Region switches controlling compliance links and disclosures | Footer legal links, compliance pages |
compliance.dmca.* | Designated Copyright Agent placeholders | DMCA page and conditional footer link |
compliance.ccpa.doNotSell | Controls "Do Not Sell or Share" footer link | Footer legal links |
compliance.ccpa.sensitivePII | Controls "Limit Use of Sensitive Personal Information" footer link | Footer legal links |
compliance.modernSlaveryStatementUrl | Optional Modern Slavery Statement URL | Footer legal links |
metadata.title | Default <title> tag and OG/Twitter title | src/app/layout.tsx metadata export |
metadata.description | Default meta description and OG/Twitter description | src/app/layout.tsx metadata export |
api.baseUrl | API base URL shown in docs and code samples | API reference page, playground code snippets |
api.docsUrl | Link to API documentation site | Available for use in custom pages |
api.sdkPackage | SDK package name shown in install/code snippets | API reference page, docs snippets |
contact.email | General contact email | Contact page |
contact.sales | Sales email | Contact page FAQ |
contact.privacy | Privacy/cookie enquiries | Privacy policy, cookie policy pages |
contact.legal | Legal enquiries | Terms of service page |
contact.security | Security reports | Security page |
contact.status | Status page contact | Status page |
contact.careers | Careers/jobs email | Careers page |
contact.ethics | Ethics/responsible-AI contact | Blog post body |
contact.phone | Phone-support placeholder | Contact page |
supportSla.* | Public support response-time promises | Contact page FAQ |
offices[] | Office city/address placeholders | Contact page |
marketing.activeTeamsCount / activeTeamsClaim | Substantiable headline metric; default null renders an obvious N demo placeholder. The hero/statement count-up animation stays dormant until activeTeamsCount is set to a positive number — until then the headline reads "Modern teams" with no animated count. | Statement, about, CTA, dashboard scripts |
trustBadges.login / trustBadges.security | Product trust badges; replace with real attestations only when held | Login, security page |
cookieConsent.enabled / version | Enables consent banner and controls re-prompt versioning | Cookie banner, analytics gating |
social.twitter | Twitter/X profile URL | Footer, community channels |
social.twitterHandle | Twitter/X @handle (display only) | Community page decorative text |
social.github | GitHub org URL | Footer, community channels |
social.youtube | YouTube channel URL | Community page channels |
social.linkedin | LinkedIn company URL | Footer |
social.discord | Discord invite URL | Footer |
Demo content (replace before production)
site-config.ts covers your brand shell — name, metadata, contact details, and social links. The following pages contain demo content (marketing copy, blog posts, testimonials, legal text) that you should replace with your own before publishing:
| Area | Files | What to replace |
|---|---|---|
| Docs hub | src/app/docs/page.tsx | SDK name, code snippets, guide links |
| Landing sections | src/components/sections/hero.tsx, features.tsx, product-proof.tsx, how-it-works.tsx, api-showcase.tsx, statement.tsx, faq.tsx | Product copy, feature descriptions, brand statement, code examples |
| About page | src/app/about/page.tsx | Company story, mission, timeline |
| Blog posts | src/app/blog/[slug]/page.tsx, src/lib/blog-data.ts | All article content and metadata |
| Testimonials | src/components/sections/testimonials.tsx | Customer quotes, names, companies |
| Legal pages | src/app/terms/page.tsx, src/app/privacy/page.tsx, src/app/security/page.tsx, src/app/cookie-policy/page.tsx | All legal/compliance text — consult your legal team |
| Dashboard demo | src/app/dashboard/page.tsx, src/components/dashboard/ | Chat messages, sample data, demo users |
| Brand Voice | src/app/dashboard/brand-voice/page.tsx, src/components/dashboard/brand-voice-data.ts | VOICE_PROFILES (Demo Marketing / Technical Docs / Customer Support Voice), tone slider seed values, training-doc filenames, sample output text — update sample output and profile name together |
| Supported languages | src/lib/supported-languages.ts | SUPPORTED_LANGUAGES array (currently 12). When buyers change the list, update related marketing copy in features.tsx, statement.tsx, about/page.tsx, testimonials.tsx, and pricing-data.ts so the "Twelve languages, one voice" pitch stays accurate. |
| Community | src/app/community/page.tsx | Channel links, project examples |
These pages contain realistic placeholder content to demonstrate the design. Do not publish them as-is — replace with your own copy. See DEMO_CONTENT.md for the full checklist.
Footer links
Edit src/components/layout/footer.tsx — the FOOTER_LINKS object. Social links and the brand name are imported from src/lib/site-config.ts.
Pricing plans
Edit both pricing data sources:
src/components/sections/pricing.tsx— homepage pricing cards and landing-page billing toggle.src/components/dashboard/pricing-data.ts— dedicated/pricingroute cards and comparison table.
Keep plan names, prices, limits, and feature wording aligned across both files unless you intentionally want the homepage to use shorter marketing copy.
FAQ
Edit src/components/sections/faq.tsx — the FAQ_ITEMS constant array.
Testimonials
Edit src/components/sections/testimonials.tsx — the TESTIMONIALS constant array.
Adding a new page
- Create
src/app/my-page/page.tsx - Export a default React component
- Add a link in
src/components/layout/navbar.tsx
Pages use the shared layout from src/app/layout.tsx automatically.
Animations
Disable all animations
Motion is controlled by src/hooks/use-motion-preference.ts. The system respects four signals:
- System preference:
prefers-reduced-motion: reduce— detected automatically - Viewport: animations are disabled on mobile (
< 768px) — static layouts shown instead - Manual toggle: if you add a
MotionTogglebutton (seeuse-motion-preference.tsfor the API) - Global override: CSS variable or HTML data attribute, useful for buyer QA
To disable animations globally regardless of system settings, add this to src/app/globals.css:
:root { --force-reduce-motion: true; }
Or set data-reduce-motion="true" on the <html> element.
Adjust animation speed
The GSAP animation easings and durations are configured in src/lib/gsap.ts and individual section components. Lenis smooth scroll speed is in src/components/providers/lenis-provider.tsx:
new Lenis({
duration: 1.2, // scroll animation duration in seconds
touchMultiplier: 2, // mobile scroll speed multiplier
})