BoilerPrompt

v0 prompts

Vercel's generator that turns written specs into working React and Next.js interfaces.

v0 prompts

34 prompts, every one free to copy. Jump to: App, Backend, Design, Features, Frontend, Refactor, Testing

What v0 is good at

v0 is Vercel's generative UI tool. You describe a screen or component in plain text and it produces working React code, typically Next.js with Tailwind and shadcn/ui, rendered in a live preview you keep iterating on with follow-up messages. You can edit the code directly, fork versions, and push the result to a Vercel project or copy it into an existing repo.

It fits developers who want React they will actually ship and designers who want a real prototype rather than a static mockup. Against its neighbors: Lovable and Bolt aim at whole hosted applications with backends attached, and Replit Agent builds and deploys inside Replit's environment, while v0 is strongest at the interface layer of a Next.js app, individual screens, flows, and components that drop into a codebase you already run. If your stack is React on Vercel, v0's output needs the least translation of the four.

How to prompt v0

Write specs, not vibes. v0 does best with a structured brief: what the screen is, the layout regions, the components inside each, the data shape, and the states. A good build prompt runs 150 to 300 words; much shorter and v0 fills the gaps with stock dashboard tropes.

Conventions that pay off:

- One screen or flow per message. Get the settings page right, then request the next screen in a follow-up so the generation stays coherent. - Name shadcn/ui components when you care: Dialog rather than a hand-rolled modal, Sheet for side panels, the Command component for palettes. v0 knows the library and follows these cues reliably. - Spell out the data model as fields with types. Left alone, v0 invents plausible fields, and every later iteration inherits them. - List the states you expect: loading, empty, error, and what each looks like. Unprompted, v0 renders only the happy path with populated mock data. - Give a styling direction in one or two lines: palette, density, radius. Without it you get the default shadcn look every time. - When iterating, reference elements by name, "the revenue stat card", not by position, and change one thing per message.

Always say how data arrives: mocked behind a single function, fetched from an endpoint you name, or wired through an integration. That one line determines how much rework the export costs you.

All 34 v0 prompts

App 5 prompts

Build a SaaS starter in Next.js with shadcn/ui: marketing site, auth screens, a workspace-scoped app shell, and {{addon}} built in from the start.

Screens:
1. Landing page: hero with product name and one CTA, three feature cards, pricing with Free and Pro tiers, footer.
2. Sign up and log in: email and password forms with inline validation errors under each field.
3. /app dashboard: left nav (Home, {{resource}}, Team, Billing, Settings), top bar with a workspace switcher and avatar menu.
4. /app/{{resource}}: table of rows with create and edit in a Dialog, plus an archive action with confirmation.
5. /app/team: member list with role badges (owner, member) and an invite form that validates email format.
6. /app/settings: profile form, and a danger zone where deleting the account requires typing the workspace name.
7. /app/billing: current plan card and an upgrade button calling a stubbed checkout function with a TODO comment where Stripe would be wired.

Integrate {{addon}} using the shell's conventions: user-facing surfaces get a left-nav entry and screen, preferences get a control on /app/settings, and third-party credentials come from env vars with a stub path when keys are absent. Where it overlaps a screen above, extend that screen rather than duplicating it.

Data model for {{database}}: users, workspaces, memberships (userId, workspaceId, role), {{resource}} rows carrying workspaceId, and any tables {{addon}} needs, scoped the same way. Every read and write filters by the active workspace, and switching workspaces changes what the table shows.

Behaviors: visiting any /app route unauthenticated redirects to log in, then back after success. A new workspace is seeded with one example row so first login never shows an empty table. Log out clears the session and lands on the marketing page.

States: forms keep user input on failure; empty tables show a create prompt; a failed save raises a toast naming the action.

Styling: restrained and product-grade, single accent color, consistent radius, no marketing gradients inside /app.
Build a to-do app as a single Next.js App Router page using shadcn/ui components. Data model: a tasks table with id, title, done, due_date, priority (low, medium, high), and created_at, stored in {{database}} through server actions in app/actions.ts. Screens: the main list at /, grouped into Today, Upcoming, and Done sections, plus a slide-over Sheet for editing a task. Behaviors: add a task from a single input pinned at the top, toggle done with a Checkbox and an optimistic update so the row moves into Done before the server action resolves, inline title editing on click, and delete with an undo Toast that restores the row if clicked in time. Filter tabs for All, Active, and Completed backed by a URL query param so refresh keeps the filter. Overdue tasks get a red Badge computed from due_date on the server, not in client JavaScript. Empty state: a plain card saying no tasks yet that autofocuses the input. Error state: if a server action throws, revert the optimistic change and surface a destructive Toast with a retry button. Styling: neutral background, one accent color, roomy row height, lucide-react icons only, no illustration packs. Acceptance: adding, completing, and deleting all work in the v0 preview without a full page reload, the Done count in the header always matches the visible list, and the chosen filter survives a refresh.
Build a blog with a self-hosted admin instead of an external CMS. Public side: an index at /blog listing posts newest first with title, one-line excerpt, and a date formatted on the server to avoid hydration mismatch, plus a detail route at /blog/[slug] rendering the post body from markdown with heading anchors and styled code blocks. Data model: a posts table in {{database}} with id, slug, title, excerpt, body_markdown, status (draft or published), and published_at. Admin side: /admin lists every post with a status Badge, and /admin/edit/[id] is a two-pane editor, markdown textarea on the left, live preview on the right, with save draft and publish buttons calling server actions. Slugs generate from the title on create, stay editable afterward, and a uniqueness check appends a suffix on collision rather than failing the save. Behaviors: draft posts return notFound() on the public route, publishing sets published_at once and never overwrites it on later edits, and revalidatePath runs for both the index and the post after any save so public pages update. Empty states: the public index with zero published posts shows a plain nothing published yet card, and the admin list shows a create your first post button. Error state: a failed save keeps the editor content intact and shows an error Toast, never a redirect that loses work. Styling: a readable measure near 65 characters, serif headings allowed, shadcn/ui confined to the admin. Acceptance: create, publish, and read a post end to end inside the v0 preview.
Build a small e-commerce storefront with three route groups: a product grid at /, product detail at /product/[slug], and a cart flow. Data model: products in {{database}} with id, slug, name, price_cents, inventory_count, and image_url, prices stored as integer cents and formatted through a single currency helper everywhere. Grid: responsive cards with a fixed image aspect ratio to prevent layout shift, name, price, and an out of stock overlay when inventory_count is zero that also disables the card link. Detail page: gallery, a quantity selector capped at available inventory, and an add to cart button that opens the cart as a shadcn/ui Sheet from the right instead of navigating away. Cart: a cookie-backed structure managed through server actions so it survives refresh in the v0 preview, line items with quantity steppers, remove buttons, a subtotal recomputed on the server, and a checkout button leading to /checkout with an order summary and an address form validated with zod. Payment stays a clearly marked stub at app/api/checkout/route.ts for a later {{addon}} integration, with the expected order payload documented in a comment. Edge cases: adding beyond inventory clamps the quantity and explains why in a Toast, a product deleted after being carted renders as unavailable with a remove prompt rather than crashing the subtotal, and unknown slugs return notFound(). Empty states: a seeded-empty grid shows a no products card, an empty cart Sheet links back to shopping. Acceptance: add, adjust, remove, and reach checkout end to end in the preview with the subtotal always matching the line items.
Build a booking app where a visitor picks a time slot from a host's availability. Screens: a public booking page at /book/[username] with a month calendar on the left and that day's open slots on the right, a confirmation step collecting name and email, and a host dashboard at /dashboard for setting weekly availability and reviewing upcoming bookings. Data model in {{database}}: availability_rules (host_id, weekday, start_minute, end_minute) and bookings (id, host_id, starts_at as timestamptz, duration_minutes, guest_name, guest_email, status), with open slots computed on the server from rules minus existing bookings, never stored. Timezones are the core difficulty: store everything in UTC, detect the visitor's timezone from the browser, render slots in it with the timezone name visible above the list, and allow overriding it through a searchable Select. Behaviors: days with no open slots render dimmed and unclickable in the calendar, picking a slot holds it visually while the form completes, and the booking server action re-checks the slot inside a transaction so two simultaneous guests cannot double-book, the loser seeing a slot just taken message with refreshed slots rather than a silent failure. Past slots on the current day are filtered on the server. Confirmation screen: date and time in the guest timezone, an add to calendar link, and a cancel link tokenized by a random id in the URL. Empty state: a host with zero availability rules shows setup instructions on their public page. Acceptance: booking a slot in the v0 preview removes it from the picker immediately, and switching timezones relabels slots without changing which ones are available.

