Color¶
Every color in Tomoda comes from frontend/constants/Themes.ts. The system is token-based: components reference semantic names (primary, surface, text) and the active theme decides what hex to render.
Two themes ship today: dark (default — the brand's primary mode) and light. Both are first-class — every component is built to work in either.
Surface ladder¶
The "tonal layering" depth model. Each step up the ladder adds about 5-8% in luminance. To create depth, place a higher-tier surface on top of a lower-tier one — no shadow required.
Dark¶
Light¶
Brand accents¶
Primary — duotone¶
The primary token is duotone across themes. Dark mode is gold-forward: primary is ember gold. Light mode is ink-forward: primary is the brand ink, so filled CTAs read as a confident dark chip on warm paper. Gold does not disappear in light mode, it moves to the Cozy Campfire accent family and the gold gradient.
Text and icons on a primary surface come from onPrimary (never a hardcoded literal): dark ink on the gold dark-mode primary, light paper on the ink light-mode primary. The gold gradient stays gold in both themes, so labels on it use onPrimaryGradient (dark ink in both).
Primary CTAs use a 135° linear gradient from primary to primaryContainer, giving the warm gold a metallic, brushed quality. Don't stop at flat fills. The gradient is gold in both themes; its labels use onPrimaryGradient.
Secondary — indigo¶
Used for non-primary interactive accents (links in certain contexts, secondary highlights). Picked deliberately as the cool complement to the warm gold.
Status colors¶
For success, warning, error states. Sized and saturated for clear differentiation in both modes.
Online / active-now¶
online is the presence green used for active-now and online-now indicators (chat header status, friend dots, cluster popup dots, location "operational" state). Same value in both modes; the saturated green reads clearly on dark chrome and warm paper alike.
Dock¶
dock is the frosted background of the bottom app-nav bar (components/layout/AppNav.tsx). Dark keeps a near-black glass; light uses the taupe surfaceContainerHigh tone at 0.82 alpha so the bar reads as a distinct elevated surface over the warm-paper background instead of blending into it.
Text¶
Three tiers per mode. Use the lowest-emphasis tier that still reads — every step up the contrast ladder is an attention cost.
Cozy Campfire — accent palette¶
A warm, autumn-toned set used for tag pills, earned-state highlights, decorative accents, and any moment where we want to gently introduce variety without leaving the brand temperature. Never use for chrome (backgrounds, app shell). Both dark- and light-mode variants are tuned for legibility against their respective base surfaces.
Dark mode¶
Light mode¶
Same palette, darkened for contrast against white surfaces. The semantic meaning of each token stays the same across modes.
Borders¶
Borders are intentionally subtle. The border and outlineVariant tokens are both rendered at 15% opacity — solid lines are forbidden by the no-line rule.
- Dark:
rgba(255, 255, 255, 0.15) - Light:
rgba(0, 0, 0, 0.10)
Map markers¶
Marker colors are mode-independent and live in MARKER_COLORS (frontend/components/ui/categoryIcons.ts), not in the theme. The marker layer (web components/map/engine/markers, native components/map/native/MarkerOverlay) renders the same on both light and dark maps, so its palette must not flip with the app theme. Web and native import the same map to stay in lock-step.
The palette covers event/location/moment tiers plus the ink and status accents painted on them:
eventDefault/eventSponsored/calendarEvent— event pin tiers.locationBase/locationActive/locationHot— location activity tiers.momentColor/momentMuted— moment pins.markerInk(#0e0e0f) — dark ink for text and borders on the gold/orange marker tiles and badges.globallyActive(#7c3aed) — the sharing-worldwide globe badge.ringEvents/ringLocations/ringMoments— geo-cluster ring segments (one source for both renderers).
The MapLegendModal legend swatches import from MARKER_COLORS too, so the legend always matches the live markers.
Map tiles¶
constants/MapStyles.ts ships a per-mode JSON array for Google Maps (mobile). On web, Leaflet styles are driven by the same theme tokens (custom CSS overrides on top of OpenStreetMap base tiles).
Adding a color¶
- Add the token to
frontend/constants/Themes.tsin both thedarkandlightblocks. Every token must have both-mode parity. - If it's a brand-level addition (a new accent), add a swatch entry to the appropriate section of this page in the same PR.
- If it's a semantic state (e.g.
info), updateComponentsto show which components use it. - Run
task docs:buildto verify swatches render correctly.