TL;DR To make UI not look AI-generated, change the defaults v0 and shadcn ship with: your own color, background and radius tokens, two typefaces with different jobs, a left-aligned asymmetric hero instead of a centered gradient, no row of three icon cards, varied spacing, and one or two interactions with real feedback.
To make UI not look AI-generated, change the defaults your tool ships with. In a v0 app that means: replace the stock shadcn theme tokens with your own colors and radius, pick two typefaces that have different jobs, break the centered gradient hero and the three icon cards, and add one interaction that feels intentional instead of fading everything in. None of this needs a redesign. Most of it lives in globals.css, one font file and two components.
People spot generated UI from a few patterns repeated across thousands of apps. A lot of advice on this stops at "use better prompts" or "add personality". Below is each pattern, why it shows up, the code I use to replace it in a Next.js App Router project with Tailwind v4 and shadcn/ui, and a small script that counts the tells in your own repo. Every snippet here was built in a fresh create-next-app@15 project with shadcn init and passes tsc --noEmit and next build.
v0 generates Next.js with Tailwind and shadcn/ui. That stack is a good choice, and the problem is only that nobody changes the starting values. A fresh npx shadcn@latest init writes a neutral grayscale palette and --radius: 0.625rem into globals.css (I checked the file it wrote in my test app). Every app built on those values shares the same gray buttons, the same corner radius and the same muted text color.
So the job is to keep the components and change what they read from.
The giveaway is a black or near-black primary button, neutral grays everywhere and the same rounded corners on every card. The shadcn components read colors and radius from CSS variables, so you can restyle all of them from one place. The shadcn theming docs list every variable.
Here is the override I appended to the bottom of src/app/globals.css, after the block shadcn generated:
The background is a warm off-white (a little chroma at hue 85) instead of pure oklch(1 0 0). Text and borders get the same warm tint so the page reads as one material.
The primary is an actual color. Pick it from your brand or your logo, not from a palette generator's "modern blue".
The radius drops from 0.625rem to 0.375rem. Smaller corners read as more deliberate. If your brand is soft and friendly, go the other way and make them clearly rounder. The point is to pick a value on purpose.
Because shadcn's generated @theme inline block maps --color-primary to var(--primary) and derives --radius-sm to --radius-4xl from --radius, every bg-primary, rounded-lg and border-border in the app picks this up without touching a component.
Default output uses a single sans (Geist from create-next-app, or Inter) for headings, body, buttons and labels. It's readable, and nobody remembers it.
Use two typefaces with different jobs: something with character for headings, and a plain workhorse for reading. I load them with next/font, which self-hosts the files and exposes each font as a CSS variable (next/font docs):
Now font-display is a utility and font-sans (the default body font) is Plex. Fraunces and Plex are only an example. A grotesk paired with a mono works, and so does a condensed sans with a humanist one. What matters is that headings and body text don't look like the same font at two sizes.
A centered H1, a gradient blob behind it, a subtitle and two buttons side by side. This is the most recognizable generated layout there is.
Things that break it without much effort:
Left-align the headline and let it run large. text-balance keeps the line breaks even.
Cap the paragraph at a reading width (max-w-[52ch]) instead of centering it under the title.
Use one primary button and a plain text link for the secondary action.
Put a second, smaller element on the right of the grid, so the hero has an asymmetric layout instead of a centered axis.
Here is the hero from my test page:
tsx
<section className="grid gap-10 md:grid-cols-[1.4fr_1fr] md:items-end"> <div> <h1 className="font-display text-5xl leading-[1.05] tracking-tight text-balance md:text-7xl"> Invoices your clients actually open </h1> <p className="mt-6 max-w-[52ch] text-lg text-muted-foreground"> Send a link, get paid by card or transfer, and stop chasing people over email. </p> <div className="mt-10 flex items-center gap-6"> <PressButton>Send your first invoice</PressButton> <Link href="#how" className="text-sm underline underline-offset-4"> How it works </Link> </div> </div> <p className="border-l border-border pl-6 text-sm text-muted-foreground"> Used by freelancers who would rather not explain bank details twice. </p></section>
No gradient. If you want color in the hero, use a real image of your product.
Three equal cards, each with a lucide icon in a tinted rounded square, a bold two-word title and one line of text. It shows up because it's the safest way to fill a section.
Your features are probably not three equal things, and the icons rarely add information. A numbered list with dividers reads as written by someone, and it scales to two or five items without looking broken:
The number in the display font is the only decoration, and it uses the brand color from Tell 1. If one feature matters more than the others, give it more space instead of forcing it into the grid.
Generated pages often use the same py-16 or py-24 on every section and center everything. The rhythm is flat, so nothing stands out.
Two rules I follow:
Vary vertical space on purpose. The gap after the hero (mt-32 above) is bigger than the gap inside a section, which groups related things and separates unrelated ones.
Pick one alignment axis and stick to it. In the test page everything hangs off the left edge of a max-w-5xl container. Centering is fine for a single short CTA block, not for every section.
Spacing is where AI-built pages and designed pages differ the most, and fixing it costs nothing in bundle size.
There are two opposite tells here. Some apps have no feedback at all, so buttons feel dead. Others fade and slide every section in on scroll, which looks like a template demo.
Pick one or two moments that matter and make them feel physical. The main button is a good first one. This one lifts a pixel on hover and presses down on click, using only Tailwind transitions, so it works in a Server Component with no JavaScript:
The motion-reduce: variants turn it off for people who set "reduce motion" in their OS (prefers-reduced-motion on MDN). The inset highlight in the shadow is a small detail, and small details like it are a big part of why a button looks designed.
When you need more than a press (a toggle that springs, a tab indicator that slides, a login form that reacts), that's where a ready-made piece saves time. The micro-interactions and animated login forms collections have examples with the source code.
Reading every file is slow, so I start with a count. This loop greps the .tsx files under src for the class names and imports that show up most in default output: gradient backgrounds (bg-gradient-to-* in Tailwind v3, bg-linear-to-* in v4), purple and indigo stops, centered text, extra-round corners, lucide imports, the same big section padding, and the default fonts.
bash
for p in 'bg-(gradient|linear)-to-' 'from-(purple|indigo|violet)-' 'text-center' 'rounded-(xl|2xl)' 'lucide-react' 'py-(16|20|24)' 'font-(inter|geist)'; do printf '%-30s %s\n' "$p" "$(grep -rEio "$p" src --include='*.tsx' | wc -l)"done
I ran it on a small hero written the way generated code usually looks (centered, gradient, three icon cards) and got:
None of these is wrong on its own. A text-center on a short CTA is fine, and lucide is a good icon set. The count tells you where to look. If one file has four text-center lines and a purple gradient, that's the section to redo first, using the fixes above.
globals.css has your own --primary, --background and --radius, not the shadcn defaults.
Headings and body use different typefaces, loaded with next/font.
The hero isn't centered on a gradient.
No section is three identical icon cards.
Section spacing varies, and everything shares one alignment edge.
One or two interactions have real feedback, and they respect reduced motion.
Copy names your product and your user. "Supercharge your workflow" is a tell too.
You can paste this list into v0 or Cursor as a follow-up prompt. Include the exact tokens and font names from your globals.css instead of asking it to "make it look less generic", so it has something concrete to apply.
Sometimes the fastest win is replacing one stock piece with something people haven't seen in a hundred other apps: a theme switch, a navigation bar, a checkout. If you want to try that without paying, the /free page gives you one component with its full source, and a day/night toggle is a good one to start with because every app has a theme switch.
I make animated React components at qodesign, and this checklist is what I run on every v0 project before I add any of them.
Change the defaults rather than the prompt. Set your own color, background and radius tokens, use two typefaces with different jobs, break the centered hero and the three-card row, vary your spacing, and give one or two controls real feedback.
Most AI builders generate the same stack, Next.js with Tailwind and shadcn/ui, and almost nobody changes its starting theme. The result is the same grays, the same radius, the same font and the same section layouts across thousands of apps.
Vague prompts like "make it look less generic" rarely work because there's nothing concrete to apply. Give it the exact tokens, font names and layout rules from your own globals.css, or a screenshot of a design you like, and it has something specific to follow.
Change the CSS variables first. v0's docs say it is trained on the default shadcn/ui implementations and may struggle with customized ones, so theming through tokens keeps later generations working while still changing how everything looks.