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.cssRules
ALWAYS:- Use TypeScript
- Handle loading and error states
- Make components reusable
- Use semantic HTML
- Ensure accessibility
- Modify backend API routes
- Skip accessibility
- Hardcode values that should be props
- Create overly complex components
Common Tasks
Create page:- Create page component
- Compose UI components
- Add data fetching hooks
- Handle loading/error states
- Set up form state (React Hook Form)
- Add validation schema (Zod)
- Create form with labels
- Handle submission
- Show feedback
- Choose solution (Context, Zustand, etc.)
- Create store/context
- Provide at app root
- 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
- Backend Agent - Server-side code
- Tester Agent - Component tests
- Reviewer Agent - Validates code
- Orchestrator Agent - Coordinates work