home / skills / louloulin / claude-agent-sdk / 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-developer

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
21.6 KB
---
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/)

Overview

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.

How this skill works

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.

When to use it

  • Build single-page apps that need rich client-side interactivity and routing.
  • Standardize state and side effects across teams with Redux Toolkit, Pinia, or NgRx.
  • Migrate legacy code to modern framework idioms and hooks/composition patterns.
  • Optimize rendering, caching, and network usage for performance-sensitive pages.
  • Design responsive, accessible UIs that work across devices and assistive tech.

Best practices

  • Favor composition (hooks/Composition API) over large class components for reusability and testability.
  • Centralize side effects and caching with React Query, Pinia actions, or NgRx effects to simplify components.
  • Keep components small and presentational; lift state or use global stores for shared data.
  • Use semantic HTML and ARIA where appropriate; test keyboard and screen reader flows early.
  • Measure performance (Lighthouse, profiling) and optimize critical render paths, lazy-load routes and images.

Example use cases

  • User profile and account dashboards with cached data, optimistic updates, and theme support.
  • Admin panels with paginated lists, filters, and centralized state management.
  • Multi-route consumer apps with client-side routing, code-splitting, and protected routes.
  • Migration plan from legacy jQuery or class-based components to modern hooks/Composition API.
  • Design system components implemented as reusable styled components or utility-first CSS modules.

FAQ

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.