A remote lays out like its content in a block. Width comes from your layout. Height comes from the content.
The element you style is the slot. className and style on <RemoteComponent> go to it. Think of it as a <div style="display: flow-root"> around the page's content.
Inside the frame, the page's <html> element is the slot. The runtime owns its box: width, height, position, margin, padding, border and overflow. Everything inside it is yours, and your CSS wins there. The body has no default margin.
Width and height
The host sends the exact width, fractions included. The remote measures its height at that width and reports it back. Drag the slider.
<div style={{ width }}>
<RemoteComponent
src="https://widgets.example/note.html"
title="Quarterly report"
text="Revenue grew in every region…"
/>
</div><div style="font-weight: 500" id="title"></div>
<p class="muted" style="margin: 4px 0 0" id="text"></p>
<script>
function render() {
document.querySelector("#title").textContent = rc.props.title ?? "";
document.querySelector("#text").textContent = rc.props.text ?? "";
}
render();
rc.addEventListener("props-changed", render);
</script>Flex, grid and fit-content
The remote also reports its min-content and max-content widths. Shrink-to-fit layouts use them like they would for inline content. The tags take their natural width, then wrap when there is no room.
<div style={{ display: "flex", gap: 8 }}>
<span>Labels</span>
<RemoteComponent src={tagsUrl} tags={tags} style={{ flex: "0 1 auto", minWidth: 0 }} />
<span>Edit</span>
</div>
<div style={{ display: "grid", gridTemplateColumns: "auto minmax(min-content, max-content) auto", gap: 8 }}>
<span>Labels</span>
<RemoteComponent src={tagsUrl} tags={tags} />
<span>Edit</span>
</div>
<span>Labels</span>
<RemoteComponent src={tagsUrl} tags={tags} style={{ width: "fit-content", maxWidth: "100%" }} />
<span>Edit</span><style>
.tag { padding: 2px 8px; border: 1px solid var(--color-border); border-radius: 999px; font-size: 0.875em; }
</style>
<div class="row" style="gap: 4px"></div>
<script>
const row = document.querySelector(".row");
function render() {
row.replaceChildren(
...(rc.props.tags ?? []).map((tag) => {
const span = document.createElement("span");
span.className = "tag";
span.textContent = tag;
return span;
}),
);
}
render();
rc.addEventListener("props-changed", render);
</script>Filling the slot
When the slot has a definite height, percentage heights in the remote resolve against it. That covers flex and grid stretch, flex: 1 in a column, aspect-ratio and fixed heights. Otherwise height: 100% behaves as auto.
<div style={{ display: "flex", gap: 8, height }}>
<nav>Sidebar</nav>
<RemoteComponent src={panelUrl} title="Remote panel" style={{ flex: 1 }} />
</div><style>
.panel {
height: 100%;
min-height: 48px;
display: grid;
place-items: center;
border: 1px dashed var(--color-border-strong);
border-radius: var(--radius-md);
background: var(--color-surface-muted);
}
</style>
<div class="panel"><span class="muted"></span></div>
<script>
const render = () => (document.querySelector(".panel span").textContent = rc.props.title ?? "Panel");
render();
rc.addEventListener("props-changed", render);
</script>The slot is the viewport
Pages are often written for a browser window. They use 100vh and center themselves in it. In a frame that grows with its content, that would loop: the content fills the viewport, the viewport grows, and so on.
So inside a remote, vh means the slot. With a height you set, 100vh is that height. With an auto height, height and min-height in vh become auto, and the content decides. The page below sets min-height: 100vh on its body.
<RemoteComponent src="https://widgets.example/centered.html" style={{ height }} /><!doctype html>
<html>
<head>
<!-- Written for a browser window: full height, content in the middle. -->
<style>
body { min-height: 100vh; display: grid; place-items: center; }
.card { display: grid; gap: 4px; padding: 12px 16px; border: 1px solid var(--color-border); border-radius: var(--radius-md); text-align: center; }
.muted { color: var(--color-text-muted); }
</style>
</head>
<body>
<div class="card">
<strong>Payment received</strong>
<span class="muted">$240.00 from Acme Inc.</span>
</div>
</body>
</html>vw is the slot width. Container queries and cq units see your containers, even when the slot sits inside a wider one. The rewrite covers stylesheets from the page's origin, inline styles and CSS-in-JS. JavaScript that reads innerHeight still sees the frame.
What does not cross
- Baselines. The slot has no text baseline, so baseline alignment uses its bottom edge.
- Overflow past a height you constrain is clipped by the frame.
position: fixedis relative to the slot. A fixed bar sits at the bottom of the component. To draw outside it, use an overlay.- LCD text anti-aliasing. Text in a transparent frame is grayscale.
Margins inside the page don't collapse through the slot, as with flow-root.