| name | react-component-generator |
| description | Generates React components with TypeScript props types, hooks, accessibility
attributes, and basic test stubs. Use this skill whenever a user says "create
a React component for...", "write a React component that...", "build a [name]
component in React", "generate a reusable React component", "scaffold a React
hook for...", or "add a component to my React app". Also activate when someone
describes UI behavior and wants it as a React component. Produces modern
functional components with hooks; does not generate class components. Works
with React 18+, TypeScript, and either CSS Modules, Tailwind, or styled-components.
Do NOT use for full-page layout scaffolding (use css-layout-helper) or
state management architecture.
|
React Component Generator
Generate clean, typed, accessible React components with props interfaces,
hooks patterns, and test stubs ready to drop into a codebase.
When to Use
- Building a new UI component from a description or design spec
- Creating a reusable component to standardize across a codebase
- Scaffolding a custom hook that encapsulates shared stateful logic
- Adding a compound component (e.g., Tabs with Tab, TabList, TabPanel)
When NOT to Use
- Full-page layout or grid systems (use
css-layout-helper)
- State management patterns (Redux, Zustand, Context architecture)
- Server Components (React Server Components have different constraints)
- Non-React UI frameworks (Vue, Svelte, Angular)
Workflow
Step 1 — Understand the Component
Ask for:
- Component name and purpose: What does it do?
- Props: What data does it accept? Any callbacks?
- State: Does it manage its own state? What interactions does it handle?
- Styling approach: Tailwind / CSS Modules / styled-components / plain CSS
- Tests: Jest + React Testing Library stubs needed?
Step 2 — Define the Props Interface
Start with a tight TypeScript interface:
export interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
'aria-label'?: string;
className?: string;
}
Step 3 — Generate the Component
Standard functional component (Tailwind):
import { forwardRef } from 'react';
import { clsx } from 'clsx';
const VARIANT_CLASSES = {
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-400',
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
} as const;
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ label, onClick, variant = 'primary', disabled = false, className, ...props }, ref) => {
return (
<button
ref={ref}
type="button"
onClick={onClick}
disabled={disabled}
className={clsx(
'inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium',
'focus:outline-none focus:ring-2 focus:ring-offset-2',
'disabled:cursor-not-allowed disabled:opacity-50',
VARIANT_CLASSES[variant],
className
)}
{...props}
>
{label}
</button>
);
}
);
Button.displayName = 'Button';
Component with local state:
import { useState, useId } from 'react';
export interface CollapsibleProps {
title: string;
children: React.ReactNode;
defaultOpen?: boolean;
}
export function Collapsible({ title, children, defaultOpen = false }: CollapsibleProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
const contentId = useId();
return (
<div>
<button
type="button"
aria-expanded={isOpen}
aria-controls={contentId}
onClick={() => setIsOpen(prev => !prev)}
className="flex w-full items-center justify-between"
>
<span>{title}</span>
<span aria-hidden="true">{isOpen ? '−' : '+'}</span>
</button>
<div id={contentId} hidden={!isOpen} role="region">
{children}
</div>
</div>
);
}
Custom hook extraction (when logic is complex):
import { useState, useCallback } from 'react';
export function useToggle(initialState = false) {
const [state, setState] = useState(initialState);
const toggle = useCallback(() => setState(s => !s), []);
const setOn = useCallback(() => setState(true), []);
const setOff = useCallback(() => setState(false), []);
return { state, toggle, setOn, setOff } as const;
}
Step 4 — Add the Barrel Export
export { Button } from './Button';
export type { ButtonProps } from './Button';
Step 5 — Generate a Test Stub
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from './Button';
describe('Button', () => {
it('renders with the given label', () => {
render(<Button label="Click me" onClick={() => {}} />);
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
});
it('calls onClick when clicked', async () => {
const onClick = jest.fn();
render(<Button label="Click me" onClick={onClick} />);
await userEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
it('is disabled when disabled prop is true', () => {
render(<Button label="Click me" onClick={() => {}} disabled />);
expect(screen.getByRole('button')).toBeDisabled();
});
});
Accessibility Defaults
Every component should include by default:
- Correct semantic HTML element (
button not div, etc.)
aria- attributes for interactive state (aria-expanded, aria-controls, aria-label)
focus:ring or equivalent visible focus indicator
- Keyboard interaction support matching the WAI-ARIA pattern
Output Format
- Props interface — TypeScript interface
- Component file — complete, ready-to-use
- Barrel index —
index.ts export
- Test stub — RTL test file with 3 baseline tests
- Usage example — a quick code snippet showing how to use the component
Safety & Confirmation
- Use
forwardRef for components that consumers might need to access the DOM node.
- Never use
any as a prop type — use unknown or a specific type.
- Always check: Does this component need
key management if rendered in a list?
- Warn if a component does direct DOM manipulation (ref misuse) vs. declarative React.