react-component Skill
Create accessible, well-typed React components following best practices.
Overview#
The react-component skill generates production-ready React components with TypeScript, accessibility features, and optional testing.
Usage#
Use the react-component skill to create a Button component with variants and sizes.
Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Component name (PascalCase) |
props | array | No | Component props |
variants | array | No | Component variants |
includeTests | boolean | No | Generate test file |
styling | string | No | Styling approach: tailwind, css-modules, styled |
Generated Output#
Basic Component#
Use the react-component skill with:
- name: "Button"
- props: ["children", "onClick", "disabled"]
- styling: "tailwind"
Result:
1// components/Button.tsx
2import { forwardRef, type ButtonHTMLAttributes } from 'react';
3import { cn } from '@/lib/utils';
4
5export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
6 /** Button content */
7 children: React.ReactNode;
8 /** Click handler */
9 onClick?: () => void;
10 /** Disabled state */
11 disabled?: boolean;
12}
13
14export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
15 ({ children, onClick, disabled, className, ...props }, ref) => {
16 return (
17 <button
18 ref={ref}
19 type="button"
20 onClick={onClick}
21 disabled={disabled}
22 className={cn(
23 'inline-flex items-center justify-center rounded-lg px-4 py-2',
24 'text-sm font-medium transition-colors',
25 'focus:outline-none focus:ring-2 focus:ring-offset-2',
26 'disabled:opacity-50 disabled:pointer-events-none',
27 className
28 )}
29 {...props}
30 >
31 {children}
32 </button>
33 );
34 }
35);
36
37Button.displayName = 'Button';Component with Variants#
Use the react-component skill with:
- name: "Button"
- variants: ["primary", "secondary", "ghost", "danger"]
- includeTests: true
Result:
1// components/Button.tsx
2import { forwardRef, type ButtonHTMLAttributes } from 'react';
3import { cva, type VariantProps } from 'class-variance-authority';
4import { cn } from '@/lib/utils';
5
6const buttonVariants = cva(
7 'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',
8 {
9 variants: {
10 variant: {
11 primary: 'bg-brand-600 text-white hover:bg-brand-700 focus:ring-brand-500',
12 secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500',
13 ghost: 'bg-transparent hover:bg-gray-100 focus:ring-gray-500',
14 danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
15 },
16 size: {
17 sm: 'h-8 px-3 text-xs',
18 md: 'h-10 px-4 text-sm',
19 lg: 'h-12 px-6 text-base',
20 },
21 },
22 defaultVariants: {
23 variant: 'primary',
24 size: 'md',
25 },
26 }
27);
28
29export interface ButtonProps
30 extends ButtonHTMLAttributes<HTMLButtonElement>,
31 VariantProps<typeof buttonVariants> {
32 /** Loading state */
33 loading?: boolean;
34}
35
36export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
37 ({ className, variant, size, loading, disabled, children, ...props }, ref) => {
38 return (
39 <button
40 ref={ref}
41 className={cn(buttonVariants({ variant, size }), className)}
42 disabled={disabled || loading}
43 {...props}
44 >
45 {loading && (
46 <svg
47 className="mr-2 h-4 w-4 animate-spin"
48 fill="none"
49 viewBox="0 0 24 24"
50 >
51 <circle
52 className="opacity-25"
53 cx="12"
54 cy="12"
55 r="10"
56 stroke="currentColor"
57 strokeWidth="4"
58 />
59 <path
60 className="opacity-75"
61 fill="currentColor"
62 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"
63 />
64 </svg>
65 )}
66 {children}
67 </button>
68 );
69 }
70);
71
72Button.displayName = 'Button';Test file:
1// components/Button.test.tsx
2import { render, screen, fireEvent } from '@testing-library/react';
3import { Button } from './Button';
4
5describe('Button', () => {
6 it('renders children', () => {
7 render(<Button>Click me</Button>);
8 expect(screen.getByText('Click me')).toBeInTheDocument();
9 });
10
11 it('calls onClick when clicked', () => {
12 const handleClick = vi.fn();
13 render(<Button onClick={handleClick}>Click me</Button>);
14 fireEvent.click(screen.getByRole('button'));
15 expect(handleClick).toHaveBeenCalledTimes(1);
16 });
17
18 it('is disabled when disabled prop is true', () => {
19 render(<Button disabled>Click me</Button>);
20 expect(screen.getByRole('button')).toBeDisabled();
21 });
22
23 it('shows loading state', () => {
24 render(<Button loading>Click me</Button>);
25 expect(screen.getByRole('button')).toBeDisabled();
26 });
27
28 it('applies variant classes', () => {
29 render(<Button variant="danger">Click me</Button>);
30 expect(screen.getByRole('button')).toHaveClass('bg-red-600');
31 });
32});Features Included#
- TypeScript types
- forwardRef for ref forwarding
- Accessibility (button type, focus styles)
- CVA for variants (when applicable)
- Loading state
- Disabled state
- Test file (optional)
Customization#
1// bootspring.config.js
2module.exports = {
3 skills: {
4 preferred: {
5 components: 'functional',
6 styling: 'tailwind',
7 },
8 templates: {
9 'react-component': './templates/component.tsx',
10 },
11 },
12};Related Skills#
- test-suite - Component testing
- validation - Form validation