Creates Next.js 16 frontends with shadcn/ui. Use when building React UIs, components, pages, or applications with shadcn, Tailwind, or modern frontend patterns.
Build distinctive, production-grade interfaces that avoid generic "AI slop" aesthetics.
globals.css, never hardcode colorsThose four are the summary, not the method. Load frontend-design before
the first component of a new view, not after the result already looks generic:
typography, palette and the one element the page spends its boldness on are
create-time decisions, and retrofitting them costs more than making them.
Then check the built view in a browser rather than from a screenshot β read tap target sizes, contrast and overflow out of the DOM, because a screenshot cannot tell you a computed style and may not even have rendered.
bunx --bun shadcn@latest init --template next --base base
--base selects the primitive library: base (Base UI, the default since July
2026), radix (projects already on Radix β still fully supported, not
deprecated), or aria (React Aria). The same component has different props per
base β Base UI composes with render={<Link href="/" />} where Radix uses
asChild β and the docs are base-scoped (/docs/components/base/sidebar vs
/docs/components/radix/sidebar).
For a custom design system, generate a preset code in shadcn/create and apply it:
bunx --bun shadcn@latest init --preset <CODE> --template next
bunx --bun shadcn@latest info --json # base, framework, aliases, installed components
bunx --bun shadcn@latest docs <component> # API reference resolved to THIS project's base
Run these instead of writing component code from memory. See references/shadcn-platform.md for the full CLI surface, typeset, and the shimmer/scroll-fade utilities.
Put shared navigation/layout chrome in layouts; keep route-specific content in
pages. A route group named (protected) does not enforce authorization.
"use client" boundaries as narrow as practical; providers and interactive subtrees may need a higher boundarychildrenNever use relative paths (../../lib/utils). Default to the @/ alias
(@/lib/utils) in new projects. In an existing project, read components.json
and follow the alias style already configured β shadcn also supports Node
package imports (#components/ui/button). Never mix both styles.
Use the project's cn() helper when merging conditional Tailwind classes. Keep
route-specific code near its route and shared components in the existing shared
directories; do not impose a new folder tree on an established app.
export default async function Page({
params,
searchParams,
}: {
params: Promise<{ id: string }>;
searchParams: Promise<{ q?: string }>;
}) {
const { id } = await params;
const { q } = await searchParams;
}
Prefer Server Components or Route Handlers for reads and Server Actions for mutations. Actions can read data, but client dispatch is designed for mutations and can serialize calls; they are not a general read-query transport.
"use cache", cacheTag and cacheLife require cacheComponents: true.
updateTag is restricted to Server Actions, but does not itself require that
flag; it can invalidate fetch tags too. Choose caching from freshness and
authorization requirements, not simply because a function reads data.
"use cache";
import { cacheTag, cacheLife } from "next/cache";
export async function getProducts() {
cacheTag("products");
cacheLife("hours");
return await db.products.findMany();
}
"use server";
import { updateTag, revalidateTag } from "next/cache";
import { z } from "zod";
const schema = z.object({
title: z.string().min(1),
content: z.string(),
});
export async function createPost(formData: FormData) {
// Authenticate and authorize the caller before the write.
// Validate input as well.
const parsed = schema.parse({
title: formData.get("title"),
content: formData.get("content"),
});
await db.insert(posts).values(parsed);
updateTag("posts"); // Read-your-writes
// Or SWR-style revalidation: revalidateTag("posts", "max")
}
Use proxy.ts for request interception (replaces middleware). Place at project root:
// proxy.ts (project root, same level as app/)
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function proxy(request: NextRequest) {
// Auth checks, redirects, etc.
}
export const config = {
matcher: ['/dashboard/:path*'],
}
Next.js 16+ ships an MCP endpoint at /_next/mcp that exposes the dev server's
internals to coding agents. When working in a Next.js 16 project, recommend the
user add next-devtools-mcp to .mcp.json:
{
"mcpServers": {
"next-devtools": {
"command": "npx",
"args": ["-y", "next-devtools-mcp@latest"]
}
}
}
Tools it provides (when dev server is running):
get_errors β live build/runtime/type errors (esp. helpful for hydration mismatches)get_logs β dev log file path (browser console + server output)get_routes β all entry-point routes grouped by router typeget_page_metadata β route, components, rendering details for a specific pageget_project_metadata β project structure + dev server URLget_server_action_by_id β locate Server Action source from its hashed IDget_compilation_issues / compile_route β bundler warnings for the project,
or compile one route on demand without requesting it (Turbopack only)It also acts as a docs gateway: it points at the version-accurate docs shipped
inside node_modules/next/dist/docs/, which beat any remembered API shape.
Use these instead of asking the user to copy-paste error messages. Reference: nextjs.org/docs/app/guides/mcp.
Don't hand-roll CSS for these β shadcn ships them:
--typeset-size, --typeset-leading, --typeset-flow), one
preset per context. Streaming-stable: new blocks don't restyle earlier ones.<div className="typeset typeset-chat">{markdown}</div>
className="shimmer". Use
Skeleton only for placeholders with a known shape; don't stack both.className="scroll-fade overflow-y-auto".Details and the full class tables: references/shadcn-platform.md.
bunx --bun skills add shadcn/ui - live project config + CLI/registry reference. Install alongside this skill; it covers CLI mechanics, this one covers conventions.shadcn docs <component>Always use bun in new projects, never npm or npx:
bun install (not npm install)bun add (not npm install package)bunx --bun (not npx)In an existing repo, respect the project's packageManager field and lockfile instead of switching to bun.