Kanzo UI

Metadata form

Posting a contract to the board — grouped Cards, repeatable and nested fields, live serialisation, and a rule engine that only ever hands the library a boolean and a node.

Open full size

A realistic contract-posting screen at full viewport, the form a quartermaster fills before work and, like the discovery showcase, built as a Workspace on the Shell regions. ShellHeader carries ONE row: the title, the Contract switcher that says which posting this is, and then Share / Source / Output / validation / Preferences. The standing-orders switcher is not up there — it lives in the Source panel's own header, against the document it replaces. Below it a three-column Resizable workspace reads left→right exactly like the breadcrumb: the hall's standing orders (a leading ShellAside) → the editable form (ShellMain, the single <main>, groups The work · The posting · The terms · The party) → generated Output (a trailing ShellAside). Each side column toggles independently from its header button and is drag-resizable.

It exists to answer the question a single field never can: does a whole form hold together — required markers, repeatable rows, nested objects, dates, AI assistance and validation — without the library growing a domain. Switch the Data selector from Empty to Q-1041 to watch the header badge flip from 3 issues to Valid and the Output column reserialise live; open Source and Output together and drag the splitters to rebalance the three columns.

What it composes

Almost nothing here is new — it is existing parts, arranged:

  • ShellRoot / ShellHeader / ShellBody / ShellMain / ShellAside + Resizable — the Workspace frame. Exactly one <main>; the Source and Output columns are <aside> landmarks (ShellAside side="start" / side="end", logical and RTL-safe) rendered only when open, sized by a drag-resizable Resizable splitter keyed on the open-set.

  • Field · FieldLabel · FieldHelper · FieldError — every row. A required field shows a red * (FieldRequiredIndicator); an invalid one gets Field's own error channel.

  • FieldArray — the repeatable fields (what to expect, who to ask, the party), each row keyed by a stable id so editing never steals focus.

  • DatePicker — the posting and due dates. The ISO-string adapter the form wants is written out locally in the showcase, which is where that seam belongs.

  • AssistProvider + Assist — one provider around the form, and Assist around three controls: the title's Input (whole titles as chips under it), the notice's Textarea (a continuation at the caret, Tab to take it) and the Tags TagsInput (tags to add, never one already there). The control decides how the proposal is drawn; the showcase writes nothing per field but the value. The provider's context is the writ as it stands, read when a field asks, so a title is proposed against the notice the poster has written since.

    The model is told which field it is filling by the field itself — its label, and its description through aria-describedby. That is why every row's description is a FieldHelper rather than a FieldDescription: Ark wires the helper to the control and leaves a plain paragraph out, so a FieldDescription would be text the reader sees and the model does not. The model is @kanzo-tech/llm/testing's double answering by field name, so the ✨ works with no key and no network; localModel in its place changes nothing else.

  • Steps / Tabs — the form re-lays-out live as sequential Cards, Tabs, or a Steps wizard.

  • The preferences panel (extended) — the library's own PreferencesRoot + PreferencesPanel (its "Preferences [P]" drawer) is reused as-is and given custom children: a Layout SegmentGroup and display Switches that drive the ShellMain form live, above the library's own theme axes. Flat exports, per the RSC boundary.

  • HoverCard + Badge — the header validation summary, listing every failing field. The Output column is the generated writ and record, kept distinct from validation.

Where the domain lives

The library is domain-free, so the entire rule engine lives in data.tsx — a plain validate(values) that walks a posting and returns Issue[] with resolved, tri-state (violation / warning / info) messages. That is the forms decision made visible: the library models validation as a boolean + a ReactNode and nothing more, so a violation flows through Field's invalid + FieldError, while the non-blocking severities the library deliberately does not model are product-rendered nodes. Where the boolean came from — here a fake engine, in the product a real one over the whole archive — is never the library's business.

Source

docs/showcases/metadata-form/
├── default.tsx   — the Workspace: Shell chrome, the resizable Source/form/Output columns, the
│                   four groups, and the three live layouts driven by the Preferences drawer
└── data.tsx      — the rule engine, three seeded postings, the world's vocabularies,
                    the writ / record serialisers, and the faked model

Imports come from two packages: @kanzo-tech/ui for the form and chrome, and @kanzo-tech/ai for Assist — a sibling package, so a form with no ✨ installs no model SDK.