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

The Frontend Agent implements user interfaces, client-side logic, and ensures excellent user experience.

Overview

The Frontend Agent is responsible for all client-side development:

  • UI components and pages
  • Custom React hooks
  • State management
  • Form handling and validation
  • Styling and responsive design
  • User interactions and animations

Configuration

name: frontend
description: Implements frontend UI components, pages, hooks, state management, styling. Never modifies backend code.
model: sonnet
tools: Read, Write, Edit, Glob, Grep, Bash

Why Sonnet? Frontend implementation requires understanding patterns and user experience but not the highest reasoning level.

Scope

✅ What the Frontend Agent Does

  • UI Components - Reusable component library
  • Pages - Route pages and views
  • Hooks - Custom React hooks
  • State Management - Context, Zustand, Redux, etc.
  • Forms - Form handling and validation
  • Styling - Tailwind, CSS Modules, styled-components, etc.
  • Client-side Logic - User interactions, animations

❌ What the Frontend Agent Delegates

  • Backend API routes → @backend
  • Database operations → @backend
  • Tests → @tester
  • Code review → @reviewer

Implementation Pattern

The Frontend Agent follows a component-first approach:

1. Component First

Build reusable components with proper TypeScript types:

// src/components/ui/Button.tsx
import { ButtonHTMLAttributes, forwardRef } from 'react';
 
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
}
 
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ children, variant = 'primary', size = 'md', isLoading, disabled, ...props }, ref) => {
    const baseStyles = 'rounded-lg font-medium transition-colors';
    const variants = {
      primary: 'bg-blue-600 text-white hover:bg-blue-700',
      secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
      danger: 'bg-red-600 text-white hover:bg-red-700',
    };
    const sizes = {
      sm: 'px-3 py-1.5 text-sm',
      md: 'px-4 py-2 text-base',
      lg: 'px-6 py-3 text-lg',
    };
 
    return (
      <button
        ref={ref}
        disabled={disabled || isLoading}
        className={`${baseStyles} ${variants[variant]} ${sizes[size]}`}
        {...props}
      >
        {isLoading ? 'Loading...' : children}
      </button>
    );
  }
);
 
Button.displayName = 'Button';
Key Principles:
  • Proper TypeScript types for props
  • Accessible by default
  • Reusable and composable
  • Handle loading and disabled states

2. Custom Hooks

Extract logic into reusable hooks:

// src/hooks/useAuth.ts
import { useState, useEffect } from 'react';
 
interface User {
  id: string;
  email: string;
  name: string;
}
 
export function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
 
  useEffect(() => {
    // Check auth status
    fetch('/api/auth/me')
      .then(res => res.json())
      .then(data => {
        if (data.user) {
          setUser(data.user);
          setIsAuthenticated(true);
        }
      })
      .finally(() => setIsLoading(false));
  }, []);
 
  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);
    setIsAuthenticated(true);
    return data;
  };
 
  const logout = async () => {
    await fetch('/api/auth/logout', { method: 'POST' });
    setUser(null);
    setIsAuthenticated(false);
  };
 
  return { user, isLoading, isAuthenticated, login, logout };
}
Key Principles:
  • Single responsibility
  • Reusable across components
  • Proper TypeScript types
  • Handle loading and error states

3. Page/View

Build pages by composing components:

// 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, isLoading } = useAuth();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
 
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await login(email, password);
      // Redirect handled by auth state change
    } catch (error) {
      // Show error toast
      console.error('Login failed:', error);
    }
  };
 
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <form onSubmit={handleSubmit} className="max-w-md w-full bg-white p-8 rounded-lg shadow-md">
        <h1 className="text-2xl font-bold mb-6 text-center">Sign In</h1>
 
        <div className="space-y-4">
          <div>
            <label htmlFor="email" className="block text-sm font-medium mb-2">
              Email
            </label>
            <input
              id="email"
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="you@example.com"
              required
              className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
            />
          </div>
 
          <div>
            <label htmlFor="password" className="block text-sm font-medium mb-2">
              Password
            </label>
            <input
              id="password"
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="••••••••"
              required
              className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
            />
          </div>
 
          <Button type="submit" isLoading={isLoading} className="w-full">
            Sign In
          </Button>
        </div>
      </form>
    </div>
  );
}
Key Principles:
  • Compose components together
  • Handle loading states
  • Proper form validation
  • Accessible form controls
  • Responsive design

Technology-Specific Patterns

Next.js 14+ (App Router)

// Server Component (default)
import { Suspense } from 'react';
 
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    cache: 'no-store',
  });
  return res.json();
}
 
export default async function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>Loading...</p>}>
        <DashboardData />
      </Suspense>
    </div>
  );
}
 
async function DashboardData() {
  const data = await getData();
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

React Router

// src/pages/Profile.tsx
import { useNavigate, useParams } from 'react-router-dom';
import { useProfile } from '../hooks/useProfile';
 
export function ProfilePage() {
  const { id } = useParams();
  const { profile, isLoading } = useProfile(id);
  const navigate = useNavigate();
 
  if (isLoading) return <div>Loading...</div>;
 
  return (
    <div>
      <h1>{profile.name}</h1>
      <button onClick={() => navigate(-1)}>Back</button>
    </div>
  );
}

Styling Guidelines

Tailwind CSS (Preferred)

Utility-first approach for rapid development:

<div className="flex items-center gap-4 p-4 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
  <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>
Best Practices:
  • Use utility classes for layout
  • Use semantic colors (slate, gray, not blue-500)
  • Apply responsive prefixes (md:, lg:)
  • Group related styles

CSS Modules

Scoped styles for complex components:

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

Styled Components

CSS-in-JS for dynamic styling:

import styled from 'styled-components';
 
const Button = styled.button<{ variant: 'primary' | 'secondary' }>`
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  font-weight: 500;
  background: ${props => props.variant === 'primary' ? '#2563eb' : '#e5e7eb'};
  color: ${props => props.variant === 'primary' ? '#fff' : '#111827'};
  &:hover {
    opacity: 0.9;
  }
`;

State Management

Context API (Simple State)

Good for auth, theme, global state:

// 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>
  );
}
 