Backend 5 prompts

Build a REST API for {{resource}} records as Next.js route handlers under app/api, backed by {{database}}. No pages or UI, route handlers only.

Endpoints:
- GET /api/{{resource}}: paginated list. Accept page and limit query params (default 20, max 100) plus sort=createdAt:desc. Respond with { data, page, total }.
- POST /api/{{resource}}: create from a JSON body. Return 201 and the created record.
- GET /api/{{resource}}/[id]: one record, or 404 with { error: "not_found" }.
- PATCH /api/{{resource}}/[id]: partial update. Unknown fields are a 400, not silently dropped.
- DELETE /api/{{resource}}/[id]: 204 on success, 404 if the id does not exist.
- GET /api/health: { ok: true } for smoke checks.

Data model: id (uuid), name (string, 1 to 120 chars), status ("active" | "archived"), createdAt, updatedAt. Define one zod schema and derive the POST and PATCH validators from it so the rules never drift apart.

Error contract: every failure is JSON { error, message } with the correct status code. Validation failures return 400 with zod's field-level issues. A malformed JSON body must also be a 400, never an unhandled 500.

Put all {{database}} access in lib/db.ts behind list, get, create, update, and remove functions; handlers never import the client directly. Finish with a README block showing one sample curl per endpoint so I can verify each route from a terminal.
Build a GraphQL API inside a Next.js App Router project, served from one route handler at app/api/graphql/route.ts using graphql-yoga. Schema: a {{resource}} type with id, name, status, and updatedAt, a paginated list query using cursor pagination (first, after) rather than offsets, a single-item query by id, and create, update, and delete mutations that return the mutated object. Resolvers read {{database}} through a db.ts helper so the data layer stays swappable. Validation: reject an empty name with a GraphQL error carrying extensions.code BAD_USER_INPUT, and return null with a NOT_FOUND code when an id does not exist instead of throwing. Add a query depth limit so nested selections cannot recurse unbounded, and disable introspection when NODE_ENV is production. Also generate a minimal /playground page in the same project: a textarea for the query, a variables field, a run button, and a pretty-printed JSON response panel using shadcn/ui Card and Tabs, so the API is exercisable inside the v0 preview without external tooling. Playground empty state: a preloaded example query for the paginated list. Playground error state: network failures render the raw error body in a red monospace block, never a blank panel. Acceptance: the example query returns rows in the preview, a create mutation followed by the list query shows the new item, and a malformed query comes back as a structured GraphQL error response, not a 500.
Add JWT authentication to an existing Next.js App Router project without pulling in a full auth provider. Route handlers: app/api/auth/login/route.ts verifies credentials against a users table in {{database}} using bcrypt comparison, then signs a short-lived access token and a longer refresh token with the jose library, both delivered as httpOnly, secure, sameSite strict cookies, never returned in the JSON body or stored in localStorage. app/api/auth/refresh/route.ts rotates the pair, and app/api/auth/logout/route.ts clears both cookies. Protection: middleware.ts guards everything under /dashboard and /api/protected, verifying signature and expiry, redirecting browsers to /login with a from query param for post-login return, and answering API calls with a JSON 401 instead of an HTML redirect. Secrets: read JWT_SECRET from env, and if it is missing show a plain configuration notice in the preview instead of crashing. UI: a /login page using shadcn/ui Form with inline field errors from server-side zod validation, a generic invalid credentials message that never reveals whether the email exists, and a disabled submit state while the request is in flight. Edge cases: an expired access token with a valid refresh token rotates silently on the next request, a tampered token clears both cookies and forces login, and the payload carries only the user id, no email or role claims. Acceptance: in the v0 preview, logging in lands on /dashboard, an incognito visit to /dashboard bounces to /login, and logout revokes access immediately.
Set up a Postgres schema for a project tracker using the Neon integration and Drizzle ORM, with everything in code so the schema is reviewable. Files: db/schema.ts defines the tables, db/index.ts exports a client reading DATABASE_URL from env, and db/seed.ts inserts believable sample rows so the v0 preview has data immediately. Tables: users (id uuid defaulting to gen_random_uuid, email unique not null, created_at timestamptz defaulting to now), projects (id, owner_id referencing users with on delete cascade, name not null, archived boolean default false), and tasks (id, project_id referencing projects with on delete cascade, title not null, status as a Postgres enum of todo, doing, done, position integer for manual ordering, due_date nullable). Indexes: tasks on (project_id, status) because the board view filters on both, plus a partial index on projects where archived is false. Constraints live in the database, not only the app: a check that position is non-negative, and email uniqueness enforced by Postgres rather than a lookup before insert. Generate Drizzle relations so joined queries come back typed, and add db/queries.ts with one example, getProjectWithTasks ordered by position. Seed the awkward rows deliberately: one archived project, one task with a null due_date, and one project with zero tasks, so later UI work hits them early. Never store timestamps as text, and never expose serial ids across an API boundary where a uuid belongs. Acceptance: the seed runs cleanly twice thanks to onConflictDoNothing, and getProjectWithTasks returns typed rows in the preview.
Build a file upload endpoint on Vercel Blob using the client upload pattern, so files travel from the browser straight to Blob storage and never through a serverless function body limit. Server: app/api/upload/route.ts uses handleUpload to issue client tokens, restricting allowed content types to png, jpeg, and pdf, setting a maximum size in the token so oversized files are rejected before transfer, and prefixing pathnames with the authenticated user id so one user cannot overwrite another's files. The onUploadCompleted callback writes a row to an uploads table in {{database}} with url, pathname, size, and content_type, and that table is the source of truth for listing, never Blob list calls at request time. Client: a drop zone component with drag state styling, a per-file progress bar driven by the upload helper's progress events, an image preview from an object URL before the upload finishes, and a cancel control mid-flight. Rejections happen client side first, wrong type or too large shows an inline message naming the limit without a network round trip, and the server enforces the same rules regardless. Edge cases: duplicate filenames get random suffixes from the Blob helper rather than overwriting, a network drop mid-upload leaves a retry action on the failed item, and deleting an upload removes the Blob first and the database row second, tolerating a Blob that is already gone. A missing BLOB_READ_WRITE_TOKEN shows a setup notice in the v0 preview rather than a crash. Acceptance: upload a png, see its row in the list, delete it, and confirm its URL then returns not found.

