Everything on this page came out of porting four real component libraries — Flowbite, HyperUI, Windmill — into VelquView and measuring the result against Chromium. No theory; every replacement below shipped in a verified sample.
Browser markup uses the full Tailwind language. The v0 profile is a deliberate subset. This is the complete mapping we applied across all four sample sets:
| Browser pattern | v0 replacement | Seen in |
|---|---|---|
shadow-sm/md/xs | 1px border; emphasized cards use border-2 in the accent color; dark UIs use tonal steps instead | pricing, windmill |
<button> label alignment | browsers center button text via the UA stylesheet; v0 does not — add text-center to every button (a no-op in Chromium, so the shared A/B file stays byte-identical for both engines) | sign-in card, component stack |
rounded-e-lg (per-corner) | uniform rounded-xl | chat bubbles |
mx-auto / ml-auto | center: parent flex justify-center; push-right: justify-between with a wrapper | all sets |
absolute left-indicator bar | author CSS: .vv-bar { border-left: 4px solid … } | windmill sidebar |
py-2.5, mt-0.5 | nearest integer step (py-2/py-3) — the scale is integers × 4px | all sets |
space-x-3 | gap-3 on a flex parent | all sets |
uppercase class | literal uppercase text | windmill table head |
sm:/md:/lg: variants | single static layout (no media queries in v0) | all sets |
hover: on utilities | author CSS .vv-nav:hover { … } — paint properties only | sidebar navs |
fixed inset-0 modal + opacity backdrop | takeover: state swap to a full-viewport layer, solid backdrop, flex items-center justify-center | dash export modal |
| SVG stroke icons | tonal tiles: tinted rounded square + small solid mark (or a text glyph — ✓ and ★ render fine in DejaVu) | features, pricing, windmill |
object-cover images | flat panels, or initials avatars (a stretched bitmap smears — no object-fit in v0) | team grid, avatars |
| photos via URL | local files via --app-dir assets; remote images don't load | live showcase |
Tabs. One state key, a nav row, and content sections gated by vx-show:
<div @click="current = 'traffic'"
:class="current === 'traffic'
? 'bg-teal-600 text-white vv-nav … FULL list …'
: 'text-slate-400 vv-nav … FULL list …'">Traffic</div>
…
<section vx-show="current === 'traffic'">…</section>
Data ranges (7d/30d). No text interpolation exists — numbers can't bind. Duplicate the block per range with real data and switch structurally:
<div vx-show="range === '7d'">… 1,284 trucks · +12.4% vs last week …</div>
<div vx-show="range === '30d'">… 5,132 trucks · +18.9% vs last month …</div>
Takeover modal. v0 has no fixed positioning or opacity, so a modal is a viewport-level state swap — the app layers hide, the dialog layer centers on a solid backdrop:
<main vx-state="{ current: 'overview', modal: '', fmt: 'csv', }">
<div vx-show="modal === ''">…app: topbar + content…</div>
<div vx-show="modal === 'export'"
class="h-full bg-slate-950 flex items-center justify-center">
<div class="w-full max-w-lg bg-slate-900 border rounded-xl p-6">
<!-- header / body (fmt rows set fmt) / footer -->
<div @click="modal = ''">Cancel</div>
</div>
</div>
</main>
Charts without SVG. Bars are flex columns with fixed heights; the container's items-end is the baseline:
<div class="flex items-end gap-3">
<div class="flex-1 flex flex-col items-center gap-2">
<div class="w-full h-32 bg-teal-500 rounded-sm"></div>
<div class="text-xs text-slate-400">Tue</div>
</div>…
</div>
Each of these cost a debugging round before it became a rule. They are engine behaviors, documented as facts.
The binding swaps the entire class attribute. Any static classes on the element vanish when it applies — put the complete list in every branch.
<div class="flex items-center gap-3"
:class="on ? 'bg-teal-600' : ''">
{{ value }} is plain text, not a binding. Bake values per state; duplicate nodes and switch with vx-show.
<div>Export {{ fmt === 'csv' ? 'CSV' : 'JSON' }}</div>
A span inside a flex container flows as text — it does not become a flex item. Name/subtitle stacks collapse into one wrapped line. Use block leaves.
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full"></div>
<div>Bonnie Green</div> <!-- not span -->
</div>
Declarations emit in class-attribute order. mt-6 m-0 is flush (the shorthand wins); m-0 mt-6 keeps the gap. Shorthands first, always.
<p class="text-sm mt-6 m-0">label</p>
<p> like a browser — every <p> needs an explicit m-0 or you'll chase phantom spacing (this exact bug made one sidebar's first item sit lower than the rest).
The starter binary doubles as the test harness. Three layers, cheapest first:
./velqu-view-starter --headless \
--app-dir app --out /tmp/f.png
# stdout: 'tailwind diagnostic: …' lines
# ZERO lines = every class in profile
… --headless --out base.png --frames 4
# renders 4×, fails unless all byte-identical;
# diff base.png vs run.png in CI
npx tailwindcss@3.4.17 --content app/index.html …
# render same file in Chrome w/ parity base;
# measure geometry, don't eyeball — our sets:
# most sections 0px, worst 4px cumulative
For interaction flows, drive the real window with synthetic input on a scratch display and assert on screenshots per step — the samples' open → select → cancel sequences were verified exactly that way, with per-step captures.
All patterns above are executable: every snippet corresponds to a file in velqu-showcase you can run with one command.