Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Frontend Agent

Implements client-side code: UI components, pages, hooks, state management, styling.

Model: Sonnet

Tools: Read, Write, Edit, Glob, Grep, Bash

Scope: Frontend only. Delegates backend to @backend, tests to @tester.

Implementation Pattern

1. Component First

// src/components/ui/Button.tsx
import { ButtonHTMLAttributes, forwardRef } from 'react';
 
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'danger';
  isLoading?: boolean;
}
 
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ children, variant = 'primary', isLoading, disabled, ...props }, ref) => {
    return (
      <button
        ref={ref}
        disabled={disabled || isLoading}
        className={`btn btn-${variant}`}
        {...props}
      >
        {isLoading ? 'Loading...' : children}
      </button>
    );
  }
);

2. Custom Hooks

// src/hooks/useAuth.ts
import { useState, useEffect } from 'react';
 
export function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
 
  const login = async (email: string, password: string) => {
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password })
    });
    const data = await res.json();
    setUser(data.user);
    return data;
  };
 
  return { user, isLoading, login };
}

3. Pages

// src/app/login/page.tsx
'use client';
 
import { useState } from 'react';
import { useAuth } from '@/hooks/useAuth';
import { Button } from '@/components/ui/Button';
 
export default function LoginPage() {
  const { login } = useAuth();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
 
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await login(email, password);
  };
 
  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      <Button type="submit">Sign In</Button>
    </form>
  );
}

Styling

Tailwind CSS

<div className="flex items-center gap-4 p-4 bg-white rounded-lg shadow-md">
  <img src={avatar} alt={name} className="w-12 h-12 rounded-full" />
  <div>
    <h3 className="font-semibold text-gray-900">{name}</h3>
    <p className="text-sm text-gray-600">{role}</p>
  </div>
</div>

CSS Modules

// components/Card.module.css
.card {
  @apply rounded-lg shadow-md p-4;
  transition: all 0.2s ease;
}
 
// components/Card.tsx
import styles from './Card.module.css';
 
export function Card({ children }) {
  return <div className={styles.card}>{children}</div>;
}

State Management

Context API

// src/contexts/AuthContext.tsx
const AuthContext = createContext<AuthContextValue | null>(null);
 
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
}

Zustand

// src/store/useStore.ts
import { create } from 'zustand';
 
export const useStore = create<StoreState>((set) => ({
  user: null,
  setUser: (user) => set({ user })
}));

TanStack Query

import { useQuery, useMutation } from '@tanstack/react-query';
 
function UserProfile() {
  const { data: user, isLoading } = useQuery({
    queryKey: ['user'],
    queryFn: () => fetch('/api/user').then(r => r.json())
  });
 
  const updateMutation = useMutation({
    mutationFn: (data) => fetch('/api/user', {
      method: 'PUT',
      body: JSON.stringify(data)
    })
  });
}

Form Handling

React Hook Form

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
 
const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
});
 
function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema)
  });
 
  const onSubmit = (data) => console.log(data);
 
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}
      <button type="submit">Login</button>
    </form>
  );
}

File Structure

src/
├── components/
│   ├── ui/              # Reusable UI primitives
│   │   ├── Button.tsx
│   │   └── Input.tsx
│   └── features/        # Feature-specific
│       └── auth/
│           └── LoginForm.tsx
├── hooks/               # Custom hooks
│   └── useAuth.ts
├── pages/               # Pages (or app/ for Next.js)
│   └── login.tsx
├── contexts/            # React contexts
│   └── AuthContext.tsx
├── store/               # State management
│   └── useStore.ts
└── styles/              # Global styles
    └── globals.css

Rules

ALWAYS:
  • Use TypeScript
  • Handle loading and error states
  • Make components reusable
  • Use semantic HTML
  • Ensure accessibility
NEVER:
  • Modify backend API routes
  • Skip accessibility
  • Hardcode values that should be props
  • Create overly complex components

Common Tasks

Create page:
  1. Create page component
  2. Compose UI components
  3. Add data fetching hooks
  4. Handle loading/error states
Build form:
  1. Set up form state (React Hook Form)
  2. Add validation schema (Zod)
  3. Create form with labels
  4. Handle submission
  5. Show feedback
Add state:
  1. Choose solution (Context, Zustand, etc.)
  2. Create store/context
  3. Provide at app root
  4. Consume in components

After Implementation

{
  "files_created": [
    "src/components/ui/Button.tsx",
    "src/app/login/page.tsx"
  ],
  "implementation": "Login page with form validation",
  "dependencies_added": ["react-hook-form", "zod"],
  "next_steps": [
    "Write tests for Button component",
    "Add integration tests for login flow"
  ]
}

See Also