Edit Layout
Arrange the form body, group inputs, and place the toolbar.
<Edit> renders a title, a header actions slot, and its children —
the form. Because inputs compose directly as children, the layout is plain JSX:
wrap inputs in your own containers, split them into columns, or group them into
tabs and steps. The children mount once the record has loaded.
The form body
The simplest form is a stack of inputs followed by a toolbar:
import { Edit, TextInput, Toolbar } from "@chelone/react";
<Edit>
<TextInput source="title" />
<TextInput source="body" multiline />
<Toolbar />
</Edit>;Wrap inputs in ordinary layout elements to shape the form — Chelone imposes no form container:
import { Edit, NumberInput, TextInput, Toolbar } from "@chelone/react";
<Edit>
<div className="grid grid-cols-2 gap-4">
<TextInput source="title" />
<NumberInput source="views" />
</div>
<TextInput source="body" multiline />
<Toolbar />
</Edit>;Tabs and steps
For long forms, <TabbedForm> splits inputs across tabs and <WizardForm>
across sequential steps. Both compose as <Edit> children in place of a plain
stack. Given no save prop, they drive the form <Edit> already built: the
record hydration, the schema validation and the save pipeline stay in effect.
Form options such as validate belong on <Edit>, not on the tabs or steps.
Neither renders a toolbar, so add one after them:
import { Edit, TabbedForm, TextInput, Toolbar } from "@chelone/react";
<Edit>
<TabbedForm>
<TabbedForm.Tab name="summary" label="Summary">
<TextInput source="title" />
</TabbedForm.Tab>
<TabbedForm.Tab name="body" label="Body">
<TextInput source="body" multiline />
</TabbedForm.Tab>
</TabbedForm>
<Toolbar />
</Edit>;Every tab stays mounted (inactive ones are hidden), so a single save submits
all of them and a failing input on a hidden tab still blocks the save. A tab
header shows a dot while its inputs are invalid. Tabs sync with the URL by
default, which makes them deep-linkable; pass syncWithLocation={false} to keep
the active tab in local state.
<WizardForm> works the same way, with <WizardForm.Step> children. It renders
no navigation: drive it with useWizardFormContext. next() validates the
current step and only advances when it passes:
import {
Edit,
SaveButton,
TextInput,
useWizardFormContext,
WizardForm,
} from "@chelone/react";
function StepNav() {
const { hasNext, hasPrev, next, prev } = useWizardFormContext();
return (
<div className="flex gap-2">
<button type="button" disabled={!hasPrev} onClick={prev}>
Back
</button>
{hasNext ? (
<button type="button" onClick={() => next()}>
Next
</button>
) : (
<SaveButton />
)}
</div>
);
}
<Edit>
<WizardForm>
<WizardForm.Step name="summary">
<TextInput source="title" />
</WizardForm.Step>
<WizardForm.Step name="body">
<TextInput source="body" multiline />
</WizardForm.Step>
<StepNav />
</WizardForm>
</Edit>;The toolbar
With no children, <Toolbar> always renders Save and Delete. Pass children to
replace them, or drop in a bare <SaveButton> if you want just the save action:
import { Edit, SaveButton, TextInput } from "@chelone/react";
<Edit>
<TextInput source="title" />
<SaveButton />
</Edit>;Because the default toolbar includes Delete, <Create> uses a bare
<SaveButton> too: there is nothing to delete yet.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| actions | ReactNode | — | Header actions (show/delete buttons); nothing by default. |
| children | ReactNode | — | |
| className | string | — | |
| component | ElementType<any, keyof IntrinsicElements> | — | |
| id | Identifier | — | Override the record id (defaults to the `:id` route param). |
| onSuccess | (record: TRecord) => void | Promise<void> | — | Runs after a successful save with the SERVER record, post-reconciliation. |
| resource | string | — | Override the ambient resource (defaults to the `<Resource>` context). |
| sanitizeEmptyValues | boolean | — | ra parity: nullify/strip empty TOP-LEVEL values before save. Default false. |
| schema | StandardSchemaV1<Partial<Record<string, unknown>>, unknown> | — | Resource schema (zod or any Standard Schema) — wired as the default form validator by the submit pipeline (T2). |
| title | ReactNode | — | Header title. A string/element renders as an `<h2>`; `false` omits it. |
| validate | FormLevelValidate<Partial<Record<string, unknown>>> | — | Form-level validate fn (ra contract) — consumed by the submit pipeline (T2). |
Tips
- The
componentprop swaps the element wrapping the children (default adiv) — pass aCardto frame the form. title={false}drops the heading;actions={false}drops the header slot.