Design 14 prompts

Generate a self-contained Hero section for {{product}} as a React component using Tailwind and shadcn/ui. This is one section, not a page — I will drop <Hero /> above the sections my page already has, so no header, footer, or sibling sections.

Internal anatomy, top to bottom:
1. Eyebrow — tiny uppercase category label naming what {{product}} is, one or two words.
2. H1 — the outcome {{product}} delivers for {{audience}}, nine words max, never wrapping past two lines. Emphasize the outcome phrase, de-emphasize the qualifier.
3. Subhead — one sentence of 18–24 words that names {{audience}} and the mechanism behind the outcome.
4. CTA pair — a primary shadcn Button and a ghost secondary. The primary call to action is "{{cta}}"; the secondary points to a demo.
5. Proof element — exactly one idea: either a three-logo strip or a single stat with a one-line source. Not both.
6. Visual slot — an aspect-video bordered placeholder in the right column where a product screenshot will go.

Layout is a 7/5 two-column grid with gap-12, copy left, visual right. Rhythm: pt-20 pb-16 on the section, space-y-6 through the copy stack, mt-10 before the proof row. Hard constraint: everything must fit a 1280x800 viewport with zero scrolling — shrink the visual slot before letting the proof row clip.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Responsive: below md, collapse to one column — copy first, buttons full-width and stacked, visual next, proof row allowed to wrap.

After building, check that: (1) there is no vertical overflow at 1280x800, (2) the H1 occupies at most two lines at desktop width, (3) the button pair sits inline on desktop and stacks on mobile, (4) keyboard focus reaches the primary button before the secondary. If any check fails, regenerate only this component, not the page.
Generate a testimonial wall section for the {{product}} landing page in v0 — a TestimonialsSection React component (Tailwind + shadcn/ui) that drops in after the features section. Despite what people search for, this is not a rotating carousel on desktop: build a static masonry-style wall of quote cards that {{audience}} can scan in one pass, and reserve carousel behavior for small screens only.

Anatomy:
1. Header: kicker, then an h2 that claims a specific result customers get from {{product}} — no subhead.
2. One featured pull-quote spanning the full content width: the strongest single sentence you can write for {{product}}, set larger, with name, role, and company beneath it.
3. The wall: eight quote cards in a 3-column masonry layout (CSS columns or grid) with visibly varied card heights — write two short, four medium, and two long quotes. Every card carries quote, avatar placeholder, name, role, and company; attribution is non-negotiable, no anonymous cards.
4. Logos strip: a single muted row of five or six company marks under the wall, behind a showLogos prop so it can be toggled off.
5. Close with an inline text link reading "{{cta}}", aligned right beneath the wall.

Write the quotes as outcomes with numbers where possible ("cut onboarding from 3 weeks to 4 days"), voiced for {{audience}}. Ban the phrases "amazing", "game-changer", and "love this tool".

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing and responsive: py-24 section padding, 1.5rem card gutters, a 3rem gap separating the featured quote from the wall. Below md, collapse the wall into a swipeable single-column carousel — scroll-snap, one card per viewport, dot indicators — while the featured quote stays static above it.

Check the result: the masonry shows clearly different card heights; every quote has name, role, and company; exactly one featured pull-quote exists; at mobile width the wall becomes a snap-scrolling carousel; the logos strip disappears when showLogos is false.
Build a FAQ section for the landing page of {{product}} in v0 — one self-contained FAQSection component (React + Tailwind + shadcn/ui) I can drop in near the end of the page, just before the final CTA band, aimed at {{audience}}.

Use the shadcn Accordion primitive with type="single" collapsible so exactly one answer is open at a time — opening a question closes the previous one. Note in a code comment that type="multiple" is the one-line switch if I later want several open. Keyboard and screen-reader behavior must come through Radix intact: Enter/Space toggles, arrow keys move focus between triggers, and each trigger is a real button wired via aria-expanded and aria-controls to its panel. Do not rebuild the accordion by hand with divs and onClick.

Section anatomy:
1. Left-aligned header: kicker ("Questions"), h2, and one line inviting the reader to skim.
2. Six accordion items. Write the questions as {{audience}}'s actual pre-purchase objections about {{product}}: cost, migration or setup effort, security and data handling, what happens if it doesn't work out, how it compares to doing nothing, and support. Phrase each in first person ("Can I ..."); answer in 2–3 sentences ending on a concrete fact, never "it depends".
3. A closing row under the accordion: "Still deciding?" plus a link-style button labeled "{{cta}}" pointing at the page's primary action.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing and responsive:
- Section padding around py-20; items separated by hairline dividers, not boxed cards; question rows get tap targets of at least 48px.
- On mobile the header stacks above the accordion at full width; on desktop the layout may split header left, accordion right in a 1:2 grid.

