AgentStack
SKILL verified MIT Self-run

React Component Generator

skill-nembie-claude-code-skills-react-component-generator · by Nembie

Generate modern React components with TypeScript, hooks, and accessibility. Use when asked to create React components, UI components, scaffold frontend elements, or build reusable UI.

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-nembie-claude-code-skills-react-component-generator

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of React Component Generator? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

React Component Generator

Before generating any output, read config/defaults.md and adapt all patterns, imports, and code examples to the user's configured stack.

Generation Process

  1. Determine component type and requirements
  2. Generate TypeScript props interface
  3. Create functional component with hooks
  4. Add accessibility attributes
  5. Include keyboard navigation where applicable

Component Types

Data Display Component

interface DataTableProps {
  data: T[];
  columns: ColumnDef[];
  onRowClick?: (item: T) => void;
  loading?: boolean;
  emptyMessage?: string;
}

function DataTable({
  data,
  columns,
  onRowClick,
  loading = false,
  emptyMessage = 'No data available',
}: DataTableProps) {
  if (loading) {
    return Loading...;
  }

  if (data.length === 0) {
    return {emptyMessage};
  }

  return (
    
      
        
          {columns.map((col) => (
            {col.header}
          ))}
        
      
      
        {data.map((item, index) => (
           onRowClick?.(item)}
            onKeyDown={(e) => e.key === 'Enter' && onRowClick?.(item)}
            tabIndex={onRowClick ? 0 : undefined}
            role={onRowClick ? 'button' : undefined}
            aria-rowindex={index + 1}
          >
            {columns.map((col) => (
              {col.render(item)}
            ))}
          
        ))}
      
    
  );
}

Form Component

interface FormFieldProps {
  label: string;
  name: string;
  type?: 'text' | 'email' | 'password' | 'number';
  value: string;
  onChange: (value: string) => void;
  error?: string;
  required?: boolean;
  disabled?: boolean;
  placeholder?: string;
}

function FormField({
  label,
  name,
  type = 'text',
  value,
  onChange,
  error,
  required = false,
  disabled = false,
  placeholder,
}: FormFieldProps) {
  const id = `field-${name}`;
  const errorId = `${id}-error`;

  return (
    
      
        {label}
        {required &&  *}
      
       onChange(e.target.value)}
        disabled={disabled}
        placeholder={placeholder}
        required={required}
        aria-required={required}
        aria-invalid={!!error}
        aria-describedby={error ? errorId : undefined}
      />
      {error && (
        
          {error}
        
      )}
    
  );
}

Modal/Dialog Component

interface ModalProps {
  open: boolean;
  onClose: () => void;
  title: string;
  children: React.ReactNode;
  actions?: React.ReactNode;
}

function Modal({ open, onClose, title, children, actions }: ModalProps) {
  const modalRef = useRef(null);
  const previousFocus = useRef(null);

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

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && open) {
        onClose();
      }
    };
    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [open, onClose]);

  if (!open) return null;

  return (
    
       e.stopPropagation()}
        tabIndex={-1}
      >
        
          {title}
          
            ×
          
        
        {children}
        {actions && {actions}}
      
    
  );
}

Layout Component

interface PageLayoutProps {
  children: React.ReactNode;
  sidebar?: React.ReactNode;
  header?: React.ReactNode;
  footer?: React.ReactNode;
}

function PageLayout({ children, sidebar, header, footer }: PageLayoutProps) {
  return (
    
      {header && {header}}
      
        {sidebar && {sidebar}}
        {children}
      
      {footer && {footer}}
    
  );
}

Button Component

interface ButtonProps extends React.ButtonHTMLAttributes {
  variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
}

const Button = forwardRef(
  (
    {
      variant = 'primary',
      size = 'md',
      loading = false,
      leftIcon,
      rightIcon,
      disabled,
      children,
      className,
      ...props
    },
    ref
  ) => {
    return (
      
        {loading ? (
          
        ) : (
          leftIcon
        )}
        {children}
        {!loading && rightIcon}
      
    );
  }
);

Button.displayName = 'Button';

Composable Patterns

Compound Components

interface SelectContextValue {
  value: string;
  onChange: (value: string) => void;
}

const SelectContext = createContext(null);

function Select({ value, onChange, children }: {
  value: string;
  onChange: (value: string) => void;
  children: React.ReactNode;
}) {
  return (
    
      {children}
    
  );
}

function Option({ value, children }: { value: string; children: React.ReactNode }) {
  const ctx = useContext(SelectContext);
  if (!ctx) throw new Error('Option must be used within Select');

  const selected = ctx.value === value;

  return (
     ctx.onChange(value)}
      onKeyDown={(e) => e.key === 'Enter' && ctx.onChange(value)}
      tabIndex={0}
    >
      {children}
    
  );
}

Select.Option = Option;

Render Props

interface ToggleRenderProps {
  on: boolean;
  toggle: () => void;
  setOn: () => void;
  setOff: () => void;
}

interface ToggleProps {
  initialOn?: boolean;
  children: (props: ToggleRenderProps) => React.ReactNode;
}

function Toggle({ initialOn = false, children }: ToggleProps) {
  const [on, setOn] = useState(initialOn);

  const renderProps: ToggleRenderProps = {
    on,
    toggle: () => setOn((prev) => !prev),
    setOn: () => setOn(true),
    setOff: () => setOn(false),
  };

  return <>{children(renderProps)};
}

Accessibility Checklist

  • [ ] Semantic HTML elements (button, nav, main, etc.)
  • [ ] ARIA roles where semantic HTML insufficient
  • [ ] aria-label or aria-labelledby for non-text content
  • [ ] aria-describedby for error messages
  • [ ] Focus management (modals, dynamic content)
  • [ ] Keyboard navigation (Enter, Escape, Arrow keys)
  • [ ] Visible focus indicators
  • [ ] Color contrast (don't rely on color alone)
  • [ ] Loading states announced (aria-busy, role="status")
  • [ ] Error states announced (role="alert")

Completeness Check

After generating a component, verify completeness: does it export a TypeScript props interface? Does it handle the loading/error/empty states if it fetches data? Do interactive elements have keyboard handlers and aria attributes? If any check fails, fix the component before delivering.

Asset

See assets/component-template/Component.tsx for a minimal starter template.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.