Comprehensive frontend skill. Use when building UI (components, pages, apps), reviewing/auditing design, writing Tailwind CSS layouts, or implementing Next.js App Router patterns.
Handles four modes automatically based on the request:
Triggered when the user asks to create, design, or build a component, page, app, poster, dashboard, or any visual interface.
This guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
Before coding, understand the context and commit to a BOLD aesthetic direction:
CRITICAL: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work — the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), or predictable layouts that lack context-specific character.
IMPORTANT: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations. Minimalist designs need restraint, precision, and careful attention to spacing and typography.
Triggered when the user asks to review, audit, check accessibility, or check UX of existing UI code.
file:line formatFetch fresh guidelines before each review:
https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md
Use WebFetch to retrieve the latest rules. The fetched content contains all the rules and output format instructions.
If no files are specified by the user, ask which files to review.
Triggered when the user is writing Tailwind CSS and needs Grid, Flexbox, Container Queries, or other layout patterns. Apply these patterns directly in the implementation.
<!-- Holy Grail Layout -->
<div class="grid min-h-screen grid-rows-[auto_1fr_auto]">
<header class="bg-white shadow">Header</header>
<div class="grid grid-cols-[250px_1fr_300px]">
<aside class="bg-gray-50 p-4">Sidebar</aside>
<main class="p-6">Main Content</main>
<aside class="bg-gray-50 p-4">Right Sidebar</aside>
</div>
<footer class="bg-gray-800 text-white">Footer</footer>
</div>
<!-- Responsive Holy Grail -->
<div class="grid min-h-screen grid-rows-[auto_1fr_auto]">
<header>Header</header>
<div class="grid grid-cols-1 md:grid-cols-[250px_1fr] lg:grid-cols-[250px_1fr_300px]">
<aside class="order-2 md:order-1">Sidebar</aside>
<main class="order-1 md:order-2">Main</main>
<aside class="order-3 hidden lg:block">Right</aside>
</div>
<footer>Footer</footer>
</div>
<!-- Auto-fill responsive grid -->
<div class="grid grid-cols-[repeat(auto-fill,minmax(250px,1fr))] gap-6">
<div class="bg-white rounded-lg shadow p-4">Card</div>
</div>
@utility grid-areas-dashboard {
grid-template-areas:
"header header header"
"nav main aside"
"nav footer footer";
}
@utility area-header { grid-area: header; }
@utility area-nav { grid-area: nav; }
@utility area-main { grid-area: main; }
@utility area-aside { grid-area: aside; }
@utility area-footer { grid-area: footer; }
<!-- Fixed + flexible -->
<div class="flex">
<div class="w-64 shrink-0">Fixed 256px</div>
<div class="flex-1 min-w-0">Flexible (can shrink)</div>
</div>
<!-- Prevent overflow with text -->
<div class="flex min-w-0">
<div class="shrink-0">Icon</div>
<div class="min-w-0 truncate">Very long text that should truncate</div>
</div>
<div class="@container">
<div class="flex flex-col @md:flex-row @lg:grid @lg:grid-cols-3 gap-4">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>
</div>
@theme {
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-backdrop: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;
--z-toast: 800;
}
<!-- Horizontal carousel -->
<div class="flex snap-x snap-mandatory overflow-x-auto gap-4">
<div class="snap-start shrink-0 w-80">Card 1</div>
<div class="snap-start shrink-0 w-80">Card 2</div>
</div>
<!-- Full-page sections -->
<div class="h-screen snap-y snap-mandatory overflow-y-auto">
<section class="h-screen snap-start">Section 1</section>
<section class="h-screen snap-start">Section 2</section>
</div>
<section class="py-[clamp(2rem,5vw,6rem)] px-[clamp(1rem,3vw,4rem)]">
Content with responsive padding
</section>
min-w-0 to flex children that contain truncated textscroll-mt-{n} on anchor targets to offset for fixed headersaspect-video, aspect-square, or aspect-[w/h] for ratio containersTriggered when the user is building or working on a Next.js application using App Router (Next.js 14+).
| Mode | Where | When to Use | |---|---|---| | Server Components | Server only | Data fetching, secrets, heavy computation | | Client Components | Browser | Interactivity, hooks, browser APIs | | Static | Build time | Content that rarely changes | | Dynamic | Request time | Personalized or real-time data | | Streaming | Progressive | Large pages, slow data sources |
Rule: Start with Server Components. Add 'use client' only when needed.
app/
├── layout.tsx # Shared UI wrapper
├── page.tsx # Route UI
├── loading.tsx # Suspense fallback
├── error.tsx # Error boundary
├── not-found.tsx # 404 UI
├── route.ts # API endpoint
└── template.tsx # Re-mounted layout
// app/products/page.tsx
import { Suspense } from 'react'
export default async function ProductsPage({
searchParams,
}: {
searchParams: Promise<{ category?: string; page?: string }>
}) {
const params = await searchParams
return (
<div className="flex gap-8">
<FilterSidebar />
<Suspense key={JSON.stringify(params)} fallback={<ProductListSkeleton />}>
<ProductList category={params.category} page={Number(params.page) || 1} />
</Suspense>
</div>
)
}
// app/actions/cart.ts
'use server'
import { revalidateTag } from 'next/cache'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
export async function addToCart(productId: string) {
const cookieStore = await cookies()
const sessionId = cookieStore.get('session')?.value
if (!sessionId) redirect('/login')
try {
await db.cart.upsert({
where: { sessionId_productId: { sessionId, productId } },
update: { quantity: { increment: 1 } },
create: { sessionId, productId, quantity: 1 },
})
revalidateTag('cart')
return { success: true }
} catch {
return { error: 'Failed to add item to cart' }
}
}
// app/dashboard/layout.tsx
export default function DashboardLayout({
children, analytics, team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<div className="dashboard-grid">
<main>{children}</main>
<aside>{analytics}</aside>
<aside>{team}</aside>
</div>
)
}
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getProduct(id) // Fast, blocking
return (
<div>
<ProductHeader product={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={id} /> {/* Slow, streamed */}
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations productId={id} /> {/* Slow, streamed */}
</Suspense>
</div>
)
}
fetch(url, { cache: 'no-store' }) // Always fresh
fetch(url, { cache: 'force-cache' }) // Cache forever
fetch(url, { next: { revalidate: 60 } }) // ISR: 60s
fetch(url, { next: { tags: ['products'] } }) // Tag-based
// Invalidate
revalidateTag('products')
revalidatePath('/products')
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const category = request.nextUrl.searchParams.get('category')
const products = await db.product.findMany({
where: category ? { category } : undefined,
take: 20,
})
return NextResponse.json(products)
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const product = await getProduct(slug)
return {
title: product.name,
description: product.description,
openGraph: { images: [{ url: product.image, width: 1200, height: 630 }] },
}
}
export async function generateStaticParams() {
const products = await db.product.findMany({ select: { slug: true } })
return products.map((p) => ({ slug: p.slug }))
}
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer