v0 Component Generation Tutorial: Prompts and Patterns
The v0 component generation workflow that produces consistent results: set up your shadcn/ui theme before generating anything, write layout-explicit prompts naming specific shadcn components, use follow-up prompts to refine rather than restart, and always read the generated code before exporting. This tutorial covers those patterns by component type — navigation, data display, forms, and feedback states.
I was building a user management dashboard. Needed a data table — columns for name, email, role badge, last-active timestamp, and a row actions dropdown. Wrote out the description in one v0 prompt: column names, badge variants per role, shadcn DropdownMenu for the actions. The generated component had the right column structure, a TypeScript interface for a User type matching what I’d described, and properly-nested Radix UI menu items. All of it. I pressed Copy, then read the component again because the first reaction to a tool producing exactly what you expected is a moment of quiet suspicion.
That’s the pattern that makes v0 component generation useful in practice — not dramatic, just specific. The output quality correlates directly with how precisely you described the component. Vague prompts produce generic output. Specific prompts produce things close to what you’d write yourself.
This tutorial covers v0 component generation by category: navigation, data display, forms, and feedback states. For each type, you’ll find prompts that work, what to expect from the output, and what cleanup is required after export. If you’re new to v0 entirely, start with the v0 by Vercel tutorial for beginners — this post assumes you have v0 running and want to generate specific component types reliably.
Before You Generate: Set Up Your Design System
The most common source of extra work with v0 isn’t a bad prompt. It’s generating components before your Tailwind configuration and shadcn/ui theme are properly set up.
Every component v0 generates uses shadcn/ui semantic color tokens — bg-background, text-foreground, border-border, text-muted-foreground. If your project’s CSS variables are configured correctly, those tokens resolve to your actual brand colors automatically. If they’re not, every generated component will use default shadcn values. You’ll fix them after the fact, one component at a time.
Run this once before you generate your first component:
npx shadcn@latest init
That command creates the CSS variable structure in your globals.css, sets up the components/ui/ directory, and configures Tailwind. After that, update the variables for your brand palette. The shadcn themes page provides the full variable set for multiple preset themes if you want a starting point.
| Setup item | What breaks if skipped |
|---|---|
| shadcn/ui initialized | Component imports fail; CLI export errors |
| CSS variables set in globals.css | Components use default light/dark values instead of your brand |
| Tailwind config extended | Custom utility classes in prompts produce no output |
| Font configured in layout | v0 output uses default font family, needs manual update per component |
--radius variable set |
Border radius inconsistent between your existing UI and v0 components |
The setup takes 15 minutes. Retroactive theming across 20 components takes considerably longer. Set it up first.
Navigation Components
Navigation is where most frontend projects start and where v0 component generation saves the most time upfront. A complete top navigation — logo, links, responsive collapse, mobile drawer — takes 45 minutes to write cleanly from scratch. v0 generates a solid structural starting point in one prompt.
Top navigation bar
Prompt: “Sticky top navigation bar: logo text placeholder on the left, 4 links in the center using shadcn NavigationMenu, and a CTA button on the right (shadcn Button default variant). Mobile: hamburger icon (Lucide Menu) that opens a shadcn Sheet from the left containing the same 4 links.”
Sidebar navigation
Prompt: “Fixed left sidebar 240px wide: app name at the top, 6 navigation links each with a Lucide icon (provide specific icon names), active link highlighted with bg-accent and font-medium. Collapse toggle button at the bottom using a Lucide ChevronLeft icon. On collapse: sidebar narrows to 60px and shows only icons.”
Breadcrumbs
Prompt: “Breadcrumb navigation using shadcn Breadcrumb: three levels — Home / Section / Current Page — with a ChevronRight separator. Current page not linked, previous levels use shadcn BreadcrumbLink.”
| Component | Prompt detail level | Typical cleanup time |
|---|---|---|
| Top navigation bar | High — specify every section | 10–15 min |
| Sidebar with links | High — name icons and states | 10–15 min |
| Mobile nav drawer | Medium | 10 min |
| Breadcrumbs | Low — simple pattern | 5 min |
| Tab navigation | Medium | 8–10 min |
What to adjust after generation: Replace placeholder link text with actual routes. Wire the mobile menu open/close state to real useState calls — v0 generates the Sheet component with an open state variable but leaves it uncontrolled. If your project uses Next.js App Router, update the active state detection to use usePathname() from next/navigation rather than the hardcoded placeholder value v0 inserts.
Data Display Components
Data display is v0’s most reliable territory. Data tables, stat cards, and list views have consistent structural patterns that v0 handles well. This is also where the “it matched exactly what I had in mind” moment tends to happen — because data layouts are specific enough to describe precisely and common enough that v0 has strong pattern recognition for them.
Data tables
Prompt: “Data table with five columns: full name, email, role (shadcn Badge — Admin=indigo, Member=zinc, Viewer=slate), last active date, and an actions column with a shadcn DropdownMenu containing Edit and Remove items. Include a search input above the table using shadcn Input. TypeScript interface for a User type with id, name, email, role, and lastActive fields.”
The TypeScript interface request is important. Without it, v0 generates a generic any[] type for the data prop. With it, the generated interface will closely match what you described — you’ll still need to align it with your actual data model, but the field names and types give you a starting point rather than a blank prop type.
Stats cards
Prompt: “Four stat cards in a responsive grid (2 columns on mobile, 4 on desktop): each card shows a label, a large formatted number value, a percentage change (positive in emerald, negative in red), and a Lucide icon (TrendingUp, Users, DollarSign, Activity). Use shadcn Card for each.”
List views
Prompt: “User list view: each item shows an avatar circle with initials (bg-muted, text-muted-foreground), full name, email beneath in a smaller weight, role badge on the right. Items separated by a subtle border. Each row is a clickable div with hover:bg-muted/50.”
onSort callback and page change handlers are empty functions. Wire those to your data fetching layer — whether that’s React Query, SWR, or a server action — after export.
Form and Input Components
Forms are where v0 component generation saves the most repetitive structural work. Field-label-error-submit is a pattern that repeats across every application, and v0 produces it reliably. The business logic — validation schema, API calls, error handling — is always your job after export.
Contact and general-purpose forms
Prompt: “Contact form: name field (shadcn Input with label), email field with type=’email’, message field (shadcn Textarea, rows=5), and a full-width submit button (shadcn Button). Show inline error text in text-destructive below each field when validation fails. Form is contained in a shadcn Card.”
Settings forms
Prompt: “Account settings page: three sections — Profile (avatar placeholder, name input, email input, bio textarea), Notifications (four shadcn Switch toggles with labels and descriptions), Danger Zone (delete account button, destructive variant, with a confirmation warning text above it). Each section in a separate shadcn Card with a title and a subtext description.”
Search and filter bars
Prompt: “Search and filter bar: search input (shadcn Input with Lucide Search icon inside, left-aligned), a Status filter (shadcn Select — All / Active / Inactive), a Date Range filter (shadcn Popover with a Calendar inside), and a Reset button (shadcn Button variant ghost). All items in a flex row, stack to column on mobile.”
| Form type | Cleanup focus areas |
|---|---|
| Contact / general form | Add react-hook-form, zod schema, API call on submit |
| Settings form | Wire Switch state to user preferences, add save mutation |
| Search / filter bar | Connect input state to URL params or query variables |
| Multi-step form | Implement step state machine, validate each step on advance |
| File upload form | Replace placeholder with actual file input handler and upload logic |
A useful follow-up prompt once you have a form generated: “Add react-hook-form with a zod schema. Required fields should show an error message below them when the form is submitted empty.” v0 handles this follow-up well and adds the schema structure and useForm wiring — you’ll still need to npm install react-hook-form zod @hookform/resolvers, but the integration code saves 20 minutes.
Feedback and State Components
Empty states, loading skeletons, and error messages are the components that get built last on most projects and designed least carefully. v0 generates them quickly and consistently — and having a consistent set across your application makes a noticeable difference to how polished it feels.
Empty states
Prompt: “Empty state: centered layout with padding, Lucide Inbox icon (size-12, text-muted-foreground), h3 heading ‘No items found’, descriptive subtext ‘Try adjusting your filters or add a new item’, and a primary CTA button (shadcn Button). Full-width container.”
Loading skeletons
Prompt: “Loading skeleton for a user list with 6 rows. Each row: circular avatar skeleton (shadcn Skeleton, size-10), a wide skeleton for the name, a medium skeleton for the email below it, and a narrow skeleton for a badge. Use shadcn Skeleton component throughout.”
Error states
Prompt: “Error message component: centered container, Lucide AlertCircle icon (size-10, text-destructive), heading ‘Something went wrong’, short description text, and a ‘Try again’ button (shadcn Button variant outline). Can be used as a full-page error or within a card.”
Toast notifications
Prompt: “Toast notification setup using shadcn Sonner: show success toast on form submit (green, ‘Changes saved’), error toast on API failure (red, ‘Something went wrong — please try again’), and info toast for background operations (neutral, ‘Syncing your data…’). Include the Toaster component placement in the layout.”
| Component | Avg. prompts needed | Cleanup time |
|---|---|---|
| Empty state | 1 | 5 min |
| Loading skeleton | 1–2 | 5–8 min |
| Error message | 1 | 5 min |
| Toast system | 1 | 10 min |
| Confirmation dialog | 1 | 8–10 min |
Feedback components are where v0’s return is highest per prompt. They’re structurally simple, the patterns are standard, and they require minimal cleanup. If you’re building a new application, generate the full feedback component set in one session — empty, loading, error, toast — before you build anything else. Having them done means you can use them immediately as you build the real features.
Configure Before You Generate
There’s a specific mistake worth naming directly: generating components before your design system is configured. Every codebase I’ve worked on where this happened resulted in the same cleanup pattern — going through each generated component and replacing hardcoded Tailwind values with the project’s actual semantic tokens.
The correct order is: npx shadcn@latest init, set your CSS variables, configure your Tailwind theme — then generate. Not the other way around. This is the same principle as setting up linting and formatting before writing any application code. You will not regret doing it first. You will reliably regret skipping it.
The specific problem with generating first: v0 uses shadcn’s default theme values when it can’t infer yours. You get bg-background tokens in some places and hardcoded bg-white in others, depending on how v0 interpreted the prompt. Cleaning that up across 15 components takes a full afternoon. Configuring the design system first takes 15 minutes.
A related point about component scope: generate one component type at a time, export it, review it, and integrate it before moving to the next. Generating 10 components in a batch and reviewing them all at the end is how inconsistencies accumulate. The per-component review catches issues — a mismatched spacing value, a TypeScript interface that doesn’t match your data model — while the context is still fresh.
When v0 Component Generation Is the Wrong Choice
The component types above are where v0 adds clear value. These are the situations where it doesn’t.
The component is tightly coupled to your existing data model. v0 generates TypeScript interfaces from your prompt description. If the component needs to consume types already defined in your codebase — a Project type with 15 fields, complex nested relationships, discriminated unions — the generated interface will diverge from your actual types. You’ll spend more time reconciling the two than writing the component natively. Use Cursor instead; it understands your existing type definitions.
The component has significant business logic woven into its structure. A pricing table that needs to check feature flags, display differently per subscription tier, and update in real time based on a billing API — that’s not a layout problem. v0 generates layouts. Business logic is yours to write, and when the logic is fundamental to the component’s shape, writing the whole thing from scratch is often faster than adapting v0’s output.
You’re generating variations of something you’ve already built. If your project has an established card component and you need a slightly different version of it, copy and modify the existing component. Don’t generate a new one from v0 and reconcile the styling differences. Consistency within a codebase is worth more than the time saved on the second generation.
The component needs custom animation beyond Tailwind utilities. v0 generates transition- and animate- Tailwind classes. Staggered entrance animations, scroll-triggered sequences, drag handles with spring physics — these need a dedicated library (Framer Motion, GSAP) that v0 doesn’t generate. Set that up manually from the start if your design requires it. Generating a v0 component and then migrating its animation to Framer Motion is a rebuild, not an edit.
Your stack doesn’t include shadcn/ui. This one is straightforward. v0 generates shadcn/ui components. If your project uses a different component library or none at all, the output is not directly usable. For teams building a full application from scratch with Bolt.new or similar tools, the stack choice determines whether v0 fits into the workflow at all.
Conclusion
Back to the data table that prompted a moment of quiet disbelief. The column structure was right, the TypeScript interface matched the data shape, the row actions used the correct shadcn component. It needed 15 minutes of cleanup — prop type alignment, placeholder data replaced, sort handler wired up. That’s the real number for a complex component.
v0 component generation is most useful when the component type has a standard pattern, the prompt is specific enough to describe it precisely, and your design system is already configured to receive the output. Those three conditions together produce the “it matched what I had in mind” result reliably. Any one of them missing and the iteration count goes up.
- Run
npx shadcn@latest initand configure your CSS variables before generating any components - Navigation, data display, forms, and feedback components are v0’s strongest territory
- Include the TypeScript interface in your data table prompts — it saves meaningful cleanup time
- Use follow-up prompts to refine, not full restarts
- Review each component fully before integrating — placeholder handlers are always present
- Skip v0 when the component is tightly coupled to your existing types or contains significant business logic
The most efficient session ends with one component reviewed, exported, integrated, and working. Then you start the next one. Batching generates components faster; reviewing them one at a time generates fewer surprises later.
↑ Back to topFrequently Asked Questions
How do I generate React components with v0?
Go to v0.dev, write a specific prompt describing the component — layout, purpose, shadcn/ui components to use, and interactive states. v0 generates React/JSX with shadcn/ui and Tailwind and shows a live preview. Refine with follow-up prompts, then export using the CLI command v0 provides or copy the code directly into your project.
What components can v0 generate?
v0 handles navigation bars, sidebars, data tables, stat cards, contact forms, settings pages, empty states, loading skeletons, error messages, modals, and most standard SaaS UI patterns. All output uses shadcn/ui and Tailwind CSS. Components requiring complex state logic or deep integration with your existing data model need meaningful manual work after export.
How do I get better v0 component generation output?
Specificity drives quality. Name the layout explicitly, state the component’s purpose, reference shadcn/ui components by name, and describe interactive states. Set up your shadcn/ui theme and Tailwind config before generating — components generated before setup require retroactive theming adjustments that compound across every component you’ve already exported.
Do I need to know shadcn/ui to use v0?
You don’t need to know it deeply before your first prompt. You will need to understand it to maintain what v0 generates. The output uses Radix UI primitives — Dialogs, Popovers, NavigationMenus — that handle keyboard navigation and ARIA attributes in specific ways. Knowing what these do is required for debugging edge cases. The shadcn/ui documentation is good and worth reading before you ship anything to production.
How long does it take to clean up a v0 component after export?
Feedback components (empty states, loading skeletons) need 5–10 minutes. Navigation components take 10–15 minutes to wire up routing and active states. Data tables take 15–20 minutes to align TypeScript interfaces and wire sort/pagination logic. Forms take 15–25 minutes to add react-hook-form integration and API calls. These are consistent numbers for components generated from well-specified prompts.
Can v0 generate components for a non-Next.js React project?
Yes. v0 generates standard React/JSX that works in any React project — Next.js, Remix, Vite, or plain React. The adjustments are small: replace next/link and next/navigation imports with your routing library’s equivalents, and update active state detection to use your router’s current-path hook rather than the placeholder value v0 inserts.