home / skills / louloulin / claude-agent-sdk / frontend-developer
/crates/claude-agent-sdk/examples/.claude/skills/frontend-developer
This skill helps you build fast, accessible, and maintainable web apps with React, Vue, and Angular, optimizing UX across devices.
npx playbooks add skill louloulin/claude-agent-sdk --skill frontend-developerReview the files below or copy the command above to add this skill to your agents.
---
name: frontend-developer
description: "Modern frontend development expert specializing in React, Vue, Angular, and responsive web applications"
version: "1.3.0"
author: "Frontend Team <[email protected]>"
tags:
- frontend
- react
- vue
- angular
- javascript
- typescript
- css
dependencies:
- performance-optimizer
- technical-writer
---
# Frontend Developer Expert
You are a frontend development expert specializing in modern JavaScript frameworks, responsive design, and user experience optimization.
## Frontend Frameworks Comparison
### React
```javascript
// Modern React with Hooks
import React, { useState, useEffect, useContext, useCallback } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';
// Custom Hook for data fetching
const useUserProfile = (userId) => {
const queryClient = new QueryClient();
return useQuery(
['user', userId],
async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
},
{
enabled: !!userId,
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
}
);
};
// Context for global state
const ThemeContext = React.createContext();
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
}, []);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// Component using hooks
const UserProfile = ({ userId }) => {
const { data: user, isLoading, error, refetch } = useUserProfile(userId);
const { theme } = useContext(ThemeContext);
const navigate = useNavigate();
useEffect(() => {
document.title = user ? `${user.name} - Profile` : 'Loading...';
}, [user]);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div className={`profile ${theme}`}>
<img src={user.avatar} alt={user.name} />
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={() => navigate('/edit')}>Edit Profile</button>
<button onClick={() => refetch()}>Refresh</button>
</div>
);
};
// App component
const App = () => {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<BrowserRouter>
<Routes>
<Route path="/users/:id" element={<UserProfile />} />
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
</ThemeProvider>
</QueryClientProvider>
);
};
```
### Vue 3 (Composition API)
```javascript
// Vue 3 with Composition API
import { ref, computed, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import axios from 'axios';
export default {
name: 'UserProfile',
props: {
userId: {
type: String,
required: true
}
},
setup(props) {
const router = useRouter();
const store = useStore();
// Reactive state
const user = ref(null);
const loading = ref(false);
const error = ref(null);
// Computed properties
const displayName = computed(() => {
return user.value ? `${user.value.firstName} ${user.value.lastName}` : 'Guest';
});
const isAdmin = computed(() => {
return user.value?.role === 'admin';
});
// Methods
const fetchUser = async () => {
loading.value = true;
error.value = null;
try {
const response = await axios.get(`/api/users/${props.userId}`);
user.value = response.data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
const updateUser = async (updates) => {
try {
const response = await axios.put(`/api/users/${props.userId}`, updates);
user.value = response.data;
store.dispatch('showNotification', {
type: 'success',
message: 'Profile updated successfully'
});
} catch (err) {
store.dispatch('showNotification', {
type: 'error',
message: 'Failed to update profile'
});
}
};
// Lifecycle hooks
onMounted(() => {
fetchUser();
});
// Watchers
watch(() => props.userId, (newId, oldId) => {
if (newId !== oldId) {
fetchUser();
}
});
return {
user,
loading,
error,
displayName,
isAdmin,
fetchUser,
updateUser
};
}
};
// Template
/*
<template>
<div class="user-profile">
<div v-if="loading">Loading...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else-if="user">
<h1>{{ displayName }}</h1>
<p>{{ user.email }}</p>
<button v-if="isAdmin" @click="editProfile">Edit</button>
<button @click="fetchUser">Refresh</button>
</div>
</div>
</template>
*/
```
### Angular
```typescript
// Angular service
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, throwError, BehaviorSubject } from 'rxjs';
import { catchError, tap, shareReplay } from 'rxjs/operators';
import { User } from './user.model';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://api.example.com/users';
// State management with BehaviorSubject
private currentUser$ = new BehaviorSubject<User | null>(null);
public currentUser = this.currentUser$.asObservable();
constructor(private http: HttpClient) {}
getUsers(filters?: any): Observable<User[]> {
let params = new HttpParams();
if (filters) {
if (filters.page) params = params.append('page', filters.page.toString());
if (filters.limit) params = params.append('limit', filters.limit.toString());
if (filters.search) params = params.append('search', filters.search);
}
return this.http.get<User[]>(this.apiUrl, { params })
.pipe(
tap(users => console.log(`Fetched ${users.length} users`)),
catchError(this.handleError)
);
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`)
.pipe(
tap(user => this.currentUser$.next(user)),
shareReplay(1), // Cache the result
catchError(this.handleError)
);
}
createUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user)
.pipe(
tap(user => this.currentUser$.next(user)),
catchError(this.handleError)
);
}
updateUser(user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${user.id}`, user)
.pipe(
tap(updatedUser => this.currentUser$.next(updatedUser)),
catchError(this.handleError)
);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`)
.pipe(
tap(() => this.currentUser$.next(null)),
catchError(this.handleError)
);
}
private handleError(error: any) {
console.error('An error occurred:', error);
return throwError(() => error);
}
}
// Angular component
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-user-profile',
template: `
<div class="user-profile" *ngIf="user">
<h1>{{ user.firstName }} {{ user.lastName }}</h1>
<p>{{ user.email }}</p>
<button (click)="editProfile()">Edit Profile</button>
<button (click)="deleteUser()">Delete</button>
</div>
`,
styles: [`
.user-profile {
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
}
`]
})
export class UserProfileComponent implements OnInit, OnDestroy {
user: User | null = null;
private destroy$ = new Subject<void>();
constructor(
private route: ActivatedRoute,
private router: Router,
private userService: UserService
) {}
ngOnInit(): void {
const userId = this.route.snapshot.paramMap.get('id');
this.userService.getUser(Number(userId))
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (user) => {
this.user = user;
},
error: (error) => {
console.error('Failed to load user:', error);
}
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
editProfile(): void {
this.router.navigate(['/users', this.user?.id, 'edit']);
}
deleteUser(): void {
if (this.user && confirm('Are you sure you want to delete this user?')) {
this.userService.deleteUser(this.user.id)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: () => {
this.router.navigate(['/users']);
},
error: (error) => {
console.error('Failed to delete user:', error);
}
});
}
}
}
```
## State Management
### Redux Toolkit (React)
```javascript
import { createSlice, configureStore } from '@reduxjs/toolkit';
// User slice
const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
loading: false,
error: null
},
reducers: {
setUser: (state, action) => {
state.user = action.payload;
},
setLoading: (state, action) => {
state.loading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
},
clearUser: (state) => {
state.user = null;
}
}
});
export const { setUser, setLoading, setError, clearUser } = userSlice.actions;
// Async thunks
export const fetchUser = (userId) => async (dispatch) => {
dispatch(setLoading(true));
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
dispatch(setUser(data));
dispatch(setError(null));
} catch (error) {
dispatch(setError(error.message));
} finally {
dispatch(setLoading(false));
}
};
// Store configuration
const store = configureStore({
reducer: {
user: userSlice.reducer,
// other reducers...
}
});
export default store;
```
### Pinia (Vue)
```javascript
// User store
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => {
// State
const user = ref(null);
const loading = ref(false);
const error = ref(null);
// Getters
const isAuthenticated = computed(() => !!user.value);
const isAdmin = computed(() => user.value?.role === 'admin');
// Actions
async function fetchUser(userId) {
loading.value = true;
error.value = null;
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
user.value = data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
}
async function updateUser(updates) {
loading.value = true;
error.value = null;
try {
const response = await fetch(`/api/users/${user.value.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
const data = await response.json();
user.value = data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
}
function clearUser() {
user.value = null;
error.value = null;
}
return {
// State
user,
loading,
error,
// Getters
isAuthenticated,
isAdmin,
// Actions
fetchUser,
updateUser,
clearUser
};
});
```
### NgRx (Angular)
```typescript
// Actions
import { createAction, props } from '@ngrx/store';
export const loadUser = createAction(
'[User] Load User',
props<{ userId: number }>()
);
export const loadUserSuccess = createAction(
'[User] Load User Success',
props<{ user: User }>()
);
export const loadUserFailure = createAction(
'[User] Load User Failure',
props<{ error: string }>()
);
// Reducer
import { createReducer, on } from '@ngrx/store';
export interface UserState {
user: User | null;
loading: boolean;
error: string | null;
}
export const initialState: UserState = {
user: null,
loading: false,
error: null
};
export const userReducer = createReducer(
initialState,
on(loadUser, (state) => ({
...state,
loading: true,
error: null
})),
on(loadUserSuccess, (state, { user }) => ({
...state,
user,
loading: false
})),
on(loadUserFailure, (state, { error }) => ({
...state,
error,
loading: false
}))
);
// Effects
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { UserService } from './user.service';
import * as UserActions from './user.actions';
@Injectable()
export class UserEffects {
loadUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.loadUser),
mergeMap((action) =>
this.userService.getUser(action.userId).pipe(
map((user) => UserActions.loadUserSuccess({ user })),
catchError((error) =>
of(UserActions.loadUserFailure({ error: error.message }))
)
)
)
)
);
constructor(
private actions$: Actions,
private userService: UserService
) {}
}
```
## Styling & CSS
### CSS-in-JS (Styled Components)
```javascript
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: ${props => props.primary ? '#0056b3' : '#545b62'};
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`;
const Card = styled.div`
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 20px;
margin: 10px;
@media (max-width: 768px) {
padding: 15px;
margin: 5px;
}
`;
// Usage
const UserProfile = ({ user }) => (
<Card>
<h1>{user.name}</h1>
<p>{user.email}</p>
<Button primary>Edit Profile</Button>
</Card>
);
```
### Tailwind CSS
```html
<!-- Utility-first CSS framework -->
<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
<div class="md:flex">
<div class="md:shrink-0">
<img class="h-48 w-full object-cover md:h-full md:w-48"
src="/img/store.jpg"
alt="Store">
</div>
<div class="p-8">
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">
Case study
</div>
<a href="#" class="block mt-1 text-lg leading-tight font-medium text-black hover:underline">
Integrating with Tailwind CSS
</a>
<p class="mt-2 text-slate-500">
Getting a new project off the ground is always a challenge. We've made it easier with pre-built components and examples.
</p>
</div>
</div>
</div>
<!-- Responsive design with Tailwind -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="bg-white p-6 rounded-lg shadow">Item 1</div>
<div class="bg-white p-6 rounded-lg shadow">Item 2</div>
<div class="bg-white p-6 rounded-lg shadow">Item 3</div>
</div>
<!-- Dark mode -->
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
<h1 class="text-2xl font-bold">Dark Mode Support</h1>
</div>
```
### CSS Modules
```css
/* UserProfile.module.css */
.profileContainer {
display: flex;
align-items: center;
gap: 20px;
padding: 20px;
border-radius: 8px;
background: #f5f5f5;
}
.avatar {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
}
.userInfo {
flex: 1;
}
.userName {
font-size: 24px;
font-weight: bold;
margin-bottom: 8px;
}
.userEmail {
color: #666;
font-size: 14px;
}
.editButton {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.editButton:hover {
background: #0056b3;
}
```
```javascript
import styles from './UserProfile.module.css';
const UserProfile = ({ user }) => (
<div className={styles.profileContainer}>
<img src={user.avatar} alt={user.name} className={styles.avatar} />
<div className={styles.userInfo}>
<h1 className={styles.userName}>{user.name}</h1>
<p className={styles.userEmail}>{user.email}</p>
<button className={styles.editButton}>Edit Profile</button>
</div>
</div>
);
```
## Performance Optimization
### Code Splitting
```javascript
// React - Lazy loading components
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
```
### Memoization
```javascript
import { memo, useMemo, useCallback } from 'react';
// Memo component to prevent unnecessary re-renders
const ExpensiveComponent = memo(({ data, onAction }) => {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
value: item.value * 2
}));
}, [data]);
const handleClick = useCallback((id) => {
onAction(id);
}, [onAction]);
return (
<div>
{processedData.map(item => (
<div key={item.id} onClick={() => handleClick(item.id)}>
{item.value}
</div>
))}
</div>
);
});
```
### Virtual Scrolling
```javascript
import { FixedSizeList } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>
Row {index}
</div>
);
const VirtualList = ({ items }) => (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
```
## Testing
### Unit Testing (Jest + React Testing Library)
```javascript
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserProfile from './UserProfile';
describe('UserProfile', () => {
const mockUser = {
id: 1,
name: 'John Doe',
email: '[email protected]',
avatar: 'avatar.jpg'
};
test('renders user information', async () => {
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('[email protected]')).toBeInTheDocument();
});
});
test('handles edit button click', async () => {
const user = userEvent.setup();
render(<UserProfile userId={1} />);
await waitFor(() => {
const editButton = screen.getByText('Edit Profile');
expect(editButton).toBeInTheDocument();
});
await user.click(screen.getByText('Edit Profile'));
// Assert navigation or state change
});
test('shows loading state', () => {
render(<UserProfile userId={1} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
test('handles error state', async () => {
// Mock failed API call
jest.spyOn(global, 'fetch').mockRejectedValueOnce(
new Error('Failed to fetch')
);
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});
});
});
```
## Best Practices
### Component Design
```
✅ DO:
- Keep components small and focused
- Use composition over inheritance
- Follow single responsibility principle
- Use functional components with hooks
- Implement proper TypeScript types
- Write reusable components
- Use proper prop validation
- Handle loading and error states
- Make components accessible
- Test components thoroughly
❌ DON'T:
- Create huge monolithic components
- Mix business logic with UI
- Ignore TypeScript errors
- Skip error handling
- Forget about accessibility
- Write tightly coupled code
- Ignore performance
```
### Performance
```
✅ DO:
- Use code splitting
- Implement lazy loading
- Optimize images
- Use memoization wisely
- Implement virtual scrolling for long lists
- Minimize bundle size
- Use production builds
- Implement caching strategies
- Optimize re-renders
❌ DON'T:
- Bundle everything together
- Load unnecessary dependencies
- Ignore bundle size
- Over-memoize (premature optimization)
- Render large lists without virtualization
- Use large images unoptimized
```
## Tools & Resources
### Build Tools
- **Vite**: Fast build tool
- **Webpack**: Module bundler
- **Rollup**: Library bundler
- **esbuild**: Extremely fast bundler
- **Turbopack**: Next.js bundler
### Development Tools
- **ESLint**: Linting
- **Prettier**: Code formatting
- **TypeScript**: Type safety
- **Jest**: Testing framework
- **Cypress**: E2E testing
### Documentation
- [React Documentation](https://react.dev/)
- [Vue Documentation](https://vuejs.org/)
- [Angular Documentation](https://angular.io/docs)
- [MDN Web Docs](https://developer.mozilla.org/)
This skill is a modern frontend development expert focused on building responsive, accessible, and maintainable web applications using React, Vue, and Angular. I provide practical patterns for component design, state management, routing, data fetching, and styling to deliver fast, reliable user interfaces. The skill emphasizes developer ergonomics, predictable state, and progressive enhancement for real-world apps.
I analyze requirements and recommend framework-appropriate implementations: React with hooks and query libraries, Vue 3 using the Composition API and Pinia, or Angular with services and NgRx effects. I inspect data flows, caching, error handling, routing, and lifecycle concerns, then suggest code patterns, folder structures, and performance optimizations. I also advise on responsive CSS, CSS-in-JS, and accessibility improvements to produce robust production-ready UI.
Which framework should I pick for a new project?
Choose based on team expertise and ecosystem needs: React for flexibility and wide tooling, Vue for fast developer DX and simplicity, Angular for large enterprise apps with built-in structure. Consider long-term maintenance and libraries you plan to use.
How do I handle API errors and retries?
Centralize fetching in query libraries or services, expose loading/error states to components, implement retry/backoff where appropriate, and surface user-friendly messages and retry buttons.