Skip to content
Back to skills

Frontend UI Generation Skill

ASecurity

Define the frontend skill that wraps react_advanced_skill and frontend-design skill for creating production-grade React components with TypeScript and Tailwind CSS.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 5, 2026
designtypescriptgoreacttestingapifrontend

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 21 files and shows the line behind each finding

Scanned October 1, 2026

npx -y skills add sharmapuneet1510/awesome-prompts --skill skills --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Frontend UI Generation Skill?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Frontend UI Generation Skill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sharmapuneet1510-frontend-ui-generation-skill-awesome-prompts/badge)](https://www.skillsdirectory.com/skills/sharmapuneet1510-frontend-ui-generation-skill-awesome-prompts)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Frontend UI Generation Skill
version: 1.1
description: Define the frontend skill that wraps react_advanced_skill and frontend-design skill for creating production-grade React components with TypeScript and Tailwind CSS.
applies_to: [react, typescript, tailwindcss, frontend]
---

# Frontend UI Generation Skill — v1.1

## Quick Card

> Read this card first. Load a section below only when the task needs it.

| | |
|---|---|
| **Use when** | Generating React + TypeScript components and pages — `implementer:build`, `architect:frontend` |
| **Skip when** | React idioms alone — `react_advanced_skill` |
| **Inputs** | Component spec, design system, API contract, accessibility level |
| **Produces** | Components, pages, hooks, Tailwind styles, tests |
| **Steps** | 1. Analyse requirements → 2. Design prop interfaces → 3. Build presentational then container components → 4. Wire data via TanStack Query → 5. Accessibility pass → 6. Tests |
| **Done when** | §Validation Checklist passes; loading, error, and empty states rendered and tested |
| **Senior defaults** | Server state in TanStack Query, never copied into local state · every async view renders loading / error / empty · forms: react-hook-form + Zod schema shared with the API types · semantic HTML first, ARIA only to fill gaps · no `any`; props typed from the API contract |
| **Load on demand** | §Input · §Output · §Process · §Code Example · §Validation Checklist |
| **Run report** | `html_report_skill` — adds: Component tree · Accessibility checks |
| **Pairs with** | `react_advanced_skill`, `test_skill` §4 |

---

## Purpose

This skill provides a comprehensive guide for generating production-grade frontend components using React 18+, TypeScript, and Tailwind CSS. It wraps and integrates `react_advanced_skill` and `frontend-design` skill to ensure components are not only functionally correct but also visually distinctive and accessible.

## Input

### Task Specification
- Component requirements (name, props, functionality)
- UI/UX requirements (layout, design patterns, accessibility)
- Integration points (APIs, state management, routing)
- Form handling requirements (validation, submission, error display)

### Context
- Existing component library patterns
- Design system and Tailwind CSS configuration
- Application's TypeScript strict mode requirements
- Accessibility audit results and WCAG 2.1 compliance level

### Tech Stack
- React 18+
- TypeScript 5.x (strict mode enabled)
- Tailwind CSS 3.x+
- React Router v6+ (for routing)
- TanStack Query (for data fetching)
- Zustand (for state management)
- Vitest + React Testing Library (for testing)

## Output

### Components
- Functional React components with `React.FC<Props>` TypeScript interface
- Reusable prop interfaces for all components
- Custom hooks for cross-cutting concerns (useAuth, useForm, useApi)
- Proper separation of presentational and container components

### Pages
- Full-page layouts with header, sidebar, and content sections
- Responsive design that works on mobile, tablet, and desktop
- Proper route integration using React Router

### Hooks
- Custom hooks with TypeScript typing for all parameters and return types
- Proper cleanup and dependency arrays in useEffect
- Testing utilities for hook testing

### Styles
- Tailwind CSS classNames for all styling
- CSS modules for component-scoped styles where needed
- Responsive design using Tailwind breakpoints (sm, md, lg, xl, 2xl)
- Dark mode support using Tailwind's dark mode utilities

### Tests
- Unit tests for components using Vitest + React Testing Library
- Integration tests for multi-component workflows
- Accessibility tests using @testing-library/jest-dom and jest-axe
- Minimum 85% code coverage

## Process

### 1. Analyze Requirements
- Read the component specification and design requirements
- Identify all props and their TypeScript types
- Map out user interactions and form validation rules
- Document accessibility requirements and WCAG level

### 2. Design TypeScript Interfaces
- Create prop interfaces for all components
- Use strict TypeScript mode (no implicit any)
- Leverage union types and discriminated unions for complex prop combinations
- Document all props with JSDoc comments

### 3. Build Component Structure
- Create the functional component with React.FC<Props> type
- Implement all hooks (useState, useEffect, useContext, useCallback)
- Add error boundaries where appropriate
- Implement proper loading and error states

### 4. Add Form Handling
- Validate input using controlled components
- Implement try/catch blocks for async operations
- Display user-friendly error messages
- Show loading states during form submission
- Use useCallback to memoize form handlers

### 5. Style with Tailwind CSS
- Use semantic HTML elements (button, input, form, label, etc.)
- Apply Tailwind classes for layout, spacing, colors, and typography
- Ensure responsive design with mobile-first approach
- Support dark mode using dark: prefix

### 6. Implement Accessibility
- Add htmlFor attributes on labels and id on inputs
- Use semantic HTML for better screen reader support
- Add ARIA labels where semantic HTML is insufficient
- Test with accessibility audit tools (axe, Lighthouse)

### 7. Write Comprehensive Tests
- Create unit tests for component rendering and interactions
- Test all form submission scenarios (success, error, validation)
- Test accessibility with jest-axe
- Achieve minimum 85% code coverage

## Code Example

### LoginForm.tsx Component

```typescript
import React, { useState, useCallback } from 'react';
import { useAuth } from '../hooks/useAuth';

/**
 * LoginFormProps - Props interface for LoginForm component
 */
interface LoginFormProps {
  onSuccess?: () => void;
  redirectTo?: string;
}

/**
 * LoginForm - User login form component with email and password fields
 * 
 * Features:
 * - Email and password input validation
 * - Loading state during submission
 * - Error message display
 * - Accessible form with proper labels and ARIA attributes
 * - Tailwind CSS responsive styling
 * 
 * @component
 * @example
 * <LoginForm onSuccess={() => navigate('/dashboard')} />
 */
const LoginForm: React.FC<LoginFormProps> = ({ onSuccess, redirectTo = '/' }) => {
  const [email, setEmail] = useState<string>('');
  const [password, setPassword] = useState<string>('');
  const [error, setError] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(false);
  const { login } = useAuth();

  /**
   * Validate email format using regex
   */
  const validateEmail = useCallback((email: string): boolean => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }, []);

  /**
   * Validate password minimum length
   */
  const validatePassword = useCallback((password: string): boolean => {
    return password.length >= 8;
  }, []);

  /**
   * Handle form submission with error handling
   */
  const handleSubmit = useCallback(
    async (e: React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
      setError('');

      // Validate inputs
      if (!email || !password) {
        setError('Email and password are required');
        return;
      }

      if (!validateEmail(email)) {
        setError('Please enter a valid email address');
        return;
      }

      if (!validatePassword(password)) {
        setError('Password must be at least 8 characters');
        return;
      }

      try {
        setLoading(true);
        
        // Call authentication hook
        await login({
          email: email.toLowerCase(),
          password,
        });

        // Clear form on success
        setEmail('');
        setPassword('');

        // Call success callback if provided
        if (onSuccess) {
          onSuccess();
        }
      } catch (err) {
        // Handle authentication errors
        const errorMessage = err instanceof Error ? err.message : 'Login failed. Please try again.';
        setError(errorMessage);
      } finally {
        setLoading(false);
      }
    },
    [email, password, validateEmail, validatePassword, login, onSuccess],
  );

  /**
   * Handle email input change
   */
  const handleEmailChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setEmail(e.target.value);
    setError(''); // Clear error on input change
  }, []);

  /**
   * Handle password input change
   */
  const handlePasswordChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setPassword(e.target.value);
    setError(''); // Clear error on input change
  }, []);

  return (
    <div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-900 py-12 px-4 sm:px-6 lg:px-8">
      <div className="w-full max-w-md space-y-8">
        {/* Header */}
        <div className="text-center">
          <h1 className="text-3xl font-extrabold text-gray-900 dark:text-white">
            Sign in
          </h1>
          <p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
            Access your account to continue
          </p>
        </div>

        {/* Form */}
        <form
          className="mt-8 space-y-6"
          onSubmit={handleSubmit}
          noValidate
          aria-label="Login form"
        >
          {/* Error Message */}
          {error && (
            <div
              className="rounded-md bg-red-50 dark:bg-red-900/20 p-4 border border-red-200 dark:border-red-800"
              role="alert"
              aria-live="polite"
            >
              <p className="text-sm font-medium text-red-800 dark:text-red-200">
                {error}
              </p>
            </div>
          )}

          {/* Email Field */}
          <div>
            <label
              htmlFor="email"
              className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
            >
              Email address
            </label>
            <input
              id="email"
              name="email"
              type="email"
              autoComplete="email"
              required
              value={email}
              onChange={handleEmailChange}
              disabled={loading}
              aria-label="Email address"
              aria-describedby={error ? 'error-message' : undefined}
              className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:text-white disabled:opacity-50 disabled:cursor-not-allowed transition"
              placeholder="you@example.com"
            />
          </div>

          {/* Password Field */}
          <div>
            <label
              htmlFor="password"
              className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
            >
              Password
            </label>
            <input
              id="password"
              name="password"
              type="password"
              autoComplete="current-password"
              required
              value={password}
              onChange={handlePasswordChange}
              disabled={loading}
              aria-label="Password"
              aria-describedby={error ? 'error-message' : undefined}
              className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:text-white disabled:opacity-50 disabled:cursor-not-allowed transition"
              placeholder="••••••••"
            />
          </div>

          {/* Submit Button */}
          <button
            type="submit"
            disabled={loading}
            aria-busy={loading}
            className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-900 disabled:opacity-50 disabled:cursor-not-allowed transition"
          >
            {loading ? (
              <span className="flex items-center justify-center">
                <svg
                  className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  aria-hidden="true"
                >
                  <circle
                    className="opacity-25"
                    cx="12"
                    cy="12"
                    r="10"
                    stroke="currentColor"
                    strokeWidth="4"
                  />
                  <path
                    className="opacity-75"
                    fill="currentColor"
                    d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
                  />
                </svg>
                Signing in...
              </span>
            ) : (
              'Sign in'
            )}
          </button>

          {/* Footer */}
          <p className="text-center text-sm text-gray-600 dark:text-gray-400">
            Don't have an account?{' '}
            <a
              href="/signup"
              className="font-medium text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300"
            >
              Sign up
            </a>
          </p>
        </form>
      </div>
    </div>
  );
};

export default LoginForm;
```

### useAuth Hook

```typescript
import { useCallback } from 'react';
import { useAuthStore } from '../store/authStore';
import { apiClient } from '../services/apiClient';

interface LoginPayload {
  email: string;
  password: string;
}

interface AuthResponse {
  token: string;
  user: {
    id: string;
    email: string;
    name: string;
  };
}

/**
 * useAuth - Custom hook for authentication operations
 */
export const useAuth = () => {
  const { setUser, setToken, clearAuth } = useAuthStore();

  const login = useCallback(async (payload: LoginPayload): Promise<void> => {
    const response = await apiClient.post<AuthResponse>('/auth/login', payload);
    
    if (!response.token) {
      throw new Error('Invalid authentication response');
    }

    setToken(response.token);
    setUser(response.user);
  }, [setUser, setToken]);

  const logout = useCallback(() => {
    clearAuth();
  }, [clearAuth]);

  return { login, logout };
};
```

### LoginForm.test.tsx

```typescript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe, toHaveNoViolations } from 'jest-axe';
import LoginForm from './LoginForm';
import * as authHooks from '../hooks/useAuth';

expect.extend(toHaveNoViolations);

describe('LoginForm', () => {
  const mockLogin = vi.fn();
  const mockOnSuccess = vi.fn();

  beforeEach(() => {
    mockLogin.mockClear();
    mockOnSuccess.mockClear();
    vi.spyOn(authHooks, 'useAuth').mockReturnValue({
      login: mockLogin,
      logout: vi.fn(),
    } as any);
  });

  describe('Rendering', () => {
    it('should render login form with email and password fields', () => {
      render(<LoginForm />);

      expect(screen.getByLabelText(/email address/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
      expect(screen.getByRole('button', { name: /sign in/i })).toBeInTheDocument();
    });

    it('should render sign up link', () => {
      render(<LoginForm />);

      const signUpLink = screen.getByRole('link', { name: /sign up/i });
      expect(signUpLink).toBeInTheDocument();
      expect(signUpLink).toHaveAttribute('href', '/signup');
    });
  });

  describe('Validation', () => {
    it('should show error for empty email and password', async () => {
      const user = userEvent.setup();
      render(<LoginForm />);

      const submitButton = screen.getByRole('button', { name: /sign in/i });
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/email and password are required/i),
        ).toBeInTheDocument();
      });
    });

    it('should show error for invalid email format', async () => {
      const user = userEvent.setup();
      render(<LoginForm />);

      const emailInput = screen.getByLabelText(/email address/i);
      const passwordInput = screen.getByLabelText(/password/i);
      const submitButton = screen.getByRole('button', { name: /sign in/i });

      await user.type(emailInput, 'invalid-email');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/please enter a valid email address/i),
        ).toBeInTheDocument();
      });
    });

    it('should show error for password less than 8 characters', async () => {
      const user = userEvent.setup();
      render(<LoginForm />);

      const emailInput = screen.getByLabelText(/email address/i);
      const passwordInput = screen.getByLabelText(/password/i);
      const submitButton = screen.getByRole('button', { name: /sign in/i });

      await user.type(emailInput, 'user@example.com');
      await user.type(passwordInput, 'short');
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/password must be at least 8 characters/i),
        ).toBeInTheDocument();
      });
    });
  });

  describe('Form Submission', () => {
    it('should call login with email and password on successful validation', async () => {
      const user = userEvent.setup();
      mockLogin.mockResolvedValue(undefined);

      render(<LoginForm />);

      const emailInput = screen.getByLabelText(/email address/i);
      const passwordInput = screen.getByLabelText(/password/i);
      const submitButton = screen.getByRole('button', { name: /sign in/i });

      await user.type(emailInput, 'user@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(mockLogin).toHaveBeenCalledWith({
          email: 'user@example.com',
          password: 'password123',
        });
      });
    });

    it('should clear form after successful login', async () => {
      const user = userEvent.setup();
      mockLogin.mockResolvedValue(undefined);

      render(<LoginForm />);

      const emailInput = screen.getByLabelText(/email address/i) as HTMLInputElement;
      const passwordInput = screen.getByLabelText(/password/i) as HTMLInputElement;
      const submitButton = screen.getByRole('button', { name: /sign in/i });

      await user.type(emailInput, 'user@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(emailInput.value).toBe('');
        expect(passwordInput.value).toBe('');
      });
    });

    it('should call onSuccess callback after successful login', async () => {
      const user = userEvent.setup();
      mockLogin.mockResolvedValue(undefined);

      render(<LoginForm onSuccess={mockOnSuccess} />);

      const emailInput = screen.getByLabelText(/email address/i);
      const passwordInput = screen.getByLabelText(/password/i);
      const submitButton = screen.getByRole('button', { name: /sign in/i });

      await user.type(emailInput, 'user@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(mockOnSuccess).toHaveBeenCalled();
      });
    });

    it('should show error message on login failure', async () => {
      const user = userEvent.setup();
      mockLogin.mockRejectedValue(new Error('Invalid credentials'));

      render(<LoginForm />);

      const emailInput = screen.getByLabelText(/email address/i);
      const passwordInput = screen.getByLabelText(/password/i);
      const submitButton = screen.getByRole('button', { name: /sign in/i });

      await user.type(emailInput, 'user@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
      });
    });
  });

  describe('Loading State', () => {
    it('should show loading state during form submission', async () => {
      const user = userEvent.setup();
      mockLogin.mockImplementation(
        () => new Promise((resolve) => setTimeout(resolve, 100)),
      );

      render(<LoginForm />);

      const emailInput = screen.getByLabelText(/email address/i);
      const passwordInput = screen.getByLabelText(/password/i);
      const submitButton = screen.getByRole('button', { name: /sign in/i });

      await user.type(emailInput, 'user@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      expect(screen.getByRole('button', { name: /signing in/i })).toBeInTheDocument();
    });

    it('should disable inputs during form submission', async () => {
      const user = userEvent.setup();
      mockLogin.mockImplementation(
        () => new Promise((resolve) => setTimeout(resolve, 100)),
      );

      render(<LoginForm />);

      const emailInput = screen.getByLabelText(/email address/i) as HTMLInputElement;
      const passwordInput = screen.getByLabelText(/password/i) as HTMLInputElement;
      const submitButton = screen.getByRole('button', { name: /sign in/i });

      await user.type(emailInput, 'user@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      expect(emailInput.disabled).toBe(true);
      expect(passwordInput.disabled).toBe(true);
      expect(submitButton).toBeDisabled();
    });
  });

  describe('Accessibility', () => {
    it('should not have accessibility violations', async () => {
      const { container } = render(<LoginForm />);
      const results = await axe(container);

      expect(results).toHaveNoViolations();
    });

    it('should have proper labels for form fields', () => {
      render(<LoginForm />);

      const emailInput = screen.getByLabelText(/email address/i);
      const passwordInput = screen.getByLabelText(/password/i);

      expect(emailInput).toHaveAttribute('id', 'email');
      expect(passwordInput).toHaveAttribute('id', 'password');
    });

    it('should display error message with proper ARIA attributes', async () => {
      const user = userEvent.setup();
      render(<LoginForm />);

      const submitButton = screen.getByRole('button', { name: /sign in/i });
      await user.click(submitButton);

      await waitFor(() => {
        const errorAlert = screen.getByRole('alert');
        expect(errorAlert).toBeInTheDocument();
      });
    });

    it('should clear error on input change', async () => {
      const user = userEvent.setup();
      render(<LoginForm />);

      const submitButton = screen.getByRole('button', { name: /sign in/i });
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/email and password are required/i),
        ).toBeInTheDocument();
      });

      const emailInput = screen.getByLabelText(/email address/i);
      await user.type(emailInput, 'user@example.com');

      await waitFor(() => {
        expect(
          screen.queryByText(/email and password are required/i),
        ).not.toBeInTheDocument();
      });
    });
  });
});
```

## Validation Checklist

- [ ] TypeScript strict mode enabled (`strict: true` in tsconfig.json)
- [ ] All component props have interfaces with JSDoc comments
- [ ] All hooks have proper TypeScript typing
- [ ] Responsive design implemented with Tailwind breakpoints
- [ ] Dark mode support using `dark:` prefix in Tailwind classes
- [ ] Accessibility: All form inputs have labels with matching `htmlFor` and `id` attributes
- [ ] Accessibility: Semantic HTML used (button, input, form, label, a)
- [ ] Accessibility: ARIA attributes added where semantic HTML is insufficient (aria-label, aria-describedby, role)
- [ ] Error handling with try/catch blocks for async operations
- [ ] Loading states for async operations
- [ ] No hardcoded strings - all user-facing text is in appropriate places
- [ ] All event handlers use useCallback to prevent unnecessary re-renders
- [ ] Component prop defaults are sensible and documented
- [ ] CSS classes follow Tailwind naming conventions

## Success Criteria

- [ ] Components render correctly in browser
- [ ] All form submissions work as expected
- [ ] Mobile responsive design works on all breakpoints
- [ ] Test coverage reaches minimum 85%
- [ ] Accessibility audit passes (axe or Lighthouse)
- [ ] All TypeScript types are correct (no implicit any)
- [ ] Error messages are user-friendly and helpful
- [ ] Loading states prevent multiple submissions

## Internal Calls

This skill internally calls and wraps:
- **frontend-design** - For high-quality UI/UX design patterns and visual polish
- **react_advanced_skill** - For React 18+ patterns, TypeScript typing, and component best practices

Files in this skill

  • README.md10.2 KB
  • adr_skill.md8.4 KB
  • agent_skill_design_skill.md3.1 KB
  • apache_camel_skill.md15.5 KB
  • apache_pulsar_skill.md17.1 KB
  • ba_create_skill.md18.9 KB
  • backend_skill.md22.1 KB
  • code_documentation_skill.md13.4 KB
  • code_formatting_skill.md11.7 KB
  • code_health_skill.md9.8 KB
  • code_review_skill.md36.7 KB
  • context_builder_skill.md11.7 KB
  • current_tech_spec_skill.md6.3 KB
  • database_skill.md18.4 KB
  • debugging_skill.md3.4 KB
  • error_handling_skill.md18.4 KB
  • frontend_skill.md23.6 KB
  • java_advanced_skill.md15 KB
  • jira_html_report_skill.md15.5 KB
  • jira_incremental_spec_generator_skill.md19.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…