@happnest/client
Off-white surfaces, near-black ink, a single confident deep-teal accent. Display in Fraunces, body in Manrope.
Each hotel can paint over the accent without touching the chrome by scoping a [data-theme] file.
Architecture
Tokens own the look. Tailwind reads them through arbitrary hsl(var(--…)) values. Hotel themes layer on top via [data-theme="<slug>"].
HSL CSS variables on :root
overrides --accent, --radius-lg…
standalone config (no core spread)
Tokens
The exact values from packages/client/src/styles/tokens.css.
Surfaces & ink
Accent (default = HappNest deep teal)
Status
Radius & motion
Typography
Loaded via next/font/google — no remote stylesheet at runtime. Display feels editorial; body stays neutral.
Hotel theming, live
Same component, two themes. The default :root sets deep teal; [data-theme="example-hotel"] overrides only --accent + --accent-soft + --radius-lg.
Surfaces, ink, and borders stay the same.
Casa Margarita
Sample card with the default deep-teal accent.
Casa Margarita
Same component, coral accent — only the override file changed.
The redo
What changed in component code when the design system landed.
Tailwind config
// packages/client/tailwind.config.js const coreConfig = require("@happnest/core/tailwind.config"); module.exports = { ...coreConfig, content: [ "./src/**/*.{js,ts,jsx,tsx}", "../core/src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: { colors: { primary: "#3b82f6", }, }, }, };
// packages/client/tailwind.config.js module.exports = { content: ["./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: { colors: { bg: "hsl(var(--bg))", surface: "hsl(var(--surface))", ink: "hsl(var(--ink))", accent: { DEFAULT: "hsl(var(--accent))", fg: "hsl(var(--accent-fg))", soft: "hsl(var(--accent-soft))", }, }, borderRadius: { lg: "var(--radius-lg)", }, }, }, };
ExperienceCard
// packages/client/src/components/ExperienceCard.tsx return ( <div className="bg-white rounded-xl"> <div className="bg-gradient-to-br from-blue-100 to-purple-100"> <h3 className="text-gray-900 font-bold">{title}</h3> <p className="text-gray-400">{subtitle}</p> </div> <button style={{ background: "#1e3a8a" }}> Reservar </button> </div> );
// packages/client/src/components/ExperienceCard.tsx return ( <Card> <div className="bg-surface-2 rounded-lg"> <h3 className="font-display text-ink">{title}</h3> <p className="text-ink-muted">{subtitle}</p> </div> <Button variant="primary"> Reservar </Button> </Card> );
The plan, walked through
Each step from packages/client/DESIGN_SYSTEM_PLAN.md in plain language. All steps have shipped.
Step 1 Baseline audit
bg-blue-…, #hex, and core visual import in client source.
Produce .audit.md as the checklist for Step 6. Acceptance: ≥ 20 leak sites listed.
Step 2 Decouple Tailwind config
coreConfig. Client gets a standalone tailwind.config.js that reads only from its own tokens.
Step 3 Create the token system
src/styles/tokens.css with HSL variables. Example override at themes/example-hotel.css shows how a hotel re-themes by scoping under [data-theme="example-hotel"].
Step 4 Fonts via next/font
Public_Sans + Cal_Sans for Fraunces (display, variable) and Manrope (body, variable). Drop the import of core's globals.css from layout.tsx.
Step 5 Client-owned primitives
src/components/ui/: Card, Badge, Chip, Modal, BottomSheet, Avatar, Skeleton, Toast. Rewrite existing Button + Input. Rules: no hex, no bg-blue-500, ≥ 44 × 44 touch targets, focus-visible rings.
Step 6 Replace core visual imports
import { Button } from "@happnest/core" becomes from "@/components/ui". Functional imports (ApiClient, types, hooks) stay.
Login + register composites deliberately left for a follow-up.
Step 7 Hotel theming via [data-theme]
ClientHotelBranding writes document.documentElement.dataset.theme = hotel.slug. Any CSS file scoped under [data-theme="<slug>"] activates automatically.
Step 8 Sample migration: /public/experiences
Step 9 Documentation
DESIGN.md sections "Tokens" and "Tailwind"; keep FOLLOW_UP_CORE_COMPONENT_THEMING.md accurate.