Untitled
Theming Guide: shadcn + Tailwind CSS in a Turborepo Monorepo#
Overview#
This guide explains the theming approach for our monorepo, which uses shadcn/ui components with Tailwind CSS 4 in a Turborepo workspace. The philosophy is simple: consistent design tokens expressed through utilities.
Core Principles#
1. Design Tokens = Your Design System#
CSS variables define your design system. Change one variable, update the entire system.
2. Tailwind Utilities = How You Apply Them#
Utilities are composable and predictable. No fighting with class hierarchies.
3. Component Variants = Controlled Variations#
When you need structured options, use class-variance-authority (CVA) for component variants.
Project Structure#
textAll apps import the same globals.css, ensuring consistency across the entire monorepo.
Method 1: CSS Variables (Primary)#
This is the main way to customize themes broadly.
Location#
packages/ui/src/styles/globals.css
How It Works#
shadcn uses CSS variables for colors that map to Tailwind utility classes. Define your colors once, use them everywhere:
cssUsage in Components#
These variables are used via Tailwind utility classes:
tsxWhy HSL Format?#
Colors are defined in HSL (Hue, Saturation, Lightness) without the hsl() wrapper:
cssThis allows Tailwind to add opacity modifiers:
tsxMethod 2: Tailwind Config for Global Patterns#
Since we're using Tailwind CSS 4, you can define design tokens in CSS using the @theme directive.
Typography & Spacing#
cssBase Styles#
Apply defaults to all elements:
cssMethod 3: Component Layer Utilities#
For repeating patterns that aren't quite full components but more than single utilities.
When to Use#
Only create these when:
- The pattern appears 5+ times
- It's not worth making a full React component
- It's a specific combination that has semantic meaning
cssUsage#
tsxApp-Specific Theming#
Option A: Single Global Theme (Recommended)#
All apps share the same theme from packages/ui/src/styles/globals.css. This is the simplest and most consistent approach.
tsxOption B: App-Specific Overrides#
If an app needs unique styling, import the global theme first, then add overrides:
tsxcssOption C: Scoped Theme Classes#
For page-specific themes, use scoped classes:
csstsxDo's and Don'ts#
✅ Do#
-
Use CSS variables for colors and theme tokens
cssUse Tailwind utilities in components
tsxCreate component variants with CVA
tsxUse semantic color names
primary,secondary,destructive✅- Not
blue,red,green❌
Embrace utility-first approach
- Inline utilities in JSX are expected and encouraged
- Composition over abstraction
Test both light and dark modes
- Always define both
:rootand.darkvariables
❌ Don't#
-
Don't create abstracted CSS classes for everything
cssDon't fight the inline utility approach
tsxDon't use hsl() wrapper in CSS variables
cssDon't create semantic class names
cssDon't override Tailwind defaults globally without reason
cssDon't use !important to solve specificity issues
- Use
cn()helper to properly merge classes - Trust Tailwind's specificity model
Working with Existing Components#
Adding Variants#
When you need multiple versions of a component, use CVA:
tsxUsage#
tsxClass Merging with cn()#
Always use the cn() helper to merge classes properly:
tsxThe cn() helper (from clsx + tailwind-merge):
- Conditionally applies classes
- Deduplicates conflicting utilities
- Preserves the last class when conflicts occur
tsxColor Palette Reference#
Semantic Colors#
| Variable | Purpose | Example Usage |
|---|---|---|
background |
Page background | bg-background |
foreground |
Primary text | text-foreground |
card |
Card backgrounds | bg-card |
card-foreground |
Card text | text-card-foreground |
primary |
Primary actions | bg-primary |
primary-foreground |
Text on primary | text-primary-foreground |
secondary |
Secondary actions | bg-secondary |
muted |
Subtle backgrounds | bg-muted |
accent |
Highlighted elements | bg-accent |
destructive |
Dangerous actions | bg-destructive |
border |
Borders | border-border |
input |
Input borders | border-input |
ring |
Focus rings | ring-ring |
Opacity Modifiers#
All colors support opacity:
tsxDark Mode#
Dark mode is handled through the .dark class and next-themes:
tsxToggle Dark Mode#
tsxWorkflow Summary#
-
Define design tokens in
packages/ui/src/styles/globals.css- Colors, spacing, typography, border radius
-
Use Tailwind utilities everywhere
- Compose utilities in component JSX
- Don't create custom CSS classes
-
Create component variants when needed
- Use CVA for structured variations
- Keep variants in the component file
-
Override per-app only when necessary
- Import global theme first
- Add app-specific overrides second
-
Change one variable, update entire system
- Modify
--primary, all primary buttons update - Modify
--radius, all rounded elements update
- Modify
Examples#
Example 1: Changing Brand Color#
cssEvery button, link, and element using bg-primary now uses purple.
Example 2: Custom Card Styling#
tsxExample 3: Form Input with Validation States#
tsxResources#
Summary#
The shadcn philosophy: Consistent design tokens expressed through utilities.
- ✅ CSS variables for colors and theme tokens
- ✅ Tailwind utilities for styling
- ✅ Component variants (CVA) for structured variations
- ❌ No custom CSS classes
- ❌ No fighting the utility-first approach
Change one CSS variable → entire system updates. Need tweaks? Compose utilities per component. Need structure? Create component variants.
90% of your styling work is using utilities that reference design tokens.