Senior frontend

Frontend development skill for React, Next.js, TypeScript, and Tailwind CSS applications.

How to use it

Claude Code
  1. Run the line below. It pulls the whole folder into ~/.claude/skills/senior-frontend, including the files SKILL.md points to.
  2. Describe your job in plain words. Claude Code follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit alirezarezvani/claude-skills/engineering-team/skills/senior-frontend#main ~/.claude/skills/senior-frontend

For one project only, change the path to .claude/skills/senior-frontend. This skill also uses Next.js, NextAuth.js, package.json, r.json, frontend_decision_engine.py — copying SKILL.md alone won't be enough. See the folder on GitHub.

Claude (web or desktop app)
  1. On this page open ⋯ → Download .md.
  2. Save it as SKILL.md in a folder, zip the folder, then Customize → Skills → + → Create skill → Upload a skill.
  3. Pick the file and Save. Claude shows the name and description and runs a security scan.
  4. Check the skill is switched on.
  5. Start a new chat and describe your job in plain words. The AI follows the skill from there.
ChatGPT or another app
  1. ChatGPT: make a Project and paste it into Instructions.
  2. Neither? Paste it at the top of a new chat — it works for that chat.
Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Source of Senior frontend

Show the full text573 lines
namedescription
senior-frontendFrontend development skill for React, Next.js, TypeScript, and Tailwind CSS applications. Use when building React components, optimizing Next.js performance, analyzing bundle sizes, scaffolding frontend projects, implementing accessibility, or reviewing frontend code quality.

Senior Frontend

Frontend development patterns, performance optimization, and automation tools for React/Next.js applications.

Table of Contents


Project Scaffolding

Generate a new Next.js or React project with TypeScript, Tailwind CSS, and best practice configurations.

Workflow: Create New Frontend Project
  1. Run the scaffolder with your project name and template:

    python scripts/frontend_scaffolder.py my-app --template nextjs
    
  2. Add optional features (auth, api, forms, testing, storybook):

    python scripts/frontend_scaffolder.py dashboard --template nextjs --features auth,api
    
  3. Navigate to the project and install dependencies:

    cd my-app && npm install
    
  4. Start the development server:

    npm run dev
    
Scaffolder Options
Option Description
--template nextjs Next.js 14+ with App Router and Server Components
--template react React + Vite with TypeScript
--features auth Add NextAuth.js authentication
--features api Add React Query + API client
--features forms Add React Hook Form + Zod validation
--features testing Add Vitest + Testing Library
--dry-run Preview files without creating them
Generated Structure (Next.js)
my-app/
├── app/
│   ├── layout.tsx        # Root layout with fonts
│   ├── page.tsx          # Home page
│   ├── globals.css       # Tailwind + CSS variables
│   └── api/health/route.ts
├── components/
│   ├── ui/               # Button, Input, Card
│   └── layout/           # Header, Footer, Sidebar
├── hooks/                # useDebounce, useLocalStorage
├── lib/                  # utils (cn), constants
├── types/                # TypeScript interfaces
├── tailwind.config.ts
├── next.config.js
└── package.json

Component Generation

Generate React components with TypeScript, tests, and Storybook stories.

Workflow: Create a New Component
  1. Generate a client component:

    python scripts/component_generator.py Button --dir src/components/ui
    
  2. Generate a server component:

    python scripts/component_generator.py ProductCard --type server
    
  3. Generate with test and story files:

    python scripts/component_generator.py UserProfile --with-test --with-story
    
  4. Generate a custom hook:

    python scripts/component_generator.py FormValidation --type hook
    
Generator Options
Option Description
--type client Client component with 'use client' (default)
--type server Async server component
--type hook Custom React hook
--with-test Include test file
--with-story Include Storybook story
--flat Create in output dir without subdirectory
--dry-run Preview without creating files
Generated Component Example
'use client';

import { useState } from 'react';
import { cn } from '@/lib/utils';

interface ButtonProps {
  className?: string;
  children?: React.ReactNode;
}

export function Button({ className, children }: ButtonProps) {
  return (
    <div className={cn('', className)}>
      {children}
    </div>
  );
}

Bundle Analysis

