Boundaries
A boundary is a region of the DOM that has meaning to the application. Not just “this is a card” — that’s a component concern. But “this is the donation form on the campaign page for campaign abc123” — that’s product context.
<kit-boundary surface="campaign-page" feature="donations"> <kit-boundary surface="donation-form" entity-type="campaign" entity-id="abc123"> <kit-button meta-event="donation.started">Donate</kit-button> </kit-boundary></kit-boundary>A click on Donate produces:
{ type: 'donation.started', context: { surfaces: ['campaign-page', 'donation-form'], surface: 'donation-form', feature: 'donations', entity: { type: 'campaign', id: 'abc123' } }}The button doesn’t know any of those values. It just declares its event. The boundary does the rest.
What a boundary defines
Section titled “What a boundary defines”The four primary fields are conventional but not magical — you can add your own:
| Attribute | Meaning |
|---|---|
surface | The visible region of UI (“workspace”, “note-editor”, “settings-page”). Stacks into surfaces. |
feature | The product area (“notes”, “billing”, “design-system”). Inherited from parent if not redeclared. |
entity-type + entity-id | The current entity, becomes entity: { type, id }. Inherited if not redeclared. |
You can also add arbitrary context via the boundary’s runtime API:
const boundary = runtime.createBoundary({ context: { surface: 'editor', workspace: 'designs', permissions: ['note:write'], },})Any keys are kept as-is and propagated.
How surfaces stack
Section titled “How surfaces stack”Each surface adds to a stack. The deepest one is the current surface. The full list is in surfaces.
<kit-boundary surface="app"> <kit-boundary surface="workspace"> <kit-boundary surface="note-editor"> <kit-button meta-event="note.saved">Save</kit-button> </kit-boundary> </kit-boundary></kit-boundary>Event context:
{ surface: 'note-editor', surfaces: ['app', 'workspace', 'note-editor'],}This lets analytics or audit modules ask either question:
- What is the user looking at right now? →
surface - What’s the path that got them here? →
surfaces
How features and entities inherit
Section titled “How features and entities inherit”feature and entity cascade through the DOM tree. A child boundary that doesn’t redeclare them keeps the parent’s.
<kit-boundary surface="workspace" feature="notes"> <kit-boundary surface="note-list"> <!-- still feature: notes --> <kit-boundary surface="note-card" entity-type="note" entity-id="42"> <!-- feature: notes, entity: note/42 --> </kit-boundary> </kit-boundary></kit-boundary>Redeclaring overrides:
<kit-boundary feature="notes"> <kit-boundary feature="billing"> <!-- feature: billing now --> </kit-boundary></kit-boundary>When to add a boundary
Section titled “When to add a boundary”A useful test: would I want this context attached to events that happen here?
Add a boundary at:
- The application root (the
<kit-shell>itself usually carries one). - Each route or page.
- Each major surface (sidebar, main, dialog).
- Each form or widget that handles its own entity.
- Each list item that represents an entity (so events like
note.openedcarry which note).
Don’t add a boundary for:
- Layout-only wrappers.
- Components that don’t change product context.
- Performance reasons (boundaries are cheap, but the meaning of a redundant boundary is confusing).
A few well-placed boundaries beat many low-information ones.
Boundaries vs React Context
Section titled “Boundaries vs React Context”If you’re coming from React: a boundary is like a context provider, but it’s a real DOM element. That has consequences:
- Boundaries are visible in DevTools.
- Boundaries are inspectable with
document.querySelectorAll('kit-boundary'). - Boundaries are styleable with
[surface=note-editor] { ... }. - Boundaries don’t trigger framework re-renders when their context changes — there’s no virtual DOM.
- Boundaries cross framework lines: a Lit component and a React component inside the same boundary see the same context.
The trade-off: you can’t supply context to every descendant the way Context’s value flows through render. You can to the descendants that emit events or commands inside the boundary, which is the relevant case.
Boundaries in React
Section titled “Boundaries in React”The React adapter’s <KitBoundary> works the same way:
<KitBoundary surface="note-editor" entityType="note" entityId="42"> <button data-meta-event="note.saved">Save</button></KitBoundary>Plain HTML buttons with data-meta-* attributes participate. So do <kit-button> web components inside React. Both produce the same event.
Read next
Section titled “Read next”- Events and Commands — what travels through the runtime
- The Metadata Protocol — what attributes the boundary reads
- Reference: kit-boundary