From gocallum-nextjs16-agent-skills
Installation, components, blocks, forms, theming, and MCP guidance for shadcn/ui in modern Next.js projects using pnpm
npx claudepluginhub joshuarweaver/cascade-code-languages-misc-1 --plugin gocallum-nextjs16-agent-skillsThis skill uses the workspace's default tool permissions.
**Status:** Next.js 16 ready
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Status: Next.js 16 ready
Package Manager: pnpm (required)
Official Docs:
pnpm dlx shadcn@latest init
components.json and installs required deps (Tailwind, class utilities).pnpm dlx shadcn@latest add button card input textarea select
# add blocks or utilities on demand
pnpm lint && pnpm test # if configured
pnpm dev # verify styles render
tailwind.config.(ts|js) includes shadcn paths:
// tailwind.config.ts
import type { Config } from "tailwindcss"
import { fontFamily } from "tailwindcss/defaultTheme"
const config: Config = {
darkMode: ["class"],
content: [
"./app/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
theme: {
extend: {
fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans],
},
},
},
plugins: [require("tailwindcss-animate")],
}
export default config
"use client".globals.css with CSS variables from shadcn init; do not remove the color tokens.components/ui. Import directly:
import { Button } from "@/components/ui/button"
export function CTA() {
return <Button size="lg">Get started</Button>
}
asChild to compose with links:
<Button asChild>
<Link href="/docs">Docs</Link>
</Button>
components/ui/icons or use lucide-react (installed during init).pnpm dlx shadcn@latest add blocks/application-shells/sidebar-02
components/blocks/* to avoid mixing with low-level UI primitives.react-hook-form + @hookform/resolvers/zod:
"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, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
const schema = z.object({
email: z.string().email(),
name: z.string().min(2),
})
export function ProfileForm() {
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: { email: "", name: "" },
})
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(console.log)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="you@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Save</Button>
</form>
</Form>
)
}
FormMessage.toast is deprecated; use Sonner.pnpm dlx shadcn@latest add sonner
import { Toaster } from "@/components/ui/sonner"
import { toast } from "sonner"
export function RootProviders({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<Toaster richColors position="top-right" />
</>
)
}
// usage
toast.success("Profile saved")
app/(marketing) vs app/(dashboard) duplication to avoid multiple toasters.next-themes.components/theme-provider.tsx):
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({ children }: { children: React.ReactNode }) {
return (
<NextThemesProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</NextThemesProvider>
)
}
// app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}
suppressHydrationWarning on <html> to avoid mismatches when switching themes.components.json lives.next/font to load fonts; wire them into --font-sans in globals.css.