Skip to content

Regions

Region is a resizable, draggable, labeled box a host can place other shapes inside of — the generic shell behind a “group” or “area” affordance on a board. Like Note, it is presentational and fully controlled: it owns no position, size, or name state of its own, and reads its region prop fresh every render.

import { useState } from 'react';
import { Canvas, CanvasItem } from '@cratis/components/Canvas';
import { Region, type RegionData } from '@cratis/components/Canvas';
function Board() {
const [region, setRegion] = useState<RegionData>({
id: 'planning', x: 40, y: 40, width: 400, height: 260, name: 'Planning',
});
const [selected, setSelected] = useState(false);
return (
<Canvas>
<CanvasItem x={region.x} y={region.y}>
<Region
region={region}
selected={selected}
onSelect={() => setSelected(true)}
onMove={(id, x, y) => setRegion(current => ({ ...current, x, y }))}
onResize={(id, x, y, width, height) => setRegion(current => ({ ...current, x, y, width, height }))}
onNameChange={(id, name) => setRegion(current => ({ ...current, name }))}
/>
</CanvasItem>
</Canvas>
);
}

Only a drag started on the region’s title bar moves it — a press on the region’s body deliberately bubbles up to the host instead, so the same background can be swept for a rubber-band selection or clicked to select the region itself, rather than the region swallowing every gesture that lands on it.

FieldTypeDescription
idstringIdentifies the region across renders
x / ynumberWorld-space position
width / heightnumberSize in world-space units
namestringThe label shown in the title bar
PropDescription
region: RegionDataThe region to render
selected: booleanWhether the region shows its selection outline and resize handles
onSelect(id, additive)The title bar was clicked/pressed. additive reports a shift/meta/ctrl modifier, for multi-select
onMove(id, x, y)Fired continuously while dragging from the title bar
onMoveEnd?(id)Fired once when a drag ends
onResize(id, x, y, width, height)Fired continuously while resizing from any of the eight handles
onResizeEnd?(id)Fired once when a resize ends
onNameChange(id, name)Fired when a rename is committed (double-click the title bar to rename)
children?Rendered inside the region’s own coordinate space, for visually nesting other shapes inside its bounds

children is visual nesting only — membership is detected and reported, never owned

Section titled “children is visual nesting only — membership is detected and reported, never owned”

Region renders its children positioned relative to its own top-left corner, so items placed at region-relative coordinates line up correctly inside it:

<CanvasItem id={region.id} x={region.x} y={region.y}>
<Region region={region} selected={false} onSelect={() => {}} onMove={() => {}} onResize={() => {}} onNameChange={() => {}}>
{/* Rendered inside the region's own coordinate space */}
<div style={{ position: 'absolute', left: 20, top: 40 }}>
<Note note={noteInsideRegion} selected={false} onSelect={() => {}} onMove={() => {}} onResize={() => {}} onTextChange={() => {}} />
</div>
</Region>
</CanvasItem>

Rendering something as children does not make it a “member” — the two mechanisms are independent. What Region does do is detect containment: it watches the Canvas item registry, and whenever a sibling CanvasItem that carries an id gains or loses containment (its center point entering or leaving the region’s bounds — whether the item moved, or the region was moved/resized over it), it publishes ItemAddedToRegion / ItemRemovedFromRegion over the @cratis/arc.react messenger. See Messaging for the full catalog and the opt-in rules; without an ArcContext above the Canvas, or for items without an id, this is silently inert.

Note the required convention in the snippet above: the CanvasItem wrapping a Region is given id={region.id}. That is how the region recognizes its own registry entry and excludes itself from its containment reports. Overlapping regions may each claim the same item — both publish an ItemAddedToRegion — and that is by design: resolving exclusivity is the host’s call.

Detection is where it ends: Region still never moves members, persists nothing, and knows nothing about item types. It does not move contained items along with it when dragged or resized, and it holds no membership state a host could query. Deciding what membership means, storing it, rendering members as children, and keeping them moving together is board-level orchestration the host owns entirely. This is a deliberate design boundary, not a missing feature: a generic shell that reports what it sees is reusable in ways a shell with its own opinion about membership would not be.