styling-with-shadcn
Build beautiful, accessible UIs with shadcn/ui components in Next.js. Use when creating forms, dialogs, tables, sidebars, or any UI components. Covers installation, component patterns, react-hook-form + Zod validation, and dark mode setup. NOT when building non-React applications or using different component libraries.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install mjunaidca-mjs-agent-skills-styling-with-shadcn
Repository
Skill path: .claude/skills/styling-with-shadcn
Build beautiful, accessible UIs with shadcn/ui components in Next.js. Use when creating forms, dialogs, tables, sidebars, or any UI components. Covers installation, component patterns, react-hook-form + Zod validation, and dark mode setup. NOT when building non-React applications or using different component libraries.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Frontend.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: mjunaidca.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install styling-with-shadcn into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/mjunaidca/mjs-agent-skills before adding styling-with-shadcn to shared team environments
- Use styling-with-shadcn for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: styling-with-shadcn
description: |
Build beautiful, accessible UIs with shadcn/ui components in Next.js. Use when creating
forms, dialogs, tables, sidebars, or any UI components. Covers installation, component
patterns, react-hook-form + Zod validation, and dark mode setup.
NOT when building non-React applications or using different component libraries.
---
# shadcn/ui
Build beautiful, accessible UIs with copy-paste components built on Radix UI and Tailwind CSS.
## Quick Start
```bash
# Initialize shadcn/ui in your Next.js project
npx shadcn@latest init
# Add components as needed
npx shadcn@latest add button form dialog table sidebar
```
## Common Component Install
```bash
npx shadcn@latest add button card form input label dialog \
table badge sidebar dropdown-menu avatar separator \
select textarea tabs toast sonner
```
---
## Core Patterns
### 1. Button Variants
```tsx
import { Button } from "@/components/ui/button"
<Button variant="default">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
// Sizes: sm, default, lg, icon
<Button size="icon"><Plus /></Button>
// Loading state
<Button disabled>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Loading...
</Button>
// As Next.js Link
<Button asChild>
<Link href="/dashboard">Go to Dashboard</Link>
</Button>
```
### 2. Forms with react-hook-form + Zod
```tsx
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
const schema = z.object({
title: z.string().min(1, "Required"),
priority: z.enum(["low", "medium", "high"]),
})
export function TaskForm({ onSubmit }) {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { title: "", priority: "medium" },
})
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
```
See [references/component-examples.md](references/component-examples.md) for complete form with Select, Textarea.
### 3. Dialog / Modal
```tsx
import {
Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger
} from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button>Create Task</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Create New Task</DialogTitle>
<DialogDescription>Add a new task to your project.</DialogDescription>
</DialogHeader>
<TaskForm onSubmit={handleSubmit} />
</DialogContent>
</Dialog>
// Controlled dialog
const [open, setOpen] = useState(false)
<Dialog open={open} onOpenChange={setOpen}>...</Dialog>
```
### 4. Alert Dialog (Confirmation)
```tsx
import {
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader,
AlertDialogTitle, AlertDialogTrigger
} from "@/components/ui/alert-dialog"
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">Delete</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete}>Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
```
### 5. Data Table (TanStack)
```tsx
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
const columns: ColumnDef<Task>[] = [
{ accessorKey: "title", header: "Title" },
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => <Badge>{row.getValue("status")}</Badge>,
},
]
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
})
```
See [references/component-examples.md](references/component-examples.md) for full DataTable with sorting/pagination.
### 6. Card Component
```tsx
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
<Card>
<CardHeader>
<CardTitle>{task.title}</CardTitle>
<CardDescription>Assigned to {task.assignee}</CardDescription>
</CardHeader>
<CardContent>
<p>{task.description}</p>
</CardContent>
<CardFooter>
<Button>Start</Button>
</CardFooter>
</Card>
```
### 7. Toast Notifications (Sonner)
```tsx
// Add Toaster to layout
import { Toaster } from "@/components/ui/sonner"
<Toaster />
// Use in components
import { toast } from "sonner"
toast.success("Task created")
toast.error("Failed to create task")
toast("Task Updated", { description: "Status changed to in progress" })
toast.promise(createTask(data), {
loading: "Creating...",
success: "Created!",
error: "Failed",
})
```
### 8. Sidebar Navigation
```tsx
import {
Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent,
SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger
} from "@/components/ui/sidebar"
<SidebarProvider>
<Sidebar>
<SidebarContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}><item.icon />{item.title}</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarContent>
</Sidebar>
<main><SidebarTrigger />{children}</main>
</SidebarProvider>
```
See [references/component-examples.md](references/component-examples.md) for full sidebar with persistent state.
### 9. Dark Mode
```tsx
// npm install next-themes
// components/theme-provider.tsx
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({ children, ...props }) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
// layout.tsx
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
// Theme toggle
import { useTheme } from "next-themes"
const { setTheme } = useTheme()
setTheme("dark") // or "light" or "system"
```
---
## Dependencies
```json
{
"dependencies": {
"@hookform/resolvers": "^3.x",
"@radix-ui/react-*": "latest",
"@tanstack/react-table": "^8.x",
"class-variance-authority": "^0.7.x",
"clsx": "^2.x",
"lucide-react": "^0.x",
"next-themes": "^0.4.x",
"react-hook-form": "^7.x",
"sonner": "^1.x",
"tailwind-merge": "^2.x",
"zod": "^3.x"
}
}
```
---
## Verification
Run: `python3 scripts/verify.py`
Expected: `✓ styling-with-shadcn skill ready`
## If Verification Fails
1. Check: references/ folder exists with component-examples.md
2. **Stop and report** if still failing
## Related Skills
- **fetching-library-docs** - Latest shadcn/ui docs: `--library-id /shadcn-ui/ui --topic components`
- **building-nextjs-apps** - Next.js 16 patterns for app structure
## References
- [references/component-examples.md](references/component-examples.md) - Full code examples
- [references/taskflow-theme.md](references/taskflow-theme.md) - Custom theme configuration
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/component-examples.md
```markdown
# shadcn/ui Component Examples
Full code examples for shadcn/ui components. See SKILL.md for quick patterns.
## Forms with react-hook-form + Zod
```tsx
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
// Define schema
const taskSchema = z.object({
title: z.string().min(1, "Title is required").max(200),
description: z.string().optional(),
priority: z.enum(["low", "medium", "high", "critical"]),
assignee: z.string().optional(),
})
type TaskFormValues = z.infer<typeof taskSchema>
export function TaskForm({ onSubmit }: { onSubmit: (data: TaskFormValues) => void }) {
const form = useForm<TaskFormValues>({
resolver: zodResolver(taskSchema),
defaultValues: {
title: "",
description: "",
priority: "medium",
},
})
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input placeholder="Task title..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea placeholder="Describe the task..." className="resize-none" {...field} />
</FormControl>
<FormDescription>Optional details about the task.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="priority"
render={({ field }) => (
<FormItem>
<FormLabel>Priority</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select priority" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="critical">Critical</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? "Creating..." : "Create Task"}
</Button>
</form>
</Form>
)
}
```
## Data Table with TanStack
```tsx
"use client"
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
getSortedRowModel,
SortingState,
} from "@tanstack/react-table"
import { useState } from "react"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
// Define columns
const columns: ColumnDef<Task>[] = [
{
accessorKey: "title",
header: "Title",
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const status = row.getValue("status") as string
return (
<Badge variant={status === "completed" ? "default" : "secondary"}>
{status}
</Badge>
)
},
},
{
accessorKey: "priority",
header: "Priority",
cell: ({ row }) => {
const priority = row.getValue("priority") as string
const colors = {
low: "bg-gray-100 text-gray-800",
medium: "bg-blue-100 text-blue-800",
high: "bg-orange-100 text-orange-800",
critical: "bg-red-100 text-red-800",
}
return (
<Badge className={colors[priority as keyof typeof colors]}>
{priority}
</Badge>
)
},
},
{
id: "actions",
cell: ({ row }) => {
const task = row.original
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleEdit(task)}>Edit</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleDelete(task.id)}>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
},
},
]
// DataTable component
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
state: { sorting },
})
return (
<div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button variant="outline" size="sm" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
Previous
</Button>
<Button variant="outline" size="sm" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
Next
</Button>
</div>
</div>
)
}
```
## Sidebar Navigation
```tsx
import { cookies } from "next/headers"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar"
import { LayoutDashboard, ListTodo, Users, Settings, Bot } from "lucide-react"
const menuItems = [
{ title: "Dashboard", url: "/dashboard", icon: LayoutDashboard },
{ title: "Tasks", url: "/tasks", icon: ListTodo, badge: "12" },
{ title: "Workers", url: "/workers", icon: Users },
{ title: "Agents", url: "/agents", icon: Bot },
{ title: "Settings", url: "/settings", icon: Settings },
]
export function AppSidebar() {
return (
<Sidebar>
<SidebarHeader>
<div className="flex items-center gap-2 px-4 py-2">
<Bot className="h-6 w-6" />
<span className="font-semibold">TaskFlow</span>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{menuItems.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
{item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<UserMenu />
</SidebarFooter>
</Sidebar>
)
}
// Layout with persistent sidebar state
export async function DashboardLayout({ children }: { children: React.ReactNode }) {
const cookieStore = await cookies()
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
return (
<SidebarProvider defaultOpen={defaultOpen}>
<AppSidebar />
<main className="flex-1">
<header className="flex h-14 items-center gap-4 border-b px-4">
<SidebarTrigger />
<h1 className="text-lg font-semibold">Dashboard</h1>
</header>
<div className="p-4">{children}</div>
</main>
</SidebarProvider>
)
}
```
## Dark Mode Setup
```tsx
// Install next-themes
// npm install next-themes
// components/theme-provider.tsx
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
// Add to layout
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</body>
</html>
)
}
// Theme toggle component
"use client"
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="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>Light</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>Dark</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>System</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
```
```
### references/taskflow-theme.md
```markdown
# TaskFlow Theme - Industrial-Kinetic Futurism
Adapted from RoboLearn's IFK design system for TaskFlow's task management UI.
## Design Philosophy
**Industrial-Kinetic Futurism (IFK)**: Dark industrial backgrounds with kinetic cyan + amber accents. Feels like a mission control center for human-agent collaboration.
## Color Palette
### Dark Theme (Primary)
```css
/* CSS Variables for globals.css */
:root {
/* Background Hierarchy */
--background: 222 15% 8%; /* #141418 - Deep industrial */
--background-secondary: 222 11% 11%; /* #1a1a1e - Elevated surfaces */
--background-tertiary: 222 8% 14%; /* #222226 - Cards, inputs */
/* Foreground/Text */
--foreground: 0 0% 100%; /* #ffffff - Primary text */
--foreground-secondary: 0 0% 72%; /* #b8b8b8 - Secondary text */
--foreground-muted: 0 0% 53%; /* #888888 - Muted text */
/* Primary: Kinetic Cyan (sensor/HUD feel) */
--primary: 191 100% 50%; /* #00d4ff - Main actions */
--primary-foreground: 222 15% 8%; /* Dark text on cyan */
--primary-glow: rgba(0, 212, 255, 0.4);
/* Secondary: Humanoid Amber (warmth, warnings) */
--secondary: 35 100% 50%; /* #ff9500 - Secondary actions */
--secondary-foreground: 222 15% 8%;
--secondary-glow: rgba(255, 149, 0, 0.3);
/* Accent (same as primary for consistency) */
--accent: 191 100% 50%;
--accent-foreground: 222 15% 8%;
/* Status Colors */
--success: 145 80% 42%; /* #22c55e - Completed, online */
--warning: 35 100% 50%; /* #ff9500 - Amber for warnings */
--destructive: 0 84% 60%; /* #ef4444 - Errors, delete */
--destructive-foreground: 0 0% 100%;
/* UI Elements */
--border: 0 0% 100% / 0.1; /* Subtle borders */
--input: 222 8% 14%; /* Input backgrounds */
--ring: 191 100% 50%; /* Focus rings - cyan */
/* Cards & Popovers */
--card: 222 11% 11%;
--card-foreground: 0 0% 100%;
--popover: 222 11% 11%;
--popover-foreground: 0 0% 100%;
/* Muted backgrounds */
--muted: 222 8% 14%;
--muted-foreground: 0 0% 53%;
}
```
### Light Theme
```css
.light {
--background: 0 0% 100%; /* #ffffff */
--background-secondary: 0 0% 96%; /* #f5f5f7 */
--background-tertiary: 0 0% 91%; /* #e8e8ed */
--foreground: 0 0% 11%; /* #1d1d1f */
--foreground-secondary: 0 0% 32%; /* #515154 */
--foreground-muted: 0 0% 53%; /* #86868b */
/* Adjusted cyan for light backgrounds */
--primary: 204 100% 40%; /* #0077cc */
--primary-foreground: 0 0% 100%;
/* Adjusted amber for light backgrounds */
--secondary: 30 100% 40%; /* #cc6600 */
--secondary-foreground: 0 0% 100%;
--border: 0 0% 0% / 0.1;
--input: 0 0% 96%;
}
```
## Typography
### Font Stack
```css
:root {
/* Display: Technical, monospace for headings and labels */
--font-display: 'JetBrains Mono', 'IBM Plex Mono', 'Courier New', monospace;
/* Body: Clean, modern sans-serif */
--font-body: 'Space Grotesk', 'Syne', system-ui, -apple-system, sans-serif;
}
```
### Installation
```bash
# Add to your Next.js project
npm install @fontsource/jetbrains-mono @fontsource/space-grotesk
```
```tsx
// app/layout.tsx
import '@fontsource/jetbrains-mono/400.css'
import '@fontsource/jetbrains-mono/500.css'
import '@fontsource/jetbrains-mono/600.css'
import '@fontsource/space-grotesk/400.css'
import '@fontsource/space-grotesk/500.css'
import '@fontsource/space-grotesk/600.css'
```
### Usage
```tsx
// Headings use display font
<h1 className="font-mono text-2xl font-semibold tracking-tight">
Task Dashboard
</h1>
// Body text uses sans font
<p className="font-sans text-sm text-muted-foreground">
Manage tasks across humans and agents
</p>
// Status labels use display font
<span className="font-mono text-xs uppercase tracking-wider text-primary">
IN PROGRESS
</span>
```
## Tailwind Configuration
```ts
// tailwind.config.ts
import type { Config } from "tailwindcss"
const config: Config = {
darkMode: ["class"],
content: ["./src/**/*.{ts,tsx}"],
theme: {
extend: {
colors: {
// IFK Color System
ifk: {
cyan: "hsl(191, 100%, 50%)",
"cyan-light": "hsl(191, 100%, 60%)",
"cyan-dark": "hsl(191, 100%, 40%)",
amber: "hsl(35, 100%, 50%)",
"amber-light": "hsl(35, 100%, 60%)",
"amber-dark": "hsl(35, 100%, 40%)",
},
},
fontFamily: {
mono: ["JetBrains Mono", "IBM Plex Mono", "monospace"],
sans: ["Space Grotesk", "Syne", "system-ui", "sans-serif"],
},
animation: {
"fade-in-up": "fadeInUp 0.6s ease forwards",
"pulse-glow": "pulseGlow 2s ease-in-out infinite",
"scan-line": "scanLine 4s linear infinite",
},
keyframes: {
fadeInUp: {
from: { opacity: "0", transform: "translateY(20px)" },
to: { opacity: "1", transform: "translateY(0)" },
},
pulseGlow: {
"0%, 100%": { opacity: "0.6" },
"50%": { opacity: "1" },
},
scanLine: {
"0%": { transform: "translateY(-100%)", opacity: "0" },
"10%": { opacity: "1" },
"90%": { opacity: "1" },
"100%": { transform: "translateY(100vh)", opacity: "0" },
},
},
boxShadow: {
"cyan-glow": "0 0 20px rgba(0, 212, 255, 0.4)",
"amber-glow": "0 0 20px rgba(255, 149, 0, 0.3)",
},
},
},
plugins: [require("tailwindcss-animate")],
}
export default config
```
## Component Customizations
### Button Variants
```tsx
// components/ui/button.tsx - Add IFK variants
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 font-mono text-sm font-medium uppercase tracking-wider transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-cyan-glow/50",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-white/10 bg-transparent hover:bg-white/5 hover:border-white/20",
ghost: "hover:bg-accent/10 hover:text-accent",
link: "text-primary underline-offset-4 hover:underline",
// IFK-specific variants
cyan: "bg-ifk-cyan text-background hover:bg-ifk-cyan-light shadow-cyan-glow/30 hover:shadow-cyan-glow",
amber: "bg-ifk-amber text-background hover:bg-ifk-amber-light shadow-amber-glow/30 hover:shadow-amber-glow",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 px-3",
lg: "h-12 px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
```
### Card with Glow Effect
```tsx
// Task card with IFK styling
<Card className="group relative border-white/10 bg-background-secondary transition-all hover:border-primary hover:shadow-cyan-glow/20">
{/* Accent line on hover */}
<div className="absolute bottom-0 left-1/2 h-0.5 w-0 -translate-x-1/2 bg-gradient-to-r from-primary to-secondary transition-all group-hover:w-3/5" />
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="font-mono text-lg tracking-tight">
{task.title}
</CardTitle>
<Badge variant={getStatusVariant(task.status)}>
{task.status}
</Badge>
</div>
</CardHeader>
<CardContent>
{/* ... */}
</CardContent>
</Card>
```
### Badge Variants for TaskFlow
```tsx
// components/ui/badge.tsx - Status-specific variants
const badgeVariants = cva(
"inline-flex items-center rounded px-2 py-0.5 font-mono text-xs font-medium uppercase tracking-wider transition-colors",
{
variants: {
variant: {
default: "bg-primary/20 text-primary border border-primary/30",
secondary: "bg-secondary/20 text-secondary border border-secondary/30",
// Task status variants
pending: "bg-muted text-muted-foreground border border-white/10",
"in-progress": "bg-primary/20 text-primary border border-primary/30",
completed: "bg-success/20 text-success border border-success/30",
blocked: "bg-destructive/20 text-destructive border border-destructive/30",
// Actor type variants
human: "bg-amber-500/20 text-amber-400 border border-amber-500/30",
agent: "bg-cyan-500/20 text-cyan-400 border border-cyan-500/30",
},
},
defaultVariants: {
variant: "default",
},
}
)
```
## Layout Patterns
### Dashboard Grid
```tsx
// IFK-styled dashboard layout
<div className="min-h-screen bg-background">
{/* Grid background pattern */}
<div
className="pointer-events-none fixed inset-0 z-0"
style={{
backgroundImage: `
linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px)
`,
backgroundSize: '60px 60px',
maskImage: 'radial-gradient(ellipse at center, black 30%, transparent 70%)',
}}
/>
{/* Content */}
<div className="relative z-10">
<SidebarProvider>
<AppSidebar />
<main className="flex-1 p-6">
{children}
</main>
</SidebarProvider>
</div>
</div>
```
### Section Headers
```tsx
// IFK-styled section header
<div className="mb-8 text-center">
<span className="mb-2 inline-block rounded border border-primary/20 bg-primary/10 px-3 py-1 font-mono text-xs font-semibold uppercase tracking-widest text-primary">
Active Tasks
</span>
<h2 className="font-mono text-2xl font-bold tracking-tight">
Mission Control
</h2>
<p className="mt-2 font-sans text-muted-foreground">
Monitor and manage task assignments across your team
</p>
</div>
```
## Status Indicators
### Pulse Dot
```tsx
// Status indicator with pulse animation
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-success opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-success" />
</span>
```
### Progress with Glow
```tsx
// Task progress with IFK styling
<div className="space-y-1">
<div className="flex justify-between font-mono text-xs">
<span>Progress</span>
<span className="text-primary">{progress}%</span>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-muted">
<div
className="h-full rounded-full bg-gradient-to-r from-primary to-secondary shadow-cyan-glow/50 transition-all"
style={{ width: `${progress}%` }}
/>
</div>
</div>
```
## Globals.css Template
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Font imports */
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap');
@layer base {
:root {
--background: 222 15% 8%;
--foreground: 0 0% 100%;
--card: 222 11% 11%;
--card-foreground: 0 0% 100%;
--popover: 222 11% 11%;
--popover-foreground: 0 0% 100%;
--primary: 191 100% 50%;
--primary-foreground: 222 15% 8%;
--secondary: 35 100% 50%;
--secondary-foreground: 222 15% 8%;
--muted: 222 8% 14%;
--muted-foreground: 0 0% 53%;
--accent: 191 100% 50%;
--accent-foreground: 222 15% 8%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 100% / 0.1;
--input: 222 8% 14%;
--ring: 191 100% 50%;
--radius: 0.5rem;
/* Success green */
--success: 145 80% 42%;
}
.light {
--background: 0 0% 100%;
--foreground: 0 0% 11%;
--card: 0 0% 96%;
--card-foreground: 0 0% 11%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 11%;
--primary: 204 100% 40%;
--primary-foreground: 0 0% 100%;
--secondary: 30 100% 40%;
--secondary-foreground: 0 0% 100%;
--muted: 0 0% 96%;
--muted-foreground: 0 0% 45%;
--accent: 204 100% 40%;
--accent-foreground: 0 0% 100%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 0% / 0.1;
--input: 0 0% 96%;
--ring: 204 100% 40%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground font-sans antialiased;
}
h1, h2, h3, h4, h5, h6 {
@apply font-mono tracking-tight;
}
}
/* Custom scrollbar for dark theme */
@layer utilities {
.scrollbar-thin {
scrollbar-width: thin;
scrollbar-color: hsl(var(--primary) / 0.2) transparent;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: transparent;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background: hsl(var(--primary) / 0.2);
border-radius: 3px;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
background: hsl(var(--primary) / 0.35);
}
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
## Quick Reference
| Element | Dark Theme | Light Theme |
|---------|------------|-------------|
| Background | `#141418` | `#ffffff` |
| Surface | `#1a1a1e` | `#f5f5f7` |
| Primary (Cyan) | `#00d4ff` | `#0077cc` |
| Secondary (Amber) | `#ff9500` | `#cc6600` |
| Success | `#22c55e` | `#22c55e` |
| Text Primary | `#ffffff` | `#1d1d1f` |
| Text Muted | `#888888` | `#86868b` |
| Border | `rgba(255,255,255,0.1)` | `rgba(0,0,0,0.1)` |
## Actor Type Styling
```tsx
// Human vs Agent visual distinction
const actorStyles = {
human: {
badge: "bg-amber-500/20 text-amber-400 border-amber-500/30",
icon: "text-amber-400",
glow: "shadow-amber-glow",
},
agent: {
badge: "bg-cyan-500/20 text-cyan-400 border-cyan-500/30",
icon: "text-cyan-400",
glow: "shadow-cyan-glow",
},
}
// Usage
<div className={cn("flex items-center gap-2", actorStyles[actorType].glow)}>
{actorType === "agent" ? <Bot className={actorStyles.agent.icon} /> : <User className={actorStyles.human.icon} />}
<Badge className={actorStyles[actorType].badge}>
@{name}
</Badge>
</div>
```
```
### scripts/verify.py
```python
#!/usr/bin/env python3
"""Verify styling-with-shadcn skill has required references."""
import os
import sys
def main():
skill_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
refs_dir = os.path.join(skill_dir, "references")
if os.path.isdir(refs_dir) and os.path.isfile(os.path.join(refs_dir, "component-examples.md")):
print("✓ styling-with-shadcn skill ready")
sys.exit(0)
else:
print("✗ Missing references/component-examples.md")
sys.exit(1)
if __name__ == "__main__":
main()
```