I wanted to know, concretely, what "AI-built software looks off" actually means, not as a vibe but as a list of specific, repeatable defects. So I stopped speculating and pulled real examples: apps other people built with v0, published to its public community gallery, with genuine traction: hundreds of forks and likes, not cherry-picked outliers.
Eleven apps. Two verticals: internal tools and fintech. I opened every one, clicked through every screen, and wrote down exactly what broke. I expected the failures to be scattered: a bad color choice here, a missing empty state there. They weren't. The same handful of mistakes showed up over and over, across apps that had nothing to do with each other, built by different people, for different purposes. That repetition is the finding. If eleven unrelated apps make the same mistake, it isn't eleven mistakes. It's one mistake the tooling keeps making, and no amount of better prompting is going to fix it, because the model is getting the logic right every time. It's the layer above logic (what a number, a color, or a blank screen is supposed to communicate) that keeps falling through.
The method
Six internal-tools apps, pulled from v0's community gallery: VetCRM (a veterinary clinic dashboard, 300 remixes, 97 likes), two Etihad Cargo tools (an AWB tracker and an airport performance table), UXBooster (an analytics dashboard, 295 remixes), CampaignHub (a marketing dashboard), and Meridian (a financial analytics dashboard, 420 remixes, 115 likes, the most popular app in the sample).
Five fintech apps, same gallery, same standard, searched across "fintech dashboard," "banking dashboard," "expense tracker," "trading," and "invoice," kept everything with meaningful engagement: LoanTech (193 forks), an Expense Tracker (344 forks), the Primium Gold banking dashboard (203 forks), a real-time trading chart (326 forks), and an Advance Invoice Generator (487 forks, the highest-engagement app the fintech search turned up).
I tried to extend this to Bolt.new and Lovable output too, for a fairer picture. I couldn't. Bolt's public gallery is gone. The link 404s into a broken project-import view, and there's no showcase left in their nav, only a Discord invite. Lovable never had a comparable gallery to begin with. GitHub searches for Bolt or Lovable fintech projects top out at one star, nowhere near the traction that made the v0 sample worth trusting. So this is a v0 audit, honestly labeled as one, worth a revisit if either tool ships a real gallery later.
What kept breaking
Color meant nothing. VetCRM's stat-card icons are green, blue, orange, and pink for no discernible reason. A tile reading "14 Pending" renders in alarm red. LoanTech's "Biggest daily movers" list shows the identical fake "+4.5%" next to all three stocks, regardless of which way they actually moved. Expense Tracker assigns yellow, red, blue, orange, and green to spending categories with no logic connecting color to meaning. In every case, color was picked to look varied, not to say anything, which means it actively lies to anyone who's learned to trust red-means-bad, green-means-good.
Numbers didn't agree with themselves. Primium Gold's own account card can't settle on a single currency format: the headline balance reads "$34,280.56" with a thousands separator, while "Remaining Limit: $1376.98" (same card, same currency, one field down) drops the comma entirely. Its Expense Tracker cousin shows "$0.00 this month" next to a running total that clearly includes March transactions, and a "Recent Expenses" list that isn't sorted by date, despite the label promising it is. These aren't edge cases; they're the first screen a user sees.
Nothing else existed besides the happy path. Five of the six internal-tools apps show sample data forever. There's no evidence any of them has ever rendered a loading, empty, or error state, because none of them were built to. VetCRM was the lone exception, with a genuinely good "All clear for now" empty state, proof this isn't hard, just skipped. When something does go wrong, it goes wrong loudly: both LoanTech and the Advance Invoice Generator (two apps with nothing else in common, built for different purposes, by different people) threw the exact same raw React error overlay mid-workflow. Not a similar error. The identical one.
Real-time data wasn't real, and said so quietly. The trading-chart app's own author description states the chart "only animates the last data point which creates a more realistic trading chart experience." That's synthetic movement, deliberately dressed up as live, disclosed only in the fine print a user would never read. Nothing on the actual screen tells you what you're looking at is fake.
Structure kept getting borrowed from the wrong place. Etihad's internal cargo-tracking tool sits under a full public marketing nav (About, Products, Services, News) bolted onto what is unmistakably an operational screen. Etihad's other tool, the airport performance table, has columns labeled "ERT," "COL," and "CRT" that are never defined anywhere, rendering a 64% failure rate in the same plain black text as a 5% one. Primium Gold's entire top navigation is icon-only, no labels, no tooltips, on buttons tied to real account actions. And right next to LoanTech's masked account balances ("****", correctly hidden) sit two visually different button styles doing the same job, a small inconsistency that undermines trust in the one part of the screen where trust matters most.
Filter bars and dense tables had no plan for their own size. Etihad's AWB search page packs a MAWB search box, a status dropdown, an excursions dropdown, an origin picker, a destination picker, two date-range fields, and Clear/Search buttons (ten-plus controls) into the same row at every viewport, with no logic for what happens as the window narrows. It doesn't crash or overflow; it does something quieter and worse. Below desktop width the row wraps, but each field keeps the same fixed width, so labels just get cut off mid-word: "Select Destination" becomes "Select…", "Tagged at start date" becomes "Tagged," "Updated at start date" becomes "Updated." Nothing is broken, but a user can no longer tell which field is which. This is what happens when a layout is generated to look complete at one specific viewport width and never stress-tested past it.
Why this keeps happening
None of these are bugs in the sense of broken logic. The totals compute, the forms submit, the charts render. What's missing is judgment: the layer that decides what a visual choice means. Current AI coding tools are extremely good at producing a screen that works and looks plausible in a screenshot. They have no consistent opinion on whether red should mean "bad" every time, whether every number on a page should round the same way, or whether a blank state deserves the same design attention as a full one. Every app in this sample got that judgment call wrong somewhere, because nothing in the generation process makes that judgment structural. It's up to whichever prompt happened to ask for it, on whichever screen someone remembered to ask.
That's a component-library problem, not a prompting problem. It's fixable exactly once, at the level of the building blocks, instead of once per screen, forever, by every person using these tools.
What we built instead
That's the whole premise behind OnSystem. Two kits, Internal Tools and Fintech: drop-in React and Tailwind components built directly against this failure list, not a general design-system wishlist. Five semantic color roles (success, warning, danger, info, neutral) that every status indicator, delta, and category tag has to pull from, so no more identical fake deltas or alarm-red for no reason. Shared currency, percent, and decimal formatters baked into the components instead of left to whoever wrote that screen. Every data-bearing component ships loading, empty, and error states as defaults, not afterthoughts, including a masked-value component with one consistent reveal pattern, and a live-vs-demo-data badge for anything claiming to be real-time. An app shell that's structurally impossible to confuse with a marketing site. A filter bar with an actual overflow strategy past four controls.
Same install either way: npx shadcn add, or just ask your AI assistant to add the component directly. The registry is MCP-ready, so Claude, Cursor, and the rest read the same config. One-time purchase, perpetual access and free updates.
If any of this looked familiar, if you've shipped a screen with a fake delta, an unlabeled icon button, or a "live" badge on data that wasn't, you're not alone, and it's not a you problem. It's what the tools currently do by default.
Ship screens that look intentional
One-time purchase, instant delivery, perpetual access and free updates.