Component reference
RegionLayout
The parent container allocates space; a narrow window combines reading and input, and going back keeps the draft and list focus.
Preview
List, reading and input
render: () => (
<div style={{ height: "min(680px, calc(100dvh - 90px))", minHeight: 0 }}>
<PageFrame
header={
<PageHeader
title="Messages"
description="Named areas and content use the same shared layout."
/>
}
>
<div
ref={(host: HTMLElement) => {
const surface = mountRegionLayout(
host,
{
areas: [
["list", "thread"],
["list", "messages"],
["list", "compose"],
],
columns: ["compact", "fill"],
rows: ["content", "fill", "content"],
regions: {
list: { group: "browse" },
thread: { group: "detail" },
messages: { group: "detail", scrollOwner: "content" },
compose: { group: "detail" },
},
},
{
backLabel: "Back to list",
regionLabels: {
list: "Conversation list",
thread: "Current conversation",
compose: "Message input",
},
},
);
const draft = signal("");
const selected = signal(-1);
const mounts = [
render(
<Stack gap="compact">
{Array.from({ length: 35 }, (_, i) => (
<NavItem
label={`Conversation ${i + 1}`}
active={mapSignal(selected, (value) => value === i)}
onClick={() => {
selected.value = i;
surface.showGroup("detail");
}}
/>
))}
</Stack>,
surface.regions.list!,
),
render(
<strong>
Continuous review work · Long conversation names wrap naturally and do not widen
the layout
</strong>,
surface.regions.thread!,
),
render(
<ChatConversation ariaLabel="Message reading area">
{Array.from({ length: 40 }, (_, i) => (
<p>
Message {i + 1}: Content stays readable; history and drafts are not lost when
the window changes.
</p>
))}
</ChatConversation>,
surface.regions.messages!,
),
render(
<Input
aria-label="Message draft"
value={draft}
onInput={(event: Event) => {
draft.value = (event.target as HTMLInputElement).value;
}}
/>,
surface.regions.compose!,
),
];
return () => {
for (const mount of mounts) mount.unmount();
surface.dispose();
};
}}
/>
</PageFrame>
</div>
)Installation
bun add meowtail
Examples
The other stories that show this component.
Menus and choices in local content
Content only enters leaf regions; the shared menu, choices and focus still use real components.
render: () => (
<div
style={{ height: "320px" }}
ref={(host: HTMLElement) => {
const surface = mountRegionLayout(
host,
{ areas: [["content"]], regions: { content: {} } },
{ isolateContent: true },
);
const content = surface.regions.content!;
const shadow = content.getRootNode() as ShadowRoot;
const style = host.ownerDocument.createElement("style");
style.textContent = standardTheme.cssText;
shadow.append(style);
const removeTheme = applyTheme(content, standardTheme);
const selected = signal("Not selected");
const mounted = render(
<Stack align="start">
<Menu
id="isolated-menu"
trigger={(bindings) => <Button {...bindings}>Region actions</Button>}
items={[
{
id: "read",
label: "Read",
onSelect: () => {
selected.value = "Read";
},
},
{
id: "copy",
label: "Copy",
onSelect: () => {
selected.value = "Copy";
},
},
{
id: "more",
label: "More",
children: [
{
id: "detail",
label: "View details",
onSelect: () => {
selected.value = "Details";
},
},
],
},
]}
/>
<ChoiceSelect
id="isolated-choice"
options={[
{ value: "one", label: "First option" },
{ value: "two", label: "Second option" },
]}
/>
<span role="status">{selected}</span>
</Stack>,
content,
);
return () => {
mounted.unmount();
removeTheme();
surface.dispose();
};
}}
/>
)Narrow-window navigation beside content
Two columns on desktop; a narrow window keeps navigation and content in the same group. Changing region geometry does not rebuild the input or the category.
render: () => (
<div style={{ height: "calc(100dvh - 90px)", minHeight: 0 }}>
<PageFrame variant="workspace">
<div
ref={(host: HTMLElement) => {
const surface = mountRegionLayout(
host,
{
areas: [["navigation", "content"]],
columns: ["compact", "fill"],
gap: "compact",
regions: {
navigation: { compactRow: "content" },
content: { compactRow: "fill" },
},
},
{
regionLabels: {
navigation: "Category navigation",
content: "Category body",
},
},
);
const selected = signal(0);
const draft = signal("");
const mounts = [
render(
<Grid minColumnWidth="compact" gap="compact">
{Array.from({ length: 5 }, (_, i) => (
<NavItem
label={`Category ${i + 1}`}
active={mapSignal(selected, (value) => value === i)}
onClick={() => {
selected.value = i;
}}
/>
))}
</Grid>,
surface.regions.navigation!,
),
render(
<Stack gap="compact">
<strong>{mapSignal(selected, (value) => `Current category ${value + 1}`)}</strong>
<Input
aria-label="Category draft"
value={draft}
onInput={(event: Event) => {
draft.value = (event.target as HTMLInputElement).value;
}}
/>
{Array.from({ length: 20 }, (_, i) => (
<p>
Content {i + 1}: Changing the window only moves regions; the current category
and draft keep their identity.
</p>
))}
<Button>End action</Button>
</Stack>,
surface.regions.content!,
),
];
return () => {
for (const mount of mounts) mount.unmount();
surface.dispose();
};
}}
/>
</PageFrame>
</div>
)API reference
Props
This component documents no props of its own.
DOM parts
rootbackgridregioncontent
Registry
The component is registered as region-layout in the meowtail component registry.