pm4ai

Minimal DOM

Fewest DOM nodes with React and Tailwind

Minimal DOM (React + Tailwind)

Same UI, fewest DOM nodes. Every element must earn its place. If you can delete it and nothing breaks (semantics, layout, behavior, required styling) → it shouldn’t exist.

A node is allowed only if it provides:

  • Semantics/a11y — correct elements (ul/li, button, label, form, nav, section), ARIA patterns, focus behavior
  • Layout constraint — needs its own containing block / positioning / clipping / scroll / stacking context (relative, overflow-*, sticky, z-*, min-w-0)
  • Behavior — measurement refs, observers, portals, event boundary, virtualization
  • Component API — can’t pass props/classes to the real root (and you tried as/asChild/prop forwarding)

Before adding wrappers:

  • Spacing → parent gap-* (flex/grid) or space-x/y-*
  • Separators → parent divide-y / divide-x
  • Alignment → flex/grid on existing parent
  • Visual (padding/bg/border/shadow/radius) → on the element that owns the box
  • JSX grouping → <>...</> (Fragment), not <div>

Styling children — props first, selectors second:

  • Mapped component → pass className to the item
  • Uniform direct children → *: or [&>tag]: to avoid repeating classes
// bad: repeated classes
<div className='divide-y'>
  <p className='px-3 py-2'>A</p>
  <p className='px-3 py-2'>B</p>
</div>
// good: selector pushdown
<div className='divide-y [&>p]:px-3 [&>p]:py-2'>
  <p>A</p>
  <p>B</p>
</div>

Tailwind selector tools:

  • *: direct children · [&>li]:py-2 targeted · [&_a]:underline descendant (sparingly)
  • group/peer on existing nodes → group-hover:*, peer-focus:*
  • data-[state=open]:*, aria-expanded:*, disabled:*
  • first: last: odd: even: only: — structural variants

Review checklist: Can I delete this node? → delete. Can gap/space/divide replace it? → do it. Can I pass className? → do it. Can [&>...]: remove repetition? → do it.

On this page