Analyze package.json and project structure for bundle optimization opportunities.

Workflow: Optimize Bundle Size
  1. Run the analyzer on your project:

    python scripts/bundle_analyzer.py /path/to/project
    
  2. Review the health score and issues:

    Bundle Health Score: 75/100 (C)
    
    HEAVY DEPENDENCIES:
      moment (290KB)
        Alternative: date-fns (12KB) or dayjs (2KB)
    
      lodash (71KB)
        Alternative: lodash-es with tree-shaking
    
  3. Apply the recommended fixes by replacing heavy dependencies.

  4. Re-run with verbose mode to check import patterns:

    python scripts/bundle_analyzer.py . --verbose
    
Bundle Score Interpretation
Score Grade Action
90-100 A Bundle is well-optimized
80-89 B Minor optimizations available
70-79 C Replace heavy dependencies
60-69 D Multiple issues need attention
0-59 F Critical bundle size problems
Heavy Dependencies Detected

The analyzer identifies these common heavy packages:

Package Size Alternative
moment 290KB date-fns (12KB) or dayjs (2KB)
lodash 71KB lodash-es with tree-shaking
axios 14KB Native fetch or ky (3KB)
jquery 87KB Native DOM APIs
@mui/material Large shadcn/ui or Radix UI

React Patterns

Reference: references/react_patterns.md

Compound Components

Share state between related components:

const Tabs = ({ children }) => {
  const [active, setActive] = useState(0);
  return (
    <TabsContext.Provider value={{ active, setActive }}>
      {children}
    </TabsContext.Provider>
  );
};

Tabs.List = TabList;
Tabs.Panel = TabPanel;

// Usage
<Tabs>
  <Tabs.List>
    <Tabs.Tab>One</Tabs.Tab>
    <Tabs.Tab>Two</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel>Content 1</Tabs.Panel>
  <Tabs.Panel>Content 2</Tabs.Panel>
</Tabs>
Custom Hooks

Extract reusable logic:

function useDebounce<T>(value: T, delay = 500): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Usage
const debouncedSearch = useDebounce(searchTerm, 300);
Render Props

Share rendering logic:

function DataFetcher({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url).then(r => r.json()).then(setData).finally(() => setLoading(false));
  }, [url]);

  return render({ data, loading });
}

// Usage
<DataFetcher
  url="/api/users"
  render={({ data, loading }) =>
    loading ? <Spinner /> : <UserList users={data} />
  }
/>

Next.js Optimization

Reference: references/nextjs_optimization_guide.md

Server vs Client Components

Use Server Components by default. Add 'use client' only when you need:

  • Event handlers (onClick, onChange)
  • State (useState, useReducer)
  • Effects (useEffect)
  • Browser APIs
// Server Component (default) - no 'use client'
async function ProductPage({ params }) {
  const product = await getProduct(params.id);  // Server-side fetch

  return (
    <div>
      <h1>{product.name}</h1>
      <AddToCartButton productId={product.id} />  {/* Client component */}
    </div>
  );
}

// Client Component
'use client';
function AddToCartButton({ productId }) {
  const [adding, setAdding] = useState(false);
  return <button onClick={() => addToCart(productId)}>Add</button>;
}
Image Optimization
import Image from 'next/image';

// Above the fold - load immediately
<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority
/>

// Responsive image with fill
<div className="relative aspect-video">
  <Image
    src="/product.jpg"
    alt="Product"
    fill
    sizes="(max-width: 768px) 100vw, 50vw"
    className="object-cover"
  />
</div>
Data Fetching Patterns
// Parallel fetching
async function Dashboard() {
  const [user, stats] = await Promise.all([
    getUser(),
    getStats()
  ]);
  return <div>...</div>;
}

// Streaming with Suspense
async function ProductPage({ params }) {
  return (
    <div>
      <ProductDetails id={params.id} />
      <Suspense fallback={<ReviewsSkeleton />}>
        <Reviews productId={params.id} />
      </Suspense>
    </div>
  );
}

Accessibility and Testing

Reference: references/frontend_best_practices.md

