Form Section

A titled group of fields, and the form that holds them.

Billing

Where we send your invoices.

import { Field, FieldControl, FieldLabel } from "@hel/ui/field"
import { FormSection } from "@hel/ui/form-section"
import { Stack } from "@hel/ui/stack"

When to use it

Use a form section when a form has more than a few fields and they fall into groups, like Profile, Contact and Billing. The title names the group, both visually and for screen readers, and you can turn off a whole group with one prop.

This page also covers Form, which wraps the sections and fields and handles errors that come back from a server.

Import

import { FormSection } from "@hel/ui/form-section"
import { Form } from "@hel/ui/form"

Form section

FormSection renders a fieldset. The title is its legend, which a screen reader reads as the name of the group when someone enters a field inside it, and description adds a line under it. It spaces its fields for you, so put each Field directly inside it.

disabled on a section turns off every field inside, which is handy for a part of the form people are not allowed to edit.

Folding a section away

With collapsible, the title becomes a button that opens and closes the fields. They stay on the page while closed, so their values and any errors are kept. defaultOpen sets the starting state, and open with onOpenChange controls it. It suits optional or advanced fields that most people can skip.

Customer

Who the invoice is for.

Optional. Most invoices don't need these.

import { Field, FieldControl, FieldLabel } from "@hel/ui/field"
import { FormSection } from "@hel/ui/form-section"
import { Stack } from "@hel/ui/stack"

Form

Form lays out its children in a column and sets noValidate, so the browser's own error bubbles don't compete with your messages. Pass noValidate={false} to bring them back.

The useful part is errors, an object of messages keyed by the name of a field. A field whose name matches is marked invalid and shows its FieldError. This is how an error from the server, such as "This email is already used", ends up under the right field. Give each Field a name, and put a FieldError in it with no children, so it shows the message from the form.

This email is already used.
import { Button } from "@hel/ui/button"
import { Field, FieldControl, FieldError, FieldLabel } from "@hel/ui/field"
import { Form } from "@hel/ui/form"

Putting it together

A settings page is a form with a few sections and a row of buttons at the end. The buttons sit in a Flex with justify="end".

Profile

Shown on your invoices.

Contact
import { Button } from "@hel/ui/button"
import { Field, FieldControl, FieldLabel } from "@hel/ui/field"
import { Flex } from "@hel/ui/flex"

Props

FormSection

PropTypeDefault
titleReactNode, the name of the groupnone
descriptionReactNode, a line under the titlenone
collapsiblebooleanfalse
defaultOpenbooleantrue
open / onOpenChangeboolean / (open: boolean) => voidnone
disabledboolean, turns off every field insidefalse

Form

PropTypeDefault
errorsRecord<string, string>, messages by field namenone
noValidatebooleantrue
onSubmit(event) => voidnone

Form also takes every prop of the Base UI form.