VelquView
COOKBOOK

Porting real-world Tailwind to profile v0.

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.

Adaptation table§

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 patternv0 replacementSeen in
shadow-sm/md/xs1px border; emphasized cards use border-2 in the accent color; dark UIs use tonal steps insteadpricing, windmill
<button> label alignmentbrowsers 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-xlchat bubbles
mx-auto / ml-autocenter: parent flex justify-center; push-right: justify-between with a wrapperall sets
absolute left-indicator barauthor CSS: .vv-bar { border-left: 4px solid … }windmill sidebar
py-2.5, mt-0.5nearest integer step (py-2/py-3) — the scale is integers × 4pxall sets
space-x-3gap-3 on a flex parentall sets
uppercase classliteral uppercase textwindmill table head
sm:/md:/lg: variantssingle static layout (no media queries in v0)all sets
hover: on utilitiesauthor CSS .vv-nav:hover { … } — paint properties onlysidebar navs
fixed inset-0 modal + opacity backdroptakeover: state swap to a full-viewport layer, solid backdrop, flex items-center justify-centerdash export modal
SVG stroke iconstonal tiles: tinted rounded square + small solid mark (or a text glyph — ✓ and ★ render fine in DejaVu)features, pricing, windmill
object-cover imagesflat panels, or initials avatars (a stretched bitmap smears — no object-fit in v0)team grid, avatars
photos via URLlocal files via --app-dir assets; remote images don't loadlive showcase

Interaction patterns§

Tabs. One state key, a nav row, and content sections gated by vx-show:

tabs
<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:

range-reactive counters
<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:

export dialog (Flowbite structure)
<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:

bar chart
<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>

The four edges§

Each of these cost a debugging round before it became a rule. They are engine behaviors, documented as facts.

1

:class replaces everything

The binding swaps the entire class attribute. Any static classes on the element vanish when it applies — put the complete list in every branch.

✗ breaks
<div class="flex items-center gap-3"
     :class="on ? 'bg-teal-600' : ''">
2

No interpolation

{{ value }} is plain text, not a binding. Bake values per state; duplicate nodes and switch with vx-show.

✗ renders literally
<div>Export {{ fmt === 'csv' ? 'CSV' : 'JSON' }}</div>
3

Inline leaves in flex

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>
4

Class order wins

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.

✗ margin silently gone
<p class="text-sm mt-6 m-0">label</p>
Also real: the UA stylesheet margins <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).

Testing your UI§

The starter binary doubles as the test harness. Three layers, cheapest first:

1 · compile gate
./velqu-view-starter --headless \
  --app-dir app --out /tmp/f.png
# stdout: 'tailwind diagnostic: …' lines
# ZERO lines = every class in profile
2 · golden frames
… --headless --out base.png --frames 4
# renders 4×, fails unless all byte-identical;
# diff base.png vs run.png in CI
3 · differential vs Chromium
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.