The WhatsApp Chat application uses a comprehensive theming system built on CSS custom properties with OKLCH color space for perceptually uniform colors across light and dark modes.
Color palette
The theme is defined using CSS variables in app/globals.css:46-115. The application features a WhatsApp-inspired green color palette with carefully crafted color tokens.
Primary colors
The primary color uses a green hue (145° in OKLCH) that matches WhatsApp’s branding:
:root {
--primary: oklch(0.45 0.15 145);
--primary-foreground: oklch(1 0 0);
--accent: oklch(0.55 0.18 145);
--accent-foreground: oklch(1 0 0);
}
.dark {
--primary: oklch(0.6 0.18 145);
--primary-foreground: oklch(0.1 0.01 140);
--accent: oklch(0.68 0.2 145);
--accent-foreground: oklch(0.16 0.02 145);
}
OKLCH (Lightness, Chroma, Hue) provides better perceptual uniformity than RGB or HSL, ensuring colors appear consistent across different lightness levels.
Message bubble colors
Custom color tokens define the appearance of outgoing and incoming message bubbles:
--bubble-outgoing: oklch(0.92 0.04 145);
--bubble-incoming: oklch(0.995 0.015 95);
These are used in the MessageBubble component:
components/chat/message-bubble.tsx
const bubbleClasses = cn(
"relative rounded-3xl px-4 py-2 shadow-sm transition-colors",
isOutgoing
? "rounded-br-lg bg-bubble-outgoing text-foreground"
: "rounded-bl-lg bg-bubble-incoming text-foreground"
)
Message status colors
Status indicators use distinct colors to show message delivery states:
:root {
--status-sent: oklch(0.65 0.02 140);
--status-delivered: oklch(0.55 0.18 145);
--status-read: oklch(0.6 0.2 210);
}
.dark {
--status-sent: oklch(0.72 0.02 140);
--status-delivered: oklch(0.68 0.2 145);
--status-read: oklch(0.72 0.2 220);
}
Implementation in the status icon component:
components/chat/message-bubble.tsx
const statusColor: Record<Message["status"], string> = {
queued: "text-muted-foreground",
sending: "text-muted-foreground",
sent: "text-muted-foreground",
delivered: "text-status-delivered",
read: "text-status-read",
error: "text-destructive",
}
Theme configuration
The theme system uses Tailwind CSS v4’s @theme directive to expose CSS variables as Tailwind utilities.
Defining theme tokens
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-bubble-outgoing: var(--bubble-outgoing);
--color-bubble-incoming: var(--bubble-incoming);
--color-status-delivered: var(--status-delivered);
--color-status-read: var(--status-read);
/* ... */
}
All CSS variables prefixed with --color- become available as Tailwind color utilities like bg-bubble-outgoing or text-status-read.
Radius tokens
Border radius values are calculated relative to a base radius:
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.75rem;
}
Dark mode support
The application supports automatic dark mode detection through the suppressHydrationWarning attribute:
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${inter.variable} font-sans antialiased bg-app-surface`}>
{children}
</body>
</html>
)
}
Dark variant
The dark mode is controlled using a custom variant defined in the CSS:
@custom-variant dark (&:is(.dark *));
This allows dark mode styles to cascade properly when the .dark class is applied to a parent element.
Customizing colors
To customize the color palette, modify the CSS variables in app/globals.css:
- Change the primary hue: Adjust the third parameter (hue) in OKLCH values:
/* Change from green (145) to blue (220) */
--primary: oklch(0.45 0.15 220);
--accent: oklch(0.55 0.18 220);
- Adjust lightness: Modify the first parameter for lighter or darker colors:
/* Make bubbles darker */
--bubble-outgoing: oklch(0.85 0.04 145); /* was 0.92 */
- Increase saturation: Change the second parameter (chroma) for more vivid colors:
/* More saturated accent color */
--accent: oklch(0.55 0.25 145); /* was 0.18 */
Keep lightness values above 0.4 for light mode and below 0.7 for dark mode to maintain readability.
The sidebar has dedicated color tokens for independent styling:
:root {
--sidebar: oklch(0.992 0.004 140);
--sidebar-foreground: oklch(0.25 0.02 140);
--sidebar-primary: oklch(0.48 0.17 145);
--sidebar-accent: oklch(0.96 0.05 145);
--sidebar-border: oklch(0.9 0.01 140);
}
Used throughout the sidebar component:
components/chat/sidebar.tsx
<aside className="flex h-full w-full flex-col border-r border-sidebar-border/70 bg-sidebar/80 backdrop-blur-xl">
<Button
variant="outline"
className="border-sidebar-border/80 bg-sidebar"
>
<Plus className="h-5 w-5" />
</Button>
</aside>