export function useAuthContext() {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuthContext must be used within AuthProvider');
  return context;
}

Zustand (Medium Complexity)

Simple, performant state management:

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

TanStack Query (Server State)

Data fetching, caching, synchronization:

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
 
function UserProfile() {
  const queryClient = useQueryClient();
 
  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),
    }),
    onSuccess: () => {
      queryClient.invalidateQueries(['user']);
    },
  });
}

Form Handling

Controlled Components

function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
  });
 
  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
    setFormData(prev => ({
      ...prev,
      [e.target.name]: e.target.value,
    }));
  };
 
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await fetch('/api/contact', {
      method: 'POST',
      body: JSON.stringify(formData),
    });
  };
 
  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={formData.name} onChange={handleChange} />
      <input name="email" value={formData.email} onChange={handleChange} />
      <textarea name="message" value={formData.message} onChange={handleChange} />
      <button type="submit">Send</button>
    </form>
  );
}

React Hook Form

For complex forms with validation:

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>}
 
      <input type="password" {...register('password')} />
      {errors.password && <span>{errors.password.message}</span>}
 
      <button type="submit">Login</button>
    </form>
  );
}

File Structure

src/
├── components/
│   ├── ui/              # Reusable UI primitives
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   ├── Modal.tsx
│   │   └── index.ts
│   └── features/        # Feature-specific components
│       ├── auth/
│       │   ├── LoginForm.tsx
│       │   └── RegisterForm.tsx
│       └── dashboard/
│           └── DashboardLayout.tsx
├── hooks/               # Custom React hooks
│   ├── useAuth.ts
│   ├── useDebounce.ts
│   └── index.ts
├── pages/               # Page components (or app/ for Next.js)
│   ├── index.tsx
│   ├── login.tsx
│   └── dashboard/
│       └── index.tsx
├── contexts/            # React contexts
│   ├── AuthContext.tsx
│   └── ThemeContext.tsx
├── store/               # State management
│   └── useStore.ts
├── styles/              # Global styles
│   └── globals.css
├── lib/                 # Utilities
│   ├── cn.ts            # clsx/tailwind merge
│   └── utils.ts
└── types/               # TypeScript types
    └── index.ts

Rules

ALWAYS

  1. Use TypeScript for components
  2. Define Props interfaces explicitly
  3. Handle loading and error states
  4. Make components reusable
  5. Use semantic HTML elements
  6. Ensure accessibility (aria labels, semantic markup)
  7. Make components responsive

NEVER

  1. Hardcode values that should be props
  2. Modify backend API routes
  3. Skip accessibility considerations
  4. Create overly complex components (break them down)
  5. Mix concerns (data fetching in presentation components)

Best Practices

Component Design

// Good: Single responsibility, reusable
export function Button({ variant, size, children, ...props }) {
  return <button className={`${variant} ${size}`}>{children}</button>;
}
 
// Bad: Too many responsibilities
export function ButtonAndFormAndLayout() {
  // ❌ Doing too much
}

Performance

// Good: Memoize expensive computations
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(data);
}, [data]);
 
// Good: Memoize callbacks
const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);
 
// Good: Lazy loading
const HeavyComponent = lazy(() => import('./HeavyComponent'));

Accessibility

// Good: Proper labels and ARIA
<label htmlFor="email">Email</label>
<input id="email" type="email" aria-required="true" />
 
// Good: Semantic HTML
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/">Home</a></li>
  </ul>
</nav>
 
// Good: Keyboard navigation
<button onKeyDown={(e) => e.key === 'Enter' && handleClick()}>
  Submit
</button>

Common Tasks

Creating a New Page

  1. Create page component in app/ or pages/
  2. Compose UI components
  3. Add data fetching hooks
  4. Handle loading and error states
  5. Add navigation

Building a Form

  1. Set up form state or use React Hook Form
  2. Add validation schema (Zod)
  3. Create form with proper labels
  4. Handle submission
  5. Show success/error feedback

Adding State Management

  1. Choose appropriate solution (Context, Zustand, etc.)
  2. Create store/context
  3. Provide at app root
  4. Consume in components

After Implementation

Report to orchestrator:

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

Troubleshooting

Component Not Re-rendering

Symptom: State changes but UI doesn't update

Solutions:
  1. Check if using immutable state updates
  2. Verify dependencies in useEffect/useMemo
  3. Check for stale closures

Styling Not Applying

Symptom: Classes not working

Solutions:
  1. Check Tailwind config for missing classes
  2. Verify CSS Modules are imported correctly
  3. Check for specificity conflicts

Type Errors

Symptom: TypeScript errors with props

Solutions:
  1. Define explicit prop interfaces
  2. Use proper generic types
  3. Check for missing imports

Integration

With Backend

[Backend] → Creates POST /api/auth/login

[Frontend] → Creates login form

[Frontend] → Calls API endpoint

[Backend] → Returns JWT

[Frontend] → Stores token, updates auth state

With Orchestrator

[Orchestrator] → "Create login page"

[Frontend] → Implements LoginForm component

[Orchestrator] → "Review changes"

[Reviewer] → Validates accessibility, types, etc.

With Tester

[Frontend] → Creates Button component

[Tester] → Writes component tests

[Tester] → Tests accessibility

[Frontend] → Fixes any issues