docs/solutions/logic-errors/2026-04-26-slate-react-custom-voids-must-render-children-through-spacer.md
/examples/embeds and /examples/images hand-rolled custom void wrappers and
rendered {children} directly in app-owned layout. The required Slate void
child then created a visible line box next to the real UI, breaking legacy
visual parity.
38.390625px between the URL input and
the following paragraph.22.390625px between the image void top and
the actual image content.22.4px of extra height after the input.16px top margin.Render custom voids through VoidElement so app-owned UI goes in content and
Slate children go in spacer:
<VoidElement
content={
<>
<VideoFrame />
<UrlInput />
</>
}
contentAs="div"
spacer={children}
/>
The regression test should assert the user-visible gap, not just DOM presence:
expect(gap).toBeGreaterThanOrEqual(12)
expect(gap).toBeLessThanOrEqual(24)
For image-style voids, assert the visible content starts at the void node top:
expect(contentOffset).toBeGreaterThanOrEqual(0)
expect(contentOffset).toBeLessThanOrEqual(1)
Void elements still need a Slate child for selection and DOM mapping, but that
child is not content. VoidElement puts it in SlateSpacer, whose default
style is absolute and zero-height, so it remains available to Slate without
participating in layout.
renderElement code, do not render void {children} directly after
app-owned UI.VoidElement for selectable voids unless the app has a proven custom
spacer wrapper.contentEditable=false
focus contracts; changing those needs their own browser proof.docs/solutions/logic-errors/2026-04-04-v2-element-primitives-should-compose-element-and-void-contracts.mddocs/solutions/logic-errors/2026-04-26-slate-v2-selectable-voids-should-be-atomic-navigation-points.md