2.0 alpha
These docs describe 2.0. Install it with @once-ui-system/core@alpha.
On 1.8.x? Read the 1.x docs
TrademarkTrademarkDocs
Ctrl k
Search docs...
Form Controls

Form

Updated: September 21, 2026
View on GitHub
The Form component lays a set of fields out and decides how they read together. density carries the gap and the grouping as one choice, because they are one choice: "stacked" fuses the fields into a single control, while "tight" and "spacious" leave each one its own. A stacked group is often built by passing corners="top", "none" and "bottom" down a list. That is positional bookkeeping, and it breaks the moment the list changes: hide the third of four fields behind a condition and the group ends on a square edge, because the field that was told it was in the middle is now last. Form works the other way round. It looks at which children actually rendered, works out where each one lands in the grid, and rounds only the corners that end up on the outside of the group. Nothing has to be told its position. "stacked" collapses the gap to nothing and pulls adjacent borders onto a single shared hairline. "tight" and "spacious" are ordinary gaps, and every field keeps its own border and radius. columns lays the fields out in a grid. A field can take more than one column with span, which Form reads off the child and strips before it reaches the DOM. A last row that does not fill is handled too. Three fields in two columns make an L, and the second field keeps its bottom-right corner because that corner is on the outside of the group — even though it is not the last child. columns also takes a per-breakpoint map. The column count changes with the viewport, and so does which field sits in a corner — so Form works the placement out for every breakpoint and lets CSS pick. Nothing is measured and nothing re-renders on resize. size sets the size of every field in the group. A field that sets its own size keeps it. It travels by context rather than being cloned onto the children, so a child that is not a field picks up nothing. In a stacked group the fields overlap by a pixel, so a focused field has to paint above its neighbours — all four of them in two dimensions. Form lifts the focused cell for you. Turn the ring itself on with focusRing on the fields, which is worth doing for a form a keyboard user has to get through. Form rounds the corners of children that mark their own bordered box as a fusable surface. Input, Textarea and the fields built on them do. Anything else is laid out in the grid and spaced normally, but keeps its own corners — so a Button or a block of text can sit in a form without being absorbed into the group. Pass as="form" to render a real <form> and take onSubmit.
Prop
Type
Default
density
Gap and grouping as one decision. "stacked" fuses the fields into a single control; "tight" and "spacious" leave each its own border and radius.
stackedtightspacious
tight
columns
Grid columns — a number, or a per-breakpoint map like { xs: 1, m: 2 }.
numberPartial<Record<Breakpoint, number>>
size
Size for every field in the group. A field that sets its own size keeps it.
xllmsxs
as
Render a real <form> rather than a <div>.
divform
div
onSubmit
Submit handler. Only attached when as is "form".
React.FormEventHandler<HTMLFormElement>
className
string
style
React.CSSProperties
children
The fields. Conditional children that render nothing are ignored, and the corners follow.
React.ReactNode
…and every React.HTMLAttributes propinherited
Edit this page on GitHub
TrademarkTrademark
Built with curiosity by Lorant One and the Design Engineers Club