13 KiB
Goodwalk — Design Language & Theme Reference
Source of truth for visual language, spacing, tone, interaction style, and component feel across the Goodwalk frontend.
Brand Palette
| Token | Value | Usage |
|---|---|---|
--gw-accent-color-1 |
#FFD100 |
Highlight / CTA yellow |
--gw-accent-color-2 |
#213021 |
Deep forest green (primary brand) |
--gw-accent-color-3 |
#59606D |
Muted slate |
--gw-accent-color-4 |
#E5D6C2 |
Warm sand |
--gw-accent-color-5 |
#FFFFFF |
White |
--gw-accent-color-6 |
#000000 |
Black |
Background texture: warm cream gradient (#f4f1ea → #f1ede5).
Typography: DM Sans (body), system heading scale.
Desktop Design Language
Tone: Premium, intentional, warm. Not corporate. Feels like a trusted local service with high craft.
Spacing: 8-point grid. Components breathe. Generous white-space is never wasted.
Surfaces: Semi-transparent white (rgba(255,255,255,0.94)) on warm cream background. Cards carry a subtle multi-layer shadow.
Borders: Warm tan #e7ddd1, never cold grey.
Shadows: Multi-stop warm shadows — 0 4px 8px rgba(17,16,14,0.04), 0 16px 34px rgba(17,16,14,0.08).
Navigation: Sticky header with glassmorphism on scroll. Bottom tab bar on mobile (see below).
Radius scale: 8px (inputs), 12px (cards), 16px (modals/sheets), 28px (pills/nav).
Interactions: Subtle hover lifts (translateY(-1px)), scale-down press (scale(0.96–0.98)), smooth 150ms ease transitions.
Admin Design System
Admin uses a theming layer on top of the base brand. The active theme is stored in localStorage as gw_admin_theme.
| Theme | Accent | Highlight |
|---|---|---|
| main | #213021 |
#ffd100 |
| pink | #c54b8c |
#ffcadf |
| purple | #6f55d9 |
#d8cbff |
| yellow | #9a7700 |
#ffe08a |
Admin CSS variables (set via inline style on the shell element):
--admin-accent, --admin-accent-dark, --admin-accent-rgb
--admin-topnav-start, --admin-topnav-end
--admin-highlight, --admin-highlight-rgb
--admin-button-text
--admin-font-scale
The admin header uses a gradient from --admin-topnav-start → --admin-topnav-end.
Members Design System
Members uses the core brand palette directly (no theme switching).
--members-accent: #213021
--members-accent-dark: #172217
--members-highlight: #ffd100
--members-border: #e7ddd1
--members-surface: rgba(255, 255, 255, 0.94)
--members-surface-soft: #f8f4ed
--members-surface-muted: #f2ece3
--members-text: #1d261d
--members-text-muted: #6b635c
Mobile Design Language
Mobile users receive a dedicated, purpose-built experience — not a squeezed desktop page. All mobile screens live under
/admin/m/(admin) or/members/m/(members).
Philosophy
- Native-app inspired. Feel fast, responsive, and deliberate. Every tap should feel satisfying.
- One-handed first. Primary actions sit at thumb-reach (bottom half of screen). Navigation is bottom-anchored.
- Dense but not cramped. Maximum information per screen without feeling overwhelming.
- No desktop patterns forced onto phones. Never squeeze a data table onto a 390px screen. Use lists, cards, and sheets instead.
Mobile vs Desktop Rules
| Rule | Desktop | Mobile |
|---|---|---|
| Navigation | Top sticky header + subnav tabs | Fixed bottom tab bar |
| Primary actions | Anywhere sensible | Bottom-anchored action bar or prominent nav item |
| Modals | Standard centred modal | Slide-up bottom sheet |
| Tables / data grids | Full table layout | Vertical list with disclosure rows |
| Forms | Multi-column layouts OK | Single column, full-width fields |
| Page titles | In header nav | Sticky per-page header (MobilePageShell) |
| Content width | Max-width capped at 1320px | Full bleed (16px horizontal padding) |
Mobile Token Layer
All mobile screens inherit tokens from the MobileAppShell component. These tokens provide a consistent design system shared between Admin and Member mobile experiences:
/* Palette */
--m-accent: #213021 /* primary action colour */
--m-accent-dark: #172217 /* hover/active state */
--m-accent-rgb: 33, 48, 33 /* for rgba() usage */
--m-highlight: #ffd100
--m-bg: #f4f1ea
--m-surface: rgba(255,255,255,0.96)
--m-surface-soft: #f8f4ed
--m-surface-muted: #f2ece3
--m-border: #e7ddd1
--m-border-soft: rgba(231,221,209,0.6)
--m-text: #1d261d
--m-text-muted: #6b635c
--m-text-faint: #a09890
/* Shadows */
--m-shadow-card: 0 2px 8px rgba(17,16,14,0.06), 0 8px 20px rgba(17,16,14,0.08)
--m-shadow-sheet: 0 -8px 40px rgba(17,16,14,0.18)
--m-shadow-button: 0 4px 16px rgba(33,48,33,0.28)
/* Radii */
--m-radius-xs: 6px
--m-radius-sm: 10px
--m-radius: 16px /* default card/button radius */
--m-radius-lg: 24px
--m-radius-xl: 32px /* bottom sheet corners */
/* Spacing scale (4-point base) */
--m-space-1: 4px
--m-space-2: 8px
--m-space-3: 12px
--m-space-4: 16px
--m-space-5: 20px
--m-space-6: 24px
--m-space-8: 32px
--m-space-10: 40px
/* Type scale */
--m-text-xs: 11px
--m-text-sm: 13px
--m-text-md: 15px /* body text */
--m-text-lg: 17px /* page titles */
--m-text-xl: 20px
--m-text-2xl: 24px
--m-text-3xl: 30px /* stat card values */
/* Touch */
--m-touch-min: 44px /* minimum touch target (iOS HIG / WCAG) */
/* Layout */
--m-bottom-nav-h: 72px
--m-header-h: 56px
--m-page-px: 16px /* horizontal page padding */
/* Safe areas (iOS notch/home bar) */
--m-safe-top: env(safe-area-inset-top, 0px)
--m-safe-bottom: env(safe-area-inset-bottom, 0px)
For the Admin mobile variant, --m-accent, --m-accent-dark, --m-accent-rgb, and --m-highlight are overridden by the admin's active theme variables (--admin-accent etc.), so the admin theme switcher works on mobile too.
Touch Target Minimums
- Every interactive element must be at least 44 × 44px (iOS HIG) / 48 × 48dp (Material).
- Use
min-height: var(--m-touch-min)on buttons and list rows. - Pad tappable areas generously — don't rely on text size alone for target size.
Bottom Navigation Principles
The bottom tab bar is the primary navigation on mobile. It is always visible and never hides during scroll.
- Max 5 items per bar. Fewer is better.
- Icons must be recognisable without labels — labels are shown but should not be the only differentiator.
- One prominent item allowed per bar (filled background, accent colour). Use for the most important CTA (e.g. Add Client, Complete Onboarding).
- Badge dots use red
#e03e3efor unread counts (messages). - The bar uses glassmorphism:
rgba(251,247,240,0.92)background,backdrop-filter: blur(18px),border-radius: 28px. Never make it a plain opaque bar. - Accounts for safe area:
padding-bottom: calc(12px + env(safe-area-inset-bottom)). - Active state: light green tint
rgba(33,48,33,0.08)+ accent text colour. - Inactive state:
--m-text-mutedcolour. - Press:
scale(0.94)transform withtransition: 0.15s.
Spacing and Rhythm (Small Screens)
- Page content padding:
16pxhorizontal. - Section vertical spacing:
24pxtop (tight variant:16px). - Section labels:
11px,800weight,0.08emletter-spacing, uppercase, muted colour. - Card internal padding:
16px. - List row minimum height:
44px. Default padding:12px 0. - Group gaps:
8pxbetween sibling cards/list items,12pxbetween elements within a card. - Never use horizontal padding smaller than
8pxinside a card.
Mobile Card / List Patterns
Cards:
border-radius: 16px, warm border#e7ddd1, multi-layer warm shadow.- Default: white surface with shadow.
- Subtle:
#f8f4edsurface, no shadow (for secondary content blocks). - Highlight:
3pxleft border in accent colour (for time-sensitive or important content). - Press:
scale(0.98).
List items (MobileListItem):
- Full-width rows with leading slot (icon/avatar), label, sublabel, trailing area (meta, badge, chevron).
- Divider is a
1pxline at60%opacity — not a full-width separator. - Never nest more than 2 lines of text in a list item on mobile.
- Chevron (
›) indicates navigable rows.
Mobile Form Behaviour
- Single-column layouts only.
- Labels above inputs (
13px,700weight). - Full-width inputs with
min-height: 44px,border-radius: 10px. - Focus ring:
3pxat0.12opacity using accent colour. - Error messages below the field in red (
#991b1b), not in a toast. - Use
MobileFormFieldto wrap every input — it ensures consistent spacing and error handling. - Avoid more than 4-5 fields visible at once without sectioning.
Hierarchy of Headers and Page Titles
- App header (
MobileHeader) — optional sticky brand strip at the very top. Use only when brand context is needed (e.g. on first entry to a section). - Page header (
MobilePageShell) — per-page sticky title bar. Always present. Contains: back button (44px touch), centred title (17px, 700), right-side action slot. - Section headings (
MobileSection title=) —11px, 800, uppercase, muted. Acts as a visual grouping label, not a navigational heading. - Card titles / list labels —
15px, 600for primary labels,13pxfor sublabels.
Do not repeat the page title in the body content. The sticky page header is the single source of truth for where you are.
Motion and Transitions
- Duration:
150msfor micro-interactions (hover, press),260msfor sheet entry. - Easing:
cubic-bezier(0.25, 0.46, 0.45, 0.94)(ease-out feel). - Sheet entry:
flytransition upward 320px, 260ms. - Press scales:
0.94–0.98depending on element size (buttons larger → less scale, small icons → more scale). - No bouncing or spring physics on primary navigation (too distracting).
- Reserve spring easing (
cubic-bezier(0.34, 1.56, 0.64, 1)) for playful one-off moments only. - Skeleton loading: sinusoidal opacity pulse
0.45 → 0.85,1.4speriod, staggered delays.
Empty / Loading / Error States
Use MobileEmptyState for all three variants:
- Loading: animated skeleton cards (3 staggered shimmer bars).
- Empty: emoji icon, title, optional body text, optional CTA button.
- Error: same as empty but
role="alert", red title, warning emoji.
Rules:
- Every list/data section must handle all three states explicitly.
- Never show a spinner in place of a full-page experience — use skeleton screens.
- Empty state CTAs should always provide a path forward (e.g. "Book a Walk").
Admin and Member Mobile Alignment
Admin and Member mobile experiences are visually indistinguishable in structure — they share:
- The same
MobileAppShelltoken layer. - The same component library.
- The same bottom nav design, touch targets, motion, and type scale.
- The same card/list/section patterns.
They differ in:
- Admin inherits the admin theme (accent colour changes with theme switcher).
- Members always uses the green brand palette.
- Different nav items and routes.
- Different auth patterns (admin: CSR localStorage; members: server cookie + localStorage).
- Different feature sets and business logic.
What Must Never Happen in Mobile Views
- No horizontal scrolling in page content. Horizontal scroll only inside explicit scroll containers (e.g. a chip row).
- No data tables. Replace with vertical lists with disclosure rows or summary cards.
- No fixed-position elements other than the bottom nav and action bar. Avoid top banners that take up screen space.
- No touch targets smaller than 44px. Not even "less important" actions.
- No modals that cover the full screen (except full-screen sheets on purpose). Prefer slide-up panels.
- No desktop responsive hacks in mobile components. Do not import mobile tokens into desktop components or add
@media (max-width)patches to the mobile components file. - No mixing desktop and mobile layouts in the same file (e.g., showing/hiding via CSS in a shared component). Use separate route/layout files.
- No navigation items that do not exist on mobile. If a feature isn't in the mobile nav, provide a clear path to the desktop version or a dedicated in-app screen.
Component Library Summary
Shared Desktop Components
Located in src/components/ and src/lib/.
Shared Mobile Components
Located in src/lib/mobile/components/. All use the --m-* token layer.
| Component | Purpose |
|---|---|
MobileAppShell |
Top-level layout wrapper, token layer, bottom nav integration |
MobilePageShell |
Per-page container with sticky title header |
MobileHeader |
Optional branded app header strip |
MobileBottomNav |
Fixed bottom tab bar |
MobileSection |
Content group with optional title and action link |
MobileCard |
Touchable surface card |
MobileListItem |
Single list row (leading/label/sublabel/trailing) |
MobileStatCard |
Metric display card for dashboards |
MobileActionBar |
Sticky bottom CTA button area |
MobileSearchBar |
Touch-friendly search input with clear button |
MobileFormField |
Form field wrapper (label + input slot + error/hint) |
MobileEmptyState |
Empty / loading skeleton / error state |
MobileFilterSheet |
Slide-up bottom sheet for filters |
MobileTabs |
Segmented tab switcher |