---
title: Composition
description: Write the domain surface yourself over the flow hook, place its pieces in your own layout, and add a domain through the zone picker your accounts already reach.
sidebar:
  label: Composition
seo:
  title: Compose a DomainKit React surface
---

`Domain.useFlow` returns everything a surface needs and renders nothing, so the markup is yours from
the first element. The [registry block](/components/registry/domain-flow) is one such surface, on
shadcn; this page is the shape of writing another.

## What the flow hands you

| Field                     | Carries                                                              |
| ------------------------- | -------------------------------------------------------------------- |
| `state`                   | What DomainKit has to say about the domain, as plain fields          |
| `connection`              | The connect controller: `connect`, `reuse`, `detach`, `disconnect`   |
| `provisioning`, `cleanup` | The plan, approve, apply machine for adding and for removing         |
| `verification`            | The observer, with the readiness it last read                        |
| `plan`                    | The plan still awaiting its apply, which is what the rows report     |
| `readiness`               | The last observation, which is what the rows report after an apply   |
| `capabilities`            | The groups the transport declares, so nothing offers a step it lacks |
| `requirements`, `domain`  | What the host asked for, and for which domain                        |

## One row's standing

`Records.standingOf` answers both facts a row carries about one record, and leaves the surface to
choose which of them its column is about: `planned` is the operation a plan still awaiting its apply
holds for that record, and `observed` is the requirement the last observation stored for it. Either
is `null` when nothing covers the record.

<Snippet file="examples/react/controllers.tsx" region="hooks" title="A surface over the hooks" />

## The one action

`provisioning.approve()` authorizes the plan's digest and applies it in the same call. Name the
operation ids to approve part of a plan, which is how a blocked record is left out while the rest
goes in; approving a whole plan that holds a conflict is refused.

<Snippet file="examples/react/controllers.tsx" region="commands" title="Approve and decline" />

## Adding a domain

`Connect.useZones` lists every zone the workspace's accounts reach, and `Connect.useDomainField`
turns one text input into a combobox over them: it returns the props for the input, the listbox, and
each option, moves the highlight with the arrow keys, and completes on Tab or Enter while keeping
whatever subdomain was typed in front of the zone.

<Snippet file="examples/react/add-domain.tsx" region="field" title="The zone picker" />

<Component path="registry/domain-field" />

A domain outside every connected zone leaves the input a plain text field, because the customer will
add those records by hand. `onResolve` reports the account a value settles in, so a form submits the
domain and its connection together.

<Snippet file="examples/react/add-domain.tsx" region="accounts" title="Connecting an account" />

Connecting an account names no domain: what the customer grants is the account itself.
`Connect.describeMethods` arranges one provider's methods into the interactive ones a customer
clicks through and the ones that ask for credentials, so a surface asks for one decision at a time.

## Readiness the host holds

By default the flow observes on mount and re-observes at each `nextCheckAt`, which is what a host
with no clock of its own wants. A host that already observes — a cron, a durable job, a server
render that read `Verify.latest` — passes what it holds as `verification` instead. The flow then
makes no observation and sets no timer, `flow.readiness` is the supplied value, and
`flow.verification.observe` and `retry` call the host's own. Drift replanning runs off it exactly as
it does off an observation the flow made.

<Snippet
  file="examples/react/host-readiness.tsx"
  region="host-readiness"
  title="A surface the host feeds"
/>

`Verify.useController` takes the same thing as `supplied`, for a surface built on that controller
alone. `Verify.summary` counts a readiness, or `null`, into `{ observed, total, satisfied, missing,
mismatch, unknown }`.

## Read-only

`readOnly` on `DomainKit.Root`, or on one `Domain.useFlow`, refuses every command that would change
the domain, in the controller rather than in your markup, and reports itself on
`flow.state.readOnly`. A surface reads it and says who may connect rather than rendering an empty
row.

<Snippet file="examples/react/read-only.tsx" region="hook" title="A surface that knows the mode" />

`DomainKit.useReadOnly()` answers for a component of your own, and `DomainKit.ReadOnly` narrows one
subtree without touching the rest of the page.