Verify after generation: only one panel opens at a time; arrow keys traverse all six triggers; the chevron rotates on the open item; the "{{cta}}" row sits below the last item; the questions read as objections, not feature descriptions.
Generate a portfolio site for {{product}}, built to convince {{audience}} to reach out. Split it into components I can regenerate independently — Hero, WorkGrid, WorkCard, AboutStrip, Services, Contact — Next.js with Tailwind, using shadcn/ui where it earns its place (Button, Card).

Structure, top to bottom:
1. Hero: an identity statement, not a slogan — line one is the name or studio, line two a single sentence stating the craft and the client it serves, set large; beneath it two links, one to the work anchor and one mailto.
2. WorkGrid: six WorkCard entries in a 2x3 grid; each card carries an image placeholder, project name, a one-line outcome ("what changed for the client"), and a tag row; on hover the image scales slightly and the outcome line slides into view — the hover states are the point of this section.
3. AboutStrip: one wide band — portrait placeholder left, a three-sentence bio right covering where they've worked, what they believe about the craft, and one human detail.
4. Services: four noun-phrase offerings in a row, each with a one-line deliverable description, no icons required.
5. Contact: a closing headline inviting the right kind of project, the email set at display size, and a current-availability line.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing: sections py-28 on desktop, py-16 on mobile, on a 4px base; the work grid runs gap-4 so images dominate the section.
Responsive: below md the grid drops to one column and the hover reveal is replaced by an always-visible outcome line; cap the hero statement near 20ch so it never sets as one long line.

The primary call to action is "{{cta}}" — it labels the hero's first link and the Contact button.

After building, check that: every WorkCard shows its outcome line on mobile without hover; headings run h1 in the hero then h2 per section with no skips; the email appears in both hero and Contact; and tab focus reaches every card link in grid order.
Build a pre-launch waitlist page for {{product}}, aimed at {{audience}}, as a Next.js screen split into small components — Hero, ProofRow, WaitlistForm, Faq, Footer — one file each so any section can be regenerated later without touching the rest. Use Tailwind plus shadcn/ui primitives (Input, Button, Accordion, Badge).

Sections, top to bottom:
1. Hero — a "Coming soon" Badge, a headline stating the single outcome {{product}} unlocks (nine words max), a one-sentence subhead naming who it serves and what changes for them, and the email capture directly beneath.
2. ProofRow — three cards: a product-still placeholder, a momentum line ("2,300+ already in line" style), and a short tester quote with name and role.
3. WaitlistForm — restate the promise in one line above the form; Input and Button inline on desktop; validate email format on blur, disable the button while submitting, and on success swap the form for a card showing the signup's position in line plus a "share to move up" referral link.
4. Faq — an Accordion with four items: launch timing, launch pricing, email policy, who gets access first.
5. Footer — one centered line: product name, contact email, one social link.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing: an 8px scale — sections py-24 on desktop and py-16 below md, gap-6 inside the card row; give the hero py-32 so the form clears the fold at 1440x900.
Responsive: at md and below, ProofRow stacks vertically and the inline form becomes a stacked Input over a full-width Button.

The primary call to action is "{{cta}}" — the submit label in both the hero and WaitlistForm, and nowhere else. Render the capture once as a shared component in both spots.

After building, check that: an invalid email shows an inline error with no browser alert; the success state renders a position number; both capture points use the same WaitlistForm component; the Accordion works by keyboard; and no section except the hero exceeds one viewport height on mobile.
Generate a complete SaaS landing page for {{product}}, aimed at {{audience}}. Build it in Next.js with Tailwind and shadcn/ui, and split every section into its own component (Nav, Hero, SocialProof, Features, ProductShot, PricingTeaser, Faq, FinalCta, Footer) so I can ask you to regenerate one section later without touching the rest.

Sections, top to bottom:
1. Sticky nav — wordmark left, Product / Pricing / Docs links center, ghost login plus a solid CTA button right.
2. Hero — one benefit-led H1 of 8 words or fewer derived from what {{product}} does, a two-line subhead naming the outcome {{audience}} gets, primary CTA beside a secondary "See how it works" text link.
3. Social proof strip — "Trusted by teams at" microcopy above a single row of six grayscale logo placeholders.
4. Feature grid — three cards, each with an icon, a 3–5 word feature name, and a two-line benefit sentence; equal card heights.
5. Product shot — full-width browser-frame mockup of the {{product}} dashboard with a one-line caption.
6. Pricing teaser — two plan cards (one badged as recommended) plus a "Compare all plans" link.
7. FAQ — shadcn Accordion with four questions phrased the way {{audience}} would actually search.
8. Final CTA band — restate the H1 promise in different words, one button only.
9. Footer — four link columns and a newsletter input.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing and responsiveness:
- Section vertical padding py-24, tightening to py-16 below md; internal stacks use gap-6 / gap-12 steps only, nothing arbitrary.
- Below md, the feature grid stacks to one column and the nav collapses into a Sheet menu.

The primary call to action is "{{cta}}" — it appears in the nav, hero, and final band, styled identically each time.

After building, verify: the H1 is under 9 words; all three CTA instances render from one shared component; the logo row stays on one line at 375px; the accordion opens one item at a time; adjacent sections never share the same background treatment.
Build a landing page for {{product}}, aimed at {{audience}}. Generate it in v0 as a Next.js page assembled from separate components with Tailwind and shadcn/ui — one component per section (Nav, Hero, WorkGrid, Capabilities, Process, Manifesto, ContactFooter) — so any section can be regenerated on its own later.

Sections, top to bottom:
1. Nav — wordmark left; links to Work, Capabilities, Process, Contact; a small availability note on the right.
2. Hero — one oversized positioning line naming what {{product}} does and for whom, plus a one-sentence subline naming the specialty. No stock imagery.
3. WorkGrid — 6 selected projects in an asymmetric 2-column grid alternating tall and wide cards; each card carries project name, client type, one outcome metric, and a hover reveal with a one-line summary.
4. Capabilities — 5 services as full-width rows: index number, service name, 10–14 word description.
5. Process — 4 numbered steps in a horizontal band, each with a label and two lines on what the client receives.
6. Manifesto — a 3-sentence belief statement set large, then a compact team row (name, role) for up to 5 people.
7. ContactFooter — the email address at display size, studio location, social links.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing and responsive rules:
- Vertical padding py-24 desktop / py-14 mobile; internal grid gaps step between gap-4 and gap-8, nothing arbitrary.
- Below md, WorkGrid collapses to one column and hover reveals become permanent captions; Process stacks vertically along a left rule.

Derive the hero line and all card copy from {{product}} — name the specialty concretely; "we craft digital experiences" is banned. The primary call to action is "{{cta}}", shown as a Nav button and repeated above ContactFooter.

After building, check that: each section is its own component file; WorkGrid renders exactly 6 cards, each with a visible metric; the hero wraps to no more than 3 lines at 1280px; at 375px there is no horizontal scroll; "{{cta}}" appears exactly twice on the page.
Build a single-product landing page for {{product}}, selling to {{audience}}. Compose it in v0 from discrete components — Gallery, BuyBox, BenefitBlocks, SpecsAccordion, ReviewWall, GuaranteeStrip, StickyBuyBar — using Tailwind and shadcn/ui, so each piece can be revised independently.

Layout, top to bottom:
1. Above the fold: Gallery on the left (main image with 4 thumbnails, becoming a shadcn Carousel on mobile) and BuyBox on the right — product name, one-line value claim, star rating with review count, price with any compare-at price, quantity select, the buy button, and directly beneath it a trust row: shipping time, free-returns window, secure-checkout note.
2. BenefitBlocks — 3 alternating media/text bands; every headline is written as an outcome the buyer gets, never a feature name, with 2 supporting sentences.
3. SpecsAccordion — a shadcn Accordion with 4 panels: specifications table, what's included, materials and care, shipping and returns detail.
4. ReviewWall — overall score, a star histogram, then 6 review cards (name, verified tag, rating, a 2–3 sentence quote), with a filter by star rating.
5. GuaranteeStrip — money-back period, warranty length, and support contact in one compact band.
6. StickyBuyBar — appears only after the BuyBox scrolls out of view: thumbnail, name, price, buy button; hides again when the footer is visible.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing and mobile:
- Band padding 80px desktop, 48px mobile; the gallery-to-BuyBox gap fixed at 48px.
- On mobile the BuyBox follows the gallery, benefit bands stack text-first, and the sticky bar condenses to price plus button.

The buy action everywhere is "{{cta}}" — the BuyBox button, the sticky bar, and once after the ReviewWall. Derive benefit headlines from what {{product}} changes for the buyer.

After building, check that: the sticky bar appears only after the BuyBox leaves the viewport; all three "{{cta}}" buttons are present; the trust row sits directly under the primary button; all four accordion panels are populated; mobile renders the gallery as a swipeable carousel.
Design a 404 page for {{product}} as a single full-viewport screen — no scrolling at desktop sizes. Build it as one self-contained React component (NotFound) with Tailwind and shadcn/ui so I can drop it into app/not-found.tsx in Next.js.

Layout, one centered column, top to bottom:
- An oversized "404" as a display element, large enough to dominate the upper half of the viewport.
- A one-line headline with personality that fits {{product}}'s domain — write it as a wry observation about being lost, never an apology; the visitor is {{audience}}, so the joke can assume their vocabulary.
- One sentence of plain help text beneath it.
- A recovery row: a primary button, a ghost "Go home" button (shadcn Button variants), and a search input (shadcn Input) that submits to /search.
- A quiet footer line linking three popular destinations: Docs, Pricing, Support.

One interactive touch: the giant 404 tracks the cursor with a few pixels of parallax translate and settles back when the pointer leaves — and it must disable itself entirely under prefers-reduced-motion.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing and responsive:
- Vertical gaps between the five layers step 8/16/24/40px, with the largest gap between the 404 numeral and the headline group.
- Under 640px the recovery row stacks: search input full-width first, then the two buttons side by side below it.

The primary call to action is "{{cta}}" on the primary button.

After building, check that: the page fills exactly 100dvh with no scrollbar at 1440x900; the parallax stops entirely under prefers-reduced-motion; keyboard tab order reaches the search input right after the two buttons; the three footer links are real anchors; nothing clips at 375x667.
Add a pricing section to an existing landing page for {{product}}, aimed at {{audience}}. Build it in v0 as one self-contained React component (PricingSection) using Tailwind and shadcn/ui — Card for tiers, Switch for the billing toggle, Button for tier actions — so it can be pasted between the features section and the FAQ without touching anything else.

Section anatomy, top to bottom:
1. Eyebrow label ("Pricing") plus an h2 that names the outcome {{product}} charges for, not the word "plans".
2. Monthly/annual Switch with an "annual saves ~20%" hint on the annual side.
3. Three tier Cards in a grid: entry, recommended, top. The middle card is the recommended tier — lift it with a border-plus-badge treatment ("Most popular"), never with a background hue that fights the page.
4. Each card: tier name, price with billing unit, a one-sentence who-it's-for line written for a segment of {{audience}}, 5–7 feature bullets, full-width Button.
5. Feature strategy: the entry tier lists everything included; recommended and top open with "Everything in [previous tier], plus" and list only deltas — max 4 each.

The recommended tier's Button reads "{{cta}}" and is the only solid/primary button on the section; the outer two use outline variants with their own verbs ("Start free", "Talk to sales").

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing and responsive:
- Section padding around py-24, grid gap-6; inside a card, separate the price block and bullet list with a consistent space-y-4.
- Below md the grid stacks to one column with the recommended card first; the Switch stays above the cards, not per card.

After building, check that: exactly one primary button exists; the toggle updates all three prices without layout shift; the recommended card renders first on mobile; "Everything in ..., plus" appears on two cards; the heading is an h2 so it slots under the page's existing h1.
In v0, build a bento grid section for {{product}} — a features-and-stats mosaic that slots into an existing landing page between the hero and pricing. Generate two components: <BentoSection /> and a reusable <BentoCard /> taking colSpan and rowSpan props, with shadcn Card as the base.

Section header first: a short overline label, then an H2 of at most eight words framing the value for {{audience}}, with mb-12 below it.

The grid: 4 columns on desktop, gap-4, exactly six cells — one idea per cell, never two:
1. Flagship cell, 2x2: the single most differentiating capability of {{product}}, with a mini visual placeholder and two lines of copy.
2. Stat cell, 1x1: one quantified outcome — a number worth bragging about — with a five-word caption.
3. Stat cell, 1x1: a second, different metric; never repeat the first cell's unit.
4. Workflow cell, 2x1 wide: how {{product}} fits into the day of {{audience}}, expressed as a three-step inline flow.
5. Quote cell, 1x1: one customer sentence with a name-and-role line.
6. Action cell, 1x1: a see-it-live card whose link is labelled "{{cta}}", visually quieter than the flagship cell.

