Initial commit
This commit is contained in:
373
skills/ui-styling/references/shadcn-theming.md
Normal file
373
skills/ui-styling/references/shadcn-theming.md
Normal 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
|
||||
Reference in New Issue
Block a user