Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:48:52 +08:00
commit 6ec3196ecc
434 changed files with 125248 additions and 0 deletions

View File

@@ -0,0 +1,373 @@
# shadcn/ui Theming & Customization
Theme configuration, CSS variables, dark mode, and component customization.
## Dark Mode Setup
### Next.js App Router
**1. Install next-themes:**
```bash
npm install next-themes
```
**2. Create theme provider:**
```tsx
// components/theme-provider.tsx
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
```
**3. Wrap app:**
```tsx
// app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}
```
**4. Theme toggle component:**
```tsx
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}
```
### Vite / Other Frameworks
Use similar approach with next-themes or implement custom solution:
```javascript
// Store preference
function toggleDarkMode() {
const isDark = document.documentElement.classList.toggle('dark')
localStorage.setItem('theme', isDark ? 'dark' : 'light')
}
// Initialize on load
if (localStorage.theme === 'dark' ||
(!('theme' in localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark')
}
```
## CSS Variable System
shadcn/ui uses CSS variables for theming. Variables defined in `globals.css`:
```css
@layer base {
: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%;
}
}
```
### Color Format
Values use HSL format without `hsl()` wrapper for better opacity control:
```css
--primary: 222.2 47.4% 11.2%; /* H S L */
```
Usage in Tailwind:
```css
background: hsl(var(--primary));
background: hsl(var(--primary) / 0.5); /* 50% opacity */
```
## Tailwind Configuration
Map CSS variables to Tailwind utilities:
```ts
// tailwind.config.ts
export default {
darkMode: ["class"],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
}
```
## Color Customization
### Method 1: Update CSS Variables
Change colors by modifying CSS variables in `globals.css`:
```css
:root {
--primary: 262.1 83.3% 57.8%; /* Purple */
--primary-foreground: 210 20% 98%;
}
.dark {
--primary: 263.4 70% 50.4%; /* Darker purple */
--primary-foreground: 210 20% 98%;
}
```
### Method 2: Theme Generator
Use shadcn/ui theme generator: https://ui.shadcn.com/themes
Select base color, generate theme, copy CSS variables.
### Method 3: Multiple Themes
Create theme variants with data attributes:
```css
[data-theme="violet"] {
--primary: 262.1 83.3% 57.8%;
--primary-foreground: 210 20% 98%;
}
[data-theme="rose"] {
--primary: 346.8 77.2% 49.8%;
--primary-foreground: 355.7 100% 97.3%;
}
```
Apply theme:
```tsx
<div data-theme="violet">
<Button>Violet theme</Button>
</div>
```
## Component Customization
Components live in your codebase - modify directly.
### Customize Variants
```tsx
// components/ui/button.tsx
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground",
destructive: "bg-destructive text-destructive-foreground",
outline: "border border-input bg-background",
// Add custom variant
gradient: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
// Add custom size
xl: "h-14 rounded-md px-10 text-lg",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
```
Usage:
```tsx
<Button variant="gradient" size="xl">Custom Button</Button>
```
### Customize Styles
Modify base styles in component:
```tsx
// components/ui/card.tsx
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow-lg", // Modified
className
)}
{...props}
/>
))
```
### Override with className
Pass additional classes to override:
```tsx
<Card className="border-2 border-purple-500 shadow-2xl hover:scale-105 transition-transform">
Custom styled card
</Card>
```
## Base Color Presets
shadcn/ui provides base color presets during `init`:
- **Slate**: Cool gray tones
- **Gray**: Neutral gray
- **Zinc**: Warm gray
- **Neutral**: Balanced gray
- **Stone**: Earthy gray
Select during setup or change later by updating CSS variables.
## Style Variants
Two component styles available:
- **Default**: Softer, more rounded
- **New York**: Sharp, more contrast
Select during `init` or in `components.json`:
```json
{
"style": "new-york",
"tailwind": {
"cssVariables": true
}
}
```
## Radius Customization
Control border radius globally:
```css
:root {
--radius: 0.5rem; /* Default */
--radius: 0rem; /* Sharp corners */
--radius: 1rem; /* Rounded */
}
```
Components use radius variable:
```tsx
className="rounded-lg" /* Uses var(--radius) */
```
## Best Practices
1. **Use CSS Variables**: Enables runtime theme switching
2. **Consistent Foreground Colors**: Pair each color with appropriate foreground
3. **Test Both Themes**: Verify components in light and dark modes
4. **Semantic Naming**: Use `destructive` not `red`, `muted` not `gray`
5. **Accessibility**: Maintain sufficient color contrast (WCAG AA minimum)
6. **Component Overrides**: Use `className` prop for one-off customization
7. **Extract Patterns**: Create custom variants for repeated customizations