Accessibility Checklist
  1. Semantic HTML: Use proper elements (<button>, <nav>, <main>)
  2. Keyboard Navigation: All interactive elements focusable
  3. ARIA Labels: Provide labels for icons and complex widgets
  4. Color Contrast: Minimum 4.5:1 for normal text
  5. Focus Indicators: Visible focus states
// Accessible button
<button
  type="button"
  aria-label="Close dialog"
  onClick={onClose}
  className="focus-visible:ring-2 focus-visible:ring-blue-500"
>
  <XIcon aria-hidden="true" />
</button>

// Skip link for keyboard users
<a href="#main-content" className="sr-only focus:not-sr-only">
  Skip to main content
</a>
Testing Strategy
// Component test with React Testing Library
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('button triggers action on click', async () => {
  const onClick = vi.fn();
  render(<Button onClick={onClick}>Click me</Button>);

  await userEvent.click(screen.getByRole('button'));
  expect(onClick).toHaveBeenCalledTimes(1);
});

// Test accessibility
test('dialog is accessible', async () => {
  render(<Dialog open={true} title="Confirm" />);

  expect(screen.getByRole('dialog')).toBeInTheDocument();
  expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby');
});

Quick Reference

Common Next.js Config
// next.config.js
const nextConfig = {
  images: {
    remotePatterns: [{ protocol: 'https', hostname: 'cdn.example.com' }],
    formats: ['image/avif', 'image/webp'],
  },
  experimental: {
    optimizePackageImports: ['lucide-react', '@heroicons/react'],
  },
};
Tailwind CSS Utilities
// Conditional classes with cn()
import { cn } from '@/lib/utils';

<button className={cn(
  'px-4 py-2 rounded',
  variant === 'primary' && 'bg-blue-500 text-white',
  disabled && 'opacity-50 cursor-not-allowed'
)} />
TypeScript Patterns
// Props with children
interface CardProps {
  className?: string;
  children: React.ReactNode;
}

// Generic component
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map(renderItem)}</ul>;
}

Resources

  • React Patterns: references/react_patterns.md
  • Next.js Optimization: references/nextjs_optimization_guide.md
  • Best Practices: references/frontend_best_practices.md
  • Forcing-question library (Matt Pocock grill): references/forcing_questions.md
  • Composition map (which specialist to fork into): references/composition_map.md

Assumptions and Verifiable Success Criteria (Karpathy discipline)

Before this skill scaffolds a component, recommends a framework, or audits a bundle, the following four assumptions MUST be surfaced.

  1. Primary user device + network — mobile-4G, desktop-fiber, low-end-Android, or corporate-network. Drives every perf decision.
  2. LCP target in milliseconds — a single number, not "fast." Drives bundle budget and rendering choice.
  3. SEO-dependent vs. auth-walled — drives rendering (SSR/SSG/RSC vs. SPA).
  4. WCAG target + named a11y owner — AA, AAA, or best-effort. Drives a11y investment and CI gates.

