frontend
npx machina-cli add skill mrsknetwork/supernova/frontend --openclawFrontend Engineering
Default Stack (Ask First)
Before applying anything, ask:
"Can I use the Supernova frontend stack? Next.js 14 (App Router) + TypeScript strict + Tailwind CSS v3 + Shadcn/ui + Radix UI. Or do you have an existing frontend stack I should match?"
If a package.json exists with next, react, or a different framework already installed, detect and match it.
Progressive Disclosure
- Load
references/nextjs-app-router.mdfor advanced App Router patterns (parallel routes, intercepting routes, streaming). - Load
references/shadcn-components.mdfor Shadcn/ui component list and usage patterns.
SOP: Next.js 14 App Router Implementation
Step 1 - Project Setup (New Projects Only)
npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
npx shadcn@latest init
When shadcn init prompts:
- Style: Default
- Base color: Slate
- CSS variables: Yes
This creates the components.json config. Components are added per-use with npx shadcn@latest add <component>.
Step 2 - App Router File Conventions
Understand and use these special files. Do not create arbitrary layouts:
| File | Purpose |
|---|---|
app/layout.tsx | Root layout - HTML shell, global fonts, providers |
app/page.tsx | Route page component (Server Component by default) |
app/loading.tsx | Streaming skeleton shown during data fetch |
app/error.tsx | Error boundary for the route segment (must be "use client") |
app/not-found.tsx | Custom 404 for the route segment |
app/(group)/ | Route group - organizes routes without affecting URL |
app/[param]/ | Dynamic route segment |
Step 3 - Server Component vs Client Component Decision
This is the most important architectural decision in Next.js 14. Default to Server Components:
Use Server Component when (no "use client" directive needed):
- Fetching data directly (database, ORM, external API)
- Rendering static or session-based content
- Accessing server-only resources (env vars, file system)
Use Client Component ("use client" at top of file) when:
- Using React hooks (
useState,useEffect,useRef,useContext) - Adding event listeners (
onClick,onChange) - Using browser APIs (
window,localStorage) - Using Shadcn/ui interactive components (they are all Client Components)
The boundary rule: Push "use client" as deep into the tree as possible. A parent Server Component can import and render a Client Component, but cannot import a Server Component into a Client Component.
// app/dashboard/page.tsx - Server Component (default)
import { DashboardStats } from "@/components/DashboardStats"; // Server Component - data fetch
import { InteractiveChart } from "@/components/InteractiveChart"; // Client Component
export default async function DashboardPage() {
const stats = await getStats(); // direct DB call - works in Server Component
return (
<main>
<DashboardStats stats={stats} />
<InteractiveChart /> {/* boundary: "use client" is inside this component */}
</main>
);
}
Step 4 - TypeScript Strict Mode Patterns
Enable in tsconfig.json:
{ "compilerOptions": { "strict": true } }
Component with typed props:
// components/UserCard.tsx
interface UserCardProps {
userId: string;
displayName: string;
avatarUrl: string | null;
onRemove?: (userId: string) => void;
}
export function UserCard({ userId, displayName, avatarUrl, onRemove }: UserCardProps) {
return (
<div className="flex items-center gap-3 p-4 rounded-lg border border-border">
<img src={avatarUrl ?? "/default-avatar.png"} alt={`${displayName}'s avatar`} className="h-10 w-10 rounded-full" />
<span className="text-sm font-medium">{displayName}</span>
{onRemove && (
<button onClick={() => onRemove(userId)} aria-label={`Remove ${displayName}`} className="ml-auto">
Remove
</button>
)}
</div>
);
}
Never use any. Use unknown + type guard for dynamic shapes. Use interface for props, type for unions.
Step 5 - Shadcn/ui Component Usage
Install components as needed, not all at once:
npx shadcn@latest add button dialog form input table toast
Use Shadcn/ui for: forms, modals, dropdowns, tables, toasts, navigation menus. Build custom components on top of Radix UI primitives for headless needs.
// Correct Shadcn/ui form pattern with react-hook-form + zod
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const schema = z.object({ email: z.string().email(), password: z.string().min(8) });
type FormData = z.infer<typeof schema>;
export function LoginForm() {
const form = useForm<FormData>({ resolver: zodResolver(schema) });
async function onSubmit(data: FormData) { /* call API */ }
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField control={form.control} name="email" render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input type="email" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit" disabled={form.formState.isSubmitting}>Sign In</Button>
</form>
</Form>
);
}
Step 6 - Data Fetching Patterns
In Server Components (preferred):
async function ProductsPage() {
const products = await fetch("https://api.example.com/products", { next: { revalidate: 60 } }).then(r => r.json());
return <ProductList products={products} />;
}
In Client Components (for user-triggered or dynamic data):
"use client";
import { useQuery } from "@tanstack/react-query";
function ProductList() {
const { data, isLoading } = useQuery({ queryKey: ["products"], queryFn: () => fetch("/api/products").then(r => r.json()) });
if (isLoading) return <Skeleton />;
return data.map(p => <ProductCard key={p.id} {...p} />);
}
Step 7 - Tailwind CSS Conventions
// tailwind.config.ts - always define semantic colors
import type { Config } from "tailwindcss";
export default {
content: ["./src/**/*.{ts,tsx}"],
theme: {
extend: {
colors: {
brand: { DEFAULT: "hsl(var(--brand))", foreground: "hsl(var(--brand-foreground))" },
},
},
},
} satisfies Config;
Use Tailwind utility classes in JSX directly. Do not create custom CSS classes unless animating or working with pseudo-elements that Tailwind cannot express. Avoid style={{}} inline styles except for truly dynamic values (e.g., progress bar width).
Source
git clone https://github.com/mrsknetwork/supernova/blob/main/skills/frontend/SKILL.mdView on GitHub Overview
Provides a practical blueprint for building React/Next.js UIs with Next.js 14 App Router, TypeScript strict mode, Tailwind CSS v3, Shadcn UI, and Radix UI. It emphasizes server versus client component boundaries, typed props, accessible markup, and integrated form handling. It also advises verifying the existing stack before applying defaults to align with current projects.
How This Skill Works
The skill outlines a pattern where server components handle data fetching and static rendering while client components handle interactivity. It enforces strict TypeScript props, uses Tailwind v3 with Shadcn UI and Radix UI for cohesive styling, and requires accessible markup. The approach guides developers to default to server components, then add a deeply nested client boundary where hooks or browser APIs are needed, ensuring proper App Router conventions and type safety.
When to Use It
- Starting a new Next.js 14 project with App Router, TS strict mode, Tailwind v3, Shadcn UI, and Radix UI.
- Building data-driven pages where server components fetch data and client components render interactive UI.
- Creating reusable UI components with typed props and accessible markup across the app.
- Implementing forms and form handling with integrated validation and accessible controls.
- Styling and composing components with a consistent design system using Tailwind, Shadcn UI, and Radix UI.
Quick Start
- Step 1: Initialize a new Next.js app with TypeScript, Tailwind, App Router, and an alias like @/* using the provided commands.
- Step 2: Adhere to App Router file conventions such as app/layout.tsx and app/page.tsx, avoiding arbitrary layouts and using server components by default.
- Step 3: Decide on server vs client components: use server components for data fetching and rendering; introduce use client only where hooks or browser APIs are required, placing the directive deep in the tree.
Best Practices
- Default to server components to maximize performance; only add a client component when interactivity requires hooks or browser APIs.
- Keep use client directives as deep in the tree as possible; a server component can render a client component, but not vice versa.
- Define explicit prop interfaces for all UI components and enable TypeScript strict mode to catch type mismatches early.
- Use accessible markup for all interactive elements; provide labels, proper ARIA roles, keyboard navigation, and semantic HTML.
- Leverage App Router conventions for layout and routing (app/layout.tsx, app/page.tsx) and prefer data fetching at the server boundary when possible.
Example Use Cases
- Dashboard page where server components fetch stats and a client component renders an InteractiveChart with client-side interactivity.
- User profile page with server-fetched data and a client-side editable form that updates state without a full reload.
- Product listing with server-rendered data augmented by client-side filters and sort controls.
- Multi-step signup or settings form using accessible form controls and integrated validation via Radix UI components.
- Consistent UI library usage across pages, combining Tailwind v3 utility classes with Shadcn UI and Radix UI components.