Guest-facing booking · Shipped

@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.

Steps 1–9 shipped

Architecture

Tokens own the look. Tailwind reads them through arbitrary hsl(var(--…)) values. Hotel themes layer on top via [data-theme="<slug>"].

tokens.css
--accent, --bg, --ink…
HSL CSS variables on :root
themes/<hotel>.css
[data-theme="hotel-x"]
overrides --accent, --radius-lg…
tailwind.config.js
bg-accent → hsl(var(--accent))
standalone config (no core spread)
↓ consumed by ↓
components/ui/*
Button · Card · Modal · Badge · Chip · BottomSheet · Avatar · Skeleton · Toast

Tokens

The exact values from packages/client/src/styles/tokens.css.

Surfaces & ink

--bg
40 33% 98%
--surface
36 25% 95%
--surface-2
36 18% 91%
--ink
220 13% 9%
--ink-muted
220 7% 40%
--border
36 12% 86%

Accent (default = HappNest deep teal)

--accent
178 50% 24%
--accent-fg
0 0% 100%
--accent-soft
178 30% 92%

Status

--success
152 47% 35%
--warning
36 88% 42%
--danger
4 72% 50%

Radius & motion

--radius-sm
8px
--radius-md
12px
--radius-lg
16px
--radius-xl
24px

Typography

Loaded via next/font/google — no remote stylesheet at runtime. Display feels editorial; body stays neutral.

--font-display · Fraunces · variable, opsz
Stay a little longer.
Hero / h1 / h2 only.
--font-body · Manrope · variable
Manrope handles paragraphs, UI labels, form copy, and every byte of running text. Lowercase numerals, generous spacing, no surprises.
12px · 14px · 16px (input floor — iOS no-zoom) · 18px · 20px · 24px

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.

data-theme: default

Casa Margarita

Sample card with the default deep-teal accent.

 
data-theme: example-hotel

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

Before spread coreConfig
// 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",
      },
    },
  },
};
After standalone + tokens
// 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

Before hex + Tailwind names
// 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>
);
After tokens only
// 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
Grep every 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
Stop spreading coreConfig. Client gets a standalone tailwind.config.js that reads only from its own tokens.
Step 3 Create the token system
New 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
Swap 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
Build under 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
Every 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
One representative route is end-to-end migrated as the reference. The rest stays for follow-up PRs.
Step 9 Documentation
Update DESIGN.md sections "Tokens" and "Tailwind"; keep FOLLOW_UP_CORE_COMPONENT_THEMING.md accurate.