registry:ui · Layout
scaffold-float — the portal half
The consumer half of the float provider: portals children into the scaffold's top layer so they ride the immersive slide with the header.
scaffold-float
The float portal: render anything into the website scaffold's top layer from anywhere in the page. Nodes keep full Svelte ownership at their authoring position; a context provider adopts the live node into .jx-float-slot on mount, and teardown hands it back. Static chrome (toc rails, nav trees) belongs in the scaffold's chrome snippet instead — this is the DYNAMIC path.
portal concept · no LIVE instance
Authored in the page, adopted by the top layer
A float must dock to a website-scaffold provider — and the only one reachable from this page is the site’s own shell. So this card explains the adoption instead; every component page’s ToC rail rides the real float plane every day (it adopts itself).
authoring DOM (full Svelte ownership) .jx-top-layer (scroll-free plane)
──────────────────────────────────── ────────────────────────────────────
<ScaffoldFloat> ├── .jx-scaffold-header
└─ aside.docs-aside adopt() └── .jx-float-slot
…children… ──────► ├─ any dynamic float
(hidden anchor stays in place) └─ aside.docs-aside ← moved
(rides the immersive slide) - author — the portal renders its children wherever you place it in the page; the nodes are ordinary Svelte-owned DOM, not a serialized snapshot
- adopt — on mount it calls
api.adopt(node)from the scaffold'sjx-top-layercontext; the scaffold's effect re-parents the live node into.jx-float-slotin adoption order (moved, never cloned) - teardown — the hidden anchor keeps the authoring position; the release fn returns the node to it so Svelte finds and destroys its own nodes correctly
- ride — because the float now lives inside the top layer, the immersive hide/reveal carries it together with the header by construction: no second scroll listener exists anywhere
<script lang="ts">
import ScaffoldFloat from '@ui/scaffold-float.svelte';
import Toc from '@ui/toc.svelte';
</script>
<!-- author it anywhere in the page; on mount the live node is adopted
into the scaffold's top layer and rides the immersive slide -->
<ScaffoldFloat>
<div class="status-strip">● deploy passing — main #142</div>
</ScaffoldFloat>
<!-- (the toc now lives in the scaffold's chrome snippet — this demo
out for embedded demos) -->
<aside class="docs-aside" aria-label="On this page">
<Toc {sections} title="on this page" scrollRoot=".jx-shell-body" />
</aside>law
Move, never clone
The portal moves the live DOM node — appendChild re-parents it into the float slot — instead of serializing and re-rendering a copy. That single decision is what keeps state, event listeners, and Svelte ownership intact on both ends of the trip.
- a moved node keeps its listeners, its element state (scroll position, focus), and its Svelte hydration anchors — a clone would lose all three
- the hidden anchor at the authoring position is the return ticket: teardown hands the node back so Svelte destroys exactly what it created
- the float plane is ordered multi-node —
adopt()appends in adoption order, so multiple custom floats coexist inside.jx-float-slot(static chrome lives in its own slot — the two never mix)
<script lang="ts">
import ScaffoldFloat from '@ui/scaffold-float.svelte';
import Toc from '@ui/toc.svelte';
</script>
<!-- author it anywhere in the page; on mount the live node is adopted
into the scaffold's top layer and rides the immersive slide -->
<ScaffoldFloat>
<div class="status-strip">● deploy passing — main #142</div>
</ScaffoldFloat>
<!-- (the toc now lives in the scaffold's chrome snippet — this demo
out for embedded demos) -->
<aside class="docs-aside" aria-label="On this page">
<Toc {sections} title="on this page" scrollRoot=".jx-shell-body" />
</aside>types
Types
One portal shape; the area role decides which top-layer cell the shell grid resolves for the adopted node.
Default — the dynamic float slot inside .jx-top-layer; your custom floats.
The static chrome cells — the shell grid resolves each cell; static chrome usually lives in the scaffold's chrome snippet instead.
adopt() appends in adoption order — multiple custom floats coexist in the slot.
usage
Usage
Author the float anywhere in the page; the provider adopts the live node on mount.
<script lang="ts">
import ScaffoldFloat from '@ui/scaffold-float.svelte';
import Toc from '@ui/toc.svelte';
</script>
<!-- author it anywhere in the page; on mount the live node is adopted
into the scaffold's top layer and rides the immersive slide -->
<ScaffoldFloat>
<div class="status-strip">● deploy passing — main #142</div>
</ScaffoldFloat>
<!-- (the toc now lives in the scaffold's chrome snippet — this demo
out for embedded demos) -->
<aside class="docs-aside" aria-label="On this page">
<Toc {sections} title="on this page" scrollRoot=".jx-shell-body" />
</aside>a11y
Accessibility
The portal is a DOM move, not a visual layer change: reading order and focus follow the authored semantics.
Keyboard
| Key | Action |
|---|---|
| Tab | Focus order is unaffected — the moved node keeps its listeners, focus, and scroll state |
ARIA
| Attribute | Value | Description |
|---|---|---|
| aria-label | yours | Label the floated content yourself (e.g. aside aria-label="On this page") |
| role | inherited | The portal adds no roles; the adopted subtree keeps its authored semantics |
theming
Theming
No density footprint and no tokens of its own — the portal moves your node; the paint is entirely the floated content's.
| Token | Default | Source |
|---|---|---|
| jx-top-layer | context key | structural |
| .jx-float-slot | adoption order | structural |
axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. THE PORTAL CARRIER: the content div is adopted into the scaffold's top layer (a REAL portal — the DOM moves), so CSS custom-property inheritance from the authoring position does not reach the adopted subtree. The axes resolve HERE, in the consumer's context, and the carriers stamp the portal wrapper — self-carried across the adoption. Svelte context needs no bridge; only the CSS channel did. All no-own: placement chrome forwards opinions, it manufactures none.
ScaffoldFloat · universal props
Composed inside a website-scaffold: everything floated through this portal reads the axis tree continuously — the wrapper re-stamps the resolved carriers on the adopted root.
<ScaffoldFloat area="float" size={14} radius="large">…floats…</ScaffoldFloat>api
API
Props from the ScaffoldFloat Props interface — a children snippet, the semantic role, and the nine-slot position.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| children * | Snippet | — | The floated content; the live DOM node is adopted into the top layer on mount. |
| area | TopLayerArea: 'toc' | 'tree' | 'float' | 'float' | Semantic placement role; the shell grid resolves the cell. |
| pos | string — the nine slot names (left-top · center-top · right-top · left-center · center-center · right-center · left-bottom · center-bottom · right-bottom) | 'right-bottom' (unstated — the float rule itself carries it) | The NINE-SLOT position inside the cell (Owner R3, 2026-09-02): physical names stamped as data-float-pos; website-scaffold.css resolves each to a place-self. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |