Design Tokens
This page is the authoritative visual contract for BluAuth. It governs the hosted login, the admin shell, provider presentation (logo, label, button variant), and anything rendered inside the BluAuth chrome. It used to live as DESIGN.md in the repo root — it now lives here so it's linkable, versioned, and addressable by integrator tooling.
Every token on this page is tenant-configurable via the Theme Studio at /admin/themes/[id]. Defaults are defined in shared/utils/hosted-login-defaults.ts and resolved by server/utils/hosted-login/theme.ts.
1. Creative North Star: The Precision Curator
The BluAuth visual system is built on the principle of The Precision Curator. It moves beyond the common "SaaS-standard" look by prioritizing editorial clarity, high-contrast intentionality, and a rhythmic use of negative space.
Sophistication comes from tonal layering and asymmetric balance, not from grids and borders. Treat the UI as an architectural portfolio: expansive dark space, sharp emerald accents that signal life and action, and a hierarchy that feels authoritative yet breathable. Typography is a structural element, not just a vehicle for information.
2. Color System: Tonal Architecture
The palette is rooted in a deep obsidian foundation (#09090B) balanced by a crystalline primary emerald (#006B2C).
The "No-Line" Rule
Designers are strictly prohibited from using 1px solid borders to section off major layout areas. Boundaries must be defined through Background Color Shifts.
Surface Hierarchy & Nesting
Treat the UI as physical layers of fine paper. Importance is communicated through the "stacking" of surface tokens:
- Surface Lowest (
#0C0F0C) — reserved for the most important active cards or floating modals. - Surface (
#111411) — the base canvas of the application. - Surface Container High (
#202320) — inactive or secondary background elements.
Glass & Gradient
- Glassmorphism — for floating menus or navigation bars, use
surfacecolors at 80% opacity with a20pxbackdrop-blur. - Signature Textures — main CTAs or Hero sections should employ a subtle linear gradient from
primary(#006B2C) toprimary_container(#00873A) at a 135° angle.
3. Typography: Editorial Authority
Base family is Inter (neo-grotesque). The Theme Studio exposes five fontFamilyPreset values; the resolver is resolveHostedLoginFontFamily(preset) in app/utils/hosted-login.ts.
| Preset | Stack |
|---|---|
system | system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif |
inter | Inter, "Segoe UI", system-ui, sans-serif |
roboto | Roboto, "Helvetica Neue", Arial, sans-serif |
humanist | "Avenir Next", "Segoe UI", system-ui, sans-serif |
editorial | "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia, serif |
- Display (lg/md/sm) — high-impact hero statements. Tight tracking (-0.02em).
- Headline & Title — the structural backbone.
headline-lg(2rem) anchors page sections. - Body (lg/md/sm) —
body-md(0.875rem) is the workhorse. - Labels — small, all-caps at
label-sm(0.6875rem) with +0.05em tracking for metadata.
4. Elevation & Depth: Tonal Layering
Drop shadows are largely replaced by Ambient Elevation.
- Layering Principle — instead of adding a shadow to a card, place
surface-container-lowest(#0C0F0C) onsurface-container-low(#111411). The 2% luminance delta creates a soft lift. - Ambient Shadows — if a floating state is required, use a tinted "shadow-wash":
blur: 40px; opacity: 6%, usingon-surface(#E2E3DF) rather than pure black. - Ghost Border Fallback —
outline-variant(#404941) at 15% opacity. Never use a 100% opaque border for decorative containment.
5. Layout Presets
Five layout presets are exposed via clientThemes.layoutPreset. All presets render through the <HostedLoginSurface> component and its layout children in app/components/hosted-login/layouts/.
Enumerated in HOSTED_LOGIN_LAYOUT_ITEMS:
layoutPreset | Component | Hero treatment | Best for |
|---|---|---|---|
centered-card | LayoutCenteredCard.vue | Ticket stubs — vertical cards below the auth panel with a colored rail and arrow hover affordance | Minimal brands; product-led onboarding |
split-hero | LayoutSplitHero.vue | Hero cards — side panel with two linkable cards stacked or gridded | Enterprise tenants with dual CTAs (e.g. "Request access" + "View docs") |
minimal | LayoutMinimal.vue | Editorial list — a ·-separated inline list with ↗ arrows | Text-first brands; editorial/publishing feel |
cinematic-split | LayoutCinematicSplit.vue | Cinematic plaques — full-bleed imagery with overlaid metadata plaques | Media companies; image-driven brands (ticket stubs, posters) |
aurora | LayoutAurora.vue | Aurora chips — ambient animated gradient with small eyebrow chips | Hero-forward, animation-heavy experiences (Ziv direction) |
Each preset consumes the shared theme object and overrides only its own structural CSS — all five honor the same color, surface, field, and button tokens below.
5.1 Centered Card — Ticket Stubs
Stacked layout with the auth card centered and optional hero "stubs" below. Each stub has a colored rail (--auth-primary), an eyebrow, a title, an optional description, and an arrow affordance when a URL is configured.
BEM hooks:
.hosted-login__shell--stacked.hosted-login__hero-stubs,.hosted-login__hero-stub,.hosted-login__hero-stub--link.hosted-login__hero-stub-rail,.hosted-login__hero-stub-body,.hosted-login__hero-stub-eyebrow,.hosted-login__hero-stub-title,.hosted-login__hero-stub-desc,.hosted-login__hero-stub-arrow
Theme fields consumed: heroEyebrowText, heroCard1*, heroCard2*.
5.2 Split Hero — Hero Cards
Two-column layout: hero panel on the left with logo, title, subtitle, support text, and 1–2 linkable cards; auth panel on the right.
BEM hooks:
.hosted-login__shell--split.hosted-login__hero,.hosted-login__panel.hosted-login__hero-card,.hosted-login__hero-mark,.hosted-login__logo,.hosted-login__logo-fallback.hosted-login__title--hero,.hosted-login__subtitle--hero,.hosted-login__support.hosted-login__hero-list,.hosted-login__hero-link,.hosted-login__hero-link--active.hosted-login__hero-link-eyebrow,.hosted-login__hero-link-title,.hosted-login__hero-link-desc,.hosted-login__hero-link-arrow
Collapses to stacked on viewports < 960px.
5.3 Minimal — Editorial List
Stacked layout with an inline, footer-style hero list of title · description ↗ items. No cards, no rails — pure editorial typography.
BEM hooks:
.hosted-login__hero-lines,.hosted-login__hero-line-item.hosted-login__hero-line,.hosted-login__hero-line--link.hosted-login__hero-line-title,.hosted-login__hero-line-sep,.hosted-login__hero-line-desc,.hosted-login__hero-line-arrow
The --link modifier adds an animated underline via ::after pseudo-element on hover.
5.4 Cinematic Split — Cinematic Plaques
Full-bleed hero imagery (when backgroundImageUrl is set) with overlaid metadata plaques. Configurable via:
| Field | Values | Effect |
|---|---|---|
cinematicPanelMode | contained, edge-to-edge | Auth panel inset vs. flush |
cinematicHeightMode | full, contained | Fill viewport vs. respect document flow |
BEM hooks:
.hosted-login__shell(plus cinematic overrides on the Surface root)- Background is layered:
linear-gradient(180deg, rgb(0 0 0 / 0.3), rgb(0 0 0 / 0.65))over the user'sbackgroundImageUrl.
Hero plaques render via the same hero-card hooks as Split Hero but sit atop the imagery.
5.5 Aurora — Aurora Chips
Ambient-motion layout with an animated radial-gradient background driven by --auth-primary and --auth-background. Intensity is configurable:
auroraIntensity | Behavior |
|---|---|
subtle | Static gradient, 20% primary color-mix |
atmospheric | Slow 20s keyframe loop, 35% primary color-mix |
reactive | 10s loop with pointer-follow displacement |
Hero content renders as "chips" — small eyebrow-style capsules rather than cards. Best paired with buttonStylePreset: solid and surfaceStylePreset: glass.
6. Surface Styles
surfaceStylePreset controls how the auth card sits on the background. Enumerated in HOSTED_LOGIN_SURFACE_STYLE_ITEMS:
| Preset | BEM hook | Visual |
|---|---|---|
elevated | .hosted-login__card--elevated | Linear gradient from color-mix(--auth-surface 95%, black) to color-mix(--auth-surface 86%, black) |
solid | .hosted-login__card--solid | Flat color-mix(--auth-surface 92%, black) |
glass | .hosted-login__card--glass | color-mix(--auth-surface 55%, transparent) + backdrop-filter: blur(20px) |
Surface fill resolver: resolveHostedLoginSurfaceFill(theme, 'var(--auth-background)'). When useSurfaceColor = false, surface is computed as color-mix(in srgb, var(--auth-background) 80%, black 20%).
7. Field Styles
fieldStylePreset — how input fields are styled inside the auth card. Enumerated in HOSTED_LOGIN_FIELD_STYLE_ITEMS:
| Preset | Background | Border | Focus |
|---|---|---|---|
default | color-mix(--auth-background 72%, white 4%) | color-mix(--auth-primary 20%, --ui-border) | --auth-primary opaque |
quiet | transparent | subtle --ui-border | --auth-primary 45% fade-in |
outline | transparent | 1px color-mix(--auth-primary 30%, white) | 2px --auth-primary |
high-contrast | color-mix(black 72%, --auth-background) | color-mix(--auth-primary 30%, white) | 2px --auth-primary + shadow wash |
Input root: .hosted-login__input.
8. Button Styles
buttonStylePreset — primary action button variant. Enumerated in HOSTED_LOGIN_BUTTON_STYLE_ITEMS:
| Preset | Surface | Border | Hover |
|---|---|---|---|
solid | --auth-primary | color-mix(--auth-primary 70%, white) | 10% lift via box-shadow wash |
soft | color-mix(--auth-primary 18%, transparent) | none | Deepen to 28% |
outline | transparent | color-mix(--auth-primary 45%, --ui-border) | Fill with color-mix(--auth-primary 14%, transparent) |
Primary action root: .hosted-login__primary-action.
8.1 Provider Buttons
Provider buttons have their own variants driven by presentationMode (auto, neutral, brand) and provider type:
.hosted-login__provider-button— base.hosted-login__provider-button--neutral— monochrome.hosted-login__provider-button--soft— tinted fill.hosted-login__provider-button--outline— outline only.hosted-login__provider-button--google— brand colors for Google.hosted-login__provider-button--github— brand colors for GitHub
Provider icon resolution: resolveHostedLoginProviderIconName(provider) returns i-lucide-chrome (google), i-lucide-github (github), i-lucide-key-round (oidc), i-lucide-file-lock (saml), or i-lucide-shield (fallback).
9. Density Scale
densityPreset — vertical rhythm. Enumerated in HOSTED_LOGIN_DENSITY_ITEMS:
| Preset | Card padding | Field height | Line-height |
|---|---|---|---|
comfortable | 2rem 2.25rem | 2.75rem | 1.5 |
compact | 1.25rem 1.5rem | 2.25rem | 1.35 |
The compact variant applies .hosted-login__card--compact which tightens all internal spacing proportionally.
10. Color-Mix Formulas
All runtime color composition uses color-mix(in srgb, ...). Centralizing these formulas means overriding --auth-primary (or any other CSS custom property) automatically cascades through every derived surface.
| Role | Formula |
|---|---|
| Card elevated top | color-mix(in srgb, var(--auth-surface) 95%, black) |
| Card elevated bot | color-mix(in srgb, var(--auth-surface) 86%, black) |
| Card solid | color-mix(in srgb, var(--auth-surface) 92%, black) |
| Card glass | color-mix(in srgb, var(--auth-surface) 55%, transparent) |
| Field default bg | color-mix(in srgb, var(--auth-background) 72%, white 4%) |
| Field high-contrast | color-mix(in srgb, black 72%, var(--auth-background)) |
| Primary highlight | color-mix(in srgb, var(--auth-primary) 20%, transparent) |
| Primary gradient (start) | color-mix(in srgb, var(--auth-primary) 82%, white) |
| Primary gradient (end) | color-mix(in srgb, var(--auth-primary) 45%, black) |
| Secondary text | color-mix(in srgb, white 55%, var(--auth-primary)) |
| Tertiary text | color-mix(in srgb, white 30%, var(--auth-primary)) |
| Hairline | color-mix(in srgb, var(--auth-primary) 14%, var(--ui-border)) |
When backgroundImageMode = 'full-bleed', the shell adds a darkening gradient linear-gradient(180deg, rgb(8 16 32 / 0.22), rgb(8 16 32 / 0.62)) on top of the background image so foreground text retains WCAG AA contrast.
11. CSS Custom Properties
Exposed on the <HostedLoginSurface> root. Overridable via Advanced CSS.
| Variable | Source | Default / Example |
|---|---|---|
--auth-primary | theme.primaryColor | #3B82F6 |
--auth-background | theme.backgroundColor | #0F172A |
--auth-surface | resolveHostedLoginSurfaceFill(...) | #111827 or mix |
--auth-background-image | theme.backgroundImageUrl | url("https://...") or none |
--auth-font-family | resolveHostedLoginFontFamily(...) | resolved from fontFamilyPreset |
--auth-on-primary | Advanced CSS override (optional) | falls back to white |
--ui-border | Nuxt UI | rgba(255,255,255,0.1) |
Color inputs are normalized via normalizeHexColorInput(value) which accepts #FFF, #FFFFFF, FFFFFF, or FFFFFFFF (with alpha). Invalid inputs are returned as empty strings and fall back to defaults.
--auth-on-primary is the contrast color painted on top of a surface derived from --auth-primary (e.g., the brand-mark icon inside the logo-fallback gradient). It defaults to white, which is correct for the default obsidian-emerald palette; tenants that pick a light primary color can override it via Advanced CSS (.hosted-login { --auth-on-primary: oklch(15% 0 0); }) to maintain contrast.
Admin shell widths
Not exposed on <HostedLoginSurface> — these govern the BluAuth admin console, defined at :root in app/assets/styles/main.css:
| Variable | Used by | Default |
|---|---|---|
--bluauth-admin-content-max | .console-stack (default admin content column) | 1500px |
--bluauth-admin-content-narrow | .console-stack--narrow (form-heavy surfaces) | 64rem |
The .console-stack class applies max-width: var(--bluauth-admin-content-max); margin-inline: auto; width: 100%; so every admin page converges on one of two widths. Opt into narrow for form-heavy detail pages (settings/index, users/[id]); default wide for list/dashboard surfaces. Never re-open this decision with a page-level max-w-* utility — the design-system/no-raw-max-width-on-console-stack validator rule will flag it.
12. Advanced CSS — BEM Class Hooks
theme.customCss is injected into the page head inside a scoped <style> tag. Use the documented BEM classes below to override. All hooks are verified by test/unit/app/experience-templates.test.ts.
Shell
.hosted-login— root wrapper.hosted-login--with-full-bleed-background— adds bleed-mode overlay.hosted-login--embedded— embedded mode (no footer).hosted-login--preview-mobile— preview mode, mobile breakpoint.hosted-login__shell— layout shell.hosted-login__shell--stacked— centered-card / minimal layouts.hosted-login__shell--split— split-hero / cinematic layouts
Card
.hosted-login__card.hosted-login__card--elevated|--solid|--glass|--compact
Title & body
.hosted-login__brand-mark,.hosted-login__eyebrow,.hosted-login__title,.hosted-login__subtitle,.hosted-login__support.hosted-login__title--hero,.hosted-login__subtitle--hero(split-hero modifiers)
Hero treatments
- Ticket stubs:
.hosted-login__hero-stubs,.hosted-login__hero-stub(--link),.hosted-login__hero-stub-{rail,body,eyebrow,title,desc,arrow} - Hero cards:
.hosted-login__hero-list,.hosted-login__hero-link(--active),.hosted-login__hero-link-{eyebrow,title,desc,arrow} - Editorial list:
.hosted-login__hero-lines,.hosted-login__hero-line(--link),.hosted-login__hero-line-{title,sep,desc,arrow}
Fields & buttons
.hosted-login__input— input root for all field-style presets.hosted-login__primary-action— primary CTA.hosted-login__divider— "or sign in with" divider rail.hosted-login__provider-button+--neutral|--soft|--outline|--google|--github
Footer
.hosted-login__footer— layout-owned footer.hosted-login__footer-slot— Surface-level slot wrapper
Example override:
.hosted-login__card {
box-shadow: none;
}
.hosted-login__primary-action {
border-radius: 9999px;
}
.hosted-login__hero-link--active:hover {
transform: translateY(-2px);
}
13. Asset Constraints
Hosted assets are uploaded through POST /api/admin/assets/upload-url and stored in S3 with Cache-Control: public, max-age=31536000, immutable.
| Kind | Max size | Accepted types |
|---|---|---|
theme_logo | 512 KB | image/png, image/jpeg, image/svg+xml, image/webp |
provider_icon | 512 KB | same as above |
theme_background | 5 MB | same as above |
Limits are defined in app/utils/hosted-login.ts as HOSTED_LOGIN_MAX_ASSET_BYTES and HOSTED_LOGIN_MAX_BACKGROUND_BYTES.
backgroundImageMode values: framed (default — image fills the hero panel only) or full-bleed (image covers the entire viewport; shell layers a gradient for contrast).
14. Do's and Don'ts
Do
- Use asymmetric margins (wider left than right) to create an editorial, "magazine" feel.
- Use
primary_fixed(#7FFC97) as a subtle background highlight for success states or active badges. - Embrace white space. If a layout feels crowded, increase padding to the next step rather than adding lines.
- Prefer
color-mixover hard-coded hex derivations so overriding--auth-primarycascades properly. - Verify slot contracts against
.external-docs/nuxt-ui/components/before using Nuxt UI components.
Don't
- Don't use 1px solid black or high-contrast borders.
- Don't use standard "Material" blue or any saturated blue that reads as generic SaaS. Stay in the tenant's primary spectrum.
- Don't use pure black
#000000for text. Useon-surface(#E2E3DF) or a color-mix. - Don't hand-roll HTML controls. Always use Nuxt UI equivalents (
UButton,UInput,USelect,UForm, etc.) so global theming propagates. - Don't invent new BEM hooks — extend the documented ones via modifiers if needed.
15. Token Reference Summary
| Token Role | Value | Usage |
|---|---|---|
| Surface Base | #111411 | Main app background |
| Surface Lowest | #0C0F0C | Top-tier cards and modals |
| Primary Accent | #006B2C | Call to action, primary brand touchpoint |
| Primary Container | #00873A | Hover states, secondary brand depth |
| Primary Fixed | #7FFC97 | Active badges, success highlights |
| On Surface | #E2E3DF | Default text |
| Outline Variant | #404941 | Ghost borders (at 15% opacity) |
| Corner Radius | 0.375rem | Default for buttons and inputs |
16. Source-of-truth Files
| Purpose | File |
|---|---|
| Canonical defaults | shared/utils/hosted-login-defaults.ts |
| Runtime theme resolver | server/utils/hosted-login/theme.ts |
| Preset enumerations | app/utils/hosted-login.ts |
| Theme Studio draft + payload | app/utils/hosted-login-theme-studio.ts |
| Layout components | app/components/hosted-login/layouts/*.vue |
| Auth card surface | app/components/hosted-login/AuthFormCard.vue |
| Provider list | app/components/hosted-login/ProviderList.vue |
| Footer | app/components/hosted-login/Footer.vue |
| Theme schema (Drizzle) | drizzle/schemas/index.ts → clientThemes table |
| Theme admin API | server/api/admin/themes/** |
| Theme test guarantees | test/unit/app/experience-templates.test.ts |
All changes to these files are considered public API. Breaking changes require a versioned release and a migration path documented in the release notes.