Concept

Structuring React Components for Reliable AI Generation

Learn how to structure React components for AI code generation—clear props, semantic tokens, predictable patterns, and machine-readable documentation.

FramingUI Team6 min read

AI coding tools generate buttons with the wrong padding, colors that are close but not quite your brand, spacing that follows no pattern. The fix isn't a better prompt—it's a better component library.

Most component libraries are built for human developers. They assume readers who can open Storybook, follow documentation links, and infer conventions from context. AI tools read TypeScript types and code structure. What's obvious to a developer who knows your system is invisible to a model that hasn't seen it. Here's what to change.

Semantic Tokens Instead of Hardcoded Values

This is the single highest-leverage change you can make. When an AI reads a component that uses inline pixel values and hex codes, it extracts those numbers and reuses them—approximately. The next time it generates a button, it will use 14px 28px because it saw 12px 24px and approximated. Colors shift. Spacing drifts. Over many generations, the codebase diverges from the design system.

The fix is to give tokens names and put those names in the code:

// tokens.ts
export const tokens = {
  spacing: {
    sm: '8px',
    md: '12px',
    lg: '24px',
    xl: '32px',
  },
  color: {
    primary: { solid: '#3b82f6', hover: '#2563eb' },
    neutral: { solid: '#374151', muted: '#6b7280' },
  },
  radius: { sm: '4px', md: '8px', lg: '12px' },
};
<button
  style={{
    padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
    background: tokens.color.primary.solid,
    borderRadius: tokens.radius.md,
  }}
>
  {children}
</button>

Now the AI reads the token names. It learns that buttons use tokens.spacing.md for vertical padding and tokens.spacing.lg for horizontal. When it generates a new button, it imports the same token file and uses the same names. The generated code stays in system.

Explicit TypeScript Variants

Open-ended string props let AI guess, and guessing means inconsistency. A variant: string prop could accept "primary", "solid", "filled", "blue", or "default". The AI picks whichever seems most plausible based on patterns from other codebases.

String literal types close the gap:

type ButtonProps = {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

When AI reads this type definition, it knows exactly which values are valid. Generated code will use variant="primary" or variant="ghost", not variant="solid" or variant="outline". This applies to every prop that has a finite set of valid values—never reach for string when you can enumerate.

JSDoc as Machine-Readable Documentation

Storybook is for human readers. AI tools read source code. JSDoc comments placed directly on exported components travel with the code and get picked up by every tool that reads TypeScript:

/**
 * Button for primary actions.
 *
 * @param variant - Visual style: `primary` (default), `secondary`, `ghost`
 * @param size - Dimensions: `sm`, `md` (default), `lg`
 * @param children - Button label text
 *
 * @example
 * <Button variant="primary" size="lg">Save Changes</Button>
 */
export const Button = ({
  variant = 'primary',
  size = 'md',
  children,
  ...props
}: ButtonProps) => { ... };

The @example tag is especially useful. When the AI has seen a working example of how to use the component, generated code follows the pattern rather than inferring it.

Composition Over Configuration

Props like title, subtitle, footer, and padding="large" are ambiguous. Is the content area prop called body or content? Does footer take a ReactNode or a string? When a component accepts many configuration props, the AI has to guess prop names and nesting.

Composable sub-components eliminate that ambiguity:

// Configuration-heavy (hard for AI)
<Card
  title="User Profile"
  footer={<Button>Save</Button>}
  padding="large"
/>

// Compositional (easy for AI)
<Card>
  <CardHeader>
    <CardTitle>User Profile</CardTitle>
  </CardHeader>
  <CardContent>
    {/* form fields */}
  </CardContent>
  <CardFooter>
    <Button>Save</Button>
  </CardFooter>
</Card>

The compositional API is self-documenting. The component tree mirrors the visual structure. AI tools that have seen one card composed this way will replicate the pattern across all cards, regardless of content.

Consistent Prop Naming Across Components

When TextInput uses val and NumberInput uses value, the AI has to learn two conventions. When TextInput uses change and Checkbox uses onCheck, generated forms will mix conventions randomly.

Pick a convention and enforce it everywhere:

Component typeValue propChange handler
Text, Number inputsvalueonValueChange
Checkboxes, SwitchescheckedonCheckedChange
Selects, Radio groupsvalueonValueChange

All label props should be label, not text, caption, or title. The uniformity means the AI's mental model of your library generalizes from one component to all others.

A Single Entry Point

AI tools don't explore directory trees. When a model needs to import a component, it either knows the import path or falls back to generic components. A single barrel export solves this:

// src/design-system/index.ts
export { Button } from './components/Button';
export { TextField } from './components/TextField';
export { Card, CardHeader, CardTitle, CardContent, CardFooter } from './components/Card';
export { Field } from './components/Field';
export { tokens } from './tokens';

Add one line to your README or CLAUDE.md:

Import all components from `@/design-system`. Never import from subpaths.

Claude, Cursor, and Copilot read project documentation. This instruction gets followed.

Testing the Library Against AI

After making these changes, run a real test. Prompt the AI with: "Create a user registration form with email, password, confirm password, and a submit button. Use our design system."

Check the output against four criteria:

  • Does it import from the correct path?
  • Does it use token references, not hardcoded values?
  • Are variant and size props spelled correctly?
  • Does it follow the <Field> wrapper pattern for form inputs?

If any check fails, trace the failure back to the library structure. Missing or ambiguous JSDoc is the most common cause. The token file not being visible in context is the second most common.

Every failure is a structural signal. Fix the library, not the prompt.

A component library built this way gives AI tools the same affordances it gives human developers: clear contracts, explicit conventions, and a single source of truth. Generated code inherits your system's quality by default.

Ready to build with FramingUI?

Build consistent UI with AI-ready design tokens. No more hallucinated colors or spacing.

Try FramingUI
Share

Related Posts