Cell interiors: p-6, content top-aligned, caption text last. Section rhythm: py-24 outer, grid rows sized so 1x1 cells stay near square.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Responsive: at md collapse to 2 columns with the flagship cell spanning both and rowspans released; below sm, single column ordered flagship, stats, workflow, quote, action.

After building, check that: (1) the grid has no empty holes at 1440, 768, and 375 widths, (2) every cell contains exactly one idea and one point of emphasis, (3) the two stat cells use different units, (4) the action cell's link carries the CTA label, (5) card borders align along consistent grid lines. If one cell misses, regenerate that card alone by referencing its number in this list.
Build a SiteFooter component for {{product}} using Next.js, Tailwind, and shadcn/ui. Generate it as one self-contained file so I can regenerate the footer alone in a later message without touching the sections above it; it slots into an existing landing page as the final import, rendered after the last section. The audience is {{audience}}, so link labels should use their vocabulary, not internal team names.

Internal anatomy, top to bottom:
1. Newsletter band — a one-line pitch derived from {{product}} (name the concrete thing subscribers receive), a shadcn Input for email, and a submit button one visual size smaller than the hero's, because this is the page's secondary action. End the band with a small text link restating "{{cta}}" for readers who scrolled here still undecided.
2. Link columns — 3 or 4 columns whose headings you derive from what {{product}} actually is: Product (features, pricing, changelog), Resources (docs, guides, support), Company (about, blog, contact), plus a fourth only if genuinely needed. 4-6 links each, never padded to match counts.
3. Legal row — copyright with the current year, privacy and terms links, and 3-4 lucide-react social icons, all sitting on one baseline.
4. Optional brand moment — the product wordmark at 12-18vw, cropped by the bottom edge, at very low contrast so it reads as texture, not a headline.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing and responsive rules:
- 96px top padding; 48px from newsletter band to columns; 32px above the legal row; 48px column gap.
- Under 640px: columns collapse to 2-up, the input and button stack full width, and the legal row wraps to two lines with socials first.

After building, check that: every column has a specific heading (never "Links"), the form submits without layout shift, the legal row holds one line at 1280px, the oversized wordmark causes no horizontal scroll, and all footer text passes AA contrast against the background.
Build an animated background layer for the hero of my {{product}} landing page in v0 — a single client component, <HeroBackdrop />, rendered as the first child of the existing hero section. It sits behind the content that is already there; it must not add, move, or restyle any hero copy.

Layering contract:
- The hero root gets relative and overflow-hidden; the backdrop is absolute inset-0 with z-0 and pointer-events-none; existing content wraps in z-10. Nothing else changes.
- Keep an inner quiet zone: no high-contrast shape may pass within 80px of the headline and button block, so the copy always sits on a calm area.

Choose ONE technique to match the visual direction below: a gradient mesh of three or four blurred radial blobs drifting on CSS transforms if the direction reads soft or gradient-led; a canvas particle field if it reads technical or dark; slow-drifting geometric SVG outlines if it reads editorial or brutalist.

Performance budget (hard rules): animate transform and opacity only, never layout properties; one requestAnimationFrame loop at most; canvas capped at devicePixelRatio 2 and 80 particles; zero React state updates per frame. Under prefers-reduced-motion, render a static first frame and never start the loop.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Motion governs intensity: map it to drift speed, travel distance, and element count together — the calm end means slow, short, few.

Legibility: add a subtle scrim between backdrop and content so the headline and the primary button labelled "{{cta}}" hold at least 4.5:1 contrast for {{audience}} reading at a glance.

Responsive: below 768px, halve the element count and slow the drift by one step.

After building, check that: (1) clicking anywhere over the hero content still works — the layer captures no pointer events, (2) emulating prefers-reduced-motion in devtools freezes the layer, (3) headline and CTA pass contrast over the busiest area of the animation, (4) no layout shift occurs when the layer mounts. If a check fails, regenerate only HeroBackdrop.
Create a FeatureGrid section for {{product}} in v0 — React, Tailwind, shadcn/ui — as a standalone component that accepts a features array prop, so copy lives in data and I can regenerate this section later without touching the page. This is a uniform feature grid, not a bento: every card gets the same span and height, and the rhythm comes from repetition. If you catch yourself mixing span widths, that is a bento — stop and equalize.

Grid spec:
- 6 cards in 3 columns (two rows), or 3 cards if {{product}} honestly has only three distinct capabilities. Never 4 or 5 — an orphan card breaks the rhythm.
- Equal height enforced by the grid container stretching items, not by padding hacks or truncated copy.
- Each card: icon, feature name of 3 words or fewer, one benefit sentence.

Microcopy formula: write each benefit as what {{audience}} can now do, not what {{product}} does — "Ship refunds in one click", not "Automated refund processing". Derive all six pairs from the product; start every sentence with a verb, no two with the same verb.

Icon rules: lucide-react only, one size (20 or 24px), one treatment — all inside matching tinted squares or all bare, never mixed. Each icon should illustrate the noun in its feature name; prefer consistent-abstract over random-literal.

Hover: the whole card is the target; one effect (lift or border emphasis, chosen per the motion block), transition under 200ms, no per-icon animation.

Above the grid: eyebrow label, a headline claiming the summed benefit, one subhead sentence. Below the grid, one centered link carries "{{cta}}" for readers the grid convinced.

Visual direction:
{{style}}

Palette: {{palette}}
Typography: {{type}}
Motion: {{motion}}

Spacing: 96px section padding top and bottom; 56px header to grid; 24px card gap; inside cards, 16px icon-to-title and 8px title-to-body. Mobile: one column under 640px, two columns up to 1024px, card padding one step down.

After building, check that: cards stay equal height with uneven copy lengths, the desktop grid is strictly 3-up with no spans, every benefit line opens with a distinct verb, icons share one size and treatment, and hover fires across the full card area.

Features 4 prompts

Add search for {{resource}} to the current generation.

Placement: a search input in the top bar, focusable with the / key, plus Cmd+K (Ctrl+K on Windows) opening the same search as a command-palette style overlay.

Query behavior: debounce keystrokes by 300ms, trim whitespace, ignore queries under 2 characters, and cancel any in-flight request when a new one starts so stale responses can never overwrite fresh results. Match against name and description, case-insensitive.

Where filtering happens: server-side, in a GET /api/search?q= route handler reusing the existing data layer. If this generation only has client-side mocked data, filter the mock array through the same function signature so swapping to a real endpoint later changes one file.

Result states, all four: before any query, a hint with two example searches; while fetching, a compact skeleton list; results as rows showing name, status, and updated time with the matching substring highlighted; no results, echo the query, as in "Nothing for 'foo'", with a clear button.

