agentsclimarketplace

Frontend web

Skill queen-of-code/AI-DLC/skills/frontend-web

Modern web development patterns for React, Vue, and vanilla JS including component architecture, state management, and performance. Use when building web UIs, optimizing frontend performance, or implementing accessibility.From its SKILL.md

Install
npx -y skills add queen-of-code/AI-DLC --skill frontend-web

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 9 stars9 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.9 KB, as published. Nobody here has run it

Frontend Web Development

When to Use

  • Building web user interfaces
  • Implementing component architecture
  • Managing application state
  • Optimizing performance
  • Ensuring accessibility
  • Handling forms and validation

Interactive UI validation (Review / Validate)

When an agent must exercise UI acceptance criteria with evidence (Review Frontend/UX pass or Validate phase UI criteria), follow docs/INTERACTIVE-UI-VALIDATION.md — Chrome DevTools MCP, not ad-hoc Playwright or generic browser MCP. Playwright remains for CI and e2e/ test authoring.

Component Architecture

Component Types

TypePurposeStateExamples
PresentationalUI renderingNone/props onlyButton, Card, Avatar
ContainerData fetching, logicYesUserList, OrderPage
LayoutPage structureMinimalHeader, Sidebar, Grid
FeatureComplete featureYesLoginForm, Checkout

Component Structure

components/
├── ui/                    # Presentational (reusable)
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.test.tsx
│   │   └── Button.css
│   ├── Input/
│   └── Card/
├── features/              # Feature-specific
│   ├── auth/
│   │   ├── LoginForm.tsx
│   │   └── SignupForm.tsx
│   └── dashboard/
│       └── DashboardStats.tsx
└── layout/                # Layout components
    ├── Header.tsx
    ├── Sidebar.tsx
    └── PageLayout.tsx

Component Patterns

Composition over Props

// Good: Composable
<Card>
  <Card.Header>Title</Card.Header>
  <Card.Body>Content</Card.Body>
  <Card.Footer>Actions</Card.Footer>
</Card>

// Avoid: Prop overload
<Card 
  title="Title"
  body="Content"
  footer="Actions"
  showBorder
  variant="elevated"
  ...dozens more props
/>

Render Props / Slots

// React: Render props
<DataFetcher url="/api/users">
  {({ data, loading, error }) => (
    loading ? <Spinner /> : <UserList users={data} />
  )}
</DataFetcher>

// Vue: Slots
<template>
  <DataFetcher url="/api/users" v-slot="{ data, loading }">
    <Spinner v-if="loading" />
    <UserList v-else :users="data" />
  </DataFetcher>
</template>

State Management

When to Use What

State TypeLocationExample
Local UIComponent stateModal open/closed
FormForm libraryInput values
ServerReact Query/SWRAPI data
Global UIContext/StoreTheme, sidebar
Global AppStoreUser session

React State Patterns

// Local state
const [count, setCount] = useState(0);

// Server state with React Query
const { data, isLoading } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
});

// Global state with Context
const ThemeContext = createContext<Theme>('light');

function App() {
  const [theme, setTheme] = useState<Theme>('light');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Layout />
    </ThemeContext.Provider>
  );
}

// Zustand for complex global state
const useStore = create((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

Avoid Prop Drilling

// Bad: Passing through multiple levels
<App>
  <Header user={user} />     // needs user
    <Nav user={user} />      // just passing through
      <Avatar user={user} /> // actually uses user

// Good: Context or composition
<App>
  <UserProvider>
    <Header />
      <Nav />
        <Avatar />  // Gets user from context

Performance

React Optimization

// Memoize expensive computations
const sortedItems = useMemo(() => {
  return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);

// Memoize callbacks
const handleClick = useCallback((id: string) => {
  selectItem(id);
}, [selectItem]);

// Memoize components (use sparingly)
const ExpensiveList = memo(({ items }) => {
  return items.map(item => <Item key={item.id} {...item} />);
});

Code Splitting

// Route-based splitting
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// Component-based splitting
const HeavyChart = lazy(() => import('./components/HeavyChart'));

Image Optimization

// Use next/image or similar
<Image
  src="/photo.jpg"
  alt="Description"
  width={800}
  height={600}
  loading="lazy"
  placeholder="blur"
/>

// Responsive images
<picture>
  <source media="(min-width: 800px)" srcSet="large.jpg" />
  <source media="(min-width: 400px)" srcSet="medium.jpg" />
  <img src="small.jpg" alt="Description" />
</picture>

Virtual Lists

// For long lists (1000+ items)
import { FixedSizeList } from 'react-window';

<FixedSizeList
  height={400}
  width={300}
  itemCount={items.length}
  itemSize={50}
>
  {({ index, style }) => (
    <div style={style}>{items[index].name}</div>
  )}
</FixedSizeList>

Accessibility

Essential Practices

PracticeImplementation
Semantic HTMLUse correct elements (<button>, <nav>, <main>)
Keyboard navTab order, focus management
ARIA labelsWhen semantic HTML isn't enough
Color contrast4.5:1 for text, 3:1 for large text
Alt textDescriptive for images

Component Accessibility

// Good: Accessible button
<button
  onClick={handleClick}
  aria-label="Close dialog"
  aria-expanded={isOpen}
>
  <CloseIcon aria-hidden="true" />
</button>

// Good: Accessible form
<label htmlFor="email">Email address</label>
<input
  id="email"
  type="email"
  aria-required="true"
  aria-invalid={!!errors.email}
  aria-describedby={errors.email ? "email-error" : undefined}
/>
{errors.email && (
  <span id="email-error" role="alert">
    {errors.email}
  </span>
)}

Focus Management

// Return focus after modal closes
function Modal({ isOpen, onClose, children }) {
  const previousFocus = useRef<HTMLElement | null>(null);
  const closeButtonRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    if (isOpen) {
      previousFocus.current = document.activeElement as HTMLElement;
      closeButtonRef.current?.focus();
    } else {
      previousFocus.current?.focus();
    }
  }, [isOpen]);

  // ...
}

Forms

Form Handling

// React Hook Form
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/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 = async (data) => {
    await login(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>
  );
}

Validation Patterns

// Zod schema
const userSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  age: z.number().min(18, 'Must be 18 or older').optional(),
  role: z.enum(['admin', 'user', 'guest']),
});

// Yup schema
const userSchema = yup.object({
  name: yup.string().min(2).required(),
  email: yup.string().email().required(),
  age: yup.number().min(18),
});

Error Handling

Error Boundaries

class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    logErrorToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <ErrorFallback error={this.state.error} />;
    }
    return this.props.children;
  }
}

// Usage
<ErrorBoundary>
  <Dashboard />
</ErrorBoundary>

API Error Handling

function useApiQuery(url) {
  const [state, setState] = useState({
    data: null,
    error: null,
    loading: true,
  });

  useEffect(() => {
    fetch(url)
      .then(res => {
        if (!res.ok) throw new ApiError(res.status, res.statusText);
        return res.json();
      })
      .then(data => setState({ data, error: null, loading: false }))
      .catch(error => setState({ data: null, error, loading: false }));
  }, [url]);

  return state;
}

Responsive Design

Breakpoints

/* Mobile first */
.container {
  padding: 1rem;
}

/* Tablet */
@media (min-width: 768px) {
  .container {
    padding: 2rem;
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .container {
    padding: 3rem;
    max-width: 1200px;
  }
}

Tailwind Responsive

<div className="
  grid 
  grid-cols-1      /* Mobile: 1 column */
  md:grid-cols-2   /* Tablet: 2 columns */
  lg:grid-cols-3   /* Desktop: 3 columns */
  gap-4
">
  {items.map(item => <Card key={item.id} {...item} />)}
</div>

Additional Resources

For component examples, see examples/ directory.

Keep looking

Skills are one crate of 626,474. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.