Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "tailwind-shadcn-ui-setup",
|
||||
"description": "This skill should be used when setting up, configuring, or initializing Tailwind CSS (v3 or v4) and shadcn/ui for Next.js 16 App Router projects. Configure dark mode, design tokens, base layout with header/sidebar, accessibility defaults, and generate example components. Includes comprehensive setup automation, theme customization, and production-ready patterns. Use when the user requests \"setup Tailwind\", \"configure shadcn/ui\", \"add dark mode\", \"initialize design system\", or \"setup UI framework",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Hope Overture",
|
||||
"email": "support@worldbuilding-app-skills.dev"
|
||||
},
|
||||
"skills": [
|
||||
"./skills"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# tailwind-shadcn-ui-setup
|
||||
|
||||
This skill should be used when setting up, configuring, or initializing Tailwind CSS (v3 or v4) and shadcn/ui for Next.js 16 App Router projects. Configure dark mode, design tokens, base layout with header/sidebar, accessibility defaults, and generate example components. Includes comprehensive setup automation, theme customization, and production-ready patterns. Use when the user requests "setup Tailwind", "configure shadcn/ui", "add dark mode", "initialize design system", or "setup UI framework
|
||||
113
plugin.lock.json
Normal file
113
plugin.lock.json
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:hopeoverture/worldbuilding-app-skills:plugins/tailwind-shadcn-ui-setup",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "2a453c582511149b870f486b5ce27ae5a13a6e79",
|
||||
"treeHash": "076df72864866cc70e4f826cb101696866bc6e89804fe471d9f08f622f9826e2",
|
||||
"generatedAt": "2025-11-28T10:17:30.212016Z",
|
||||
"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-shadcn-ui-setup",
|
||||
"description": "This skill should be used when setting up, configuring, or initializing Tailwind CSS (v3 or v4) and shadcn/ui for Next.js 16 App Router projects. Configure dark mode, design tokens, base layout with header/sidebar, accessibility defaults, and generate example components. Includes comprehensive setup automation, theme customization, and production-ready patterns. Use when the user requests \"setup Tailwind\", \"configure shadcn/ui\", \"add dark mode\", \"initialize design system\", or \"setup UI framework",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "fba890e745615aa495f5d54ab4c5d1eb085900ded4074c062da3423418a7bfa4"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "0755cac23890c7efaad50afadff34524cc71bdb882294fe0739e7a2019142a9f"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/SKILL.md",
|
||||
"sha256": "fb6bf12616c516607ece08f5844e0c7d2d645f51a1549222fd6a318c1b5a6d41"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/references/tailwind-v4-migration.md",
|
||||
"sha256": "b32a1f32f1c51d9ab2c5ee726ff3dda295a5d0c32fde1d8264231a02386f4bf0"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/references/theme-tokens.md",
|
||||
"sha256": "160b0a1232386e57ddfb96870610bcba937478f60aafa84a91c1c684fea655cf"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/references/accessibility-checklist.md",
|
||||
"sha256": "1c0b1d4d39775a19db69a6ecd44a2de5897aa6084113692a215cd47dfc0b102a"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/references/shadcn-component-list.md",
|
||||
"sha256": "33b1400d39c832b2ddab7c80b38a441501c1c292cc0dec5d49809407012365ff"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/scripts/setup_tailwind_shadcn.py",
|
||||
"sha256": "6712fc39a1bc5222dfe6c2af2b06b23e442b5e705f0589da9707a4c4c51b2d4d"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/postcss.config.js.template",
|
||||
"sha256": "251ecddd4672c9cf467547e3dc535de00ad2129df26e7e7ae728fa4e5ac45fc5"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/tailwind.config.ts.template",
|
||||
"sha256": "00e4e54e5edb802f3e9b6e6522a31558977be08750429d8aaffc11bc6e226adc"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/globals.css.template",
|
||||
"sha256": "5a1cba6b1bdf564e91d110f44240eb8f77cf8eff5ac2653aef6f4536190ff7e4"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/app/page.tsx.template",
|
||||
"sha256": "97113ed69a36b408b01aaf3eed4b6cca68682cd1d3787ac8e99cea06caefec51"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/app/layout.tsx.template",
|
||||
"sha256": "b90c038a0299b4f191515269f96e9a8db694189d42ee6a10032a7e2912be56c4"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/app/examples/layout.tsx",
|
||||
"sha256": "12b9c084c56857ab4fa688c5229875cfcde36877f911f9246b7e4fcef4210778"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/app/examples/forms/page.tsx",
|
||||
"sha256": "50cd40620bedcbeffad94cbb1d4234a4b82e37d06d590d013c8f23c8ba5cfc8b"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/app/examples/theme/page.tsx",
|
||||
"sha256": "56199431367bab563056200774d29d503afc1afcbda516a5adacc400997c7fca"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/app/examples/dialogs/page.tsx",
|
||||
"sha256": "fad97b8a80358cb48062a6345ac414847676fa7373873557504eb3179bb1e20b"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/components/theme-provider.tsx",
|
||||
"sha256": "7d81e692e911cfafa5bf7c210334a0e655a87d8851cc5be4ce1799e4d14cdd97"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/components/mode-toggle.tsx",
|
||||
"sha256": "77a94853d8a6da873f5de0f90a44c9800db2af276ad33469a974a3863bd1bdd3"
|
||||
},
|
||||
{
|
||||
"path": "skills/tailwind-shadcn-ui-setup/assets/components/app-shell.tsx",
|
||||
"sha256": "1b815c60babff5c58b2c0fe53d33a9f8a07f3968a992702ff2ff5e55dc08b51a"
|
||||
}
|
||||
],
|
||||
"dirSha256": "076df72864866cc70e4f826cb101696866bc6e89804fe471d9f08f622f9826e2"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
493
skills/tailwind-shadcn-ui-setup/SKILL.md
Normal file
493
skills/tailwind-shadcn-ui-setup/SKILL.md
Normal file
@@ -0,0 +1,493 @@
|
||||
---
|
||||
name: tailwind-shadcn-ui-setup
|
||||
description: This skill should be used when setting up, configuring, or initializing Tailwind CSS (v3 or v4) and shadcn/ui for Next.js 16 App Router projects. Configure dark mode, design tokens, base layout with header/sidebar, accessibility defaults, and generate example components. Includes comprehensive setup automation, theme customization, and production-ready patterns. Use when the user requests "setup Tailwind", "configure shadcn/ui", "add dark mode", "initialize design system", or "setup UI framework" for Next.js projects.
|
||||
---
|
||||
|
||||
# Tailwind + shadcn/ui Setup for Next.js 16
|
||||
|
||||
## Overview
|
||||
|
||||
Configure a production-ready Tailwind CSS (v3/v4) + shadcn/ui setup for Next.js 16 App Router projects. This skill automates dependency installation, configuration, component generation, and provides:
|
||||
|
||||
- **Tailwind CSS v4-ready configuration** (v3 with forward-compatible patterns)
|
||||
- **shadcn/ui components** (Radix UI-based, fully accessible)
|
||||
- **Dark mode** with `next-themes` (class strategy)
|
||||
- **Base application layout** (header, optional sidebar, responsive)
|
||||
- **Design token system** (CSS variables for easy theming)
|
||||
- **Accessibility defaults** (WCAG 2.1 AA compliant)
|
||||
- **Example pages** (forms, dialogs, theme showcase)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running this skill, verify:
|
||||
|
||||
1. **Next.js 16 project exists** with App Router (`app/` directory)
|
||||
2. **Package manager**: npm (script uses `npm install`)
|
||||
3. **Project structure**: Valid `package.json` at project root
|
||||
4. **TypeScript**: Recommended (all templates use `.tsx`/`.ts`)
|
||||
|
||||
To verify:
|
||||
|
||||
```bash
|
||||
# Check Next.js version
|
||||
cat package.json | grep "\"next\":"
|
||||
|
||||
# Confirm app/ directory exists
|
||||
ls -la app/
|
||||
```
|
||||
|
||||
## Setup Workflow
|
||||
|
||||
### Step 1: Gather User Requirements
|
||||
|
||||
Use `AskUserQuestion` tool to collect configuration preferences:
|
||||
|
||||
1. **Enable dark mode?** (default: yes)
|
||||
- Installs `next-themes`, adds `ThemeProvider`, creates mode toggle
|
||||
2. **Theme preset** (zinc | slate | neutral, default: zinc)
|
||||
- Determines base color palette in CSS variables
|
||||
3. **Include sidebar layout?** (default: yes)
|
||||
- Adds responsive sidebar navigation using `Sheet` component
|
||||
4. **Include example pages?** (default: yes)
|
||||
- Generates example pages for forms, dialogs, theme showcase
|
||||
|
||||
### Step 2: Run Automation Script
|
||||
|
||||
Execute the Python setup script to install dependencies and initialize shadcn/ui:
|
||||
|
||||
```bash
|
||||
cd /path/to/nextjs-project
|
||||
python /path/to/skill/scripts/setup_tailwind_shadcn.py
|
||||
```
|
||||
|
||||
The script will:
|
||||
- Detect existing Tailwind/shadcn installations
|
||||
- Install required npm packages (non-interactive)
|
||||
- Create `components.json` for shadcn/ui
|
||||
- Add baseline shadcn components (button, card, input, label, dialog, separator)
|
||||
- Create `lib/utils.ts` with `cn()` helper
|
||||
|
||||
### Step 3: Copy Configuration Files
|
||||
|
||||
Copy and process template files from `assets/` directory:
|
||||
|
||||
1. **Tailwind Configuration**
|
||||
```bash
|
||||
# Copy and create
|
||||
cp assets/tailwind.config.ts.template project/tailwind.config.ts
|
||||
cp assets/postcss.config.js.template project/postcss.config.js
|
||||
```
|
||||
|
||||
2. **Global Styles**
|
||||
```bash
|
||||
# Copy and replace {{THEME_PRESET}} with user's choice
|
||||
cp assets/globals.css.template project/app/globals.css
|
||||
# Replace: {{THEME_PRESET}} → zinc | slate | neutral
|
||||
```
|
||||
|
||||
3. **Utility Functions**
|
||||
```bash
|
||||
cp assets/lib/utils.ts project/lib/utils.ts
|
||||
```
|
||||
|
||||
### Step 4: Add Core Components
|
||||
|
||||
Copy theme and layout components from `assets/components/`:
|
||||
|
||||
1. **Theme Provider** (if dark mode enabled)
|
||||
```bash
|
||||
cp assets/components/theme-provider.tsx project/components/theme-provider.tsx
|
||||
cp assets/components/mode-toggle.tsx project/components/mode-toggle.tsx
|
||||
```
|
||||
|
||||
2. **App Shell** (if sidebar layout enabled)
|
||||
```bash
|
||||
cp assets/components/app-shell.tsx project/components/app-shell.tsx
|
||||
```
|
||||
|
||||
### Step 5: Update App Layout
|
||||
|
||||
Update or create `app/layout.tsx`:
|
||||
|
||||
```bash
|
||||
# If layout.tsx exists, carefully merge changes
|
||||
# If not, copy template
|
||||
cp assets/app/layout.tsx.template project/app/layout.tsx
|
||||
```
|
||||
|
||||
**Key additions:**
|
||||
- Import `globals.css`
|
||||
- Wrap with `ThemeProvider` (if dark mode enabled)
|
||||
- Add skip link for accessibility
|
||||
- Include `<Toaster />` from Sonner for notifications
|
||||
|
||||
**Merge strategy if layout exists:**
|
||||
- Add missing imports at top
|
||||
- Wrap existing content with `ThemeProvider`
|
||||
- Add skip link before main content
|
||||
- Add `Toaster` before closing `body` tag
|
||||
- Ensure `suppressHydrationWarning` on `<html>` tag
|
||||
|
||||
### Step 6: Generate Example Pages (Optional)
|
||||
|
||||
If user requested examples, copy example pages:
|
||||
|
||||
```bash
|
||||
# Copy home page
|
||||
cp assets/app/page.tsx.template project/app/page.tsx
|
||||
|
||||
# Copy examples directory structure
|
||||
cp -r assets/app/examples/ project/app/examples/
|
||||
```
|
||||
|
||||
**Example pages include:**
|
||||
- **Homepage** (`app/page.tsx`): Hero, features grid, CTA
|
||||
- **Forms** (`app/examples/forms/page.tsx`): Contact form, validation patterns
|
||||
- **Dialogs** (`app/examples/dialogs/page.tsx`): Modal examples, A11y notes
|
||||
- **Theme** (`app/examples/theme/page.tsx`): Color tokens, customization guide
|
||||
|
||||
### Step 7: Add Additional shadcn Components
|
||||
|
||||
Install additional components as needed:
|
||||
|
||||
```bash
|
||||
# Common components for examples
|
||||
npx shadcn-ui@latest add dropdown-menu
|
||||
npx shadcn-ui@latest add sheet
|
||||
npx shadcn-ui@latest add separator
|
||||
|
||||
# Optional: Form components
|
||||
npx shadcn-ui@latest add form
|
||||
npx shadcn-ui@latest add checkbox
|
||||
npx shadcn-ui@latest add select
|
||||
```
|
||||
|
||||
Consult `references/shadcn-component-list.md` for full component catalog.
|
||||
|
||||
### Step 8: Verify Installation
|
||||
|
||||
Run checks to ensure setup is complete:
|
||||
|
||||
```bash
|
||||
# Check for TypeScript errors
|
||||
npx tsc --noEmit
|
||||
|
||||
# Start dev server
|
||||
npm run dev
|
||||
|
||||
# Open browser to http://localhost:3000
|
||||
```
|
||||
|
||||
**Visual verification:**
|
||||
- Page loads without errors
|
||||
- Dark mode toggle works (if enabled)
|
||||
- Colors match theme preset
|
||||
- Example pages render correctly (if included)
|
||||
- No accessibility warnings in console
|
||||
|
||||
### Step 9: Document Changes
|
||||
|
||||
Add a "Design System & UI" section to project `README.md`:
|
||||
|
||||
````markdown
|
||||
## Design System & UI
|
||||
|
||||
This project uses Tailwind CSS and shadcn/ui for styling and components.
|
||||
|
||||
### Customizing Colors
|
||||
|
||||
Edit CSS variables in `app/globals.css`:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--primary: 270 80% 45%; /* Your brand color (HSL) */
|
||||
--radius: 0.75rem; /* Border radius */
|
||||
}
|
||||
```
|
||||
|
||||
Test contrast ratios: https://webaim.org/resources/contrastchecker/
|
||||
|
||||
### Adding Components
|
||||
|
||||
```bash
|
||||
# Add any shadcn/ui component
|
||||
npx shadcn-ui@latest add [component-name]
|
||||
|
||||
# Example: Add a combobox
|
||||
npx shadcn-ui@latest add combobox
|
||||
```
|
||||
|
||||
Available components: https://ui.shadcn.com/docs/components
|
||||
|
||||
### Dark Mode
|
||||
|
||||
Toggle theme programmatically:
|
||||
|
||||
```tsx
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
export function Example() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
// theme: 'light' | 'dark' | 'system'
|
||||
// setTheme('dark')
|
||||
}
|
||||
```
|
||||
|
||||
### Accessibility
|
||||
|
||||
- All components meet WCAG 2.1 Level AA
|
||||
- Focus indicators on all interactive elements
|
||||
- Keyboard navigation supported
|
||||
- Screen reader compatible
|
||||
|
||||
See `references/accessibility-checklist.md` for full guidelines.
|
||||
````
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Theme Presets
|
||||
|
||||
**Zinc (default)** - Cool, neutral gray tones:
|
||||
```css
|
||||
--primary: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
```
|
||||
|
||||
**Slate** - Slightly cooler, tech-focused:
|
||||
```css
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
```
|
||||
|
||||
**Neutral** - True neutral grays:
|
||||
```css
|
||||
--primary: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
```
|
||||
|
||||
Customize further by editing `app/globals.css` `:root` and `.dark` blocks.
|
||||
|
||||
### Tailwind v4 Considerations
|
||||
|
||||
Check Tailwind version before setup:
|
||||
|
||||
```bash
|
||||
npm view tailwindcss version
|
||||
```
|
||||
|
||||
**If v4.0.0+ is available:**
|
||||
- Use v4 configuration format (`@theme` directive)
|
||||
- Consult `references/tailwind-v4-migration.md`
|
||||
|
||||
**If v4 not available (current default):**
|
||||
- Use v3 with forward-compatible CSS variables
|
||||
- Add comments in generated files: `// TODO: Upgrade to Tailwind v4 when stable`
|
||||
|
||||
### Sidebar Layout Options
|
||||
|
||||
If `sidebarLayout = true`:
|
||||
- Uses `AppShell` component with responsive sidebar
|
||||
- Mobile: Hamburger menu → `Sheet` (slide-over)
|
||||
- Desktop: Fixed sidebar with navigation items
|
||||
|
||||
If `sidebarLayout = false`:
|
||||
- Simple header + content layout
|
||||
- Header contains site title + actions (mode toggle)
|
||||
|
||||
Users can customize navigation in layout files by passing `navigation` prop:
|
||||
|
||||
```tsx
|
||||
<AppShell
|
||||
navigation={[
|
||||
{ title: 'Home', href: '/', icon: Home },
|
||||
{ title: 'About', href: '/about', icon: Info },
|
||||
]}
|
||||
siteTitle="My App"
|
||||
>
|
||||
{children}
|
||||
</AppShell>
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: npm install fails
|
||||
|
||||
**Cause**: Network issues, registry timeout, or package conflicts
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Clear npm cache
|
||||
npm cache clean --force
|
||||
|
||||
# Retry with verbose logging
|
||||
npm install --verbose
|
||||
|
||||
# Or use specific registry
|
||||
npm install --registry https://registry.npmjs.org/
|
||||
```
|
||||
|
||||
### Issue: TypeScript errors in components
|
||||
|
||||
**Cause**: Missing type definitions or tsconfig issues
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Install missing types
|
||||
npm install --save-dev @types/react @types/react-dom @types/node
|
||||
|
||||
# Check tsconfig.json paths
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Issue: Dark mode not working
|
||||
|
||||
**Cause**: ThemeProvider not wrapping content, or `suppressHydrationWarning` missing
|
||||
|
||||
**Solution**:
|
||||
1. Verify `<html suppressHydrationWarning>` in layout
|
||||
2. Ensure `ThemeProvider` wraps `{children}`
|
||||
3. Check `tailwind.config.ts` has `darkMode: 'class'`
|
||||
|
||||
### Issue: shadcn components not found
|
||||
|
||||
**Cause**: `components.json` misconfigured or components not installed
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Reinitialize shadcn/ui
|
||||
npx shadcn-ui@latest init
|
||||
|
||||
# Re-add components
|
||||
npx shadcn-ui@latest add button card input
|
||||
```
|
||||
|
||||
### Issue: Focus styles not visible
|
||||
|
||||
**Cause**: Global CSS focus styles not applied
|
||||
|
||||
**Solution**:
|
||||
- Verify `app/globals.css` includes focus styles layer
|
||||
- Check `*:focus-visible` rule is present
|
||||
- Ensure `--ring` CSS variable is defined
|
||||
|
||||
## Resources
|
||||
|
||||
This skill bundles comprehensive resources for reference:
|
||||
|
||||
### References (Loaded as Needed)
|
||||
|
||||
- **`tailwind-v4-migration.md`**: Tailwind v3 vs v4 differences, migration guide
|
||||
- **`shadcn-component-list.md`**: Complete shadcn/ui component catalog with usage examples
|
||||
- **accessibility-checklist.md**: WCAG 2.1 AA compliance checklist, best practices
|
||||
- **`theme-tokens.md`**: Design token system, color customization guide
|
||||
|
||||
To read a reference:
|
||||
```bash
|
||||
# From skill directory
|
||||
cat references/theme-tokens.md
|
||||
```
|
||||
|
||||
### Scripts (Executable)
|
||||
|
||||
- **`setup_tailwind_shadcn.py`**: Main automation script for dependency installation and configuration
|
||||
|
||||
Execute directly:
|
||||
```bash
|
||||
python scripts/setup_tailwind_shadcn.py
|
||||
```
|
||||
|
||||
### Assets (Templates for Output)
|
||||
|
||||
- **Config templates**: `tailwind.config.ts.template`, `postcss.config.js.template`, `globals.css.template`
|
||||
- **Component templates**: `theme-provider.tsx`, `mode-toggle.tsx`, `app-shell.tsx`
|
||||
- **Utility templates**: `lib/utils.ts`
|
||||
- **Page templates**: `app/layout.tsx.template`, `app/page.tsx.template`
|
||||
- **Example pages**: `app/examples/forms/`, `app/examples/dialogs/`, `app/examples/theme/`
|
||||
|
||||
Copy and customize as needed for the target project.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Test Both Themes
|
||||
|
||||
After setup, manually toggle dark mode and verify:
|
||||
- Color contrast ratios meet WCAG standards
|
||||
- All text remains readable
|
||||
- Focus indicators are visible
|
||||
- Components render correctly
|
||||
|
||||
### 2. Use Semantic Tokens
|
||||
|
||||
```tsx
|
||||
[OK] Good (semantic)
|
||||
<div className="bg-primary text-primary-foreground">
|
||||
|
||||
[ERROR] Bad (hard-coded)
|
||||
<div className="bg-blue-600 text-white">
|
||||
```
|
||||
|
||||
### 3. Maintain Type Safety
|
||||
|
||||
Use TypeScript for all components:
|
||||
```tsx
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'default' | 'destructive' | 'outline'
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Keep Accessibility in Mind
|
||||
|
||||
- Always pair labels with inputs
|
||||
- Provide `aria-label` for icon-only buttons
|
||||
- Test keyboard navigation
|
||||
- Use semantic HTML
|
||||
|
||||
### 5. Document Customizations
|
||||
|
||||
When customizing tokens or components, document changes in project README or inline comments.
|
||||
|
||||
## Post-Setup Tasks
|
||||
|
||||
After running this skill, recommend users:
|
||||
|
||||
1. **Customize brand colors**
|
||||
- Edit `--primary` in `app/globals.css`
|
||||
- Test contrast ratios
|
||||
|
||||
2. **Add more components**
|
||||
- Run `npx shadcn-ui add [component]` as needed
|
||||
- See `references/shadcn-component-list.md` for options
|
||||
|
||||
3. **Configure responsive breakpoints**
|
||||
- Adjust Tailwind `screens` in `tailwind.config.ts` if needed
|
||||
|
||||
4. **Set up linting**
|
||||
- Install `eslint-plugin-jsx-a11y` for accessibility linting
|
||||
- Add Prettier + Tailwind plugin for formatting
|
||||
|
||||
5. **Test production build**
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
This skill provides a complete, production-ready Tailwind CSS + shadcn/ui setup for Next.js 16 App Router projects. It handles:
|
||||
|
||||
[OK] Dependency installation (Tailwind, shadcn/ui, next-themes)
|
||||
[OK] Configuration (Tailwind config, PostCSS, global CSS)
|
||||
[OK] Dark mode setup (ThemeProvider, toggle component)
|
||||
[OK] Base layout (responsive header, optional sidebar)
|
||||
[OK] Design tokens (semantic CSS variables)
|
||||
[OK] Accessibility (WCAG 2.1 AA, keyboard nav, screen readers)
|
||||
[OK] Example pages (forms, dialogs, theme showcase)
|
||||
[OK] Documentation (README updates, customization guides)
|
||||
|
||||
The setup is forward-compatible with Tailwind v4 and follows official Anthropic skill best practices.
|
||||
@@ -0,0 +1,266 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function DialogsPage() {
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
|
||||
const handleSave = () => {
|
||||
toast.success('Changes saved successfully!')
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container max-w-4xl py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold">Dialog Examples</h1>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
Modal dialogs with focus trapping and keyboard navigation
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Basic Dialog */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Basic Dialog</CardTitle>
|
||||
<CardDescription>Simple dialog with title and content</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Open Dialog</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Welcome!</DialogTitle>
|
||||
<DialogDescription>
|
||||
This is a basic dialog example. Press ESC or click outside to
|
||||
close.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Dialogs automatically handle:
|
||||
</p>
|
||||
<ul className="ml-6 mt-2 list-disc text-sm text-muted-foreground">
|
||||
<li>Focus trapping</li>
|
||||
<li>Keyboard navigation (ESC to close)</li>
|
||||
<li>Backdrop click to dismiss</li>
|
||||
<li>Return focus on close</li>
|
||||
</ul>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => toast.success('Action completed!')}>
|
||||
Confirm
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Dialog with Form */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Dialog with Form</CardTitle>
|
||||
<CardDescription>Dialog containing a form with inputs</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're
|
||||
done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input id="name" defaultValue="John Doe" />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
defaultValue="john@example.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave}>Save Changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Confirmation Dialog */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Confirmation Dialog</CardTitle>
|
||||
<CardDescription>Destructive action confirmation</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="destructive">Delete Item</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. This will permanently delete
|
||||
your item and remove your data from our servers.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
toast.error('Item deleted')
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Info Dialog */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Information Dialog</CardTitle>
|
||||
<CardDescription>Display information without actions</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Show Info</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>About This App</DialogTitle>
|
||||
<DialogDescription>
|
||||
Built with modern web technologies
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div>
|
||||
<h4 className="mb-2 text-sm font-medium">Stack</h4>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Next.js 16 with App Router</li>
|
||||
<li>• React 19</li>
|
||||
<li>• Tailwind CSS v3/v4</li>
|
||||
<li>• shadcn/ui components</li>
|
||||
<li>• TypeScript</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-2 text-sm font-medium">Features</h4>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Dark mode support</li>
|
||||
<li>• Fully accessible (WCAG 2.1 AA)</li>
|
||||
<li>• Type-safe</li>
|
||||
<li>• Production-ready</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Accessibility Info */}
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Dialog Accessibility</CardTitle>
|
||||
<CardDescription>
|
||||
shadcn/ui dialogs are built on Radix UI with full accessibility
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4 text-sm">
|
||||
<div>
|
||||
<h4 className="mb-2 font-medium">Keyboard Navigation</h4>
|
||||
<ul className="ml-6 list-disc space-y-1 text-muted-foreground">
|
||||
<li>
|
||||
<kbd className="rounded bg-muted px-1.5 py-0.5">ESC</kbd> -
|
||||
Close dialog
|
||||
</li>
|
||||
<li>
|
||||
<kbd className="rounded bg-muted px-1.5 py-0.5">Tab</kbd> -
|
||||
Move focus forward
|
||||
</li>
|
||||
<li>
|
||||
<kbd className="rounded bg-muted px-1.5 py-0.5">
|
||||
Shift + Tab
|
||||
</kbd>{' '}
|
||||
- Move focus backward
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="mb-2 font-medium">Screen Reader Support</h4>
|
||||
<ul className="ml-6 list-disc space-y-1 text-muted-foreground">
|
||||
<li>
|
||||
Dialog title is announced via{' '}
|
||||
<code className="rounded bg-muted px-1">aria-labelledby</code>
|
||||
</li>
|
||||
<li>
|
||||
Description is read via{' '}
|
||||
<code className="rounded bg-muted px-1">aria-describedby</code>
|
||||
</li>
|
||||
<li>Focus is trapped within dialog when open</li>
|
||||
<li>Focus returns to trigger element on close</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="mb-2 font-medium">Mouse/Touch Support</h4>
|
||||
<ul className="ml-6 list-disc space-y-1 text-muted-foreground">
|
||||
<li>Click backdrop to dismiss (optional)</li>
|
||||
<li>Close button in top-right corner</li>
|
||||
<li>Scroll content if overflowing</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function FormsPage() {
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
const formData = new FormData(event.currentTarget)
|
||||
const data = {
|
||||
name: formData.get('name'),
|
||||
email: formData.get('email'),
|
||||
message: formData.get('message'),
|
||||
}
|
||||
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
console.log('Form submitted:', data)
|
||||
toast.success('Form submitted successfully!')
|
||||
|
||||
// Reset form
|
||||
event.currentTarget.reset()
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container max-w-4xl py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold">Form Examples</h1>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
Accessible form components with HTML5 validation
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{/* Basic Form */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Contact Form</CardTitle>
|
||||
<CardDescription>
|
||||
Basic form with HTML5 validation and toast notifications
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
Name <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="John Doe"
|
||||
required
|
||||
aria-required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">
|
||||
Email <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="john@example.com"
|
||||
required
|
||||
aria-required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="message">
|
||||
Message <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
className="flex min-h-[120px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
placeholder="Your message here..."
|
||||
required
|
||||
aria-required="true"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex gap-2">
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</Button>
|
||||
<Button type="reset" variant="outline" disabled={isSubmitting}>
|
||||
Reset
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
|
||||
{/* Form Validation Example */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Advanced Validation</CardTitle>
|
||||
<CardDescription>
|
||||
For production forms, consider using React Hook Form + Zod
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-lg bg-muted p-4">
|
||||
<code className="text-sm">
|
||||
<pre className="overflow-x-auto">
|
||||
{`import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8)
|
||||
})
|
||||
|
||||
export function LoginForm() {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(schema)
|
||||
})
|
||||
|
||||
// ... form implementation
|
||||
}`}
|
||||
</pre>
|
||||
</code>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Install dependencies:{' '}
|
||||
<code className="rounded bg-muted px-1 py-0.5">
|
||||
npm install react-hook-form @hookform/resolvers zod
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Accessibility Notes */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Accessibility Guidelines</CardTitle>
|
||||
<CardDescription>Best practices for accessible forms</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="ml-6 list-disc space-y-2 text-sm">
|
||||
<li>
|
||||
<strong>Always pair labels with inputs</strong> using{' '}
|
||||
<code className="rounded bg-muted px-1">htmlFor</code> and{' '}
|
||||
<code className="rounded bg-muted px-1">id</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Mark required fields</strong> visually and with{' '}
|
||||
<code className="rounded bg-muted px-1">aria-required</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Provide clear error messages</strong> linked with{' '}
|
||||
<code className="rounded bg-muted px-1">aria-describedby</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Use appropriate input types</strong> (email, tel, url,
|
||||
etc.)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Disable submit during processing</strong> with{' '}
|
||||
<code className="rounded bg-muted px-1">aria-busy</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Don't use placeholder as label</strong> - placeholders
|
||||
disappear on focus
|
||||
</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { AppShell } from '@/components/app-shell'
|
||||
import { FileText, Palette, SquareStack } from 'lucide-react'
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
title: 'Forms',
|
||||
href: '/examples/forms',
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: 'Dialogs',
|
||||
href: '/examples/dialogs',
|
||||
icon: SquareStack,
|
||||
},
|
||||
{
|
||||
title: 'Theme',
|
||||
href: '/examples/theme',
|
||||
icon: Palette,
|
||||
},
|
||||
]
|
||||
|
||||
export default function ExamplesLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<AppShell navigation={navigation} siteTitle="Examples">
|
||||
{children}
|
||||
</AppShell>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
export default function ThemePage() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
|
||||
// Prevent hydration mismatch
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container max-w-6xl py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold">Theme & Design Tokens</h1>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
Explore the color system and design tokens
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Theme Switcher */}
|
||||
<Card className="mb-8">
|
||||
<CardHeader>
|
||||
<CardTitle>Theme Switcher</CardTitle>
|
||||
<CardDescription>
|
||||
Current theme: <strong className="capitalize">{theme}</strong>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={theme === 'light' ? 'default' : 'outline'}
|
||||
onClick={() => setTheme('light')}
|
||||
>
|
||||
Light
|
||||
</Button>
|
||||
<Button
|
||||
variant={theme === 'dark' ? 'default' : 'outline'}
|
||||
onClick={() => setTheme('dark')}
|
||||
>
|
||||
Dark
|
||||
</Button>
|
||||
<Button
|
||||
variant={theme === 'system' ? 'default' : 'outline'}
|
||||
onClick={() => setTheme('system')}
|
||||
>
|
||||
System
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Color Tokens */}
|
||||
<div className="space-y-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Semantic Colors</CardTitle>
|
||||
<CardDescription>
|
||||
Colors that describe purpose, not appearance
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<ColorSwatch
|
||||
label="Background"
|
||||
description="Main background color"
|
||||
className="bg-background text-foreground"
|
||||
/>
|
||||
<ColorSwatch
|
||||
label="Foreground"
|
||||
description="Main text color"
|
||||
className="bg-foreground text-background"
|
||||
/>
|
||||
<ColorSwatch
|
||||
label="Primary"
|
||||
description="Brand color for main actions"
|
||||
className="bg-primary text-primary-foreground"
|
||||
/>
|
||||
<ColorSwatch
|
||||
label="Secondary"
|
||||
description="Less prominent actions"
|
||||
className="bg-secondary text-secondary-foreground"
|
||||
/>
|
||||
<ColorSwatch
|
||||
label="Muted"
|
||||
description="Disabled states, subtle backgrounds"
|
||||
className="bg-muted text-muted-foreground"
|
||||
/>
|
||||
<ColorSwatch
|
||||
label="Accent"
|
||||
description="Highlights, hover states"
|
||||
className="bg-accent text-accent-foreground"
|
||||
/>
|
||||
<ColorSwatch
|
||||
label="Destructive"
|
||||
description="Errors, delete actions"
|
||||
className="bg-destructive text-destructive-foreground"
|
||||
/>
|
||||
<ColorSwatch
|
||||
label="Card"
|
||||
description="Elevated surfaces"
|
||||
className="border bg-card text-card-foreground"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Component Variants */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Button Variants</CardTitle>
|
||||
<CardDescription>
|
||||
Different button styles using semantic colors
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button>Default</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="default">Default</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="icon">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="m12 5 7 7-7 7" />
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Typography */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Typography Scale</CardTitle>
|
||||
<CardDescription>Text styles with proper hierarchy</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold">Heading 1 (4xl)</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
text-4xl font-bold
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold">Heading 2 (3xl)</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
text-3xl font-bold
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-semibold">Heading 3 (2xl)</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
text-2xl font-semibold
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-xl font-semibold">Heading 4 (xl)</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
text-xl font-semibold
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-base">Body text (base)</p>
|
||||
<p className="text-sm text-muted-foreground">text-base</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm">Small text (sm)</p>
|
||||
<p className="text-sm text-muted-foreground">text-sm</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Extra small (xs)
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">text-xs</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Customization Guide */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Customization</CardTitle>
|
||||
<CardDescription>How to customize your theme</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="mb-2 font-medium">1. Update CSS Variables</h4>
|
||||
<p className="mb-2 text-sm text-muted-foreground">
|
||||
Edit <code className="rounded bg-muted px-1">app/globals.css</code>:
|
||||
</p>
|
||||
<pre className="overflow-x-auto rounded-lg bg-muted p-4 text-sm">
|
||||
<code>{`:root {
|
||||
--primary: 270 80% 45%; /* Change to your brand color */
|
||||
--radius: 0.75rem; /* Adjust border radius */
|
||||
}`}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="mb-2 font-medium">2. Use HSL Color Picker</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Find HSL values using:{' '}
|
||||
<a
|
||||
href="https://hslpicker.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline"
|
||||
>
|
||||
HSL Color Picker
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="mb-2 font-medium">3. Test Contrast</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Ensure WCAG compliance using:{' '}
|
||||
<a
|
||||
href="https://webaim.org/resources/contrastchecker/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline"
|
||||
>
|
||||
WebAIM Contrast Checker
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="mb-2 font-medium">4. Test Both Themes</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Always verify colors look good in both light and dark mode
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ColorSwatch({
|
||||
label,
|
||||
description,
|
||||
className,
|
||||
}: {
|
||||
label: string
|
||||
description: string
|
||||
className: string
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div
|
||||
className={`flex h-20 items-center justify-center rounded-lg border ${className}`}
|
||||
>
|
||||
<span className="font-medium">{label}</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium">{label}</p>
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import { ThemeProvider } from '@/components/theme-provider'
|
||||
import { Toaster } from 'sonner'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{/* Skip link for accessibility */}
|
||||
<a href="#main-content" className="skip-link">
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
{children}
|
||||
|
||||
<Toaster richColors closeButton position="top-right" />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
141
skills/tailwind-shadcn-ui-setup/assets/app/page.tsx.template
Normal file
141
skills/tailwind-shadcn-ui-setup/assets/app/page.tsx.template
Normal file
@@ -0,0 +1,141 @@
|
||||
import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main id="main-content" className="container py-8">
|
||||
{/* Hero section */}
|
||||
<section className="flex flex-col items-center gap-4 py-12 text-center">
|
||||
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl">
|
||||
Welcome to Your App
|
||||
</h1>
|
||||
<p className="max-w-[700px] text-lg text-muted-foreground sm:text-xl">
|
||||
Built with Next.js 16, Tailwind CSS, and shadcn/ui. Beautiful,
|
||||
accessible, and fully customizable.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<Button size="lg" asChild>
|
||||
<Link href="/examples">View Examples</Link>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" asChild>
|
||||
<Link href="https://ui.shadcn.com" target="_blank" rel="noopener noreferrer">
|
||||
Documentation
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features grid */}
|
||||
<section className="py-12">
|
||||
<h2 className="mb-8 text-center text-3xl font-bold">Features</h2>
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Tailwind CSS</CardTitle>
|
||||
<CardDescription>
|
||||
Utility-first CSS framework with custom design tokens
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Fully configured with CSS variables for easy theming and
|
||||
customization.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>shadcn/ui</CardTitle>
|
||||
<CardDescription>
|
||||
Beautiful, accessible component library
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Built on Radix UI primitives with full TypeScript support.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Dark Mode</CardTitle>
|
||||
<CardDescription>
|
||||
Automatic dark mode with system preference
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Seamless theme switching with high contrast in both modes.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Accessibility</CardTitle>
|
||||
<CardDescription>
|
||||
WCAG 2.1 Level AA compliant
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Built with semantic HTML, ARIA attributes, and keyboard
|
||||
navigation.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Type Safe</CardTitle>
|
||||
<CardDescription>
|
||||
Full TypeScript support
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Catch errors at compile time with comprehensive type definitions.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Production Ready</CardTitle>
|
||||
<CardDescription>
|
||||
Optimized for performance
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Fast builds, small bundles, and excellent Lighthouse scores.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA section */}
|
||||
<section className="flex flex-col items-center gap-4 py-12 text-center">
|
||||
<h2 className="text-3xl font-bold">Ready to build?</h2>
|
||||
<p className="max-w-[600px] text-muted-foreground">
|
||||
Explore the example pages to see components in action, or start
|
||||
building your own features.
|
||||
</p>
|
||||
<Button asChild>
|
||||
<Link href="/examples">Explore Examples</Link>
|
||||
</Button>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
162
skills/tailwind-shadcn-ui-setup/assets/components/app-shell.tsx
Normal file
162
skills/tailwind-shadcn-ui-setup/assets/components/app-shell.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { Menu } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { ModeToggle } from '@/components/mode-toggle'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface NavItem {
|
||||
title: string
|
||||
href: string
|
||||
icon?: React.ComponentType<{ className?: string }>
|
||||
}
|
||||
|
||||
interface AppShellProps {
|
||||
children: React.ReactNode
|
||||
navigation?: NavItem[]
|
||||
siteTitle?: string
|
||||
}
|
||||
|
||||
export function AppShell({
|
||||
children,
|
||||
navigation = [],
|
||||
siteTitle = 'App',
|
||||
}: AppShellProps) {
|
||||
const pathname = usePathname()
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container flex h-16 items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Mobile menu trigger */}
|
||||
{navigation.length > 0 && (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="md:hidden"
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-64 pr-0">
|
||||
<MobileNav
|
||||
items={navigation}
|
||||
pathname={pathname}
|
||||
onItemClick={() => setOpen(false)}
|
||||
/>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)}
|
||||
|
||||
{/* Site title */}
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<span className="text-xl font-bold">{siteTitle}</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="flex flex-1">
|
||||
{/* Desktop sidebar */}
|
||||
{navigation.length > 0 && (
|
||||
<aside className="hidden w-64 border-r bg-background md:block">
|
||||
<nav className="sticky top-16 flex h-[calc(100vh-4rem)] flex-col gap-2 p-4">
|
||||
<DesktopNav items={navigation} pathname={pathname} />
|
||||
</nav>
|
||||
</aside>
|
||||
)}
|
||||
|
||||
{/* Main content */}
|
||||
<main id="main-content" className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MobileNav({
|
||||
items,
|
||||
pathname,
|
||||
onItemClick,
|
||||
}: {
|
||||
items: NavItem[]
|
||||
pathname: string
|
||||
onItemClick: () => void
|
||||
}) {
|
||||
return (
|
||||
<nav className="flex flex-col gap-2 py-4">
|
||||
{items.map((item) => {
|
||||
const Icon = item.icon
|
||||
const isActive = pathname === item.href
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
onClick={onItemClick}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-4 w-4" />}
|
||||
{item.title}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
function DesktopNav({
|
||||
items,
|
||||
pathname,
|
||||
}: {
|
||||
items: NavItem[]
|
||||
pathname: string
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{items.map((item) => {
|
||||
const Icon = item.icon
|
||||
const isActive = pathname === item.href
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-4 w-4" />}
|
||||
{item.title}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Moon, Sun } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" aria-label="Toggle theme">
|
||||
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTheme('light')}>
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme('dark')}>
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme('system')}>
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { type ThemeProviderProps } from 'next-themes/dist/types'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
97
skills/tailwind-shadcn-ui-setup/assets/globals.css.template
Normal file
97
skills/tailwind-shadcn-ui-setup/assets/globals.css.template
Normal file
@@ -0,0 +1,97 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
html {
|
||||
@apply scroll-smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
}
|
||||
|
||||
/* Base theme: {{THEME_PRESET}} */
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-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%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-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%;
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
*:focus-visible {
|
||||
@apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-background;
|
||||
}
|
||||
|
||||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Skip link for accessibility */
|
||||
.skip-link {
|
||||
@apply sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50;
|
||||
@apply px-4 py-2 bg-background border-2 border-ring rounded-md;
|
||||
@apply text-foreground font-medium;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Text balance for headings */
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
// TODO: When Tailwind v4 is stable, consider migrating to @theme directive
|
||||
// See references/tailwind-v4-migration.md for details
|
||||
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./lib/**/*.{ts,tsx}',
|
||||
],
|
||||
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))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
keyframes: {
|
||||
'accordion-down': {
|
||||
from: { height: '0' },
|
||||
to: { height: 'var(--radix-accordion-content-height)' },
|
||||
},
|
||||
'accordion-up': {
|
||||
from: { height: 'var(--radix-accordion-content-height)' },
|
||||
to: { height: '0' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/typography'),
|
||||
],
|
||||
} satisfies Config
|
||||
@@ -0,0 +1,484 @@
|
||||
# Accessibility Checklist for Tailwind + shadcn/ui Setup
|
||||
|
||||
## Overview
|
||||
|
||||
This checklist ensures the Tailwind + shadcn/ui setup meets WCAG 2.1 Level AA standards and provides an inclusive user experience.
|
||||
|
||||
## Color & Contrast
|
||||
|
||||
### Requirements
|
||||
- [OK] Normal text (< 18pt): Contrast ratio ≥ 4.5:1
|
||||
- [OK] Large text (≥ 18pt or bold 14pt): Contrast ratio ≥ 3:1
|
||||
- [OK] UI components: Contrast ratio ≥ 3:1
|
||||
- [OK] Focus indicators: Contrast ratio ≥ 3:1
|
||||
|
||||
### Implementation
|
||||
|
||||
```css
|
||||
/* Light mode - High contrast */
|
||||
:root {
|
||||
--background: 0 0% 100%; /* White */
|
||||
--foreground: 222.2 84% 4.9%; /* Near black - 16.7:1 ratio */
|
||||
--muted: 210 40% 96.1%; /* Light gray background */
|
||||
--muted-foreground: 215.4 16.3% 46.9%; /* Medium gray text - 4.6:1 ratio */
|
||||
--border: 214.3 31.8% 91.4%; /* Light border */
|
||||
}
|
||||
|
||||
/* Dark mode - High contrast */
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%; /* Near black */
|
||||
--foreground: 210 40% 98%; /* Near white - 16.5:1 ratio */
|
||||
--muted: 217.2 32.6% 17.5%; /* Dark gray background */
|
||||
--muted-foreground: 215 20.2% 65.1%; /* Light gray text - 6.8:1 ratio */
|
||||
--border: 217.2 32.6% 17.5%; /* Dark border */
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Contrast
|
||||
|
||||
```bash
|
||||
# Use online tools:
|
||||
# - https://webaim.org/resources/contrastchecker/
|
||||
# - https://contrast-ratio.com/
|
||||
# - Chrome DevTools (Lighthouse audit)
|
||||
|
||||
# Or programmatically:
|
||||
npm install --save-dev axe-core @axe-core/playwright
|
||||
```
|
||||
|
||||
## Focus Management
|
||||
|
||||
### Visible Focus Indicators
|
||||
|
||||
```css
|
||||
/* Global focus styles */
|
||||
@layer base {
|
||||
*:focus-visible {
|
||||
@apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-background;
|
||||
}
|
||||
|
||||
/* Respect reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*:focus-visible {
|
||||
@apply transition-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Focus Order
|
||||
- [OK] Logical tab order (follows visual order)
|
||||
- [OK] No keyboard traps
|
||||
- [OK] Skip links for navigation
|
||||
- [OK] Focus moves appropriately in modals/dialogs
|
||||
|
||||
### Implementation
|
||||
|
||||
```tsx
|
||||
// Skip link (in layout)
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50"
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
{/* Rest of layout */}
|
||||
<main id="main-content">
|
||||
{children}
|
||||
</main>
|
||||
```
|
||||
|
||||
## Keyboard Navigation
|
||||
|
||||
### Requirements
|
||||
- [OK] All interactive elements keyboard accessible
|
||||
- [OK] Logical tab order
|
||||
- [OK] Keyboard shortcuts don't conflict
|
||||
- [OK] Escape closes modals/dropdowns
|
||||
- [OK] Arrow keys for menus/lists
|
||||
- [OK] Enter/Space activates buttons
|
||||
|
||||
### shadcn/ui Components
|
||||
|
||||
All shadcn/ui components support keyboard navigation out of the box:
|
||||
|
||||
```tsx
|
||||
// Dialog - auto-handles:
|
||||
// - ESC to close
|
||||
// - Focus trap
|
||||
// - Return focus on close
|
||||
<Dialog>
|
||||
<DialogContent>
|
||||
<DialogTitle>Title</DialogTitle>
|
||||
{/* Content */}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
// Dropdown - auto-handles:
|
||||
// - Arrow keys for navigation
|
||||
// - Enter to select
|
||||
// - ESC to close
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>Menu</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>Item 1</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
```
|
||||
|
||||
## Semantic HTML
|
||||
|
||||
### Use Proper Elements
|
||||
|
||||
```tsx
|
||||
[OK] Good - Semantic
|
||||
<nav aria-label="Main navigation">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Article Title</h1>
|
||||
<p>Content...</p>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Company</p>
|
||||
</footer>
|
||||
|
||||
[ERROR] Bad - Non-semantic
|
||||
<div className="nav">
|
||||
<div className="link" onClick={goHome}>Home</div>
|
||||
</div>
|
||||
|
||||
<div className="main">
|
||||
<div className="article">
|
||||
<div className="title">Article Title</div>
|
||||
<div>Content...</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Heading Hierarchy
|
||||
|
||||
```tsx
|
||||
[OK] Good - Logical hierarchy
|
||||
<h1>Page Title</h1>
|
||||
<h2>Section</h2>
|
||||
<h3>Subsection</h3>
|
||||
<h2>Another Section</h2>
|
||||
|
||||
[ERROR] Bad - Skips levels
|
||||
<h1>Page Title</h1>
|
||||
<h3>Section</h3> {/* Skipped h2 */}
|
||||
<h2>Another Section</h2> {/* Out of order */}
|
||||
```
|
||||
|
||||
## Form Accessibility
|
||||
|
||||
### Always Pair Labels with Inputs
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email address</Label>
|
||||
<Input id="email" type="email" required aria-required="true" />
|
||||
</div>
|
||||
|
||||
[ERROR] Bad
|
||||
<Input placeholder="Email address" /> {/* Placeholder is not a label */}
|
||||
```
|
||||
|
||||
### Error Messages
|
||||
|
||||
```tsx
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
aria-invalid={!!errors.password}
|
||||
aria-describedby={errors.password ? "password-error" : undefined}
|
||||
/>
|
||||
{errors.password && (
|
||||
<p id="password-error" className="text-sm text-destructive" role="alert">
|
||||
{errors.password.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Required Fields
|
||||
|
||||
```tsx
|
||||
<Label htmlFor="username">
|
||||
Username
|
||||
<span className="text-destructive" aria-label="required">*</span>
|
||||
</Label>
|
||||
<Input id="username" required aria-required="true" />
|
||||
```
|
||||
|
||||
## ARIA Attributes
|
||||
|
||||
### When to Use ARIA
|
||||
|
||||
**First Rule**: Don't use ARIA unless necessary. Use semantic HTML first.
|
||||
|
||||
```tsx
|
||||
[OK] Good - Semantic HTML (no ARIA needed)
|
||||
<button>Click me</button>
|
||||
|
||||
[ERROR] Unnecessary ARIA
|
||||
<div role="button" tabIndex={0} onClick={...}>Click me</div>
|
||||
```
|
||||
|
||||
### Common ARIA Patterns
|
||||
|
||||
```tsx
|
||||
// Live regions for dynamic content
|
||||
<div role="status" aria-live="polite" aria-atomic="true">
|
||||
{statusMessage}
|
||||
</div>
|
||||
|
||||
// Loading state
|
||||
<Button disabled={isLoading} aria-busy={isLoading}>
|
||||
{isLoading ? "Loading..." : "Submit"}
|
||||
</Button>
|
||||
|
||||
// Icon buttons need labels
|
||||
<Button size="icon" aria-label="Close menu">
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
// Expanded/collapsed state
|
||||
<Button
|
||||
aria-expanded={isOpen}
|
||||
aria-controls="content-id"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
Toggle
|
||||
</Button>
|
||||
<div id="content-id" hidden={!isOpen}>
|
||||
Content
|
||||
</div>
|
||||
```
|
||||
|
||||
## Screen Reader Support
|
||||
|
||||
### Visually Hidden Content
|
||||
|
||||
Use `.sr-only` for screen-reader-only text:
|
||||
|
||||
```tsx
|
||||
<Button>
|
||||
<span className="sr-only">Delete item</span>
|
||||
<Trash className="h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Skip Links
|
||||
|
||||
```tsx
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-background focus:ring-2"
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
```
|
||||
|
||||
### Icon-Only Buttons
|
||||
|
||||
```tsx
|
||||
// Always provide text alternative
|
||||
<Button variant="ghost" size="icon" aria-label="Search">
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
// Or use tooltip with title
|
||||
<Button variant="ghost" size="icon" title="Search">
|
||||
<Search className="h-4 w-4" />
|
||||
<span className="sr-only">Search</span>
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Motion & Animation
|
||||
|
||||
### Respect User Preferences
|
||||
|
||||
```css
|
||||
/* Disable animations for users who prefer reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation in Components
|
||||
|
||||
```tsx
|
||||
// Add to globals.css
|
||||
@layer base {
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dark Mode Accessibility
|
||||
|
||||
### Proper Contrast in Both Modes
|
||||
|
||||
```css
|
||||
/* Test both themes */
|
||||
:root {
|
||||
/* Light mode */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
/* Ensure ≥ 4.5:1 ratio */
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Dark mode */
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
/* Ensure ≥ 4.5:1 ratio */
|
||||
}
|
||||
```
|
||||
|
||||
### Flash Prevention
|
||||
|
||||
```tsx
|
||||
// In app/layout.tsx
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange {/* Prevent flash */}
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### Manual Testing
|
||||
|
||||
- [ ] Navigate entire app with keyboard only (no mouse)
|
||||
- [ ] Test with screen reader (NVDA, JAWS, VoiceOver)
|
||||
- [ ] Zoom to 200% and ensure layout doesn't break
|
||||
- [ ] Test in high contrast mode
|
||||
- [ ] Verify dark mode contrast
|
||||
- [ ] Check focus indicators on all interactive elements
|
||||
- [ ] Test form validation with screen reader
|
||||
|
||||
### Automated Testing
|
||||
|
||||
```bash
|
||||
# Install axe-core
|
||||
npm install --save-dev @axe-core/playwright
|
||||
|
||||
# Use in tests
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { injectAxe, checkA11y } from 'axe-playwright'
|
||||
|
||||
test('homepage is accessible', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000')
|
||||
await injectAxe(page)
|
||||
await checkA11y(page)
|
||||
})
|
||||
```
|
||||
|
||||
### Browser DevTools
|
||||
|
||||
- Chrome Lighthouse (Accessibility audit)
|
||||
- Firefox Accessibility Inspector
|
||||
- Edge Accessibility Insights
|
||||
- axe DevTools Extension
|
||||
|
||||
## Common Issues & Fixes
|
||||
|
||||
### Issue: Missing Form Labels
|
||||
|
||||
```tsx
|
||||
[ERROR] Problem
|
||||
<Input placeholder="Email" />
|
||||
|
||||
[OK] Fix
|
||||
<div>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" placeholder="you@example.com" />
|
||||
</div>
|
||||
```
|
||||
|
||||
### Issue: Non-Accessible Custom Components
|
||||
|
||||
```tsx
|
||||
[ERROR] Problem
|
||||
<div onClick={handleClick} className="cursor-pointer">
|
||||
Click me
|
||||
</div>
|
||||
|
||||
[OK] Fix
|
||||
<button onClick={handleClick} type="button">
|
||||
Click me
|
||||
</button>
|
||||
```
|
||||
|
||||
### Issue: Low Color Contrast
|
||||
|
||||
```tsx
|
||||
[ERROR] Problem
|
||||
<p className="text-gray-400">Important text</p> {/* 2.8:1 ratio */}
|
||||
|
||||
[OK] Fix
|
||||
<p className="text-gray-700 dark:text-gray-300">Important text</p> {/* 5.2:1 ratio */}
|
||||
```
|
||||
|
||||
### Issue: Missing Focus Indicators
|
||||
|
||||
```tsx
|
||||
[ERROR] Problem
|
||||
<Button className="focus:outline-none">Click</Button>
|
||||
|
||||
[OK] Fix
|
||||
<Button className="focus-visible:ring-2 focus-visible:ring-ring">Click</Button>
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **WCAG Guidelines**: https://www.w3.org/WAI/WCAG21/quickref/
|
||||
- **WebAIM**: https://webaim.org/
|
||||
- **a11y Project**: https://www.a11yproject.com/
|
||||
- **Radix UI (shadcn base)**: https://radix-ui.com (includes A11y)
|
||||
- **MDN Accessibility**: https://developer.mozilla.org/en-US/docs/Web/Accessibility
|
||||
|
||||
## Skill Implementation
|
||||
|
||||
This skill ensures:
|
||||
- [OK] High contrast color tokens (≥ 4.5:1 for text)
|
||||
- [OK] Visible focus styles with ring utilities
|
||||
- [OK] Skip link in base layout
|
||||
- [OK] Semantic HTML landmarks (header, nav, main, footer)
|
||||
- [OK] Proper label/input associations
|
||||
- [OK] Dark mode with accessible contrast
|
||||
- [OK] Reduced motion support
|
||||
- [OK] All shadcn components use Radix (accessible primitives)
|
||||
@@ -0,0 +1,370 @@
|
||||
# shadcn/ui Component Reference
|
||||
|
||||
## Overview
|
||||
|
||||
shadcn/ui provides a collection of re-usable components built with Radix UI and Tailwind CSS. Components are NOT installed as dependencies but copied into your project, giving you full control.
|
||||
|
||||
## Installation Command
|
||||
|
||||
```bash
|
||||
npx shadcn-ui@latest add [component-name]
|
||||
```
|
||||
|
||||
## Baseline Components for This Skill
|
||||
|
||||
The following components are installed by default:
|
||||
|
||||
### Core UI Elements
|
||||
- **button**: Primary interactive element with variants
|
||||
- **card**: Container for grouped content
|
||||
- **input**: Text input field
|
||||
- **label**: Form label with accessibility support
|
||||
- **dialog**: Modal dialog/overlay
|
||||
- **separator**: Visual or semantic separator
|
||||
|
||||
### Layout & Navigation
|
||||
- **sheet**: Slide-over panel (mobile sidebar)
|
||||
- **dropdown-menu**: Contextual menu with submenus
|
||||
- **navigation-menu**: Main navigation component
|
||||
|
||||
### Feedback & Notifications
|
||||
- **toast**: Temporary notifications (via Sonner)
|
||||
- **alert**: Static notification messages
|
||||
- **badge**: Status or category indicator
|
||||
|
||||
## Available Components by Category
|
||||
|
||||
### Form Components
|
||||
- `checkbox` - Checkbox input with label
|
||||
- `input` - Text input field
|
||||
- `label` - Form label
|
||||
- `textarea` - Multi-line text input
|
||||
- `select` - Dropdown select
|
||||
- `radio-group` - Radio button group
|
||||
- `switch` - Toggle switch
|
||||
- `slider` - Range slider
|
||||
- `form` - Form wrapper with React Hook Form integration
|
||||
- `combobox` - Searchable select (autocomplete)
|
||||
- `date-picker` - Date selection with calendar
|
||||
- `calendar` - Calendar component
|
||||
|
||||
### Layout Components
|
||||
- `card` - Content card with header/footer
|
||||
- `sheet` - Slide-over panel
|
||||
- `dialog` - Modal dialog
|
||||
- `popover` - Floating popover
|
||||
- `drawer` - Bottom drawer (mobile)
|
||||
- `separator` - Divider line
|
||||
- `scroll-area` - Custom scrollable area
|
||||
- `aspect-ratio` - Maintain aspect ratio
|
||||
- `resizable` - Resizable panels
|
||||
|
||||
### Navigation Components
|
||||
- `navigation-menu` - Main navigation
|
||||
- `menubar` - Desktop menu bar
|
||||
- `dropdown-menu` - Context/dropdown menu
|
||||
- `context-menu` - Right-click context menu
|
||||
- `tabs` - Tab navigation
|
||||
- `breadcrumb` - Breadcrumb navigation
|
||||
- `pagination` - Page navigation
|
||||
- `command` - Command palette (⌘K)
|
||||
|
||||
### Feedback Components
|
||||
- `toast` - Toast notifications
|
||||
- `alert` - Alert messages
|
||||
- `alert-dialog` - Confirmation dialog
|
||||
- `badge` - Status badge
|
||||
- `progress` - Progress indicator
|
||||
- `skeleton` - Loading skeleton
|
||||
- `spinner` - Loading spinner
|
||||
|
||||
### Data Display
|
||||
- `table` - Data table
|
||||
- `avatar` - User avatar
|
||||
- `tooltip` - Hover tooltip
|
||||
- `accordion` - Collapsible sections
|
||||
- `collapsible` - Simple collapsible
|
||||
- `hover-card` - Rich hover card
|
||||
- `data-table` - Advanced data table with sorting/filtering
|
||||
|
||||
### Utility Components
|
||||
- `toggle` - Toggle button
|
||||
- `toggle-group` - Toggle button group
|
||||
- `sonner` - Toast notification library integration
|
||||
- `carousel` - Image/content carousel
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Adding a Single Component
|
||||
|
||||
```bash
|
||||
npx shadcn-ui add button
|
||||
```
|
||||
|
||||
This creates:
|
||||
- `components/ui/button.tsx`
|
||||
- Updates `components.json` if needed
|
||||
|
||||
### Adding Multiple Components
|
||||
|
||||
```bash
|
||||
npx shadcn-ui add button card input label
|
||||
```
|
||||
|
||||
### Using a Component
|
||||
|
||||
```tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||
|
||||
export default function Example() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Example Card</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button>Click me</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Component Composition Patterns
|
||||
|
||||
### Form with Validation
|
||||
|
||||
```tsx
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import * as z from "zod"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { toast } from "sonner"
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8)
|
||||
})
|
||||
|
||||
export function LoginForm() {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(schema)
|
||||
})
|
||||
|
||||
const onSubmit = (data) => {
|
||||
toast.success("Login successful!")
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" {...form.register("email")} />
|
||||
</div>
|
||||
<Button type="submit">Login</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Dialog with Form
|
||||
|
||||
```tsx
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
export function CreateDialog() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Create New</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Item</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form>
|
||||
<Input placeholder="Item name" />
|
||||
<Button type="submit">Create</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Navigation with Dropdown
|
||||
|
||||
```tsx
|
||||
import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuLink } from "@/components/ui/navigation-menu"
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function AppNav() {
|
||||
return (
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink href="/">Home</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost">Products</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>Product A</DropdownMenuItem>
|
||||
<DropdownMenuItem>Product B</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Modifying Component Styles
|
||||
|
||||
Components use Tailwind classes and can be customized:
|
||||
|
||||
```tsx
|
||||
// Customize colors via CSS variables in globals.css
|
||||
:root {
|
||||
--primary: 200 100% 50%; /* Change primary color */
|
||||
}
|
||||
|
||||
// Or override with props
|
||||
<Button className="bg-purple-600 hover:bg-purple-700">
|
||||
Custom Color
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Creating Variants
|
||||
|
||||
Use `class-variance-authority` (CVA) for variants:
|
||||
|
||||
```tsx
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
const alertVariants = cva(
|
||||
"rounded-lg border p-4",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive: "bg-destructive text-destructive-foreground",
|
||||
success: "bg-green-50 text-green-900"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Accessibility Features
|
||||
|
||||
All shadcn/ui components include:
|
||||
- [OK] Proper ARIA attributes
|
||||
- [OK] Keyboard navigation
|
||||
- [OK] Focus management
|
||||
- [OK] Screen reader support
|
||||
- [OK] Reduced motion support
|
||||
|
||||
### Example: Dialog Accessibility
|
||||
|
||||
```tsx
|
||||
<Dialog>
|
||||
{/* Auto-handled: */}
|
||||
{/* - Focus trap */}
|
||||
{/* - ESC to close */}
|
||||
{/* - aria-labelledby */}
|
||||
{/* - Backdrop click to close */}
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Title (used for aria-labelledby)</DialogTitle>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Use Labels with Inputs
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<div>
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input id="username" />
|
||||
</div>
|
||||
|
||||
[ERROR] Bad
|
||||
<Input placeholder="Username" /> {/* Placeholder is not a label */}
|
||||
```
|
||||
|
||||
### 2. Provide Accessible Names
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<Button aria-label="Close menu">
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
[ERROR] Bad
|
||||
<Button>
|
||||
<X className="h-4 w-4" /> {/* No text or aria-label */}
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 3. Handle Loading States
|
||||
|
||||
```tsx
|
||||
<Button disabled={isLoading}>
|
||||
{isLoading ? "Loading..." : "Submit"}
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 4. Use Semantic HTML
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
|
||||
[ERROR] Bad
|
||||
<div onClick={handleSubmit}>
|
||||
<Button type="button">Submit</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **Official Docs**: https://ui.shadcn.com
|
||||
- **Component Examples**: https://ui.shadcn.com/examples
|
||||
- **Radix UI Docs**: https://radix-ui.com
|
||||
- **GitHub**: https://github.com/shadcn-ui/ui
|
||||
|
||||
## Integration with This Skill
|
||||
|
||||
The skill installs these baseline components automatically:
|
||||
1. `button` - Primary actions
|
||||
2. `card` - Content containers
|
||||
3. `input` - Form inputs
|
||||
4. `label` - Form labels
|
||||
5. `dialog` - Modals
|
||||
6. `separator` - Visual dividers
|
||||
|
||||
Additional components can be added as needed:
|
||||
```bash
|
||||
npx shadcn-ui add [component-name]
|
||||
```
|
||||
@@ -0,0 +1,206 @@
|
||||
# Tailwind CSS v4 Migration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Tailwind CSS v4 introduces significant changes to configuration and usage patterns. This document outlines the differences and provides migration guidance.
|
||||
|
||||
## Current Status (as of January 2025)
|
||||
|
||||
- **Tailwind CSS v3.x**: Stable, widely adopted
|
||||
- **Tailwind CSS v4**: In development, alpha/beta releases available
|
||||
- **Recommendation**: Use v3 with forward-compatible patterns for production projects
|
||||
|
||||
## Key Differences: v3 vs v4
|
||||
|
||||
### Configuration Format
|
||||
|
||||
**v3 (Current)**
|
||||
```ts
|
||||
// tailwind.config.ts
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: ['./app/**/*.{ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: { /* ... */ }
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
} satisfies Config
|
||||
```
|
||||
|
||||
**v4 (Future)**
|
||||
```css
|
||||
/* @config directive in CSS */
|
||||
@config "./tailwind.config.js";
|
||||
|
||||
@theme {
|
||||
--color-brand: #3b82f6;
|
||||
--font-sans: system-ui, sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
### CSS Variables & Tokens
|
||||
|
||||
**v3 Pattern (Forward-Compatible)**
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// tailwind.config.ts
|
||||
colors: {
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))'
|
||||
}
|
||||
```
|
||||
|
||||
**v4 Expected Pattern**
|
||||
```css
|
||||
@theme {
|
||||
--color-background: oklch(100% 0 0);
|
||||
--color-foreground: oklch(20% 0.02 270);
|
||||
}
|
||||
```
|
||||
|
||||
### Plugin System
|
||||
|
||||
**v3**: JavaScript-based plugins in config file
|
||||
**v4**: CSS-based plugins with `@plugin` directive
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: v3 with Forward-Compatible Patterns (Current)
|
||||
|
||||
Use CSS variables extensively:
|
||||
- Define semantic tokens (--primary, --background, etc.)
|
||||
- Use HSL/OKLCH color space for easier manipulation
|
||||
- Structure CSS variables to match expected v4 patterns
|
||||
|
||||
### Phase 2: v4 Alpha/Beta Testing (When Available)
|
||||
|
||||
Test v4 alphas in non-production projects:
|
||||
```bash
|
||||
npm install tailwindcss@next
|
||||
```
|
||||
|
||||
Update configuration:
|
||||
- Move color tokens to `@theme` directive
|
||||
- Convert plugins to CSS-based format
|
||||
- Test build performance and output
|
||||
|
||||
### Phase 3: v4 Stable Migration (Future)
|
||||
|
||||
When v4 is stable:
|
||||
1. Update package: `npm install tailwindcss@latest`
|
||||
2. Migrate config format (may have migration tool)
|
||||
3. Test thoroughly across all components
|
||||
4. Update documentation
|
||||
|
||||
## Recommended Patterns for This Skill
|
||||
|
||||
### Use CSS Custom Properties
|
||||
|
||||
[OK] **Good (Forward-Compatible)**
|
||||
```css
|
||||
:root {
|
||||
--radius: 0.5rem;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: var(--radius);
|
||||
background: hsl(var(--primary));
|
||||
}
|
||||
```
|
||||
|
||||
[ERROR] **Avoid (Hard-Coded)**
|
||||
```css
|
||||
.card {
|
||||
border-radius: 0.5rem;
|
||||
background: #1e293b;
|
||||
}
|
||||
```
|
||||
|
||||
### Semantic Color Naming
|
||||
|
||||
Use semantic names that describe purpose, not appearance:
|
||||
- [OK] `--primary`, `--destructive`, `--muted`
|
||||
- [ERROR] `--blue-500`, `--red-600`, `--gray-200`
|
||||
|
||||
### HSL Color Space
|
||||
|
||||
Use HSL (or OKLCH when v4 is available) for better color manipulation:
|
||||
```css
|
||||
/* HSL: Hue Saturation Lightness */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Usage in CSS */
|
||||
background: hsl(var(--primary));
|
||||
background: hsl(var(--primary) / 0.8); /* With alpha */
|
||||
```
|
||||
|
||||
## Checking for v4 Availability
|
||||
|
||||
```bash
|
||||
# Check installed version
|
||||
npm list tailwindcss
|
||||
|
||||
# Check latest available version
|
||||
npm view tailwindcss versions --json
|
||||
|
||||
# Install specific version
|
||||
npm install tailwindcss@4.0.0 # When available
|
||||
```
|
||||
|
||||
## V4-Specific Features to Watch For
|
||||
|
||||
### CSS-First Configuration
|
||||
- `@theme` directive for token definition
|
||||
- `@plugin` for extending functionality
|
||||
- Native CSS nesting support
|
||||
|
||||
### Improved Color System
|
||||
- OKLCH color space support
|
||||
- Better color mixing
|
||||
- Improved dark mode handling
|
||||
|
||||
### Performance Improvements
|
||||
- Faster build times
|
||||
- Smaller CSS output
|
||||
- Better JIT performance
|
||||
|
||||
### New Utilities
|
||||
- Container queries (better support)
|
||||
- Cascade layers
|
||||
- View transitions
|
||||
|
||||
## Resources
|
||||
|
||||
- **Tailwind CSS Docs**: https://tailwindcss.com/docs
|
||||
- **V4 Alpha Docs**: https://tailwindcss.com/docs/v4-alpha
|
||||
- **GitHub Discussions**: https://github.com/tailwindlabs/tailwindcss/discussions
|
||||
- **Upgrade Guide**: https://tailwindcss.com/docs/upgrade-guide (when v4 is stable)
|
||||
|
||||
## Notes for Skill Usage
|
||||
|
||||
When this skill runs:
|
||||
1. Check Tailwind version: `npm view tailwindcss version`
|
||||
2. If v4 stable is available: Use v4 configuration patterns
|
||||
3. If v4 is not available: Use v3 with forward-compatible CSS variables
|
||||
4. Add comments in generated files indicating v4 migration points:
|
||||
```ts
|
||||
// TODO: When upgrading to Tailwind v4, move these tokens to @theme directive
|
||||
```
|
||||
|
||||
This approach ensures the skill produces production-ready code today while being ready for v4 when it arrives.
|
||||
548
skills/tailwind-shadcn-ui-setup/references/theme-tokens.md
Normal file
548
skills/tailwind-shadcn-ui-setup/references/theme-tokens.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# Theme Token System
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the CSS custom property-based design token system used for Tailwind + shadcn/ui theming.
|
||||
|
||||
## Token Philosophy
|
||||
|
||||
### Semantic Naming
|
||||
Tokens describe **purpose**, not appearance:
|
||||
- [OK] `--primary` (describes role)
|
||||
- [ERROR] `--blue-600` (describes appearance)
|
||||
|
||||
### Benefits
|
||||
- Easy theme switching
|
||||
- Consistent design system
|
||||
- Automatic dark mode support
|
||||
- Forward-compatible with Tailwind v4
|
||||
|
||||
## Color Token Structure
|
||||
|
||||
### HSL Format
|
||||
|
||||
All colors use HSL (Hue, Saturation, Lightness) format without the `hsl()` wrapper:
|
||||
|
||||
```css
|
||||
/* Define as space-separated values */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Use with hsl() wrapper */
|
||||
background-color: hsl(var(--primary));
|
||||
|
||||
/* With alpha transparency */
|
||||
background-color: hsl(var(--primary) / 0.5);
|
||||
```
|
||||
|
||||
### Why HSL?
|
||||
- **Intuitive**: Easier to adjust than RGB
|
||||
- **Lightness control**: Simple to create shades
|
||||
- **Alpha transparency**: Works with modern CSS syntax
|
||||
- **Tooling**: Better design tool support
|
||||
|
||||
## Core Color Tokens
|
||||
|
||||
### Base Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Background & Foreground */
|
||||
--background: 0 0% 100%; /* Pure white */
|
||||
--foreground: 222.2 84% 4.9%; /* Near black text */
|
||||
|
||||
/* Card (elevated surfaces) */
|
||||
--card: 0 0% 100%; /* White card background */
|
||||
--card-foreground: 222.2 84% 4.9%; /* Card text */
|
||||
|
||||
/* Popover (floating UI) */
|
||||
--popover: 0 0% 100%; /* White popover */
|
||||
--popover-foreground: 222.2 84% 4.9%; /* Popover text */
|
||||
}
|
||||
```
|
||||
|
||||
### Semantic Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Primary (brand color, main actions) */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
/* Secondary (less prominent actions) */
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Muted (disabled states, subtle backgrounds) */
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
/* Accent (highlights, hover states) */
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Destructive (errors, delete actions) */
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
}
|
||||
```
|
||||
|
||||
### UI Element Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Borders */
|
||||
--border: 214.3 31.8% 91.4%; /* Subtle border */
|
||||
--input: 214.3 31.8% 91.4%; /* Input border */
|
||||
|
||||
/* Focus & Selection */
|
||||
--ring: 222.2 84% 4.9%; /* Focus ring */
|
||||
|
||||
/* Status Colors (optional) */
|
||||
--success: 142 76% 36%;
|
||||
--success-foreground: 0 0% 100%;
|
||||
--warning: 38 92% 50%;
|
||||
--warning-foreground: 0 0% 100%;
|
||||
--info: 199 89% 48%;
|
||||
--info-foreground: 0 0% 100%;
|
||||
}
|
||||
```
|
||||
|
||||
## Dark Mode Tokens
|
||||
|
||||
### Dark Mode Strategy
|
||||
|
||||
Use `.dark` class to override tokens:
|
||||
|
||||
```css
|
||||
.dark {
|
||||
/* Base */
|
||||
--background: 222.2 84% 4.9%; /* Near black */
|
||||
--foreground: 210 40% 98%; /* Near white */
|
||||
|
||||
/* Card */
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
/* Primary */
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Secondary */
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
/* Muted */
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
/* Accent */
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
/* Destructive */
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
/* Borders */
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
|
||||
/* Focus */
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
```
|
||||
|
||||
## Theme Presets
|
||||
|
||||
### Zinc (Default)
|
||||
|
||||
Cool, neutral gray tones. Professional and versatile.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--border: 240 5.9% 90%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
}
|
||||
```
|
||||
|
||||
### Slate
|
||||
|
||||
Slightly cooler than Zinc. Tech-focused feel.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
}
|
||||
```
|
||||
|
||||
### Neutral
|
||||
|
||||
True neutral grays. Clean and minimal.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--border: 0 0% 89.8%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--border: 0 0% 14.9%;
|
||||
}
|
||||
```
|
||||
|
||||
## Spacing & Sizing Tokens
|
||||
|
||||
### Border Radius
|
||||
|
||||
```css
|
||||
:root {
|
||||
--radius: 0.5rem; /* Base radius: 8px */
|
||||
}
|
||||
|
||||
/* Usage in Tailwind config */
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
}
|
||||
```
|
||||
|
||||
### Font Family (Optional)
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-sans: ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: ui-monospace, 'Cascadia Code', monospace;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage in Tailwind Config
|
||||
|
||||
### Extending Theme
|
||||
|
||||
```ts
|
||||
// tailwind.config.ts
|
||||
export default {
|
||||
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))'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))'
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))'
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Tokens in Components
|
||||
|
||||
### In CSS
|
||||
|
||||
```css
|
||||
.custom-component {
|
||||
background-color: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
/* With alpha */
|
||||
.overlay {
|
||||
background-color: hsl(var(--background) / 0.8);
|
||||
}
|
||||
```
|
||||
|
||||
### With Tailwind Utilities
|
||||
|
||||
```tsx
|
||||
<div className="bg-background text-foreground border-border">
|
||||
<h1 className="text-primary">Heading</h1>
|
||||
<p className="text-muted-foreground">Subtitle</p>
|
||||
<Button variant="destructive">Delete</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Customization Guide
|
||||
|
||||
### Changing Primary Color
|
||||
|
||||
1. **Find your HSL values**: Use a color picker that shows HSL
|
||||
2. **Update light mode**:
|
||||
```css
|
||||
:root {
|
||||
--primary: 270 80% 45%; /* Purple example */
|
||||
--primary-foreground: 0 0% 100%; /* White text */
|
||||
}
|
||||
```
|
||||
3. **Update dark mode**:
|
||||
```css
|
||||
.dark {
|
||||
--primary: 270 80% 65%; /* Lighter purple */
|
||||
--primary-foreground: 240 10% 10%; /* Dark text */
|
||||
}
|
||||
```
|
||||
4. **Test contrast**: Use WebAIM contrast checker
|
||||
|
||||
### Creating Custom Tokens
|
||||
|
||||
```css
|
||||
/* Add to globals.css */
|
||||
:root {
|
||||
/* Custom tokens */
|
||||
--sidebar-width: 16rem;
|
||||
--header-height: 4rem;
|
||||
--brand-gradient: linear-gradient(135deg, hsl(var(--primary)), hsl(var(--accent)));
|
||||
}
|
||||
|
||||
/* Use in components */
|
||||
.sidebar {
|
||||
width: var(--sidebar-width);
|
||||
}
|
||||
```
|
||||
|
||||
### Adding More Semantic Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
--success: 142 76% 36%;
|
||||
--success-foreground: 0 0% 100%;
|
||||
--warning: 38 92% 50%;
|
||||
--warning-foreground: 0 0% 100%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--success: 142 71% 45%;
|
||||
--warning: 38 92% 60%;
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// Add to tailwind.config.ts
|
||||
colors: {
|
||||
success: {
|
||||
DEFAULT: 'hsl(var(--success))',
|
||||
foreground: 'hsl(var(--success-foreground))'
|
||||
},
|
||||
warning: {
|
||||
DEFAULT: 'hsl(var(--warning))',
|
||||
foreground: 'hsl(var(--warning-foreground))'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Pair Background/Foreground
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<div className="bg-primary text-primary-foreground">
|
||||
Readable text
|
||||
</div>
|
||||
|
||||
[ERROR] Bad
|
||||
<div className="bg-primary text-foreground">
|
||||
Low contrast
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. Test Both Themes
|
||||
|
||||
Always verify colors in both light and dark mode:
|
||||
```bash
|
||||
# Use browser DevTools to toggle:
|
||||
document.documentElement.classList.toggle('dark')
|
||||
```
|
||||
|
||||
### 3. Use Semantic Tokens
|
||||
|
||||
```tsx
|
||||
[OK] Good (semantic)
|
||||
<p className="text-muted-foreground">Helper text</p>
|
||||
<Button variant="destructive">Delete</Button>
|
||||
|
||||
[ERROR] Bad (hard-coded)
|
||||
<p className="text-gray-500 dark:text-gray-400">Helper text</p>
|
||||
<Button className="bg-red-600">Delete</Button>
|
||||
```
|
||||
|
||||
### 4. Maintain Contrast Ratios
|
||||
|
||||
- Normal text: ≥ 4.5:1
|
||||
- Large text: ≥ 3:1
|
||||
- UI components: ≥ 3:1
|
||||
|
||||
## Tools for Theme Development
|
||||
|
||||
### Color Pickers with HSL
|
||||
- [HSL Color Picker](https://hslpicker.com/)
|
||||
- [Coolors](https://coolors.co/)
|
||||
- [Palettte App](https://palettte.app/)
|
||||
|
||||
### Contrast Checkers
|
||||
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
|
||||
- [Contrast Ratio](https://contrast-ratio.com/)
|
||||
- Chrome DevTools (Lighthouse)
|
||||
|
||||
### Theme Generators
|
||||
- [Realtime Colors](https://realtimecolors.com/)
|
||||
- [shadcn/ui Themes](https://ui.shadcn.com/themes)
|
||||
- [Tailwind Color Generator](https://uicolors.app/create)
|
||||
|
||||
## Example: Complete Theme
|
||||
|
||||
```css
|
||||
/* globals.css */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
/* Base */
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
|
||||
/* UI Elements */
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
|
||||
/* Primary Brand */
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
/* Secondary Actions */
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
|
||||
/* Muted Elements */
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
|
||||
/* Accents */
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
|
||||
/* Destructive */
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
/* Borders & Inputs */
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
|
||||
/* Radius */
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-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%;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **HSL Color Space**: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl
|
||||
- **shadcn/ui Theming**: https://ui.shadcn.com/docs/theming
|
||||
- **Tailwind CSS Customization**: https://tailwindcss.com/docs/customizing-colors
|
||||
- **Design Tokens**: https://www.w3.org/community/design-tokens/
|
||||
339
skills/tailwind-shadcn-ui-setup/scripts/setup_tailwind_shadcn.py
Normal file
339
skills/tailwind-shadcn-ui-setup/scripts/setup_tailwind_shadcn.py
Normal file
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tailwind CSS v4 + shadcn/ui Setup Automation Script
|
||||
|
||||
Configures Tailwind CSS and shadcn/ui for Next.js 16 App Router projects.
|
||||
Handles detection of existing setup, merging configurations, and installing dependencies.
|
||||
"""
|
||||
|
||||
import io
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Configure stdout for UTF-8 encoding (prevents Windows encoding errors)
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
|
||||
|
||||
class SetupManager:
|
||||
"""Manages the Tailwind + shadcn/ui setup process."""
|
||||
|
||||
def __init__(self, project_root: Path):
|
||||
self.project_root = project_root
|
||||
self.package_json_path = project_root / "package.json"
|
||||
self.existing_tailwind = False
|
||||
self.existing_shadcn = False
|
||||
|
||||
def detect_existing_setup(self) -> Dict[str, bool]:
|
||||
"""Detect existing Tailwind and shadcn/ui installations."""
|
||||
print("[INFO] Detecting existing setup...")
|
||||
|
||||
if not self.package_json_path.exists():
|
||||
print("[ERROR] Error: package.json not found. Is this a Next.js project?")
|
||||
sys.exit(1)
|
||||
|
||||
with open(self.package_json_path) as f:
|
||||
package_data = json.load(f)
|
||||
|
||||
dependencies = {
|
||||
**package_data.get("dependencies", {}),
|
||||
**package_data.get("devDependencies", {})
|
||||
}
|
||||
|
||||
self.existing_tailwind = "tailwindcss" in dependencies
|
||||
self.existing_shadcn = "class-variance-authority" in dependencies
|
||||
|
||||
return {
|
||||
"tailwind": self.existing_tailwind,
|
||||
"shadcn": self.existing_shadcn,
|
||||
"nextjs": "next" in dependencies
|
||||
}
|
||||
|
||||
def install_dependencies(self, use_dark_mode: bool = True):
|
||||
"""Install required npm packages."""
|
||||
print("\n[INSTALL] Installing dependencies...")
|
||||
|
||||
# Core dependencies
|
||||
core_deps = [
|
||||
"tailwindcss",
|
||||
"postcss",
|
||||
"autoprefixer",
|
||||
"class-variance-authority",
|
||||
"clsx",
|
||||
"tailwind-merge",
|
||||
"lucide-react",
|
||||
"@tailwindcss/forms",
|
||||
"@tailwindcss/typography"
|
||||
]
|
||||
|
||||
if use_dark_mode:
|
||||
core_deps.append("next-themes")
|
||||
|
||||
# shadcn/ui peer dependencies
|
||||
shadcn_deps = [
|
||||
"@radix-ui/react-slot",
|
||||
"@radix-ui/react-dialog",
|
||||
"@radix-ui/react-dropdown-menu",
|
||||
"@radix-ui/react-separator",
|
||||
"sonner"
|
||||
]
|
||||
|
||||
all_deps = core_deps + shadcn_deps
|
||||
|
||||
print(f"Installing: {', '.join(all_deps)}")
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
["npm", "install", "--save"] + all_deps,
|
||||
cwd=self.project_root,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
print("[OK] Dependencies installed successfully")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"[ERROR] Error installing dependencies: {e.stderr}")
|
||||
sys.exit(1)
|
||||
|
||||
def check_shadcn_init(self) -> bool:
|
||||
"""Check if shadcn/ui has been initialized."""
|
||||
components_json = self.project_root / "components.json"
|
||||
return components_json.exists()
|
||||
|
||||
def init_shadcn(self):
|
||||
"""Initialize shadcn/ui configuration."""
|
||||
if self.check_shadcn_init():
|
||||
print("[OK] shadcn/ui already initialized (components.json found)")
|
||||
return
|
||||
|
||||
print("\n[SETUP] Initializing shadcn/ui...")
|
||||
print("Note: This will create components.json with default settings")
|
||||
|
||||
# Create a default components.json
|
||||
components_config = {
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": True,
|
||||
"tsx": True,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": True
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
|
||||
components_json_path = self.project_root / "components.json"
|
||||
with open(components_json_path, 'w') as f:
|
||||
json.dump(components_config, f, indent=2)
|
||||
|
||||
print("[OK] Created components.json")
|
||||
|
||||
def add_shadcn_components(self, components: List[str]):
|
||||
"""Add shadcn/ui components."""
|
||||
print(f"\n[SETUP] Adding shadcn/ui components: {', '.join(components)}")
|
||||
|
||||
for component in components:
|
||||
try:
|
||||
print(f" Adding {component}...")
|
||||
subprocess.run(
|
||||
["npx", "shadcn-ui@latest", "add", component, "--yes"],
|
||||
cwd=self.project_root,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f" [WARN] Warning: Could not add {component}: {e.stderr}")
|
||||
print(f" You can add it manually later with: npx shadcn-ui add {component}")
|
||||
|
||||
print("[OK] shadcn/ui components added")
|
||||
|
||||
def copy_template_files(self, skill_dir: Path, theme_preset: str, use_dark_mode: bool, sidebar_layout: bool):
|
||||
"""Copy template files from assets directory."""
|
||||
print("\n[SETUP] Copying template files...")
|
||||
|
||||
assets_dir = skill_dir / "assets"
|
||||
|
||||
# Files to copy with their destinations
|
||||
template_mappings = {
|
||||
"tailwind.config.ts.template": self.project_root / "tailwind.config.ts",
|
||||
"postcss.config.js.template": self.project_root / "postcss.config.js",
|
||||
"globals.css.template": self.project_root / "app" / "globals.css",
|
||||
}
|
||||
|
||||
for template_name, dest_path in template_mappings.items():
|
||||
template_path = assets_dir / template_name
|
||||
if template_path.exists():
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Read template and replace placeholders
|
||||
content = template_path.read_text()
|
||||
content = content.replace("{{THEME_PRESET}}", theme_preset)
|
||||
|
||||
dest_path.write_text(content)
|
||||
print(f" [OK] Created {dest_path.relative_to(self.project_root)}")
|
||||
|
||||
def create_utils_file(self):
|
||||
"""Create lib/utils.ts with cn helper."""
|
||||
utils_dir = self.project_root / "lib"
|
||||
utils_dir.mkdir(exist_ok=True)
|
||||
|
||||
utils_content = '''import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
'''
|
||||
|
||||
utils_path = utils_dir / "utils.ts"
|
||||
if not utils_path.exists():
|
||||
utils_path.write_text(utils_content)
|
||||
print("[OK] Created lib/utils.ts")
|
||||
else:
|
||||
print("[INFO] lib/utils.ts already exists, skipping")
|
||||
|
||||
def print_summary(self, config: Dict):
|
||||
"""Print setup summary."""
|
||||
print("\n" + "="*60)
|
||||
print("[OK] Tailwind CSS + shadcn/ui Setup Complete!")
|
||||
print("="*60)
|
||||
print(f"\n[CONFIG] Configuration:")
|
||||
print(f" • Theme Preset: {config['theme_preset']}")
|
||||
print(f" • Dark Mode: {'Enabled' if config['use_dark_mode'] else 'Disabled'}")
|
||||
print(f" • Sidebar Layout: {'Yes' if config['sidebar_layout'] else 'No'}")
|
||||
print(f" • Examples: {'Included' if config['include_examples'] else 'Not included'}")
|
||||
print(f"\n[FILES] Files Created/Updated:")
|
||||
print(f" • tailwind.config.ts")
|
||||
print(f" • postcss.config.js")
|
||||
print(f" • app/globals.css")
|
||||
print(f" • components.json")
|
||||
print(f" • lib/utils.ts")
|
||||
print(f"\n[NEXT] Next Steps:")
|
||||
print(f" 1. Review tailwind.config.ts and customize tokens")
|
||||
print(f" 2. Check app/globals.css for CSS variables")
|
||||
print(f" 3. Start your dev server: npm run dev")
|
||||
print(f" 4. Add more shadcn components: npx shadcn-ui add [component]")
|
||||
print(f"\n[TIP] Tip: Run the skill again to add example pages and components")
|
||||
print("="*60 + "\n")
|
||||
|
||||
|
||||
def get_user_input(prompt: str, default: str, options: Optional[List[str]] = None) -> str:
|
||||
"""Get user input with validation."""
|
||||
while True:
|
||||
if options:
|
||||
prompt_with_options = f"{prompt} ({'/'.join(options)}) [{default}]: "
|
||||
else:
|
||||
prompt_with_options = f"{prompt} [{default}]: "
|
||||
|
||||
response = input(prompt_with_options).strip() or default
|
||||
|
||||
if options and response not in options:
|
||||
print(f"Invalid option. Please choose from: {', '.join(options)}")
|
||||
continue
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def main():
|
||||
"""Main setup function."""
|
||||
print("==> Tailwind CSS v4 + shadcn/ui Setup")
|
||||
print("="*60)
|
||||
|
||||
# Detect project root
|
||||
project_root = Path.cwd()
|
||||
|
||||
# Initialize setup manager
|
||||
manager = SetupManager(project_root)
|
||||
|
||||
# Detect existing setup
|
||||
existing = manager.detect_existing_setup()
|
||||
|
||||
if not existing["nextjs"]:
|
||||
print("[ERROR] Error: This doesn't appear to be a Next.js project")
|
||||
print(" Make sure you're in the project root with package.json")
|
||||
sys.exit(1)
|
||||
|
||||
if existing["tailwind"]:
|
||||
print("[OK] Existing Tailwind CSS installation detected")
|
||||
if existing["shadcn"]:
|
||||
print("[OK] Existing shadcn/ui installation detected")
|
||||
|
||||
print("\n[CONFIG] Configuration Options\n")
|
||||
|
||||
# Get user preferences
|
||||
use_dark_mode = get_user_input(
|
||||
"Enable dark mode?",
|
||||
"yes",
|
||||
["yes", "no"]
|
||||
) == "yes"
|
||||
|
||||
theme_preset = get_user_input(
|
||||
"Theme preset",
|
||||
"zinc",
|
||||
["zinc", "slate", "neutral"]
|
||||
)
|
||||
|
||||
sidebar_layout = get_user_input(
|
||||
"Include sidebar layout?",
|
||||
"yes",
|
||||
["yes", "no"]
|
||||
) == "yes"
|
||||
|
||||
include_examples = get_user_input(
|
||||
"Include example pages?",
|
||||
"yes",
|
||||
["yes", "no"]
|
||||
) == "yes"
|
||||
|
||||
config = {
|
||||
"use_dark_mode": use_dark_mode,
|
||||
"theme_preset": theme_preset,
|
||||
"sidebar_layout": sidebar_layout,
|
||||
"include_examples": include_examples
|
||||
}
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("Starting setup with your configuration...")
|
||||
print("="*60 + "\n")
|
||||
|
||||
# Install dependencies
|
||||
manager.install_dependencies(use_dark_mode)
|
||||
|
||||
# Initialize shadcn/ui
|
||||
manager.init_shadcn()
|
||||
|
||||
# Add base shadcn components
|
||||
base_components = ["button", "card", "input", "label", "dialog", "separator"]
|
||||
manager.add_shadcn_components(base_components)
|
||||
|
||||
# Create utils file
|
||||
manager.create_utils_file()
|
||||
|
||||
# Note: Template file copying requires the skill's asset directory
|
||||
# This would be handled by the SKILL.md instructions to copy files
|
||||
print("\n[INFO] Note: Template files should be copied by the skill instructions")
|
||||
print(" This script handles dependency installation and shadcn/ui setup")
|
||||
|
||||
# Print summary
|
||||
manager.print_summary(config)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[WARN] Setup cancelled by user")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n[ERROR] Error: {e}")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user