Verifiable success criteria (Karpathy #4) — every recommendation must include:

  • Core Web Vitals targets (LCP, INP, CLS) at p75 on the primary device
  • A per-route JS bundle budget in KB-gzip
  • A Lighthouse a11y floor + perf floor

If any of those three is not stated, the recommendation is incomplete — return to Q2 of the forcing-question library.

The scripts/frontend_decision_engine.py tool encodes these checks: it refuses to recommend a profile without the four assumption inputs and prints the verifiable thresholds for the matched profile.


Customization profiles

Four built-in profiles in profiles/ calibrate every recommendation:

Profile When to pick LCP target (mobile-4G p75) Bundle budget
next-app-router SaaS customer-facing, SEO + dynamic, RSC-first 2000ms 150 KB-gzip / route
remix-or-sveltekit Mobile-4G primary, low-JS-first, progressive enhancement 1500ms 80 KB-gzip / route
vite-spa Auth-walled app, desktop/corporate primary 2500ms 200 KB init + 80 KB / route
astro-or-static Marketing / docs / blog, near-zero write, SEO-critical 1200ms 30 KB JS / page

Pick a profile via:

python scripts/frontend_decision_engine.py \
  --primary-device mobile-4g --lcp-target-ms 2000 \
  --seo-dependent true --auth-walled false --team-size 5

The tool returns the best-fit profile, the runner-up tradeoff (if within 15%), the stack picks, the anti-patterns to avoid on that profile, and the required CI gates.

To add a custom profile (e.g., your org's internal-tool defaults): copy profiles/vite-spa.json to profiles/<your-org>.json and adjust constraints + success_thresholds.


Composition map

This skill does NOT reimplement scope owned by the POWERFUL-tier specialists. It forks into them. See references/composition_map.md for the full routing table. Key forks:

Concern Fork into
WCAG audit, contrast, screen-reader engineering-team/skills/a11y-audit/
Bundle profiling + runtime perf engineering/skills/performance-profiler/
Cinematic / scroll-storytelling landing engineering-team/skills/epic-design/
Apple HIG (iOS / macOS / visionOS) product-team/skills/apple-hig-expert/
Pre-commit Karpathy review engineering/karpathy-coder/
Pre-flight architecture grill engineering/grill-me/

The cs-frontend-engineer agent orchestrates these forks via context: fork. Invoke it from another agent with Agent({subagent_type: "cs-frontend-engineer", prompt: "..."}) or via /cs:frontend-review <your problem>.


Forcing-question library (Matt Pocock grill)

Before locking any framework or rendering decision, walk the seven forcing questions in references/forcing_questions.md. Discipline:

  1. One question per turn. No bundling.
  2. Always recommend the answer with cited canon.
  3. Track answers in /tmp/frontend-grill-<date>.md.
  4. If a kill criterion trips, stop. Don't scaffold around an unresolved gap.
  5. After Q7, run frontend_decision_engine.py with the seven answers.

Summary:

  1. Primary device + network?
  2. LCP target in ms (and INP, CLS)?
  3. RSC / SPA / SSR / SSG — pick and defend?
  4. JS bundle budget per route?
  5. SEO-dependent or auth-walled?
  6. Design-system source of truth?
  7. WCAG target + named a11y owner?

Invocation from other agents and skills

Three surfaces:

  1. Slash command: /cs:frontend-review <prompt> — full grill + decision engine + composition routing.
  2. Agent subagent: Agent({subagent_type: "cs-frontend-engineer", prompt: "..."}) — forks context, returns ≤ 200-word digest.
  3. Direct tool call: python scripts/frontend_decision_engine.py ... — deterministic profile match when inputs are known.

See agents/engineering/cs-frontend-engineer.md for the full invocation contract.

1---
2name: "senior-frontend"
3description: Frontend development skill for React, Next.js, TypeScript, and Tailwind CSS applications. Use when building React components, optimizing Next.js performance, analyzing bundle sizes, scaffolding frontend projects, implementing accessibility, or reviewing frontend code quality.
4---
5 
6# Senior Frontend
7 
8Frontend development patterns, performance optimization, and automation tools for React/Next.js applications.
9 
10## Table of Contents
11 
12- [Project Scaffolding](#project-scaffolding)
13- [Component Generation](#component-generation)
14- [Bundle Analysis](#bundle-analysis)
15- [React Patterns](#react-patterns)
16- [Next.js Optimization](#nextjs-optimization)
17- [Accessibility and Testing](#accessibility-and-testing)
18 
19---
20 
21## Project Scaffolding
22 
23Generate a new Next.js or React project with TypeScript, Tailwind CSS, and best practice configurations.
24 
25### Workflow: Create New Frontend Project
26 
271. Run the scaffolder with your project name and template:
28 ```bash
29 python scripts/frontend_scaffolder.py my-app --template nextjs
30 ```
31 
322. Add optional features (auth, api, forms, testing, storybook):
33 ```bash
34 python scripts/frontend_scaffolder.py dashboard --template nextjs --features auth,api
35 ```
36 
373. Navigate to the project and install dependencies:
38 ```bash
39 cd my-app && npm install
40 ```
41 
424. Start the development server:
43 ```bash
44 npm run dev
45 ```
46 
47### Scaffolder Options
48 
49| Option | Description |
50|--------|-------------|
51| `--template nextjs` | Next.js 14+ with App Router and Server Components |
52| `--template react` | React + Vite with TypeScript |
53| `--features auth` | Add NextAuth.js authentication |
54| `--features api` | Add React Query + API client |
55| `--features forms` | Add React Hook Form + Zod validation |
56| `--features testing` | Add Vitest + Testing Library |
57| `--dry-run` | Preview files without creating them |
58 
59### Generated Structure (Next.js)
60 
61```
62my-app/
63├── app/
64│ ├── layout.tsx # Root layout with fonts
65│ ├── page.tsx # Home page
66│ ├── globals.css # Tailwind + CSS variables
67│ └── api/health/route.ts
68├── components/
69│ ├── ui/ # Button, Input, Card
70│ └── layout/ # Header, Footer, Sidebar
71├── hooks/ # useDebounce, useLocalStorage
72├── lib/ # utils (cn), constants
73├── types/ # TypeScript interfaces
74├── tailwind.config.ts
75├── next.config.js
76└── package.json
77```
78 
79---
80 
81## Component Generation
82 
83Generate React components with TypeScript, tests, and Storybook stories.
84 
85### Workflow: Create a New Component
86 
871. Generate a client component:
88 ```bash
89 python scripts/component_generator.py Button --dir src/components/ui
90 ```
91 
922. Generate a server component:
93 ```bash
94 python scripts/component_generator.py ProductCard --type server
95 ```
96 
973. Generate with test and story files:
98 ```bash
99 python scripts/component_generator.py UserProfile --with-test --with-story
100 ```
101 
1024. Generate a custom hook:
103 ```bash
104 python scripts/component_generator.py FormValidation --type hook
105 ```
106 
107### Generator Options
108 
109| Option | Description |
110|--------|-------------|
111| `--type client` | Client component with 'use client' (default) |
112| `--type server` | Async server component |
113| `--type hook` | Custom React hook |
114| `--with-test` | Include test file |
115| `--with-story` | Include Storybook story |
116| `--flat` | Create in output dir without subdirectory |
117| `--dry-run` | Preview without creating files |
118 
119### Generated Component Example
120 
121```tsx
122'use client';
123 
124import { useState } from 'react';
125import { cn } from '@/lib/utils';
126 
127interface ButtonProps {
128 className?: string;
129 children?: React.ReactNode;
130}
131 
132export function Button({ className, children }: ButtonProps) {
133 return (
134 <div className={cn('', className)}>
135 {children}
136 </div>
137 );
138}
139```
140 
141---
142 
143## Bundle Analysis
144 
145Analyze package.json and project structure for bundle optimization opportunities.
146 
147### Workflow: Optimize Bundle Size
148 
1491. Run the analyzer on your project:
150 ```bash
151 python scripts/bundle_analyzer.py /path/to/project
152 ```
153 
1542. Review the health score and issues:
155 ```
156 Bundle Health Score: 75/100 (C)
157 
158 HEAVY DEPENDENCIES:
159 moment (290KB)
160 Alternative: date-fns (12KB) or dayjs (2KB)
161 
162 lodash (71KB)
163 Alternative: lodash-es with tree-shaking
164 ```
165 
1663. Apply the recommended fixes by replacing heavy dependencies.
167 
1684. Re-run with verbose mode to check import patterns:
169 ```bash
170 python scripts/bundle_analyzer.py . --verbose
171 ```
172 
173### Bundle Score Interpretation
174 
175| Score | Grade | Action |
176|-------|-------|--------|
177| 90-100 | A | Bundle is well-optimized |
178| 80-89 | B | Minor optimizations available |
179| 70-79 | C | Replace heavy dependencies |
180| 60-69 | D | Multiple issues need attention |
181| 0-59 | F | Critical bundle size problems |
182 
183### Heavy Dependencies Detected
184 
185The analyzer identifies these common heavy packages:
186 
187| Package | Size | Alternative |
188|---------|------|-------------|
189| moment | 290KB | date-fns (12KB) or dayjs (2KB) |
190| lodash | 71KB | lodash-es with tree-shaking |
191| axios | 14KB | Native fetch or ky (3KB) |
192| jquery | 87KB | Native DOM APIs |
193| @mui/material | Large | shadcn/ui or Radix UI |
194 
195---
196 
197## React Patterns
198 
199Reference: `references/react_patterns.md`
200 
201### Compound Components
202 
203Share state between related components:
204 
205```tsx
206const Tabs = ({ children }) => {
207 const [active, setActive] = useState(0);
208 return (
209 <TabsContext.Provider value={{ active, setActive }}>
210 {children}
211 </TabsContext.Provider>
212 );
213};
214 
215Tabs.List = TabList;
216Tabs.Panel = TabPanel;
217 
218// Usage
219<Tabs>
220 <Tabs.List>
221 <Tabs.Tab>One</Tabs.Tab>
222 <Tabs.Tab>Two</Tabs.Tab>
223 </Tabs.List>
224 <Tabs.Panel>Content 1</Tabs.Panel>
225 <Tabs.Panel>Content 2</Tabs.Panel>
226</Tabs>
227```
228 
229### Custom Hooks
230 
231Extract reusable logic:
232 
233```tsx
234function useDebounce<T>(value: T, delay = 500): T {
235 const [debouncedValue, setDebouncedValue] = useState(value);
236 
237 useEffect(() => {
238 const timer = setTimeout(() => setDebouncedValue(value), delay);
239 return () => clearTimeout(timer);
240 }, [value, delay]);
241 
242 return debouncedValue;
243}
244 
245// Usage
246const debouncedSearch = useDebounce(searchTerm, 300);
247```
248 
249### Render Props
250 
251Share rendering logic:
252 
253```tsx
254function DataFetcher({ url, render }) {
255 const [data, setData] = useState(null);
256 const [loading, setLoading] = useState(true);
257 
258 useEffect(() => {
259 fetch(url).then(r => r.json()).then(setData).finally(() => setLoading(false));
260 }, [url]);
261 
262 return render({ data, loading });
263}
264 
265// Usage
266<DataFetcher
267 url="/api/users"
268 render={({ data, loading }) =>
269 loading ? <Spinner /> : <UserList users={data} />
270 }
271/>
272```
273 
274---
275 
276## Next.js Optimization
277 
278Reference: `references/nextjs_optimization_guide.md`
279 
280### Server vs Client Components
281 
282Use Server Components by default. Add 'use client' only when you need:
283- Event handlers (onClick, onChange)
284- State (useState, useReducer)
285- Effects (useEffect)
286- Browser APIs
287 
288```tsx
289// Server Component (default) - no 'use client'
290async function ProductPage({ params }) {
291 const product = await getProduct(params.id); // Server-side fetch
292 
293 return (
294 <div>
295 <h1>{product.name}</h1>
296 <AddToCartButton productId={product.id} /> {/* Client component */}
297 </div>
298 );
299}
300 
301// Client Component
302'use client';
303function AddToCartButton({ productId }) {
304 const [adding, setAdding] = useState(false);
305 return <button onClick={() => addToCart(productId)}>Add</button>;
306}
307```
308 
309### Image Optimization
310 
311```tsx
312import Image from 'next/image';
313 
314// Above the fold - load immediately
315<Image
316 src="/hero.jpg"
317 alt="Hero"
318 width={1200}
319 height={600}
320 priority
321/>
322 
323// Responsive image with fill
324<div className="relative aspect-video">
325 <Image
326 src="/product.jpg"
327 alt="Product"
328 fill
329 sizes="(max-width: 768px) 100vw, 50vw"
330 className="object-cover"
331 />
332</div>
333```
334 
335### Data Fetching Patterns
336 
337```tsx
338// Parallel fetching
339async function Dashboard() {
340 const [user, stats] = await Promise.all([
341 getUser(),
342 getStats()
343 ]);
344 return <div>...</div>;
345}
346 
347// Streaming with Suspense
348async function ProductPage({ params }) {
349 return (
350 <div>
351 <ProductDetails id={params.id} />
352 <Suspense fallback={<ReviewsSkeleton />}>
353 <Reviews productId={params.id} />
354 </Suspense>
355 </div>
356 );
357}
358```
359 
360---
361 
362## Accessibility and Testing
363 
364Reference: `references/frontend_best_practices.md`
365 
366### Accessibility Checklist
367 
3681. **Semantic HTML**: Use proper elements (`<button>`, `<nav>`, `<main>`)
3692. **Keyboard Navigation**: All interactive elements focusable
3703. **ARIA Labels**: Provide labels for icons and complex widgets
3714. **Color Contrast**: Minimum 4.5:1 for normal text
3725. **Focus Indicators**: Visible focus states
373 
374```tsx
375// Accessible button
376<button
377 type="button"
378 aria-label="Close dialog"
379 onClick={onClose}
380 className="focus-visible:ring-2 focus-visible:ring-blue-500"
381>
382 <XIcon aria-hidden="true" />
383</button>
384 
385// Skip link for keyboard users
386<a href="#main-content" className="sr-only focus:not-sr-only">
387 Skip to main content
388</a>
389```
390 
391### Testing Strategy
392 
393```tsx
394// Component test with React Testing Library
395import { render, screen } from '@testing-library/react';
396import userEvent from '@testing-library/user-event';
397 
398test('button triggers action on click', async () => {
399 const onClick = vi.fn();
400 render(<Button onClick={onClick}>Click me</Button>);
401 
402 await userEvent.click(screen.getByRole('button'));
403 expect(onClick).toHaveBeenCalledTimes(1);
404});
405 
406// Test accessibility
407test('dialog is accessible', async () => {
408 render(<Dialog open={true} title="Confirm" />);
409 
410 expect(screen.getByRole('dialog')).toBeInTheDocument();
411 expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby');
412});
413```
414 
415---
416 
417## Quick Reference
418 
419### Common Next.js Config
420 
421```js
422// next.config.js
423const nextConfig = {
424 images: {
425 remotePatterns: [{ protocol: 'https', hostname: 'cdn.example.com' }],
426 formats: ['image/avif', 'image/webp'],
427 },
428 experimental: {
429 optimizePackageImports: ['lucide-react', '@heroicons/react'],
430 },
431};
432```
433 
434### Tailwind CSS Utilities
435 
436```tsx
437// Conditional classes with cn()
438import { cn } from '@/lib/utils';
439 
440<button className={cn(
441 'px-4 py-2 rounded',
442 variant === 'primary' && 'bg-blue-500 text-white',
443 disabled && 'opacity-50 cursor-not-allowed'
444)} />
445```
446 
447### TypeScript Patterns
448 
449```tsx
450// Props with children
451interface CardProps {
452 className?: string;
453 children: React.ReactNode;
454}
455 
456// Generic component
457interface ListProps<T> {
458 items: T[];
459 renderItem: (item: T) => React.ReactNode;
460}
461 
462function List<T>({ items, renderItem }: ListProps<T>) {
463 return <ul>{items.map(renderItem)}</ul>;
464}
465```
466 
467---
468 
469## Resources
470 
471- React Patterns: `references/react_patterns.md`
472- Next.js Optimization: `references/nextjs_optimization_guide.md`
473- Best Practices: `references/frontend_best_practices.md`
474- Forcing-question library (Matt Pocock grill): `references/forcing_questions.md`
475- Composition map (which specialist to fork into): `references/composition_map.md`
476 
477---
478 
479## Assumptions and Verifiable Success Criteria (Karpathy discipline)
480 
481Before this skill scaffolds a component, recommends a framework, or audits a bundle, the following four assumptions MUST be surfaced.
482 
4831. **Primary user device + network** — mobile-4G, desktop-fiber, low-end-Android, or corporate-network. Drives every perf decision.
4842. **LCP target in milliseconds** — a single number, not "fast." Drives bundle budget and rendering choice.
4853. **SEO-dependent vs. auth-walled** — drives rendering (SSR/SSG/RSC vs. SPA).
4864. **WCAG target + named a11y owner** — AA, AAA, or best-effort. Drives a11y investment and CI gates.
487 
488**Verifiable success criteria** (Karpathy #4) — every recommendation must include:
489 
490- Core Web Vitals targets (LCP, INP, CLS) at p75 on the primary device
491- A per-route JS bundle budget in KB-gzip
492- A Lighthouse a11y floor + perf floor
493 
494If any of those three is not stated, the recommendation is incomplete — return to Q2 of the forcing-question library.
495 
496The `scripts/frontend_decision_engine.py` tool encodes these checks: it refuses to recommend a profile without the four assumption inputs and prints the verifiable thresholds for the matched profile.
497 
498---
499 
500## Customization profiles
501 
502Four built-in profiles in `profiles/` calibrate every recommendation:
503 
504| Profile | When to pick | LCP target (mobile-4G p75) | Bundle budget |
505|---|---|---|---|
506| `next-app-router` | SaaS customer-facing, SEO + dynamic, RSC-first | 2000ms | 150 KB-gzip / route |
507| `remix-or-sveltekit` | Mobile-4G primary, low-JS-first, progressive enhancement | 1500ms | 80 KB-gzip / route |
508| `vite-spa` | Auth-walled app, desktop/corporate primary | 2500ms | 200 KB init + 80 KB / route |
509| `astro-or-static` | Marketing / docs / blog, near-zero write, SEO-critical | 1200ms | 30 KB JS / page |
510 
511Pick a profile via:
512 
513```bash
514python scripts/frontend_decision_engine.py \
515 --primary-device mobile-4g --lcp-target-ms 2000 \
516 --seo-dependent true --auth-walled false --team-size 5
517```
518 
519The tool returns the best-fit profile, the runner-up tradeoff (if within 15%), the stack picks, the anti-patterns to avoid on that profile, and the required CI gates.
520 
521To add a custom profile (e.g., your org's internal-tool defaults): copy `profiles/vite-spa.json` to `profiles/<your-org>.json` and adjust `constraints` + `success_thresholds`.
522 
523---
524 
525## Composition map
526 
527This skill does NOT reimplement scope owned by the POWERFUL-tier specialists. It forks into them. See `references/composition_map.md` for the full routing table. Key forks:
528 
529| Concern | Fork into |
530|---|---|
531| WCAG audit, contrast, screen-reader | `engineering-team/skills/a11y-audit/` |
532| Bundle profiling + runtime perf | `engineering/skills/performance-profiler/` |
533| Cinematic / scroll-storytelling landing | `engineering-team/skills/epic-design/` |
534| Apple HIG (iOS / macOS / visionOS) | `product-team/skills/apple-hig-expert/` |
535| Pre-commit Karpathy review | `engineering/karpathy-coder/` |
536| Pre-flight architecture grill | `engineering/grill-me/` |
537 
538The `cs-frontend-engineer` agent orchestrates these forks via `context: fork`. Invoke it from another agent with `Agent({subagent_type: "cs-frontend-engineer", prompt: "..."})` or via `/cs:frontend-review <your problem>`.
539 
540---
541 
542## Forcing-question library (Matt Pocock grill)
543 
544Before locking any framework or rendering decision, walk the seven forcing questions in `references/forcing_questions.md`. Discipline:
545 
5461. One question per turn. No bundling.
5472. Always recommend the answer with cited canon.
5483. Track answers in `/tmp/frontend-grill-<date>.md`.
5494. If a kill criterion trips, stop. Don't scaffold around an unresolved gap.
5505. After Q7, run `frontend_decision_engine.py` with the seven answers.
551 
552Summary:
553 
5541. Primary device + network?
5552. LCP target in ms (and INP, CLS)?
5563. RSC / SPA / SSR / SSG — pick and defend?
5574. JS bundle budget per route?
5585. SEO-dependent or auth-walled?
5596. Design-system source of truth?
5607. WCAG target + named a11y owner?
561 
562---
563 
564## Invocation from other agents and skills
565 
566Three surfaces:
567 
5681. **Slash command:** `/cs:frontend-review <prompt>` — full grill + decision engine + composition routing.
5692. **Agent subagent:** `Agent({subagent_type: "cs-frontend-engineer", prompt: "..."})` — forks context, returns ≤ 200-word digest.
5703. **Direct tool call:** `python scripts/frontend_decision_engine.py ...` — deterministic profile match when inputs are known.
571 
572See `agents/engineering/cs-frontend-engineer.md` for the full invocation contract.
573 

Discussion

Alternatives

Also in Web frameworksSee all 533 in Development →