CSS pattern gotchas
Hard-won lessons about DaisyUI v5 + Tailwind v4 base resets, sub-pixel rendering, and CSS framework drift from v1.
Written for future agents (and future-Julian) so the same class of bug doesn't re-bite. Each entry: anti-pattern → failure mode → canonical fix.
Companion to CRDT-PATTERNS.md (communication/distributed-systems patterns).
This file = visual/styling layer.
1. DaisyUI v5 sets border-width: 1px on bare <input>
Anti-pattern: Using border-b border-transparent (or border-l-2, border-t,
etc.) and assuming the other three sides are 0. They are NOT — DaisyUI v5's base
reset includes:
input:where(:not([type=button],[type=reset],[type=submit])) {
border-width: 1px;
}
So border-b only sets border-bottom-width: 1px, leaving DaisyUI's 1px on
the other three sides untouched. Net effect: +1px on each axis, ~+2px height
when the input is inside a flex/border/padding container.
Failure mode: Section title input rendered 30px instead of 29px. ItemRow
accumulated +2px vs v1. Zoom buttons rendered 25px instead of 24px. Checkbox
border-width was 1.16234px vs 1px.
Canonical fix: explicit zero on the other sides:
<input class="border-b border-transparent border-x-0 border-t-0" />
Or border-0 first, then opt-in to specific sides:
<input class="border-0 border-b border-transparent" />
Discovered: 4 separate instances this sprint (zoom buttons, checkbox border, layout-zoom-overflow, section title input).
Composes with: §3 (sub-pixel rounding allow-list).
2. DaisyUI button classes carry implicit min-height
Anti-pattern: Using class="btn" or DaisyUI's component classes inside a
tightly-sized container (e.g. a 24px row) and assuming Tailwind sizing classes
override.
Failure mode: Zoom-in/out buttons rendered taller than v1's plain
<button class="px-2 py-1 text-xs"> because DaisyUI's .btn defaults to a
4× line-height-relative min-height.
Canonical fix: explicit h-X clamp wins over DaisyUI defaults:
<button class="h-6 px-2 text-xs">−</button>
Discovered: mechanical sweep agent, commit 4874719a. Three CVL zoom controls.
3. Sub-pixel rounding diffs between v1 (React) and v2 (Svelte) renderers
Anti-pattern: Strict pixel-equality assertions in computed-style-diff for
elements that hit fractional pixels via flex layouts or aspect-ratio.
Failure mode: v1 renders an input border at 1.16234px, v2 at 1px. v1
sub-pixel-rounded button width 382.4px, v2 382px. None are visual bugs;
they're rendering-engine differences.
Canonical fix: allow-list intentional sub-pixel deltas in
e2e/style-deltas-allowed.json. Don't try to "fix" them — they're not regressions.
Discovered: 5+ allow-listed entries by now. See the file for the catalog.
4. Flex child with overflow-auto grows with max-content inner div
Anti-pattern:
<div class="flex h-screen">
<div class="cvl-preview-pane flex-1 overflow-auto">
<div class="max-content">...zoomed Typst pages...</div>
</div>
</div>
A flex child with overflow-auto will compute its scrollWidth to fit the
max-content inner div, blowing past w-1/2 on parent.
Failure mode: layout-zoom-overflow.spec.ts failed across 5 viewports.
Horizontal scrollbar appeared at zoom > 100%.
Canonical fix: split horizontal vs vertical overflow, add an inner scroll wrapper:
<div class="cvl-preview-pane flex-1 overflow-y-auto overflow-x-hidden">
<div class="cvl-preview-scroll overflow-x-auto">
<div class="max-content">...</div>
</div>
</div>
Discovered: mechanical sweep agent, commit 4874719a.
5. Tailwind v4 line-height defaults differ from v3
(Placeholder — not yet hit, but worth catching when it does. Tailwind v4
changed default line-height on text-sm, text-xs etc. If a v1↔v2 height
delta isn't explained by §1-4, this is the next suspect.)
Reference implementations
ui/src/lib/components/ui/Section.svelte— bottom-only border on section title input, with explicitborder-x-0 border-t-0ui/src/lib/components/cvl/CvGenerator.svelte— overflow split- inner scroll wrapper
ui/e2e/style-deltas-allowed.json— sub-pixel allow-list