URL sync: write the query to ?q= so results are shareable and the back button restores the previous search. Landing on a URL that already has ?q= runs the search immediately.

Keyboard: arrow keys move the highlight through results, Enter opens the selected item, Escape clears and closes the overlay. The highlighted row scrolls into view when it moves off screen.

Do not restyle anything outside the search components, and finish by listing the files you touched.
Add an LLM chatbot to an existing app screen using the Vercel AI SDK. Server side: a streaming route handler at app/api/chat/route.ts that calls streamText with a system prompt loaded from lib/prompt.ts and a model name read from an environment variable so it can be swapped without code changes. Client side: a chat panel built as a shadcn/ui Sheet sliding in from the right, driven by the useChat hook, with streamed tokens rendering progressively rather than appearing all at once. Messages: user bubbles right-aligned, assistant bubbles left with a copy button, markdown rendered including fenced code blocks. Behaviors: Enter sends, Shift plus Enter inserts a newline, a stop button aborts the stream mid-response, and the input disables while a response is streaming. Persist the transcript to sessionStorage so closing and reopening the Sheet keeps the conversation, with a clear conversation action in the header. Empty state: three suggested starter questions rendered as clickable chips that submit on click. Error states: a missing API key shows an inline notice naming the exact env var to add in project settings, and a mid-stream failure keeps the partial response visible with a retry action instead of wiping it. Rate limit the route per session and return a friendly message when the cap is hit. Acceptance: in the v0 preview, a question streams token by token, stop works mid-answer, and refreshing the page keeps the transcript.
Add transactional email notifications to an existing app using Resend and React Email templates. Templates: real React components under emails/, welcome.tsx sent on signup and {{resource}}-updated.tsx sent when a watched record changes, each with a plain-text fallback, a preview line under 90 characters, and an unsubscribe footer link. Sending: a lib/send-email.ts helper wraps the Resend client and reads RESEND_API_KEY from env, and every trigger calls it from a server action or route handler, never from client code. Preferences: a notifications section on the settings page with a shadcn/ui Switch per email type, persisted to a notification_prefs table keyed by user id and checked before every send, so an opted-out user is skipped silently. Behaviors: sends happen after the database write commits, not before, so a failed save never emails anyone, and each attempt lands in an email_log table with recipient, template, and status for debugging. Failure handling: if the Resend call throws, the originating action still succeeds, the failure is recorded in email_log with its message, and nothing retries automatically, avoiding duplicate sends. Missing API key: the v0 preview routes sent emails into an on-screen dev inbox at /dev/emails instead of failing, so the whole flow is testable before Resend is connected. Dev inbox empty state: no emails sent yet. Acceptance: switching a preference off suppresses that email type, the dev inbox shows the welcome email after a test signup, and email_log rows match what the inbox displays.
Add CSV import and export for {{resource}} records to an existing table view. Import: a Dialog opened from an Import button with three steps. Step one, a drop zone accepting only .csv with the size cap stated in the UI before parsing begins. Step two, a column mapping table where each CSV header maps to a known field through a shadcn/ui Select, with an auto-match by lowercase header name preselected. Step three, a validation preview showing the first rows with per-cell problems highlighted, bad email format, missing required name, duplicate id, plus a count of rows that will import versus be skipped. Parsing runs client side with papaparse in streaming mode so a large file never locks the UI, then rows post in batches to a server action that upserts and returns per-batch failure counts. Result screen: imported, skipped, and failed totals, with a downloadable CSV containing only the failed rows plus a reason column so users fix and retry exactly those. Export: an Export button hitting app/api/{{resource}}/export/route.ts, a route handler that streams rows with a proper Content-Disposition attachment header so the browser downloads instead of rendering, honoring the table's current filters and sort by reading them from query params. Edge cases handled with named messages rather than a generic parse error: quoted fields containing commas, a UTF-8 BOM from spreadsheet exports, an empty file, and a header-only file with zero data rows. Acceptance: importing the exported file back in produces zero failed rows in the v0 preview.

Frontend 4 prompts

Build an admin dashboard for {{resource}} data in Next.js with shadcn/ui and Tailwind.

Layout: collapsible left sidebar with logo and nav (Overview, {{resource}}, Reports, Settings), a top bar holding a date-range picker and user menu, content on a 12-column grid.

Overview screen:
- Four stat cards: total count, active this week, conversion rate, errors. Each shows the delta versus the previous period with an up or down arrow.
- A line chart of daily counts for the selected range.
- A recent activity table: name, status badge, owner, updated time. Column-header sorting, 10 rows per page.

Behaviors: changing the date range refetches every widget and writes the range to the URL query string, so refresh and back button both preserve it. Clicking a table row opens a detail drawer from the right with the full record and an edit form.

States, per widget rather than global: loading shows a skeleton matching the widget's dimensions; error shows an inline message with a retry button rather than a blank card; empty shows a short explanation and a create action. A missing metric renders a placeholder character, never NaN or undefined.

Data: everything comes from a single getDashboardData(range) function returning mocked values derived from the range, so switching ranges visibly changes the numbers. I will replace this function with a real API call later.

