Centrim Design System — a token-first, accessible React component system.
Forms & input groups
How the V2 controls are combined into complete forms: the Form foundation, FormField, field groups, input groups, sections, actions, validation, read-only and disabled fields, long forms and the multi-step foundation. Nothing here redesigns a control — it only composes them.
What is this?
A composition layer over the existing V2 controls: Form, FormHeader, FormSection, FieldGroup, FormLayout, FormField, ConditionalField, FormActions and the useMultiStepForm foundation.
When to use it
- Any screen that collects or edits data — page forms, dialog forms, drawer forms, settings.
- Multi-section records such as Create resident or Edit care plan.
- Review screens that mix editable and read-only information.
How to use it
- Wrap fields in Form and pick a density once; every section, group and field inherits it.
- One FormField per control: label, control, then helper or validation message.
- Group only logically related fields (first/last name, city/state/postal code).
- Put actions after the final field; go sticky only in long forms and overlay footers.
What to avoid
- New label styles, spacing values, controls or feedback components for a single form.
- Multi-column layouts for long or unrelated fields.
- Errors shown before the user has had a fair chance to complete the field.
Form foundation
Structure, rhythm and density for every V2 form.
Form
`<form>` wrapper that owns the vertical rhythm, the density scale and the submitting state. Density flows through context, so a form is spaced consistently without any per-field spacing overrides.
import { Form, FormHeader, FormSection, FieldGroup, FormField, FormActions } from "@/design-system";
When to use
- Every form, from a two-field dialog to a multi-section record.
- Whenever every control needs disabling at once, e.g. while submitting.
When not to use
- A single search box in a toolbar — use SearchInput on its own.
- Read-only detail pages with no editable control — use DescriptionList.
Density
Comfortable, default and compact — all three come from the 4px spacing scale. Default is the V2 baseline.
| Density | Block gap | Field gap | Label → control | Use for |
|---|---|---|---|---|
| Comfortable | 40px | 24px | 8px | Onboarding, low-frequency forms |
| Default | 32px | 20px | 6px | The V2 baseline for page and record forms |
| Compact | 20px | 12px | 4px | Dialogs, drawers, filter panels, settings rows |
FormField
One control, wired: label above, control, then helper text or a validation message. Generates ids and links label, description and message to the control via htmlFor, aria-describedby and aria-invalid.
import { FormField, ReadOnlyField } from "@/design-system";
When to use
- Every control that needs a label, helper text or validation.
- Controls whose own label props are not enough — checkbox rows, custom composites.
When not to use
- Around a control that already renders its own label (Select, DatePicker, InputGroup) — use that control's label prop instead.
States
Required, optional, helper text, error, warning, success, read-only and disabled.
Used for family portal invitations.
Error: Enter a valid email address.
Warning: This number has not been verified.
Success: Matched to an existing record.
Generated when the record is created.
Available once finance approves the placement.
Read-only information
ReadOnlyField presents a value as content, not as a broken control. Read-only means “view but not edit”; disabled means “this control is unavailable”.
Margaret Ellis
12 March 2026
Set by the admissions team.
FieldGroup
Groups closely related fields side by side and collapses them to a single column on mobile. Two columns by default, three for short address-style fields.
import { FieldGroup, FormLayout, FormLayoutFull } from "@/design-system";
When to use
- First name / last name, start date / end date, city / state / postal code.
- Fields a user reads as one answer.
When not to use
- Unrelated controls placed side by side only to shorten the page.
- Long inputs such as notes, addresses or rich text.
Name group
Address group (3 columns)
Form layout
FormLayout is the responsive field grid for a whole form body; FormLayoutFull spans a field across the row.
Input groups, prefixes & suffixes
Several controls that read as one field. Prefix and suffix content is text, not an icon — V2 inputs still carry at most one supporting icon, on the right.
InputGroup
Joins a field with prefixes, suffixes, selects and buttons inside one border, one height and one focus ring.
import { InputGroup, InputGroupField, InputGroupText, InputGroupSelect, InputGroupButton } from "@/design-system";
When to use
- A unit or symbol that belongs to the value: ₹, %, https://, minutes.
- A control that qualifies the value, such as a currency or a domain select.
- A single action bound to the field, such as Verify or Copy.
When not to use
- Decoration — never add a prefix icon just to fill the space.
- Unrelated actions; those belong in the form action row.
Prefix and suffix
Excludes optional activities.
Input + select and input + button
Sections, dependencies & actions
How long forms stay legible and how fields react to each other.
FormSection
A titled block of a longer form: title, optional description, fields and an optional divider. Sections carry the structure so extra boxes, cards and rules are unnecessary.
import { FormSection, FormHeader } from "@/design-system";
When to use
- Any form with more than roughly six fields.
- Records that combine distinct topics — resident details, address, care.
When not to use
- Short forms where a single group of fields already reads clearly.
- As a substitute for a multi-step flow that genuinely needs staged validation.
Dependent fields
Country → state and Service → service group. The dependent control stays visible and disabled with helper text — context is never removed.
Conditional fields
Fields that appear from a choice reveal in place, are announced politely, and use the standard motion tokens.
FormActions
The action row for a form: primary action last, secondary and cancel beside it, destructive actions separated. Buttons go full width on mobile so they stay reachable.
import { FormActions, Button } from "@/design-system";
When to use
- After the final field of a standard form.
- As a sticky footer only in long forms and dialog/drawer footers.
When not to use
- Floating the actions above content in short forms.
- Duplicating the primary action at both the top and bottom of a form.
Standard placement
Back / continue and destructive
Multi-step foundation
useMultiStepForm owns step state, completion and per-step status for the existing Stepper. It renders nothing itself, so a wizard is built from the same Form, FormSection, FormField and Button components as a single-page form.
import { useMultiStepForm, Stepper, Step } from "@/design-system";
When to use
- Workflows that genuinely need staged validation before the next stage is meaningful.
When not to use
- Long but linear forms — use sections instead of steps.
- Two-field forms in a dialog.
Three-step onboarding
- 1Current step
Resident
Who is being onboarded
- 2Upcoming
Care plan
Service and schedule
- 3Upcoming
Review
Confirm and submit
Validation & lifecycle
When errors appear, where they appear, and how completion is confirmed.
| Kind | When it runs | How it is shown |
|---|---|---|
| Required | On submit, and on blur once the field has been touched | FormField error message |
| Format (email, phone, postcode) | On blur, then live once the field is in error | FormField error message |
| Range (dates, numbers) | On change, because the control constrains the value | FormField error or warning |
| Cross-field (start ≤ end) | On submit and whenever either field changes after touch | Error on the field the user can fix |
| Server | On submit response | Alert above the form, plus field errors where the server maps them |
Form states
The complete lifecycle: default, filled, validation error, submitting, success and server error — all built from existing V2 feedback components.
import { Alert, SuccessState, useToast } from "@/design-system";
When to use
- Toast for a simple save that leaves the user in place.
- SuccessState for a completed workflow that replaces the form.
- Alert above the form for a server failure the user can retry.
When not to use
- Form-specific success banners or bespoke error boxes.
- Blocking errors shown before the first submit or blur.
Lifecycle
Forms in overlays
Dialog and drawer forms use the same system — only the density and the footer change.
Form in dialog & drawer
Overlay forms reuse Dialog/Drawer, FormField, the existing controls, Button and the same validation. The form body scrolls; the footer holds the actions and never covers a field.
import { Dialog, DialogContent, Drawer, DrawerContent, Form } from "@/design-system";
When to use
- Short, focused edits that keep the user in context.
- Side-by-side editing while a list or record stays visible (drawer).
When not to use
- Long, multi-section records — those belong on a page.
- Anything that needs a stepper inside a small dialog.
Dialog form
Drawer form
Complete example — Create resident
One realistic Centrim Life form using only V2 components: FormField, Input, Select, DatePicker, field groups, dependent fields, validation, required/optional and Button actions.
Why it is designed this way
Forms are the highest-traffic surface in Centrim Life, so the system standardises the combination, not the controls. Density, rhythm and label sizing come from context and tokens, which means a dialog form and a full-page record read as the same product without any per-screen spacing decisions.
Validation deliberately waits: nothing is flagged before submit or blur, and once a field is in error it validates live so the user sees their fix land. Errors sit against the field they block; only failures the user cannot attribute to a field — server errors — surface above the form.
Do
- Set density once on Form and let sections, groups and fields inherit it.
- Keep read-only values legible as content, and reserve the disabled style for unavailable controls.
- Keep dependent fields visible and disabled with helper text explaining what to answer first.
- Place actions after the final field; primary action last, cancel as a ghost button.
Don't
- Do not introduce a second label style, spacing value or feedback component for one form.
- Do not group unrelated fields into columns to shorten the page.
- Do not show validation errors before the user has had a fair chance to complete a field.
- Do not add a sticky footer to a form that fits on one screen.