One body, rendered as a dialog, a sheet or a page of its own.
The surface block decides where a piece of content appears without the content knowing. A form, a detail panel or a note is written once; whether it opens as a modal dialog, slides in as a side sheet, or navigates to a route of its own is a setting above it.
Pick a variant, then open the note. The body is the same component in both.
dialog centres a modal over the page. sheet slides a panel in from the side. page navigates instead of opening anything — the launcher pushes a route, and the modal renders nothing at all, because a page is where the content already lives.
That third one is why the variant is a setting rather than a component choice. A record that a small team edits in a dialog is the same record a large one wants on its own URL, linkable and refreshable. Moving between the two changes one value.
const surface = useSurface({ variant, basePath: "/deliveries" });
<SurfaceProvider value={surface}>
<Button onClick={() => surface.actions.open()}>Open</Button>
<SurfaceModal title="Delivery note" size="compact">
<DeliveryNote />
</SurfaceModal>
</SurfaceProvider>;useSurface owns the open state and the navigation. SurfaceProvider publishes it. SurfaceModal draws the dialog or the sheet, and returns nothing under page. The body reads useSurfaceContext() when it needs to close itself, and otherwise knows nothing about any of it.
The launcher and the surface are deliberately the same subtree: a sheet mounted on the route it represents would render as a closed panel with nothing left on the page to reopen it.
The form block's modal shell is built on this, which is how one form definition serves an add dialog, an edit sheet and a full page without a second component or a variant prop threaded through its fields.