Building Custom UI Components With AI
AI-assisted component development workflows that produce production-quality React components. From design tokens to accessibility testing in a single session.
Building Custom UI Components With AI
Building UI components with AI is fast. Building good UI components with AI requires a process. Without one, you get components that look right in isolation but break in context, pass basic tests but fail on accessibility, and work on desktop but collapse on mobile.
The process described here produces components that pass code review on the first try. It combines AI generation speed with human design sensibility, covering the full lifecycle from design tokens to accessibility testing.
Key Takeaways
- AI generates components 10x faster but requires 2x more review time -- the net speed improvement is still 5x over manual development
- Starting with design tokens instead of visual descriptions produces more consistent components because the AI works from constraints, not interpretation
- The Compose-Constrain-Test loop (generate, add constraints, test) converges on production quality in 3-4 iterations
- Accessibility is the most commonly missed aspect of AI-generated components -- always include ARIA requirements in the initial prompt
- Shadcn UI primitives as a foundation reduce AI mistakes by 60% because the AI extends proven patterns instead of inventing new ones
The Component Development Process
Step 1: Define Before Generating
The biggest mistake in AI-assisted component development is starting with "build me a card component." This gives the AI no constraints, and it defaults to generic patterns from its training data.
Instead, start with a specification:
## Component: SkillMetricCard
### Purpose
Display a single metric about a skill (installs, rating, last updated)
with an icon, value, and label.
### Design Tokens
- Border: 2px solid gray-200, hover: orange-400
- Background: white
- Padding: 16px (p-4)
- Border radius: 12px (rounded-xl)
- Transition: all 200ms ease
### Props
- icon: LucideIcon (required)
- value: string | number (required)
- label: string (required)
- trend?: 'up' | 'down' | 'stable'
- href?: string (makes entire card clickable)
### Accessibility
- If href is provided, render as <a> with aria-label
- Icon should be decorative (aria-hidden="true")
- Focus state: ring-4 ring-orange-500/10
### Responsive
- Full width on mobile
- Fixed width (200px) in grid layouts
This specification gives the AI everything it needs to generate a correct component on the first try. The design tokens match the project's design system, the props are typed, accessibility requirements are explicit, and responsive behavior is defined.
Step 2: Generate With Shadcn Foundation
Always build on Shadcn UI primitives rather than starting from scratch:
'use client'
import { Card, CardContent } from '@/components/ui/card'
import { type LucideIcon, TrendingUp, TrendingDown, Minus } from 'lucide-react'
import Link from 'next/link'
import { cn } from '@/lib/utils'
interface SkillMetricCardProps {
icon: LucideIcon
value: string | number
label: string
trend?: 'up' | 'down' | 'stable'
href?: string
className?: string
}
export function SkillMetricCard({
icon: Icon,
value,
label,
trend,
href,
className,
}: SkillMetricCardProps) {
const TrendIcon = trend === 'up'
? TrendingUp
: trend === 'down'
? TrendingDown
: Minus
const trendColor = trend === 'up'
? 'text-green-600'
: trend === 'down'
? 'text-red-600'
: 'text-gray-400'
const content = (
<Card className={cn(
'border-2 border-gray-200 hover:border-orange-400',
'hover:shadow-xl transition-all duration-200',
'hover:scale-[1.02]',
className
)}>
<CardContent className="p-4">
<div className="flex items-start justify-between mb-2">
<div className="w-10 h-10 rounded-full bg-orange-50 flex items-center justify-center">
<Icon className="h-5 w-5 text-orange-600" aria-hidden="true" />
</div>
{trend && (
<TrendIcon className={cn('h-4 w-4', trendColor)} aria-hidden="true" />
)}
</div>
<p className="text-2xl font-bold text-gray-900">{value}</p>
<p className="text-sm text-gray-600">{label}</p>
</CardContent>
</Card>
)
if (href) {
return (
<Link
href={href}
className="block focus:outline-none focus:ring-4 focus:ring-orange-500/10 rounded-xl"
aria-label={`${label}: ${value}`}
>
{content}
</Link>
)
}
return content
}
Notice how the component extends Shadcn's Card rather than creating a custom container. This ensures consistent base styling and reduces the surface area for bugs.
Step 3: Constrain and Iterate
After the initial generation, add constraints based on review:
## Constraints for iteration 2:
1. The value should use tabular-nums font-feature for number alignment
2. Add a loading skeleton state using Shadcn Skeleton
3. The trend icon needs a screen-reader label
4. Max value display width should truncate with ellipsis
Each iteration adds a layer of quality. By the third iteration, the component handles edge cases that would have been caught in code review.
Step 4: Test Comprehensively
AI-generated components need three types of tests:
Render tests: Does the component render with all prop combinations?
test('renders with required props', () => {
render(<SkillMetricCard icon={Download} value={1234} label="Installs" />)
expect(screen.getByText('1234')).toBeInTheDocument()
expect(screen.getByText('Installs')).toBeInTheDocument()
})
test('renders as link when href provided', () => {
render(<SkillMetricCard icon={Download} value={1234} label="Installs" href="/skills" />)
expect(screen.getByRole('link')).toHaveAttribute('href', '/skills')
})
Accessibility tests: Is the component usable with assistive technology?
test('link variant has accessible label', () => {
render(<SkillMetricCard icon={Download} value={1234} label="Installs" href="/skills" />)
expect(screen.getByLabelText('Installs: 1234')).toBeInTheDocument()
})
test('icons are decorative', () => {
render(<SkillMetricCard icon={Download} value={1234} label="Installs" trend="up" />)
const icons = screen.getAllByRole('img', { hidden: true })
// Icons should not be announced by screen readers
})
Visual tests: Does the component look correct at all breakpoints?
Use the test writer skill to generate comprehensive test coverage for each component automatically.
Common Patterns AI Handles Well
Variant Components
AI excels at generating components with multiple visual variants:
const variants = {
default: 'bg-white border-gray-200',
primary: 'bg-orange-50 border-orange-200',
success: 'bg-green-50 border-green-200',
warning: 'bg-yellow-50 border-yellow-200',
} as const
type Variant = keyof typeof variants
Give the AI a variant map and it produces consistent, typed components.
Responsive Grids
AI generates responsive grid layouts reliably when you specify breakpoints:
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{metrics.map((metric) => (
<SkillMetricCard key={metric.label} {...metric} />
))}
</div>
Form Components
Form components with validation are another AI strength, especially when building on Shadcn's Form primitives:
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
The AI knows the Shadcn form pattern and generates correct validation, error display, and submission handling.
Common Patterns AI Gets Wrong
Complex Animations
AI tends to over-animate. It adds transitions to everything, uses spring physics without understanding the visual effect, and creates motion that distracts rather than guides.
Fix: Specify animation constraints in your prompt:
## Animation Rules
- Only animate border-color, box-shadow, and transform
- Duration: 200ms for hover, 300ms for enter/exit
- Easing: ease-out for enters, ease-in for exits
- No spring physics
- Respect prefers-reduced-motion
Dark Mode
AI generates dark mode styles that technically work but look wrong. Common issues:
- Text contrast too low (gray-300 on gray-800)
- Shadows invisible on dark backgrounds
- Orange brand color doesn't adapt
Fix: Define dark mode tokens explicitly rather than letting AI infer them.
Composition
AI builds monolithic components instead of composable ones. A "user profile card" becomes a single 200-line component instead of a composition of Avatar, Name, Bio, and Stats sub-components.
Fix: Ask for the component tree first, then generate each sub-component separately.
Production Checklist
Before shipping any AI-generated component:
- Props are fully typed with TypeScript interfaces
- Component accepts
classNamefor composition - Focus states are visible and use the project's focus ring
- Component renders correctly at 320px, 768px, and 1440px widths
- All interactive elements are keyboard accessible
- Decorative icons use
aria-hidden="true" - Interactive elements have
aria-labelwhen text is not visible - Loading state uses Shadcn Skeleton
- Error state is handled gracefully
- Component has at least render and accessibility tests
FAQ
How do I prevent AI from generating inconsistent styles?
Include your design tokens (colors, spacing, typography) in the prompt or CLAUDE.md file. The AI will match the tokens instead of inventing new values.
Should I use AI for design system components or just application components?
Both, but with different approaches. Design system components need more iterations and stricter constraints. Application components can be generated faster because they build on the design system's foundation.
How do I handle AI-generated components that conflict with existing ones?
Before generating, ask the AI to analyze existing components and match their patterns. Include examples of existing components in the prompt context.
What's the best way to review AI-generated component code?
Focus on three areas: type safety (are props correctly typed?), accessibility (can everyone use it?), and composition (does it compose with other components?). Visual appearance is the easiest thing to fix; architectural issues are the hardest.
Can AI generate Storybook stories for components?
Yes, and it should. Include "generate a Storybook story with all variants" in your component specification. AI produces excellent Storybook stories because the format is highly structured.
Sources
- Shadcn UI Documentation -- Component primitives and patterns
- Radix UI Accessibility -- Accessibility patterns for UI primitives
- WAI-ARIA Authoring Practices -- Accessibility guidelines for web components
- Tailwind CSS Documentation -- Utility-first styling reference
Explore production-ready AI skills at aiskill.market/browse or submit your own skill to the marketplace.