From 6c7fa6696d07b537d4fd88bfc1a49b404462e765 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sun, 30 Nov 2025 08:25:32 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 12 + README.md | 3 + SKILL.md | 629 ++++++++++++++++++++++++++++++++++ plugin.lock.json | 85 +++++ references/architecture.md | 206 +++++++++++ references/common-gotchas.md | 469 +++++++++++++++++++++++++ references/dark-mode.md | 239 +++++++++++++ references/migration-guide.md | 313 +++++++++++++++++ templates/components.json | 20 ++ templates/index.css | 140 ++++++++ templates/theme-provider.tsx | 92 +++++ templates/tsconfig.app.json | 31 ++ templates/utils.ts | 6 + templates/vite.config.ts | 17 + 14 files changed, 2262 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 SKILL.md create mode 100644 plugin.lock.json create mode 100644 references/architecture.md create mode 100644 references/common-gotchas.md create mode 100644 references/dark-mode.md create mode 100644 references/migration-guide.md create mode 100644 templates/components.json create mode 100644 templates/index.css create mode 100644 templates/theme-provider.tsx create mode 100644 templates/tsconfig.app.json create mode 100644 templates/utils.ts create mode 100644 templates/vite.config.ts diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..5941231 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "tailwind-v4-shadcn", + "description": "Set up Tailwind v4 with shadcn/ui using @theme inline pattern and CSS variable architecture. Four-step mandatory pattern: define CSS variables at root, map to Tailwind utilities, apply base styles, get automatic dark mode. Use when: initializing React projects with Tailwind v4, setting up shadcn/ui dark mode, or fixing colors not working, theme not applying, CSS variables broken, tw-animate-css errors, or migrating from v3.", + "version": "1.0.0", + "author": { + "name": "Jeremy Dawes", + "email": "jeremy@jezweb.net" + }, + "skills": [ + "./" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4208c9f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# tailwind-v4-shadcn + +Set up Tailwind v4 with shadcn/ui using @theme inline pattern and CSS variable architecture. Four-step mandatory pattern: define CSS variables at root, map to Tailwind utilities, apply base styles, get automatic dark mode. Use when: initializing React projects with Tailwind v4, setting up shadcn/ui dark mode, or fixing colors not working, theme not applying, CSS variables broken, tw-animate-css errors, or migrating from v3. diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..b165616 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,629 @@ +--- +name: tailwind-v4-shadcn +description: | + Set up Tailwind v4 with shadcn/ui using @theme inline pattern and CSS variable architecture. Four-step mandatory pattern: define CSS variables at root, map to Tailwind utilities, apply base styles, get automatic dark mode. + + Use when: initializing React projects with Tailwind v4, setting up shadcn/ui dark mode, or fixing colors not working, theme not applying, CSS variables broken, tw-animate-css errors, or migrating from v3. +license: MIT +--- + +# Tailwind v4 + shadcn/ui Production Stack + +**Production-tested**: WordPress Auditor (https://wordpress-auditor.webfonts.workers.dev) +**Last Updated**: 2025-11-09 +**Status**: Production Ready ✅ + +--- + +## ⚠️ BEFORE YOU START (READ THIS!) + +**CRITICAL FOR AI AGENTS**: If you're Claude Code helping a user set up Tailwind v4: + +1. **Explicitly state you're using this skill** at the start of the conversation +2. **Reference patterns from the skill** rather than general knowledge +3. **Prevent known issues** listed in `reference/common-gotchas.md` +4. **Don't guess** - if unsure, check the skill documentation + +**USER ACTION REQUIRED**: Tell Claude to check this skill first! + +Say: **"I'm setting up Tailwind v4 + shadcn/ui - check the tailwind-v4-shadcn skill first"** + +### Why This Matters (Real-World Results) + +**Without skill activation:** +- ❌ Setup time: ~5 minutes +- ❌ Errors encountered: 2-3 (tw-animate-css, duplicate @layer base) +- ❌ Manual fixes needed: 2+ commits +- ❌ Token usage: ~65k +- ❌ User confidence: Required debugging + +**With skill activation:** +- ✅ Setup time: ~1 minute +- ✅ Errors encountered: 0 +- ✅ Manual fixes needed: 0 +- ✅ Token usage: ~20k (70% reduction) +- ✅ User confidence: Instant success + +### Known Issues This Skill Prevents + +1. **tw-animate-css import error** (deprecated in v4) +2. **Duplicate @layer base blocks** (shadcn init adds its own) +3. **Wrong template selection** (vanilla TS vs React) +4. **Missing post-init cleanup** (incompatible CSS rules) +5. **Wrong plugin syntax** (using @import or require() instead of @plugin directive) + +All of these are handled automatically when the skill is active. + +--- + +## Quick Start (5 Minutes - Follow This Exact Order) + +### 1. Install Dependencies + +```bash +pnpm add tailwindcss @tailwindcss/vite +pnpm add -D @types/node +pnpm dlx shadcn@latest init +``` + +### 2. Configure Vite + +```typescript +// vite.config.ts +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import path from 'path' + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +}) +``` + +### 3. Update components.json + +```json +{ + "tailwind": { + "config": "", // ← CRITICAL: Empty for v4 + "css": "src/index.css", + "baseColor": "slate", // Base color palette + "cssVariables": true, + "prefix": "" // No prefix for utility classes + } +} +``` + +### 4. Delete tailwind.config.ts + +```bash +rm tailwind.config.ts # v4 doesn't use this file +``` + +--- + +## The Four-Step Architecture (CRITICAL) + +This pattern is **mandatory** - skipping steps will break your theme. + +### Step 1: Define CSS Variables at Root Level + +```css +/* src/index.css */ +@import "tailwindcss"; + +:root { + --background: hsl(0 0% 100%); /* ← hsl() wrapper required */ + --foreground: hsl(222.2 84% 4.9%); + --primary: hsl(221.2 83.2% 53.3%); + /* ... all light mode colors */ +} + +.dark { + --background: hsl(222.2 84% 4.9%); + --foreground: hsl(210 40% 98%); + --primary: hsl(217.2 91.2% 59.8%); + /* ... all dark mode colors */ +} +``` + +**Critical Rules:** +- ✅ Define at root level (NOT inside `@layer base`) +- ✅ Use `hsl()` wrapper on all color values +- ✅ Use `.dark` for dark mode (NOT `.dark { @theme { } }`) + +### Step 2: Map Variables to Tailwind Utilities + +```css +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-primary: var(--primary); + /* ... map ALL CSS variables */ +} +``` + +**Why This Is Required:** +- Generates utility classes (`bg-background`, `text-primary`) +- Without this, `bg-primary` etc. won't exist + +### Step 3: Apply Base Styles + +```css +@layer base { + body { + background-color: var(--background); /* NO hsl() here */ + color: var(--foreground); + } +} +``` + +**Critical Rules:** +- ✅ Reference variables directly: `var(--background)` +- ❌ Never double-wrap: `hsl(var(--background))` + +### Step 4: Result - Automatic Dark Mode + +```tsx +
+ {/* No dark: variants needed - theme switches automatically */} +
+``` + +--- + +## Dark Mode Setup + +### 1. Create ThemeProvider + +See `reference/dark-mode.md` for full implementation or use template: + +```typescript +// Copy from: templates/theme-provider.tsx +``` + +### 2. Wrap Your App + +```typescript +// src/main.tsx +import { ThemeProvider } from '@/components/theme-provider' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , +) +``` + +### 3. Add Theme Toggle + +```bash +pnpm dlx shadcn@latest add dropdown-menu +``` + +See `reference/dark-mode.md` for ModeToggle component code. + +--- + +## Critical Rules (MUST FOLLOW) + +### ✅ Always Do: + +1. **Wrap color values with `hsl()` in `:root` and `.dark`** + ```css + --background: hsl(0 0% 100%); /* ✅ Correct */ + ``` + +2. **Use `@theme inline` to map all CSS variables** + ```css + @theme inline { + --color-background: var(--background); + } + ``` + +3. **Set `"tailwind.config": ""` in components.json** + ```json + { "tailwind": { "config": "" } } + ``` + +4. **Delete `tailwind.config.ts` if it exists** + +5. **Use `@tailwindcss/vite` plugin (NOT PostCSS)** + +6. **Use `cn()` for conditional classes** + ```typescript + import { cn } from "@/lib/utils" +
+ ``` + +### ❌ Never Do: + +1. **Put `:root` or `.dark` inside `@layer base`** + ```css + /* WRONG */ + @layer base { + :root { --background: hsl(...); } + } + ``` + +2. **Use `.dark { @theme { } }` pattern** + ```css + /* WRONG - v4 doesn't support nested @theme */ + .dark { + @theme { + --color-primary: hsl(...); + } + } + ``` + +3. **Double-wrap colors** + ```css + /* WRONG */ + body { + background-color: hsl(var(--background)); + } + ``` + +4. **Use `tailwind.config.ts` for theme colors** + ```typescript + /* WRONG - v4 ignores this */ + export default { + theme: { + extend: { + colors: { primary: 'hsl(var(--primary))' } + } + } + } + ``` + +5. **Use `@apply` directive (deprecated in v4)** + +6. **Use `dark:` variants for semantic colors** + ```tsx + /* WRONG */ +
+ + /* CORRECT */ +
+ ``` + +--- + +## Semantic Color Tokens + +Always use semantic names for colors: + +```css +:root { + --destructive: hsl(0 84.2% 60.2%); /* Red - errors, critical */ + --success: hsl(142.1 76.2% 36.3%); /* Green - success states */ + --warning: hsl(38 92% 50%); /* Yellow - warnings */ + --info: hsl(221.2 83.2% 53.3%); /* Blue - info, primary */ +} +``` + +**Usage:** +```tsx +
Critical
+
Success
+
Warning
+
Info
+``` + +--- + +## Common Issues & Quick Fixes + +| Symptom | Cause | Fix | +|---------|-------|-----| +| `bg-primary` doesn't work | Missing `@theme inline` mapping | Add `@theme inline` block | +| Colors all black/white | Double `hsl()` wrapping | Use `var(--color)` not `hsl(var(--color))` | +| Dark mode not switching | Missing ThemeProvider | Wrap app in `` | +| Build fails | `tailwind.config.ts` exists | Delete the file | +| Text invisible | Wrong contrast colors | Check color definitions in `:root`/`.dark` | + +See `reference/common-gotchas.md` for complete troubleshooting guide. + +--- + +## File Templates + +All templates are available in the `templates/` directory: + +- **index.css** - Complete CSS setup with all color variables +- **components.json** - shadcn/ui v4 configuration +- **vite.config.ts** - Vite + Tailwind plugin setup +- **tsconfig.app.json** - TypeScript with path aliases +- **theme-provider.tsx** - Dark mode provider with localStorage +- **utils.ts** - `cn()` utility for class merging + +Copy these files to your project and customize as needed. + +--- + +## Complete Setup Checklist + +- [ ] Vite + React + TypeScript project created +- [ ] `@tailwindcss/vite` installed (NOT postcss) +- [ ] `vite.config.ts` uses `tailwindcss()` plugin +- [ ] `tsconfig.json` has path aliases configured +- [ ] `components.json` exists with `"config": ""` +- [ ] NO `tailwind.config.ts` file exists +- [ ] `src/index.css` follows v4 pattern: + - [ ] `:root` and `.dark` at root level (not in @layer) + - [ ] Colors wrapped with `hsl()` + - [ ] `@theme inline` maps all variables + - [ ] `@layer base` uses unwrapped variables +- [ ] Theme provider installed and wrapping app +- [ ] Dark mode toggle component created +- [ ] Test theme switching works in browser + +--- + +## Advanced Topics + +### Custom Colors + +Add new semantic colors: + +```css +:root { + --brand: hsl(280 65% 60%); + --brand-foreground: hsl(0 0% 100%); +} + +.dark { + --brand: hsl(280 75% 70%); + --brand-foreground: hsl(280 20% 10%); +} + +@theme inline { + --color-brand: var(--brand); + --color-brand-foreground: var(--brand-foreground); +} +``` + +Usage: `
Branded
` + +### Migration from v3 + +See `reference/migration-guide.md` for complete v3 → v4 migration steps. + +### Component Best Practices + +1. **Always use semantic tokens** + ```tsx + /* ✅ */ + /* ❌ */ + ``` + +2. **Use `cn()` for conditional styling** + ```tsx + import { cn } from "@/lib/utils" + +
+ ``` + +3. **Compose shadcn/ui components** + ```tsx + + + + + + + Title + + + + ``` + +--- + +## Dependencies + +### ✅ Install These + +```json +{ + "dependencies": { + "tailwindcss": "^4.1.17", + "@tailwindcss/vite": "^4.1.17", + "clsx": "^2.1.1", + "tailwind-merge": "^3.3.1", + "@radix-ui/react-*": "latest", + "lucide-react": "^0.553.0", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@types/node": "^24.10.0", + "@vitejs/plugin-react": "^5.1.0", + "vite": "^7.2.2", + "typescript": "~5.9.0", + "tw-animate-css": "^1.4.0" + } +} +``` + +### Animation Packages (Updated Nov 2025) + +shadcn/ui has deprecated `tailwindcss-animate` in favor of `tw-animate-css` for Tailwind v4 compatibility. + +**✅ DO Install (v4-compatible)**: +```bash +pnpm add -D tw-animate-css +``` + +Then add to `src/index.css`: +```css +@import "tailwindcss"; +@import "tw-animate-css"; +``` + +**❌ DO NOT Install**: +```bash +npm install tailwindcss-animate # Deprecated - v3 only +``` + +**Why**: `tw-animate-css` is the official v4-compatible replacement for animations, required by shadcn/ui components. + +**Reference**: https://ui.shadcn.com/docs/tailwind-v4 + +--- + +## Tailwind v4 Plugins + +Tailwind v4 supports official plugins using the `@plugin` directive in CSS. + +### Official Plugins (Tailwind Labs) + +#### Typography Plugin - Style Markdown/CMS Content + +**When to use:** Displaying blog posts, documentation, or any HTML from Markdown/CMS. + +**Installation:** +```bash +pnpm add -D @tailwindcss/typography +``` + +**Configuration (v4 syntax):** +```css +/* src/index.css */ +@import "tailwindcss"; +@plugin "@tailwindcss/typography"; +``` + +**Usage:** +```html +
+ {{ markdown_content }} +
+``` + +**Available classes:** +- `prose` - Base typography styles +- `prose-sm`, `prose-base`, `prose-lg`, `prose-xl`, `prose-2xl` - Size variants +- `dark:prose-invert` - Dark mode styles + +--- + +#### Forms Plugin - Reset Form Element Styles + +**When to use:** Building custom forms without shadcn/ui components, or need consistent cross-browser form styling. + +**Installation:** +```bash +pnpm add -D @tailwindcss/forms +``` + +**Configuration (v4 syntax):** +```css +/* src/index.css */ +@import "tailwindcss"; +@plugin "@tailwindcss/forms"; +``` + +**What it does:** +- Resets browser default form styles +- Makes form elements styleable with Tailwind utilities +- Fixes cross-browser inconsistencies for inputs, selects, checkboxes, radios + +**Note:** Less critical for shadcn/ui users (they have pre-styled form components), but still useful for basic forms. + +--- + +### Common Plugin Errors + +These errors happen when using v3 syntax in v4 projects: + +**❌ WRONG (v3 config file syntax):** +```js +// tailwind.config.js +module.exports = { + plugins: [require('@tailwindcss/typography')] +} +``` + +**❌ WRONG (@import instead of @plugin):** +```css +@import "@tailwindcss/typography"; /* Doesn't work */ +``` + +**✅ CORRECT (v4 @plugin directive):** +```css +/* src/index.css */ +@import "tailwindcss"; +@plugin "@tailwindcss/typography"; +@plugin "@tailwindcss/forms"; +``` + +--- + +### Built-in Features (No Plugin Needed) + +**Container queries** are built into Tailwind v4 core - no plugin needed: + +```tsx +
+
+ Responds to container width, not viewport +
+
+``` + +**❌ Don't install:** `@tailwindcss/container-queries` (deprecated, now core feature) + +--- + +## Reference Documentation + +For deeper understanding, see: + +- **architecture.md** - Deep dive into the 4-step pattern +- **dark-mode.md** - Complete dark mode implementation +- **common-gotchas.md** - All the ways it can break (and fixes) +- **migration-guide.md** - Migrating hardcoded colors to CSS variables + +--- + +## Official Documentation + +- **shadcn/ui Vite Setup**: https://ui.shadcn.com/docs/installation/vite +- **shadcn/ui Tailwind v4 Guide**: https://ui.shadcn.com/docs/tailwind-v4 +- **shadcn/ui Dark Mode (Vite)**: https://ui.shadcn.com/docs/dark-mode/vite +- **Tailwind v4 Docs**: https://tailwindcss.com/docs +- **shadcn/ui Theming**: https://ui.shadcn.com/docs/theming + +--- + +## Production Example + +This skill is based on the WordPress Auditor project: +- **Live**: https://wordpress-auditor.webfonts.workers.dev +- **Stack**: Vite + React 19 + Tailwind v4 + shadcn/ui + Cloudflare Workers +- **Dark Mode**: Full system/light/dark support +- **Version**: Tailwind v4.1.14 + shadcn/ui latest (Oct 2025) + +All patterns in this skill have been validated in production. + +--- + +**Questions? Issues?** + +1. Check `reference/common-gotchas.md` first +2. Verify all steps in the 4-step architecture +3. Ensure `components.json` has `"config": ""` +4. Delete `tailwind.config.ts` if it exists +5. Check official docs: https://ui.shadcn.com/docs/tailwind-v4 diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..e057a3c --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,85 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:jezweb/claude-skills:skills/tailwind-v4-shadcn", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "3a4ea8133386093e3aaeb81830b149674e1a8e83", + "treeHash": "a5dcad101e5c0e16d791c9cb2c005ba605f50d0d86217e272252a3026fd47616", + "generatedAt": "2025-11-28T10:18:58.994843Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "tailwind-v4-shadcn", + "description": "Set up Tailwind v4 with shadcn/ui using @theme inline pattern and CSS variable architecture. Four-step mandatory pattern: define CSS variables at root, map to Tailwind utilities, apply base styles, get automatic dark mode. Use when: initializing React projects with Tailwind v4, setting up shadcn/ui dark mode, or fixing colors not working, theme not applying, CSS variables broken, tw-animate-css errors, or migrating from v3.", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "d8a327f4da5d3c0bc4fb7d3ff4e45cb9398833619dee5bac7cf6c9be72ccaa30" + }, + { + "path": "SKILL.md", + "sha256": "5cdeded8fc9ab0f6634b1c9a2656ba45f4045efef87d3979447129dc97221b65" + }, + { + "path": "references/architecture.md", + "sha256": "da2f6f3eeb32717728159c6133c6e9ebe83815ae41371a83a8a38eeff461f344" + }, + { + "path": "references/migration-guide.md", + "sha256": "4580c5c918d71e3ac518d236a035bd627a5e99b254bb93f05eed67fc5ae53b42" + }, + { + "path": "references/dark-mode.md", + "sha256": "3392626694dcf970bdadd4537b425de5825fc0a2a8725474110f246528c87e34" + }, + { + "path": "references/common-gotchas.md", + "sha256": "b42d35ce713ae2f7ff0a77072cbbb70f89428f01b8e0ac77ee325368841fb835" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "94ab042d7431b148cfd14aaed3b60839c319599f9611cc0adb350283f0062aa8" + }, + { + "path": "templates/theme-provider.tsx", + "sha256": "df87540b95499b272216c7b41a5d133bf0a449eb25049128f8a032ccd0e65580" + }, + { + "path": "templates/tsconfig.app.json", + "sha256": "8d20a8ea391818bebc7436709dd00e3e5422db9f811868127f1584efe8ff3047" + }, + { + "path": "templates/utils.ts", + "sha256": "51bbf14cd1f84f49aab2e0dbee420137015d56b6677bb439e83a908cd292cce1" + }, + { + "path": "templates/index.css", + "sha256": "3bb8a7a2daf4a9507f8c27deeb626b0d51166d018f7aa0d1d656dd8f652a527a" + }, + { + "path": "templates/components.json", + "sha256": "38d09e0be58f15412e01057114a5d9d4b3582200378441a3c74fd490c37b9a4b" + }, + { + "path": "templates/vite.config.ts", + "sha256": "37171894fd3d8b7709d1ea15c4db5f4ba0faa1b58d7c7418d11ce4e30eb5562d" + } + ], + "dirSha256": "a5dcad101e5c0e16d791c9cb2c005ba605f50d0d86217e272252a3026fd47616" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/references/architecture.md b/references/architecture.md new file mode 100644 index 0000000..90f6f88 --- /dev/null +++ b/references/architecture.md @@ -0,0 +1,206 @@ +# Tailwind v4 + shadcn/ui Theming Architecture + +## The Four-Step Pattern + +Tailwind v4 requires a specific architecture for CSS variable-based theming. This pattern is **mandatory** - skipping or modifying steps will break your theme. + +### Step 1: Define CSS Variables at Root Level + +```css +:root { + --background: hsl(0 0% 100%); + --foreground: hsl(222.2 84% 4.9%); + /* ... more colors */ +} + +.dark { + --background: hsl(222.2 84% 4.9%); + --foreground: hsl(210 40% 98%); + /* ... dark mode colors */ +} +``` + +**Critical Rules:** +- ✅ Define at root level (NOT inside `@layer base`) +- ✅ Use `hsl()` wrapper on all color values +- ✅ Use `.dark` for dark mode overrides (NOT `.dark { @theme { } }`) +- ❌ Never put `:root` or `.dark` inside `@layer base` + +### Step 2: Map Variables to Tailwind Utilities + +```css +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + /* ... map all CSS variables */ +} +``` + +**Why This Is Required:** +- Tailwind v4 doesn't read `tailwind.config.ts` for colors +- `@theme inline` generates utility classes (`bg-background`, `text-foreground`) +- Without this, utilities like `bg-primary` won't exist + +### Step 3: Apply Base Styles + +```css +@layer base { + body { + background-color: var(--background); /* NO hsl() wrapper here */ + color: var(--foreground); + } +} +``` + +**Critical Rules:** +- ✅ Reference variables directly: `var(--background)` +- ❌ Never double-wrap: `hsl(var(--background))` (already has hsl) + +### Step 4: Result - Automatic Dark Mode + +With this architecture: +- `
` works automatically +- No `dark:` variants needed in components +- Theme switches via `.dark` class on `` +- Single source of truth for all colors + +--- + +## Why This Architecture Works + +### Color Variable Flow + +``` +CSS Variable Definition → @theme inline Mapping → Tailwind Utility Class +--background → --color-background → bg-background +(with hsl() wrapper) (references variable) (generated class) +``` + +### Dark Mode Switching + +``` +ThemeProvider toggles `.dark` class on + ↓ +CSS variables update automatically (.dark overrides) + ↓ +Tailwind utilities reference updated variables + ↓ +UI updates without re-render +``` + +--- + +## Common Mistakes + +### ❌ Mistake 1: Variables Inside @layer base + +```css +/* WRONG */ +@layer base { + :root { + --background: hsl(0 0% 100%); + } +} +``` + +**Why It Fails:** Tailwind v4 strips CSS outside `@theme`/`@layer`, but `:root` must be at root level to persist. + +### ❌ Mistake 2: Using .dark { @theme { } } + +```css +/* WRONG */ +@theme { + --color-primary: hsl(0 0% 0%); +} + +.dark { + @theme { + --color-primary: hsl(0 0% 100%); + } +} +``` + +**Why It Fails:** Tailwind v4 doesn't support nested `@theme` directives. + +### ❌ Mistake 3: Double hsl() Wrapping + +```css +/* WRONG */ +@layer base { + body { + background-color: hsl(var(--background)); + } +} +``` + +**Why It Fails:** `--background` already contains `hsl()`, results in `hsl(hsl(...))`. + +### ❌ Mistake 4: Config-Based Colors + +```typescript +// WRONG (tailwind.config.ts) +export default { + theme: { + extend: { + colors: { + primary: 'hsl(var(--primary))' + } + } + } +} +``` + +**Why It Fails:** Tailwind v4 completely ignores `theme.extend.colors` in config files. + +--- + +## Best Practices + +### 1. Semantic Color Names + +Use semantic names, not color values: +```css +--primary /* ✅ Semantic */ +--blue-500 /* ❌ Not semantic */ +``` + +### 2. Foreground Pairing + +Every background color needs a foreground: +```css +--primary: hsl(...); +--primary-foreground: hsl(...); +``` + +### 3. WCAG Contrast Ratios + +Ensure proper contrast: +- Normal text: 4.5:1 minimum +- Large text: 3:1 minimum +- UI components: 3:1 minimum + +### 4. Chart Colors + +Charts need separate variables (don't use hsl wrapper in components): +```css +:root { + --chart-1: hsl(12 76% 61%); +} + +@theme inline { + --color-chart-1: var(--chart-1); +} +``` + +Use in components: +```tsx +
+``` + +--- + +## Official Documentation + +- shadcn/ui Tailwind v4 Guide: https://ui.shadcn.com/docs/tailwind-v4 +- Tailwind v4 Docs: https://tailwindcss.com/docs +- shadcn/ui Theming: https://ui.shadcn.com/docs/theming diff --git a/references/common-gotchas.md b/references/common-gotchas.md new file mode 100644 index 0000000..f876df1 --- /dev/null +++ b/references/common-gotchas.md @@ -0,0 +1,469 @@ +# Common Gotchas & Solutions + +## Critical Failures (Will Break Your Build) + +### 1. `:root` Inside `@layer base` + +❌ **WRONG:** +```css +@layer base { + :root { + --background: hsl(0 0% 100%); + } +} +``` + +✅ **CORRECT:** +```css +:root { + --background: hsl(0 0% 100%); +} + +@layer base { + body { + background-color: var(--background); + } +} +``` + +**Why:** Tailwind v4 strips CSS outside `@theme`/`@layer`, but `:root` must be at root level. + +--- + +### 2. Nested `@theme` Directive + +❌ **WRONG:** +```css +@theme { + --color-primary: hsl(0 0% 0%); +} + +.dark { + @theme { + --color-primary: hsl(0 0% 100%); + } +} +``` + +✅ **CORRECT:** +```css +:root { + --primary: hsl(0 0% 0%); +} + +.dark { + --primary: hsl(0 0% 100%); +} + +@theme inline { + --color-primary: var(--primary); +} +``` + +**Why:** Tailwind v4 doesn't support `@theme` inside selectors. + +--- + +### 3. Double `hsl()` Wrapping + +❌ **WRONG:** +```css +@layer base { + body { + background-color: hsl(var(--background)); + } +} +``` + +✅ **CORRECT:** +```css +@layer base { + body { + background-color: var(--background); /* Already has hsl() */ + } +} +``` + +**Why:** Variables already contain `hsl()`, double-wrapping creates `hsl(hsl(...))`. + +--- + +### 4. Colors in `tailwind.config.ts` + +❌ **WRONG:** +```typescript +// tailwind.config.ts +export default { + theme: { + extend: { + colors: { + primary: 'hsl(var(--primary))' + } + } + } +} +``` + +✅ **CORRECT:** +```typescript +// Delete tailwind.config.ts entirely OR leave it empty +export default {} + +// components.json +{ + "tailwind": { + "config": "" // ← Empty string + } +} +``` + +**Why:** Tailwind v4 completely ignores `theme.extend.colors`. + +--- + +### 5. Missing `@theme inline` Mapping + +❌ **WRONG:** +```css +:root { + --background: hsl(0 0% 100%); +} + +/* No @theme inline block */ +``` + +Result: `bg-background` class doesn't exist + +✅ **CORRECT:** +```css +:root { + --background: hsl(0 0% 100%); +} + +@theme inline { + --color-background: var(--background); +} +``` + +**Why:** `@theme inline` generates the utility classes. + +--- + +## Configuration Gotchas + +### 6. Wrong components.json Config + +❌ **WRONG:** +```json +{ + "tailwind": { + "config": "tailwind.config.ts" // ← No! + } +} +``` + +✅ **CORRECT:** +```json +{ + "tailwind": { + "config": "" // ← Empty for v4 + } +} +``` + +--- + +### 7. Using PostCSS Instead of Vite Plugin + +❌ **WRONG:** +```typescript +// vite.config.ts +export default defineConfig({ + css: { + postcss: './postcss.config.js' // Old v3 way + } +}) +``` + +✅ **CORRECT:** +```typescript +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [react(), tailwindcss()] // v4 way +}) +``` + +--- + +### 8. Missing Path Aliases + +❌ **WRONG:** +```typescript +// tsconfig.json has no paths +import { Button } from '../../components/ui/button' +``` + +✅ **CORRECT:** +```json +// tsconfig.app.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +```typescript +import { Button } from '@/components/ui/button' +``` + +--- + +## Color System Gotchas + +### 9. Using `dark:` Variants for Semantic Colors + +❌ **WRONG:** +```tsx +
+``` + +✅ **CORRECT:** +```tsx +
+``` + +**Why:** With proper CSS variable setup, `bg-primary` automatically responds to theme. + +--- + +### 10. Hardcoded Color Values + +❌ **WRONG:** +```tsx +
+``` + +✅ **CORRECT:** +```tsx +
{/* Or bg-info, bg-success, etc. */} +``` + +**Why:** Semantic tokens enable theme switching and reduce repetition. + +--- + +## Component Gotchas + +### 11. Missing `cn()` Utility + +❌ **WRONG:** +```tsx +
+``` + +✅ **CORRECT:** +```tsx +import { cn } from '@/lib/utils' +
+``` + +**Why:** `cn()` properly merges and deduplicates Tailwind classes. + +--- + +### 12. Empty String in Radix Select + +❌ **WRONG:** +```tsx +Select an option +``` + +✅ **CORRECT:** +```tsx +Select an option +``` + +**Why:** Radix UI Select doesn't allow empty string values. + +--- + +## Installation Gotchas + +### 13. Wrong Tailwind Package + +❌ **WRONG:** +```bash +npm install tailwindcss@^3.4.0 # v3 +``` + +✅ **CORRECT:** +```bash +npm install tailwindcss@^4.1.0 # v4 +npm install @tailwindcss/vite +``` + +--- + +### 14. Missing Dependencies + +❌ **WRONG:** +```json +{ + "dependencies": { + "tailwindcss": "^4.1.0" + // Missing @tailwindcss/vite + } +} +``` + +✅ **CORRECT:** +```json +{ + "dependencies": { + "tailwindcss": "^4.1.0", + "@tailwindcss/vite": "^4.1.0", + "clsx": "^2.1.1", + "tailwind-merge": "^3.3.1" + }, + "devDependencies": { + "@types/node": "^24.0.0" + } +} +``` + +--- + +### 17. tw-animate-css Import Error (REAL-WORLD ISSUE) + +❌ **WRONG:** +```bash +npm install tailwindcss-animate # Deprecated package +``` + +```css +@import "tw-animate-css"; # Package doesn't exist in v4 +``` + +✅ **CORRECT:** +```bash +# Don't install tailwindcss-animate at all +# Use native CSS animations or @tailwindcss/motion +``` + +**Why:** +- `tailwindcss-animate` is deprecated in Tailwind v4 +- Causes import errors during build +- shadcn/ui docs may still reference it (outdated) +- The skill handles animations differently in v4 + +**Impact:** Build failure, requires manual CSS file cleanup + +--- + +### 18. Duplicate @layer base After shadcn init (REAL-WORLD ISSUE) + +❌ **WRONG:** +```css +/* After running shadcn init, you might have: */ +@layer base { + body { + background-color: var(--background); + } +} + +@layer base { /* ← Duplicate added by shadcn init */ + * { + border-color: hsl(var(--border)); + } +} +``` + +✅ **CORRECT:** +```css +/* Merge into single @layer base block */ +@layer base { + * { + border-color: var(--border); + } + + body { + background-color: var(--background); + color: var(--foreground); + } +} +``` + +**Why:** +- `shadcn init` adds its own `@layer base` block +- Results in duplicate layer declarations +- Can cause unexpected CSS priority issues +- Easy to miss during setup + +**Prevention:** +- Check `src/index.css` immediately after running `shadcn init` +- Merge any duplicate `@layer base` blocks +- Keep only one base layer section + +**Impact:** CSS priority issues, harder to debug styling problems + +--- + +## Testing Gotchas + +### 15. Not Testing Both Themes + +❌ **WRONG:** +Only testing in light mode + +✅ **CORRECT:** +Test in: +- Light mode +- Dark mode +- System mode +- Both initial load and toggle + +--- + +### 16. Not Checking Contrast + +❌ **WRONG:** +Colors look good but fail WCAG + +✅ **CORRECT:** +- Use browser DevTools Lighthouse +- Check contrast ratios (4.5:1 minimum) +- Test with actual users + +--- + +## Quick Diagnosis + +**Symptoms → Likely Cause:** + +| Symptom | Likely Cause | +|---------|-------------| +| `bg-primary` doesn't work | Missing `@theme inline` mapping | +| Colors all black/white | Double `hsl()` wrapping | +| Dark mode not switching | Missing ThemeProvider | +| Build fails | `tailwind.config.ts` exists with theme config | +| Text invisible | Wrong contrast colors | +| `@/` imports fail | Missing path aliases in tsconfig | + +--- + +## Prevention Checklist + +Before deploying: +- [ ] No `tailwind.config.ts` file (or it's empty) +- [ ] `components.json` has `"config": ""` +- [ ] All colors have `hsl()` wrapper in `:root` +- [ ] `@theme inline` maps all variables +- [ ] `@layer base` doesn't wrap `:root` +- [ ] Theme provider wraps app +- [ ] Tested in both light and dark modes +- [ ] All text has sufficient contrast diff --git a/references/dark-mode.md b/references/dark-mode.md new file mode 100644 index 0000000..5c5008e --- /dev/null +++ b/references/dark-mode.md @@ -0,0 +1,239 @@ +# Dark Mode Implementation + +## Overview + +Tailwind v4 + shadcn/ui dark mode requires: +1. `ThemeProvider` component to manage state +2. `.dark` class toggling on `` element +3. localStorage persistence +4. System theme detection + +--- + +## ThemeProvider Component + +### Full Implementation + +```typescript +// src/components/theme-provider.tsx +import { createContext, useContext, useEffect, useState, ReactNode } from 'react' + +type Theme = 'dark' | 'light' | 'system' + +type ThemeProviderProps = { + children: ReactNode + defaultTheme?: Theme + storageKey?: string +} + +type ThemeProviderState = { + theme: Theme + setTheme: (theme: Theme) => void +} + +const initialState: ThemeProviderState = { + theme: 'system', + setTheme: () => null, +} + +const ThemeProviderContext = createContext(initialState) + +export function ThemeProvider({ + children, + defaultTheme = 'system', + storageKey = 'vite-ui-theme', + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState(() => { + try { + return (localStorage.getItem(storageKey) as Theme) || defaultTheme + } catch (e) { + return defaultTheme + } + }) + + useEffect(() => { + const root = window.document.documentElement + root.classList.remove('light', 'dark') + + if (theme === 'system') { + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') + .matches ? 'dark' : 'light' + root.classList.add(systemTheme) + return + } + + root.classList.add(theme) + }, [theme]) + + const value = { + theme, + setTheme: (theme: Theme) => { + try { + localStorage.setItem(storageKey, theme) + } catch (e) { + console.warn('Storage unavailable') + } + setTheme(theme) + }, + } + + return ( + + {children} + + ) +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext) + if (context === undefined) + throw new Error('useTheme must be used within a ThemeProvider') + return context +} +``` + +### Wrap Your App + +```typescript +// src/main.tsx +import { ThemeProvider } from '@/components/theme-provider' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , +) +``` + +--- + +## Theme Toggle Component + +### Using shadcn/ui Dropdown Menu + +```bash +pnpm dlx shadcn@latest add dropdown-menu +``` + +```typescript +// src/components/mode-toggle.tsx +import { Moon, Sun } from "lucide-react" +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { useTheme } from "@/components/theme-provider" + +export function ModeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ) +} +``` + +--- + +## How It Works + +### Theme Flow + +``` +User selects theme → setTheme() called + ↓ +Save to localStorage + ↓ +Update state + ↓ +useEffect triggers + ↓ +Remove existing classes (.light, .dark) + ↓ +Add new class to + ↓ +CSS variables update (.dark overrides :root) + ↓ +UI updates automatically +``` + +### System Theme Detection + +```typescript +if (theme === 'system') { + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') + .matches ? 'dark' : 'light' + root.classList.add(systemTheme) +} +``` + +This respects the user's OS preference when "System" is selected. + +--- + +## Common Issues + +### Issue: Dark mode not switching + +**Cause:** Theme provider not wrapping app +**Fix:** Ensure `` wraps your app in `main.tsx` + +### Issue: Theme resets on page refresh + +**Cause:** localStorage not working +**Fix:** Check browser privacy settings, add sessionStorage fallback + +### Issue: Flash of wrong theme on load + +**Cause:** Theme applied after initial render +**Fix:** Add inline script to `index.html` (advanced) + +### Issue: Icons not changing + +**Cause:** CSS transitions not working +**Fix:** Verify icon classes use `dark:` variants for animations + +--- + +## Testing Checklist + +- [ ] Light mode displays correctly +- [ ] Dark mode displays correctly +- [ ] System mode respects OS setting +- [ ] Theme persists after page refresh +- [ ] Toggle component shows current state +- [ ] All text has proper contrast +- [ ] No flash of wrong theme on load +- [ ] Works in incognito mode (graceful fallback) + +--- + +## Official Documentation + +- shadcn/ui Dark Mode (Vite): https://ui.shadcn.com/docs/dark-mode/vite +- Tailwind Dark Mode: https://tailwindcss.com/docs/dark-mode diff --git a/references/migration-guide.md b/references/migration-guide.md new file mode 100644 index 0000000..8ce1a0a --- /dev/null +++ b/references/migration-guide.md @@ -0,0 +1,313 @@ +# Migration Guide: Hardcoded Colors → CSS Variables + +## Overview + +This guide helps you migrate from hardcoded Tailwind colors (`bg-blue-600`) to semantic CSS variables (`bg-primary`). + +**Benefits:** +- Automatic dark mode support +- Consistent color usage +- Single source of truth +- Easy theme customization +- Better accessibility + +--- + +## Semantic Color Mapping + +| Hardcoded Color | CSS Variable | Use Case | +|----------------|--------------|----------| +| `bg-red-*` / `text-red-*` | `bg-destructive` / `text-destructive` | Critical issues, errors, delete actions | +| `bg-green-*` / `text-green-*` | `bg-success` / `text-success` | Success states, positive metrics | +| `bg-yellow-*` / `text-yellow-*` | `bg-warning` / `text-warning` | Warnings, moderate issues | +| `bg-blue-*` / `text-blue-*` | `bg-info` or `bg-primary` | Info boxes, primary actions | +| `bg-gray-*` / `text-gray-*` | `bg-muted` / `text-muted-foreground` | Backgrounds, secondary text | +| `bg-purple-*` | `bg-info` | Remove - use blue instead | +| `bg-orange-*` | `bg-warning` | Remove - use yellow instead | +| `bg-emerald-*` | `bg-success` | Remove - use green instead | + +--- + +## Migration Patterns + +### Pattern 1: Solid Backgrounds + +❌ **Before:** +```tsx +
+``` + +✅ **After:** +```tsx +
+``` + +**Note:** `/10` creates 10% opacity + +--- + +### Pattern 2: Borders + +❌ **Before:** +```tsx +
+``` + +✅ **After:** +```tsx +
+``` + +--- + +### Pattern 3: Text Colors + +❌ **Before:** +```tsx + +``` + +✅ **After:** +```tsx + +``` + +--- + +### Pattern 4: Icons + +❌ **Before:** +```tsx + +``` + +✅ **After:** +```tsx + +``` + +--- + +### Pattern 5: Gradients + +❌ **Before:** +```tsx +
+``` + +✅ **After:** +```tsx +
+``` + +--- + +## Step-by-Step Migration + +### Step 1: Add Semantic Colors to CSS + +```css +/* src/index.css */ +:root { + /* Add these if not already present */ + --destructive: hsl(0 84.2% 60.2%); + --destructive-foreground: hsl(210 40% 98%); + --success: hsl(142.1 76.2% 36.3%); + --success-foreground: hsl(210 40% 98%); + --warning: hsl(38 92% 50%); + --warning-foreground: hsl(222.2 47.4% 11.2%); + --info: hsl(221.2 83.2% 53.3%); + --info-foreground: hsl(210 40% 98%); +} + +.dark { + --destructive: hsl(0 62.8% 30.6%); + --destructive-foreground: hsl(210 40% 98%); + --success: hsl(142.1 70.6% 45.3%); + --success-foreground: hsl(222.2 47.4% 11.2%); + --warning: hsl(38 92% 55%); + --warning-foreground: hsl(222.2 47.4% 11.2%); + --info: hsl(217.2 91.2% 59.8%); + --info-foreground: hsl(222.2 47.4% 11.2%); +} + +@theme inline { + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); + --color-warning: var(--warning); + --color-warning-foreground: var(--warning-foreground); + --color-info: var(--info); + --color-info-foreground: var(--info-foreground); +} +``` + +### Step 2: Find Hardcoded Colors + +```bash +# Search for background colors +grep -r "bg-\(red\|yellow\|blue\|green\|purple\|orange\|pink\|emerald\)-[0-9]" src/ + +# Search for text colors +grep -r "text-\(red\|yellow\|blue\|green\|purple\|orange\|pink\|emerald\)-[0-9]" src/ + +# Search for border colors +grep -r "border-\(red\|yellow\|blue\|green\|purple\|orange\|pink\|emerald\)-[0-9]" src/ +``` + +### Step 3: Replace Component by Component + +Start with high-impact components: +1. Buttons +2. Badges +3. Alert boxes +4. Status indicators +5. Cards + +### Step 4: Test Both Themes + +After each component: +- [ ] Check light mode appearance +- [ ] Check dark mode appearance +- [ ] Verify text contrast +- [ ] Test hover/active states + +--- + +## Example: Badge Component + +❌ **Before:** +```tsx +const severityConfig = { + critical: { + color: 'text-red-500', + bg: 'bg-red-500/10', + border: 'border-red-500/20', + }, + warning: { + color: 'text-yellow-500', + bg: 'bg-yellow-500/10', + border: 'border-yellow-500/20', + }, + info: { + color: 'text-blue-500', + bg: 'bg-blue-500/10', + border: 'border-blue-500/20', + } +} +``` + +✅ **After:** +```tsx +const severityConfig = { + critical: { + color: 'text-destructive', + bg: 'bg-destructive/10', + border: 'border-destructive/20', + }, + warning: { + color: 'text-warning', + bg: 'bg-warning/10', + border: 'border-warning/20', + }, + info: { + color: 'text-info', + bg: 'bg-info/10', + border: 'border-info/20', + } +} +``` + +--- + +## Testing Checklist + +After migration: +- [ ] All severity levels (critical/warning/info) visually distinct +- [ ] Text has proper contrast in both light and dark modes +- [ ] No hardcoded color classes remain +- [ ] Hover states work correctly +- [ ] Gradients render smoothly +- [ ] Icons are visible and colored correctly +- [ ] Borders are visible +- [ ] No visual regressions + +--- + +## Verification Commands + +```bash +# Should return 0 results when migration complete +grep -r "text-red-[0-9]" src/components/ +grep -r "bg-blue-[0-9]" src/components/ +grep -r "border-green-[0-9]" src/components/ + +# Verify semantic colors are used +grep -r "bg-destructive" src/components/ +grep -r "text-success" src/components/ +``` + +--- + +## Performance Impact + +**Before:** Every component has `dark:` variants +```tsx +
+``` + +**After:** Single class, CSS handles switching +```tsx +
+``` + +**Result:** +- 60% fewer CSS classes in markup +- Smaller HTML payload +- Faster rendering +- Easier to maintain + +--- + +## Common Pitfalls + +### 1. Forgetting to Map in @theme inline + +Variables defined in `:root` but not mapped → utilities don't exist + +### 2. Wrong Opacity Syntax + +❌ `bg-success-10` (doesn't work) +✅ `bg-success/10` (correct) + +### 3. Mixing Approaches + +Don't mix hardcoded and semantic in same component - choose one approach. + +### 4. Not Testing Dark Mode + +Always test both themes during migration. + +--- + +## Rollback Plan + +If migration causes issues: + +1. Keep original components in git history +2. Use feature flags to toggle new theme +3. Test with subset of users first +4. Have monitoring for visual regressions + +--- + +## Further Customization + +After migration, you can easily: +- Add new semantic colors +- Create theme variants (high contrast, etc.) +- Support multiple brand themes +- Implement user-selectable color schemes + +All by editing CSS variables - no component changes needed! diff --git a/templates/components.json b/templates/components.json new file mode 100644 index 0000000..e71fe10 --- /dev/null +++ b/templates/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/templates/index.css b/templates/index.css new file mode 100644 index 0000000..8f20cc9 --- /dev/null +++ b/templates/index.css @@ -0,0 +1,140 @@ +@import "tailwindcss"; + +/* + Tailwind v4 + shadcn/ui Dark Mode Pattern + Based on: https://ui.shadcn.com/docs/tailwind-v4 + + Key Pattern: + 1. Define CSS variables at root level (NOT in @layer base) + 2. Use .dark for dark mode overrides (NOT in @theme) + 3. Use @theme inline to map variables to Tailwind utilities + 4. All color values must use hsl() wrapper +*/ + +/* Light mode colors - Define at root level */ +:root { + --background: hsl(0 0% 100%); + --foreground: hsl(222.2 84% 4.9%); + --card: hsl(0 0% 100%); + --card-foreground: hsl(222.2 84% 4.9%); + --popover: hsl(0 0% 100%); + --popover-foreground: hsl(222.2 84% 4.9%); + --primary: hsl(221.2 83.2% 53.3%); + --primary-foreground: hsl(210 40% 98%); + --secondary: hsl(210 40% 96.1%); + --secondary-foreground: hsl(222.2 47.4% 11.2%); + --muted: hsl(210 40% 96.1%); + --muted-foreground: hsl(215.4 16.3% 46.9%); + --accent: hsl(210 40% 96.1%); + --accent-foreground: hsl(222.2 47.4% 11.2%); + --destructive: hsl(0 84.2% 60.2%); + --destructive-foreground: hsl(210 40% 98%); + --success: hsl(142.1 76.2% 36.3%); + --success-foreground: hsl(210 40% 98%); + --warning: hsl(38 92% 50%); + --warning-foreground: hsl(222.2 47.4% 11.2%); + --info: hsl(221.2 83.2% 53.3%); + --info-foreground: hsl(210 40% 98%); + --border: hsl(214.3 31.8% 91.4%); + --input: hsl(214.3 31.8% 91.4%); + --ring: hsl(221.2 83.2% 53.3%); + --radius: 0.5rem; + + /* Chart colors */ + --chart-1: hsl(12 76% 61%); + --chart-2: hsl(173 58% 39%); + --chart-3: hsl(197 37% 24%); + --chart-4: hsl(43 74% 66%); + --chart-5: hsl(27 87% 67%); +} + +/* Dark mode colors - Plain CSS overrides (NOT in @theme) */ +.dark { + --background: hsl(222.2 84% 4.9%); + --foreground: hsl(210 40% 98%); + --card: hsl(222.2 84% 4.9%); + --card-foreground: hsl(210 40% 98%); + --popover: hsl(222.2 84% 4.9%); + --popover-foreground: hsl(210 40% 98%); + --primary: hsl(217.2 91.2% 59.8%); + --primary-foreground: hsl(222.2 47.4% 11.2%); + --secondary: hsl(217.2 32.6% 17.5%); + --secondary-foreground: hsl(210 40% 98%); + --muted: hsl(217.2 32.6% 17.5%); + --muted-foreground: hsl(215 20.2% 65.1%); + --accent: hsl(217.2 32.6% 17.5%); + --accent-foreground: hsl(210 40% 98%); + --destructive: hsl(0 62.8% 30.6%); + --destructive-foreground: hsl(210 40% 98%); + --success: hsl(142.1 70.6% 45.3%); + --success-foreground: hsl(222.2 47.4% 11.2%); + --warning: hsl(38 92% 55%); + --warning-foreground: hsl(222.2 47.4% 11.2%); + --info: hsl(217.2 91.2% 59.8%); + --info-foreground: hsl(222.2 47.4% 11.2%); + --border: hsl(217.2 32.6% 17.5%); + --input: hsl(217.2 32.6% 17.5%); + --ring: hsl(224.3 76.3% 48%); + + /* Chart colors for dark mode */ + --chart-1: hsl(220 70% 50%); + --chart-2: hsl(160 60% 45%); + --chart-3: hsl(30 80% 55%); + --chart-4: hsl(280 65% 60%); + --chart-5: hsl(340 75% 55%); +} + +/* Map CSS variables to Tailwind theme with @theme inline */ +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); + --color-warning: var(--warning); + --color-warning-foreground: var(--warning-foreground); + --color-info: var(--info); + --color-info-foreground: var(--info-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + + /* Border radius tokens */ + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); +} + +/* Base styles in @layer base */ +@layer base { + * { + border-color: var(--border); + } + + body { + margin: 0; + background-color: var(--background); + color: var(--foreground); + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } +} diff --git a/templates/theme-provider.tsx b/templates/theme-provider.tsx new file mode 100644 index 0000000..98126d7 --- /dev/null +++ b/templates/theme-provider.tsx @@ -0,0 +1,92 @@ +import { createContext, useContext, useEffect, useState, ReactNode } from 'react' + +type Theme = 'dark' | 'light' | 'system' + +type ThemeProviderProps = { + children: ReactNode + defaultTheme?: Theme + storageKey?: string +} + +type ThemeProviderState = { + theme: Theme + setTheme: (theme: Theme) => void +} + +const initialState: ThemeProviderState = { + theme: 'system', + setTheme: () => null, +} + +const ThemeProviderContext = createContext(initialState) + +export function ThemeProvider({ + children, + defaultTheme = 'system', + storageKey = 'vite-ui-theme', + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState(() => { + // Try localStorage first, fall back to sessionStorage, then default + try { + return (localStorage.getItem(storageKey) as Theme) || + (sessionStorage.getItem(storageKey) as Theme) || + defaultTheme + } catch (e) { + // Storage unavailable (incognito/privacy mode) - use default + return defaultTheme + } + }) + + useEffect(() => { + const root = window.document.documentElement + + root.classList.remove('light', 'dark') + + if (theme === 'system') { + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') + .matches + ? 'dark' + : 'light' + + root.classList.add(systemTheme) + return + } + + root.classList.add(theme) + }, [theme]) + + const value = { + theme, + setTheme: (theme: Theme) => { + // Try to persist to localStorage, fall back to sessionStorage + try { + localStorage.setItem(storageKey, theme) + } catch (e) { + // localStorage unavailable (incognito) - use sessionStorage + try { + sessionStorage.setItem(storageKey, theme) + } catch (err) { + // Both unavailable - just update state without persistence + console.warn('Storage unavailable, theme preference will not persist') + } + } + setTheme(theme) + }, + } + + return ( + + {children} + + ) +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext) + + if (context === undefined) + throw new Error('useTheme must be used within a ThemeProvider') + + return context +} diff --git a/templates/tsconfig.app.json b/templates/tsconfig.app.json new file mode 100644 index 0000000..9c0b41b --- /dev/null +++ b/templates/tsconfig.app.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Path aliases */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/templates/utils.ts b/templates/utils.ts new file mode 100644 index 0000000..d084cca --- /dev/null +++ b/templates/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/templates/vite.config.ts b/templates/vite.config.ts new file mode 100644 index 0000000..3409417 --- /dev/null +++ b/templates/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import path from 'path' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + react(), + tailwindcss(), + ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +})