Claude
Skills
Sign in
Back

generate-ui

Included with Lifetime
$97 forever

This skill generates or scaffolds a Next.js dashboard UI using shadcn/ui with Server Components and Server Actions. It should be used when the user asks to build a UI, dashboard, frontend, visualisation, page, or component, or when working inside a packages/ui directory. It also applies when the user mentions Next.js, shadcn, React Server Components, Server Actions, "make this visible", "add a view for this", or "build the frontend."

Design

What this skill does


# Next.js Dashboard UI Stack

Generate a Next.js App Router application using **shadcn/ui** components,
**React Server Components** by default, and **Server Actions** for mutations.
The runtime is **Bun**. This is always a dashboard-style interface.

## Defaults and Deviations

These are **team defaults**. Multiple people work on these prototypes. In **execution mode**,
follow these unless the project CLAUDE.md overrides them.

In **Plan mode**, suggest alternatives using the deviation protocol from the
`prototyping-skills:team-conventions` skill: state the default, name the alternative,
explain the trade-off, flag the blast radius, let the human decide.

## Workflow

Follow these three interactive checkpoints on every scaffold:

1. **Auth gate** — Before writing any files, use `AskUserQuestion` to ask:
   > "Do you want Google OAuth authentication on this UI? (Requires GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, NEXTAUTH_SECRET env vars)"
   If yes, scaffold the full auth layer (see Authentication section) before pages.

2. **UI verification** — After scaffolding pages, use `TaskCreate` to queue a Puppeteer screenshot task so the user can visually confirm the app renders correctly.

See the **Verification Checklist** section at the end for the exact `TaskCreate` pattern.

## Team Defaults — Follow Unless Explicitly Overridden

1. **Component library**: shadcn/ui. Not Material UI, Chakra, Ant Design, etc.
2. **Icons**: Lucide (`lucide-react`). Not Heroicons, FontAwesome, etc.
3. **Data fetching**: Server Components with async functions. Not `useEffect` + `fetch`, not Tanstack Query, not SWR.
4. **Mutations**: Server Actions. Not client-side POST calls.
5. **State for server data**: RSC handles it. No Redux, Zustand, Jotai for data from the API.
6. **Router**: App Router (`app/` directory). Not Pages Router, not `getServerSideProps`.
7. **Client components**: Only add `"use client"` when genuinely needed (event handlers, useState, browser APIs).
8. **Types**: From `@repo/types`. Never duplicate.
9. **About page**: Every app MUST include an `/about` page (see below).
10. **Design tokens**: Use CSS custom properties and shadcn theme tokens to separate content from style. Never hard-code colors, spacing, or typography values.

## Authentication

Gate this section with `AskUserQuestion` before scaffolding any files:

```
"Do you want Google OAuth authentication on this UI? (Requires GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, NEXTAUTH_SECRET env vars)"
```

If yes, scaffold the following files using the **Auth.js v5** pattern. See `references/auth.md` for full code.

**Files to create:**

- `packages/ui/auth.ts` — NextAuth config with GoogleProvider + domain allowlist callback
- `packages/ui/middleware.ts` — Route protection with public path exceptions (`/login`, `/api/auth/**`)
- `packages/ui/providers/auth-provider.tsx` — `SessionProvider` wrapper (Client Component)
- `packages/ui/app/(public)/login/page.tsx` — Login page with sign-in button
- `packages/ui/app/api/auth/[...nextauth]/route.ts` — NextAuth API route handler

**Required env vars** (add to `.env.local`):
```
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
NEXTAUTH_SECRET=
NEXTAUTH_URL=http://localhost:3000
```

**Install dependency:**
```bash
bun add next-auth
```

Wrap `app/layout.tsx` body with `<AuthProvider>`. Protect all routes except `/login` and `/api/auth/**` in middleware.

See `references/auth.md` for complete file contents including multi-domain allowlist pattern.

## Package Setup

```
packages/ui/
├── src/
│   └── app/
│       ├── layout.tsx         # Root layout with sidebar/nav
│       ├── page.tsx           # Dashboard home
│       ├── [feature]/
│       │   └── page.tsx       # Feature pages
│       ├── actions/           # Server Actions
│       │   └── [resource].ts
│       └── components/        # App-specific components
│           ├── ui/            # shadcn/ui components (managed by CLI)
│           └── [feature]/     # Feature-specific components
├── components.json            # shadcn/ui config
├── postcss.config.mjs         # Tailwind CSS v4 via @tailwindcss/postcss
├── next.config.ts
├── package.json
└── tsconfig.json
```

**package.json** must include:
```json
{
  "scripts": {
    "dev": "next dev --port 3000",
    "build": "next build"
  },
  "dependencies": {
    "next": "^16",
    "react": "^19",
    "react-dom": "^19",
    "@repo/types": "workspace:*",
    "tailwindcss": "^4",
    "@tailwindcss/postcss": "^4",
    "class-variance-authority": "latest",
    "clsx": "latest",
    "tailwind-merge": "latest",
    "lucide-react": "latest"
  }
}
```

## Data Fetching Pattern — Server Components

Data loading happens directly in async Server Components. No hooks, no client fetching:

```typescript
// app/items/page.tsx — Server Component by default, no "use client" needed
import { ItemsTable } from "@/components/items/items-table";

export default async function ItemsPage() {
  const res = await fetch("http://localhost:3001/api/items", {
    cache: "no-store",
  });
  const items = await res.json();

  return (
    <div className="space-y-4">
      <h1 className="text-2xl font-bold">Items</h1>
      <ItemsTable items={items} />
    </div>
  );
}
```

## Mutation Pattern — Server Actions

All mutations use Server Actions defined in `app/actions/`:

```typescript
// app/actions/items.ts
"use server";

import { revalidatePath } from "next/cache";

export async function createItem(formData: FormData) {
  const name = formData.get("name") as string;
  await fetch("http://localhost:3001/api/items", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name }),
  });
  revalidatePath("/items");
}

export async function deleteItem(id: string) {
  await fetch(`http://localhost:3001/api/items/${id}`, { method: "DELETE" });
  revalidatePath("/items");
}
```

Using Server Actions in components:

```typescript
// components/items/create-item-form.tsx
"use client";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { createItem } from "@/app/actions/items";

export function CreateItemForm() {
  return (
    <form action={createItem} className="flex gap-2">
      <Input name="name" placeholder="Item name" required />
      <Button type="submit">Create</Button>
    </form>
  );
}
```

## Dashboard Layout Pattern

**Preferred**: Install the full shadcn sidebar block:

```bash
npx shadcn@latest add sidebar-01
```

This installs the complete `sidebar-01` block from [ui.shadcn.com/blocks/sidebar](https://ui.shadcn.com/blocks/sidebar), including all sub-components, styles, and layout wrapper. Customize the nav items after installation.

**Fallback** (if block install is not appropriate): Use the manual pattern below.

```typescript
// app/layout.tsx
import { SidebarProvider, Sidebar, SidebarContent, SidebarMenu,
         SidebarMenuItem, SidebarMenuButton } from "@/components/ui/sidebar";
import { LayoutDashboard, Settings, Info } from "lucide-react";
import Link from "next/link";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <SidebarProvider>
          <div className="flex min-h-screen">
            <Sidebar>
              <SidebarContent>
                <SidebarMenu>
                  <SidebarMenuItem>
                    <SidebarMenuButton asChild>
                      <Link href="/"><LayoutDashboard className="mr-2 h-4 w-4" />Dashboard</Link>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                  <SidebarMenuItem>
                    <SidebarMenuButton asChild>
                      <Link href="/settings"><Settings className="mr-2 h-4 w-4" />Settings</Link>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                  <SidebarMenuItem>
                    <SidebarMenuButton asChild>
                      <Link href="/about"><Info clas

Related in Design