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#

ParameterTypeRequiredDescription
namestringYesComponent name (PascalCase)
propsarrayNoComponent props
variantsarrayNoComponent variants
includeTestsbooleanNoGenerate test file
stylingstringNoStyling 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};