Styling: neutral surface, one accent color, roomy spacing, no gradients. At 375px the sidebar collapses to icons and the stat cards stack vertically.
Build a marketing landing page for a developer tool as one App Router page composed of clearly separated section components: Nav, Hero, LogoRow, Features, CodeDemo, PricingTeaser, FAQ, and Footer, each in its own file under components/landing/ so individual sections can be regenerated in later v0 iterations without touching the rest. Hero: a headline of eight words or fewer, one subline, a primary CTA plus a secondary docs link, no background video. CodeDemo: a tabbed shadcn/ui block with an Install tab holding the shell install command and a Quickstart tab holding a short {{language}} snippet that exercises the tool, each tab with a copy button, monospace font, and real syntax highlighting, not a screenshot. Features: a three-column grid on desktop collapsing to one column below the md breakpoint, each card with a lucide-react icon, a bolded claim, and two supporting lines, no filler adjectives. FAQ: an Accordion with five questions where only one item opens at a time. Behaviors: the nav gains a border and background blur after scrolling past the hero, anchor links scroll smoothly to their sections, and the primary CTA repeats once above the footer. Every image gets explicit width, height, and alt so nothing shifts during load, and every clickable element is a real button or link. Styling: dark theme, one accent color reserved for CTAs and links, content capped near 72rem, system font stack acceptable. Acceptance: the page holds at 375px and 1440px in the v0 preview with no horizontal scroll, the nav changes state on scroll, and the copy button copies the visible tab's snippet.
Build a pricing page at /pricing with three tiers, Free, Pro, and Team, rendered from a single plans array in lib/plans.ts so copy edits never touch markup. Layout: three shadcn/ui Cards on desktop, stacked below the md breakpoint, the Pro card visually raised with a subtle ring and a Most popular Badge. Billing toggle: a monthly and annual Tabs control above the cards that swaps displayed prices from the plans array, annual showing the per-month equivalent with a small billed yearly note, and the selected term carried into every checkout link as a query param. Feature lists: check icons from lucide-react for included items, dimmed x icons for excluded ones so tiers compare at a glance, and a full comparison table further down inside an overflow-x auto wrapper so it scrolls on phones instead of breaking layout. Each tier gets a distinct CTA: Free links to signup, Pro links to a {{addon}} checkout route carrying plan and term params, Team opens a contact Dialog with a short form that validates email format inline. Edge cases: prices render from numbers through one currency formatter, never hardcoded strings, the toggle keeps keyboard focus when switching terms, and the comparison table repeats the tier CTAs in its footer row. Below the table, an Accordion with five billing questions, one open at a time. The contact Dialog needs a submitting state and a success message that replaces the form. Acceptance: toggling terms updates all three prices in the v0 preview and both paid CTAs carry the correct plan and term params.
Build a sortable data table for {{resource}} records using the shadcn/ui Table primitives with TanStack Table managing column state, the pairing v0 scaffolds for this job. Columns: name, status rendered as a colored Badge, amount right-aligned through a currency formatter, created_at formatted on the server, and a row actions column holding a DropdownMenu with edit and delete. Sorting: clickable headers cycling ascending, descending, then cleared, an ArrowUp or ArrowDown lucide icon on the active column only, amount sorting numerically rather than lexically, and sort state written to URL query params like ?sort=amount&dir=desc so refreshes and shared links reproduce the view. Keep it single-column, multi-sort is out of scope. Above the table: a debounced text filter on name and a status Select that compose with sorting instead of resetting it. Pagination: a page size selector plus previous and next buttons in the footer, with a range label like showing 21 to 40 computed from real counts. States: a loading skeleton matching the exact column widths so the layout never jumps when data lands, an empty row spanning all columns when filters match nothing with a clear filters action, and an error row with a retry button if the fetch fails. Keep the basics honest without turning this into an audit: aria-sort on the active header, and buttons rather than divs for anything clickable. Acceptance: in the v0 preview, sorting by amount orders numerically, the URL updates as you sort, and clearing filters restores the full set without losing the chosen sort.

Refactor 1 prompt

Audit the current generation for accessibility and fix everything you find. Keep the visual design intact except where contrast forces a change; this pass is about semantics, keyboard behavior, and screen reader output.

Work through this checklist:
- Landmarks and structure: exactly one main per page, nav and header where they belong, heading levels descending without skips.
- Forms: every input has a programmatically associated label, and error text is connected via aria-describedby, not just placed nearby in red.
- Buttons and icons: icon-only buttons get aria-label; decorative icons get aria-hidden; anything clickable is a button or a link, not a div with onClick.
- Keyboard: dialogs and drawers trap focus and return it to the trigger on close; menus support arrow keys; no positive tabindex anywhere; every focusable element shows a visible focus ring, and any outline: none is removed or replaced.
- Contrast: body text meets 4.5:1 and large text 3:1 against its actual background. Check muted foreground text on card backgrounds specifically, since that pairing fails most often. Adjust design tokens, not individual components.
- Media and charts: meaningful images get alt text; charts get a text summary or an accessible data table alternative.
- Motion: entrance and hover animations respect prefers-reduced-motion.
- Live updates: toasts and async result areas announce through an aria-live region.
- Add a skip-to-content link as the first focusable element on the page.

When done, list each file changed with the specific fixes in it, and flag anything you could not fix automatically, such as color decisions that need a brand call.

Testing 1 prompt

Take the current generation and retrofit error handling end to end. Nothing may fail as a blank screen, a spinner that never resolves, or a silent console log.

Boundaries: wrap each route segment in an error boundary showing a short human message and a reset action, with no stack traces exposed to the user.

Data fetching: every fetch gets a failure branch rendered inline where the data would have appeared, with a retry that refetches only that section. Add an AbortController timeout of 10 seconds so a hung request becomes a visible error instead of an eternal skeleton.

Forms: submit failures render under the submit button, name what failed, and preserve everything the user typed. Field-level validation errors sit under their fields and clear when the field changes.

Mutations: optimistic updates roll back on failure and raise a toast naming the action that failed, like "Couldn't archive the item", never a bare "Something went wrong."

Route handlers, if this generation has any: wrap each in one shared helper that catches thrown errors and returns JSON { error, message } with an appropriate status, treating an unparseable body as a 400.

Offline: listen for the browser's offline and online events and show a dismissible banner while disconnected. Queue nothing, just inform.

So I can verify each path, add a dev-only debug panel with toggles that force fetch failure, mutation failure, and slow network. End by listing every file you changed and which failure path each change covers, so I can review before accepting.

Prompting patterns that work in v0

Screen, regions, states

Structure a build prompt in three passes: what the screen is for, the layout regions from top to bottom, then the loading, empty, and error treatment for each region. v0 follows document order, so a spec organized this way maps cleanly onto the generated component tree.

Data shape first

Paste the field list or TypeScript interface before describing any UI, then refer to fields by name throughout. v0 threads real field names into tables, forms, and cards instead of inventing lorem-ipsum columns you rename later.

One change per message

When refining, isolate a single edit and name the target component. Bundled requests, fix the chart plus redo the nav plus add dark mode, make v0 rewrite more than you asked and regress parts that were already right.

The swap-point seam

Ask v0 to route all data access through one named function or file, and say you plan to replace it. You get a working mocked preview immediately and a single seam to cut when connecting the real backend after export.

Common mistakes

The whole-app prompt

Requesting a complete product in one message yields half a dozen shallow screens, none usable. Build the riskiest screen first, get it right, then extend the generation with follow-ups that reference what already exists.

Leaving data implicit

If you never state the fields, v0 invents them, and follow-ups keep building on the inventions. Renaming fields across a mature generation is the most tedious edit in the tool, so pin the model in the first message.

Restyling by adjectives

Asking for 'more modern' or 'cleaner' produces arbitrary changes on every attempt. Name tokens instead: the accent hex, the radius, the font, the spacing scale. v0 applies concrete values consistently; adjectives it reinterprets each message.

How v0 compares

Prompts for other tools