/dev-coding-frontend - Frontend Implementation
Skill Awareness: See skills/_registry.md for all available skills.
-
Loaded by: /dev-coding when UI work needed
-
References: Load tech-specific from references/ (nextjs.md, vue.md, etc.)
-
Before: Backend API contract should be documented
Frontend-specific patterns for UI components, pages, and client-side logic.
When Loaded
This skill is loaded by /dev-coding when:
-
Spec requires UI components
-
Spec requires pages/routes
-
Spec requires client-side logic
Workflow
Step 1: Understand Frontend Requirements
From the UC spec, extract:
Frontend Requirements Checklist
[ ] Pages/Routes needed? - Path - Layout - Auth required?
[ ] Components needed? - New components - Modify existing - Shared vs feature-specific
[ ] State management? - Local state - Global state - Server state (API data)
[ ] Forms? - Fields - Validation rules - Submit handling
[ ] API integration? - Endpoints to call - Request/response handling - Loading/error states
Step 2: Review API Contract
If backend was just implemented (or exists), review:
Available API
From backend implementation notes:
POST /api/auth/login
- Request:
{ email, password } - Response:
{ token, user } - Errors: 400 (validation), 401 (credentials)
Know this BEFORE building UI to match shapes.
Step 3: Component Architecture
Decide component structure:
Feature component structure: src/ ├── components/ │ ├── ui/ # Shared/base components │ │ ├── Button.tsx │ │ └── Input.tsx │ └── features/ │ └── auth/ # Feature-specific │ ├── LoginForm.tsx │ └── SignupForm.tsx ├── app/ (or pages/) │ └── login/ │ └── page.tsx # Page that uses LoginForm
Follow project conventions (from scout):
-
Where components live
-
Naming pattern
-
Export style
Step 4: Build Components
Order: Base components → Feature components → Pages
- Check if base components exist (Button, Input, etc.)
- Create feature components
- Create/modify pages
- Wire up routing
Component Pattern:
// Follow project conventions // This is a common pattern, adapt to project
interface LoginFormProps { onSuccess?: (user: User) => void; redirectTo?: string; }
export function LoginForm({ onSuccess, redirectTo = '/' }: LoginFormProps) { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setIsLoading(true); setError(null);
try {
const result = await login(formData);
onSuccess?.(result.user);
router.push(redirectTo);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
return ( <form onSubmit={handleSubmit}> {error && <Alert variant="error">{error}</Alert>} {/* Form fields */} <Button type="submit" disabled={isLoading}> {isLoading ? 'Loading...' : 'Login'} </Button> </form> ); }
Step 5: API Integration
Create API client functions:
// lib/api/auth.ts export async function login(credentials: LoginCredentials) { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials), });
if (!response.ok) { const error = await response.json(); throw new Error(error.message || 'Login failed'); }
return response.json(); }
Handle states:
// Loading state {isLoading && <Spinner />}
// Error state {error && <Alert variant="error">{error}</Alert>}
// Empty state {items.length === 0 && <EmptyState message="No items found" />}
// Success state {items.map(item => <ItemCard key={item.id} item={item} />)}
Step 6: Form Handling
Validation Pattern:
// Client-side validation const validateForm = (data: FormData) => { const errors: Record<string, string> = {};
if (!data.email) { errors.email = 'Email is required'; } else if (!isValidEmail(data.email)) { errors.email = 'Invalid email format'; }
if (!data.password) { errors.password = 'Password is required'; } else if (data.password.length < 8) { errors.password = 'Password must be at least 8 characters'; }
return errors; };
// Show errors {errors.email && <span className="error">{errors.email}</span>}
Form Libraries (use if project has them):
-
react-hook-form
-
formik
-
native form handling
Step 7: Verification
Visual verification:
// Option 1: Playwright screenshot await mcp__playwright__browser_navigate({ url: 'http://localhost:3000/login' }); await mcp__playwright__browser_snapshot({});
// Option 2: Manual check // Navigate to page in browser, verify appearance
Interaction verification:
// Fill and submit form await mcp__playwright__browser_type({ element: 'email input', ref: 'email-input-ref', text: 'test@test.com' });
await mcp__playwright__browser_click({ element: 'submit button', ref: 'submit-btn-ref' });
// Verify result await mcp__playwright__browser_snapshot({});
Verification checklist:
[ ] Page renders without errors [ ] Components display correctly [ ] Forms validate input [ ] Submit calls correct API [ ] Loading states show [ ] Errors display properly [ ] Success redirects/updates correctly [ ] Mobile responsive (if required)
Common Patterns
Conditional Rendering
// Auth guard {isAuthenticated ? <Dashboard /> : <Redirect to="/login" />}
// Loading {isLoading ? <Spinner /> : <Content />}
// Permission {user.canEdit && <EditButton />}
Data Fetching
// Server component (Next.js App Router) async function PostsPage() { const posts = await getPosts(); // Fetches on server return <PostList posts={posts} />; }
// Client component with useEffect function PostsPage() { const [posts, setPosts] = useState([]); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { getPosts().then(setPosts).finally(() => setIsLoading(false)); }, []);
if (isLoading) return <Spinner />; return <PostList posts={posts} />; }
// With SWR/React Query function PostsPage() { const { data: posts, error, isLoading } = useSWR('/api/posts', fetcher);
if (isLoading) return <Spinner />; if (error) return <Error message={error.message} />; return <PostList posts={posts} />; }
Navigation
// Next.js import { useRouter } from 'next/navigation'; const router = useRouter(); router.push('/dashboard');
// React Router import { useNavigate } from 'react-router-dom'; const navigate = useNavigate(); navigate('/dashboard');
Toast/Notifications
// Success feedback toast.success('Saved successfully');
// Error feedback toast.error('Failed to save. Please try again.');
// Use project's toast library (sonner, react-hot-toast, etc.)
Accessibility Checklist
[ ] Images have alt text [ ] Form inputs have labels [ ] Buttons have accessible names [ ] Color contrast sufficient [ ] Keyboard navigation works [ ] Focus states visible [ ] Screen reader tested (if critical)
Responsive Checklist
[ ] Mobile layout (< 768px) [ ] Tablet layout (768px - 1024px) [ ] Desktop layout (> 1024px) [ ] Touch targets large enough (44px minimum) [ ] No horizontal scroll on mobile
Debugging
Component Not Rendering
Check console for errors
Browser DevTools → Console
Check if component imported correctly
Check if props passed correctly
Check conditional rendering logic
API Call Failing
Check Network tab in DevTools
Verify URL, method, headers
Check CORS if cross-origin
Verify backend is running
Styling Issues
Check if styles imported
Check class names (typos)
Check CSS specificity
Check for conflicting styles
Use DevTools Elements panel
Using Playwright for Debug
// Take screenshot of current state await mcp__playwright__browser_take_screenshot({ filename: 'debug-screenshot.png' });
// Check console messages await mcp__playwright__browser_console_messages({ level: 'error' });
// Check network requests await mcp__playwright__browser_network_requests({});
Tech-Specific References
Load additional patterns based on detected tech:
Tech Reference File
Next.js references/nextjs.md
Vue references/vue.md
React references/react.md
shadcn/ui references/shadcn.md
Tailwind references/tailwind.md
These files contain tech-specific patterns, gotchas, and best practices. Add them as your projects use different stacks.