Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "suaveplan-theme-factory",
|
||||
"version": "1.0.0",
|
||||
"description": "Tailwind CSS + shadcn/ui theming toolkit with curated themes"
|
||||
}
|
||||
723
plugins/suaveplan-theme-factory/skills/theme-factory/SKILL.md
Normal file
723
plugins/suaveplan-theme-factory/skills/theme-factory/SKILL.md
Normal file
@@ -0,0 +1,723 @@
|
||||
---
|
||||
name: theme-factory
|
||||
description: Generate and apply Tailwind CSS + shadcn/ui themes for Suaveplan React applications using HSL CSS variables, dark mode variants, and component-level theming
|
||||
---
|
||||
|
||||
# Theme Factory for Suaveplan
|
||||
|
||||
Generate complete Tailwind CSS theme configurations compatible with `@suaveplan/react-ui` (shadcn/ui). Each theme defines HSL CSS variables for `:root` (light) and `.dark` (dark mode), extending the Tailwind config to provide consistent styling across all Suaveplan React components.
|
||||
|
||||
## Architecture
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|-----------|
|
||||
| Color Format | HSL (required for shadcn/ui compatibility) |
|
||||
| Variable Scope | CSS custom properties on `:root` and `.dark` |
|
||||
| Dark Mode | Tailwind `dark:` variant via `class` strategy |
|
||||
| Component Library | `@suaveplan/react-ui` (shadcn/ui-based) |
|
||||
| Class Merging | `cn()` from `@suaveplan/react-ui` |
|
||||
| Build | Tailwind CSS PostCSS pipeline via Vite |
|
||||
|
||||
## Theme Variables
|
||||
|
||||
Every Suaveplan theme must define these CSS variables in HSL format (hue saturation lightness without commas, no `hsl()` wrapper):
|
||||
|
||||
### Required Variables
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Page */
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
/* Card surfaces */
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
/* Popover surfaces */
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
/* Primary actions */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
/* Secondary actions */
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Subdued elements */
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
/* Highlights and hover states */
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Errors and destructive actions */
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
/* Borders and separators */
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
/* Focus rings */
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
/* Border radius scale */
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
```
|
||||
|
||||
### Optional Extended Variables
|
||||
|
||||
For advanced themes, add sidebar and chart variables:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Sidebar (used by @suaveplan/react-ui sidebar component) */
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
|
||||
/* Charts (used by @suaveplan/react-charts) */
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
}
|
||||
```
|
||||
|
||||
## Tailwind Configuration Extension
|
||||
|
||||
Extend the Tailwind config to consume CSS variables:
|
||||
|
||||
```typescript
|
||||
// tailwind.config.ts
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
const config: Config = {
|
||||
darkMode: 'class',
|
||||
content: ['./src/**/*.{ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
## 10 Built-In Themes
|
||||
|
||||
### 1. Default (Zinc)
|
||||
|
||||
The standard Suaveplan theme. Neutral zinc scale with sharp contrasts.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Slate
|
||||
|
||||
Cool blue-gray tone. Professional, corporate.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Ocean Blue
|
||||
|
||||
Vibrant blue primary. Energetic, tech-forward.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Emerald
|
||||
|
||||
Green primary. Growth, health, sustainability.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 160.1 84.1% 39.4%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 140 30% 95%;
|
||||
--secondary-foreground: 160.1 84.1% 25%;
|
||||
--muted: 140 20% 96%;
|
||||
--muted-foreground: 150 10% 45%;
|
||||
--accent: 140 30% 95%;
|
||||
--accent-foreground: 160.1 84.1% 25%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 140 20% 90%;
|
||||
--input: 140 20% 90%;
|
||||
--ring: 160.1 84.1% 39.4%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 150 30% 4%;
|
||||
--foreground: 140 20% 98%;
|
||||
--primary: 160.1 84.1% 39.4%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 150 20% 15%;
|
||||
--secondary-foreground: 140 20% 98%;
|
||||
--muted: 150 20% 15%;
|
||||
--muted-foreground: 150 15% 60%;
|
||||
--accent: 150 20% 15%;
|
||||
--accent-foreground: 140 20% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 150 20% 15%;
|
||||
--input: 150 20% 15%;
|
||||
--ring: 160.1 84.1% 39.4%;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Rose
|
||||
|
||||
Pink-red primary. Bold, creative, expressive.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 346.8 77.2% 49.8%;
|
||||
--primary-foreground: 355.7 100% 97.3%;
|
||||
--secondary: 340 30% 96%;
|
||||
--secondary-foreground: 346.8 77.2% 35%;
|
||||
--muted: 340 20% 96%;
|
||||
--muted-foreground: 340 10% 45%;
|
||||
--accent: 340 30% 96%;
|
||||
--accent-foreground: 346.8 77.2% 35%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 340 20% 91%;
|
||||
--input: 340 20% 91%;
|
||||
--ring: 346.8 77.2% 49.8%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 340 30% 4%;
|
||||
--foreground: 340 10% 98%;
|
||||
--primary: 346.8 77.2% 49.8%;
|
||||
--primary-foreground: 355.7 100% 97.3%;
|
||||
--secondary: 340 20% 15%;
|
||||
--secondary-foreground: 340 10% 98%;
|
||||
--muted: 340 20% 15%;
|
||||
--muted-foreground: 340 15% 60%;
|
||||
--accent: 340 20% 15%;
|
||||
--accent-foreground: 340 10% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 340 20% 15%;
|
||||
--input: 340 20% 15%;
|
||||
--ring: 346.8 77.2% 49.8%;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Amber
|
||||
|
||||
Warm amber/orange primary. Friendly, approachable, energetic.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 20 14.3% 4.1%;
|
||||
--primary: 24.6 95% 53.1%;
|
||||
--primary-foreground: 60 9.1% 97.8%;
|
||||
--secondary: 30 30% 96%;
|
||||
--secondary-foreground: 24.6 95% 30%;
|
||||
--muted: 30 20% 96%;
|
||||
--muted-foreground: 25 10% 45%;
|
||||
--accent: 30 30% 96%;
|
||||
--accent-foreground: 24.6 95% 30%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 30 20% 90%;
|
||||
--input: 30 20% 90%;
|
||||
--ring: 24.6 95% 53.1%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 20 14.3% 4.1%;
|
||||
--foreground: 60 9.1% 97.8%;
|
||||
--primary: 20.5 90.2% 48.2%;
|
||||
--primary-foreground: 60 9.1% 97.8%;
|
||||
--secondary: 20 20% 15%;
|
||||
--secondary-foreground: 60 9.1% 97.8%;
|
||||
--muted: 20 20% 15%;
|
||||
--muted-foreground: 25 15% 60%;
|
||||
--accent: 20 20% 15%;
|
||||
--accent-foreground: 60 9.1% 97.8%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 20 20% 15%;
|
||||
--input: 20 20% 15%;
|
||||
--ring: 20.5 90.2% 48.2%;
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Violet
|
||||
|
||||
Purple primary. Premium, creative, distinctive.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 224 71.4% 4.1%;
|
||||
--primary: 262.1 83.3% 57.8%;
|
||||
--primary-foreground: 210 20% 98%;
|
||||
--secondary: 260 30% 96%;
|
||||
--secondary-foreground: 262.1 83.3% 35%;
|
||||
--muted: 260 20% 96%;
|
||||
--muted-foreground: 260 10% 45%;
|
||||
--accent: 260 30% 96%;
|
||||
--accent-foreground: 262.1 83.3% 35%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 260 20% 91%;
|
||||
--input: 260 20% 91%;
|
||||
--ring: 262.1 83.3% 57.8%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 260 30% 4%;
|
||||
--foreground: 260 10% 98%;
|
||||
--primary: 263.4 70% 50.4%;
|
||||
--primary-foreground: 210 20% 98%;
|
||||
--secondary: 260 20% 15%;
|
||||
--secondary-foreground: 260 10% 98%;
|
||||
--muted: 260 20% 15%;
|
||||
--muted-foreground: 260 15% 60%;
|
||||
--accent: 260 20% 15%;
|
||||
--accent-foreground: 260 10% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 260 20% 15%;
|
||||
--input: 260 20% 15%;
|
||||
--ring: 263.4 70% 50.4%;
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Neutral
|
||||
|
||||
Pure grayscale. Minimal, content-focused, photography-friendly.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
```
|
||||
|
||||
### 9. Teal
|
||||
|
||||
Blue-green primary. Calming, trustworthy, health/wellness.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 175 84% 32%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 170 30% 95%;
|
||||
--secondary-foreground: 175 84% 22%;
|
||||
--muted: 170 20% 96%;
|
||||
--muted-foreground: 170 10% 45%;
|
||||
--accent: 170 30% 95%;
|
||||
--accent-foreground: 175 84% 22%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 170 20% 90%;
|
||||
--input: 170 20% 90%;
|
||||
--ring: 175 84% 32%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 175 30% 4%;
|
||||
--foreground: 170 20% 98%;
|
||||
--primary: 175 84% 32%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 175 20% 15%;
|
||||
--secondary-foreground: 170 20% 98%;
|
||||
--muted: 175 20% 15%;
|
||||
--muted-foreground: 170 15% 60%;
|
||||
--accent: 175 20% 15%;
|
||||
--accent-foreground: 170 20% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 175 20% 15%;
|
||||
--input: 175 20% 15%;
|
||||
--ring: 175 84% 32%;
|
||||
}
|
||||
```
|
||||
|
||||
### 10. Crimson
|
||||
|
||||
Deep red primary. Bold, authoritative, editorial.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 0 72.2% 50.6%;
|
||||
--primary-foreground: 0 85.7% 97.3%;
|
||||
--secondary: 0 20% 96%;
|
||||
--secondary-foreground: 0 72.2% 35%;
|
||||
--muted: 0 15% 96%;
|
||||
--muted-foreground: 0 8% 45%;
|
||||
--accent: 0 20% 96%;
|
||||
--accent-foreground: 0 72.2% 35%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 15% 91%;
|
||||
--input: 0 15% 91%;
|
||||
--ring: 0 72.2% 50.6%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 0 20% 4%;
|
||||
--foreground: 0 10% 98%;
|
||||
--primary: 0 72.2% 50.6%;
|
||||
--primary-foreground: 0 85.7% 97.3%;
|
||||
--secondary: 0 15% 15%;
|
||||
--secondary-foreground: 0 10% 98%;
|
||||
--muted: 0 15% 15%;
|
||||
--muted-foreground: 0 10% 60%;
|
||||
--accent: 0 15% 15%;
|
||||
--accent-foreground: 0 10% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 15% 15%;
|
||||
--input: 0 15% 15%;
|
||||
--ring: 0 72.2% 50.6%;
|
||||
}
|
||||
```
|
||||
|
||||
## Applying a Theme
|
||||
|
||||
### Method 1: Global CSS (Single Theme)
|
||||
|
||||
Place variables in the app's global CSS file:
|
||||
|
||||
```css
|
||||
/* apps/web/src/globals.css */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
/* ... rest of light theme variables */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
/* ... rest of dark theme variables */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Method 2: Theme Switcher (Multiple Themes)
|
||||
|
||||
Use data attributes to switch between themes at runtime:
|
||||
|
||||
```css
|
||||
/* themes.css */
|
||||
[data-theme="default"] { /* ... zinc variables ... */ }
|
||||
[data-theme="ocean"] { /* ... ocean blue variables ... */ }
|
||||
[data-theme="emerald"] { /* ... emerald variables ... */ }
|
||||
/* ... */
|
||||
|
||||
[data-theme="default"].dark { /* ... zinc dark variables ... */ }
|
||||
[data-theme="ocean"].dark { /* ... ocean blue dark variables ... */ }
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Theme switcher component
|
||||
import * as React from 'react';
|
||||
|
||||
const themes = [
|
||||
'default', 'slate', 'ocean', 'emerald', 'rose',
|
||||
'amber', 'violet', 'neutral', 'teal', 'crimson',
|
||||
] as const;
|
||||
|
||||
type Theme = (typeof themes)[number];
|
||||
|
||||
export function useTheme() {
|
||||
const [theme, setTheme] = React.useState<Theme>('default');
|
||||
const [dark, setDark] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
}, [theme, dark]);
|
||||
|
||||
return { theme, setTheme, dark, setDark, themes };
|
||||
}
|
||||
```
|
||||
|
||||
### Method 3: Component-Level Theming
|
||||
|
||||
Override variables on a specific container for scoped theming:
|
||||
|
||||
```typescript
|
||||
import { cn } from '@suaveplan/react-ui';
|
||||
|
||||
export function BrandedSection({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return (
|
||||
<section
|
||||
className={cn('rounded-lg p-6', className)}
|
||||
style={{
|
||||
'--primary': '221.2 83.2% 53.3%',
|
||||
'--primary-foreground': '210 40% 98%',
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a Custom Theme
|
||||
|
||||
### Step-by-Step
|
||||
|
||||
1. **Choose a primary hue** (0-360 on the HSL wheel)
|
||||
2. **Set primary saturation** (60-90% for vibrant, 30-50% for muted)
|
||||
3. **Derive secondary** from primary hue with lower saturation and higher lightness
|
||||
4. **Derive muted** from primary hue with very low saturation (15-25%) and high lightness (95-97%)
|
||||
5. **Set accent** equal to secondary (or slightly different hue for contrast)
|
||||
6. **Keep destructive** at red (hue 0) -- users expect red for errors
|
||||
7. **Derive border** from muted with slightly lower lightness
|
||||
8. **Set ring** equal to primary (focus rings should match primary actions)
|
||||
9. **Dark mode**: Invert lightness values -- backgrounds become dark (3-5%), foregrounds become light (95-98%)
|
||||
|
||||
### Contrast Requirements
|
||||
|
||||
All theme color pairs must meet WCAG 2.1 AA contrast ratios:
|
||||
|
||||
- **Normal text**: minimum 4.5:1 contrast ratio
|
||||
- **Large text** (18px+ or 14px+ bold): minimum 3:1 contrast ratio
|
||||
- **UI components**: minimum 3:1 against adjacent colors
|
||||
|
||||
Verify with: `foreground` against `background`, `primary-foreground` against `primary`, etc.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **HSL format only** -- no hex, rgb, or oklch in CSS variables
|
||||
- **No `hsl()` wrapper** in variable values -- Tailwind adds it via `hsl(var(--name))`
|
||||
- **All 14 required variables** must be defined in both `:root` and `.dark`
|
||||
- **WCAG AA contrast** on all foreground/background pairs
|
||||
- **`dark:` variant** must be supported -- no light-only themes
|
||||
- **`--radius`** must be included (default `0.5rem`)
|
||||
- **No Tailwind color utilities** (`bg-blue-500`) for themed elements -- always use semantic tokens (`bg-primary`)
|
||||
- Components must use `cn()` from `@suaveplan/react-ui` for class merging
|
||||
Reference in New Issue
Block a user