Centrim Design System — a token-first, accessible React component system.
Inputs & form controls
One reusable input system: text fields, native input types, selects, multi-select, textareas, icon affordances, validation messaging and three sizes — every example renders the production component.
What is this?
The V2 data-entry layer. Every control shares one chrome (border, radius, focus ring, placeholder colour), one label and messaging structure, and one set of three sizes, so a form built from any mix of them reads as a single system.
When to use it
- Any field where a person types, picks or adjusts a value.
- Forms that need consistent validation messaging across control types.
- Dense enterprise tables and toolbars (small size) as well as focused wizards (large).
How to use it
- Always pass `label`; use `aria-label` only inside composed groups.
- Express state with `errorText`, `warningText` or `successText` — never colour alone.
- Reach for the typed input (EmailInput, DateInput…) instead of a bare `type` attribute.
- Input fields use a maximum of one supporting icon, positioned on the right. Left-side supporting icons are not used in the V2 input system.
What to avoid
- Left-side icons, two icons on one field, or an icon that repeats a native control.
- One-off className overrides for height, padding or border colour.
- Placeholders used as labels.
- Helper text carrying required information that should be a description or error.
Standardized values
Every control resolves these from V1 tokens. No new token candidates were required for the input system.
| Property | Small | Medium | Large | Token |
|---|---|---|---|---|
| Height | 32px | 40px | 48px | --ds-space-8 / 10 / 12 |
| Horizontal padding | 12px | 12px | 12px | --ds-space-3 |
| Input typography | body-sm | body-md | body-md | --ds-text-body-* |
| Label typography | label-md | label-md | label-md | --ds-text-label-md |
| Label spacing | 6px | 6px | 6px | Field gap (--ds-space-1.5) |
| Icon size | 16px | 16px | 16px | --size-icon-sm |
| Icon inset (right only) | 16px inset | 16px inset | 16px inset | pr-10 |
| Icons per field | max 1, right | max 1, right | max 1, right | V2 input icon rule |
| Border | 1px | 1px | 1px | --ds-border-width-thin |
| Border radius | 8px | 8px | 8px | --ds-radius-md |
| Focus ring | 2px offset ring | 2px offset ring | 2px offset ring | ds-focus-ring |
| Placeholder | tertiary text | tertiary text | tertiary text | --color-text-tertiary |
| Disabled | muted surface + disabled text | — | — | --color-background-muted |
| Error / warning / success | border + icon + message | — | — | --color-error / warning / success |
Text inputs
The single-line field every other text control is built from: label, optional description, helper text and validation messaging in a fixed vertical rhythm.
import { Input } from "@/design-system";
When to use
- Short free-text values
- Identifiers, names, references
When not to use
- Multi-line content — use Textarea
- Choosing from a known set — use Select
States
Default, placeholder, filled, readonly, disabled, required, optional, error, success and warning.
As it appears on file.
Read-only.
Enter a valid email address.
Username is available.
Number looks incomplete — we could not verify it.
Focus
Focus the field to see the 2px offset ring.
Input types
Typed wrappers over the same control, so keyboards, pickers and validation come from the platform while chrome and messaging stay identical.
import { EmailInput, DateInput, NumberInput } from "@/design-system";
Every supported type
Minimum 12 characters.
Selects & dropdowns
Single-choice controls on Radix Select, plus a searchable Combobox for long lists. Trigger chrome matches Input exactly.
import { Select, Combobox } from "@/design-system";
When to use
- A known, finite option set
- Values that benefit from icons or grouping
When not to use
- Two options — use a switch or segmented control
- Free text — use Input
Single select, icons, helper text, disabled, error and search
Applies from the next rota period.
Choose an approver.
Multi-select
Typeahead multi-choice control that renders selections as removable chips, with an optional select-all row.
import { MultiSelect } from "@/design-system";
Multi-select
Selected people receive the handover note.
Select at least one person.
Textarea
Multi-line field sharing the Input chrome, with an optional description, a politely-announced character counter and three sizes.
import { Textarea } from "@/design-system";
Textarea states and sizes
Visible to the family portal.
A reason is required to continue.
Input icons
Input fields use a maximum of one supporting icon, positioned on the right. Left-side supporting icons are not used in the V2 input system.
import { Input, SearchInput, InputGroup } from "@/design-system";
When to use
- Password (Eye / Eye-off), Email (Mail), URL (Link), Search (Search), Telephone (Phone).
- Date, date & time, month and week (Calendar); time (Clock).
- Any field where a single affordance clarifies the expected value or offers one action.
When not to use
- Text, number and range fields — they stay icon-free; steppers and the range thumb are the control itself.
- Colour fields — the swatch is the control; no second supporting icon.
- Anywhere a native picker already draws its own calendar, clock or spinner affordance.
Icon configurations
Icon, spinner and clear share one slot.
The toggle is the field's one icon.
Do
- Use at most one supporting icon per field, inside the field on the right.
- Leave fields that need no affordance completely icon-free.
- Let the clear control, spinner or visibility toggle occupy that single right slot.
Don't
- Place a supporting icon on the left of an input.
- Show a left and a right icon on the same field.
- Stack two icons on the right, or repeat an icon the native control already provides.
Correct — one icon, right
Incorrect — do not build these
Value typography
Weight, style, case and alignment of the typed value, for data that is genuinely cased or numeric.
import { Input } from "@/design-system";
When to use
- Reference codes shown uppercase, email addresses shown lowercase.
- Money and quantities right-aligned so digits line up.
- One emphasised value in a form that otherwise reads evenly.
When not to use
- As decoration — emphasis loses meaning when every field has it.
- To normalise data; casing on save is a data rule, not a visual one.
Weight, style, case and alignment
Do
- Use uppercase or lowercase only where the data itself is cased that way — codes, emails.
- Right-align numeric values so digits line up down a column.
- Keep bold for a single value that carries the decision.
Don't
- Bold or italicise every field — emphasis stops meaning anything.
- Use a case transform to fix data that should be normalised on save.
- Centre ordinary text; only short codes read well centred.
Helper text emphasis
Helper copy can sit left, centred or right under a field, and step up to a tinted or solid label when the consequence must be noticed.
import { Field, FieldLabel, FieldHelperText } from "@/design-system";
When to use
- Character counters aligned to the end of the field.
- A visibility or consequence note that people routinely miss.
When not to use
- Validation results — those belong in FieldMessage.
- More than one emphasised helper per section.
Alignment and emphasis
Quiet supporting copy, aligned to the field.
0 / 240 characters
This field is visible to the resident's family.
Changing this re-issues every invitation.
Do
- Keep the plain emphasis for almost every hint.
- Right-align a helper only when it is a counter tied to the field's end.
- Reserve solid emphasis for a consequence the person must not miss.
Don't
- Use a tinted helper to report validation — that is FieldMessage.
- Put more than one emphasised helper in the same form section.
- Rely on the tint alone; the sentence must still say what happens.
Labels, helper text & validation
Four message slots with one job each — and one rule: state is never colour-only.
| Slot | Prop | Rendered as | Use for |
|---|---|---|---|
| Label | label | FieldLabel + required / optional marker | The name of the field |
| Description | description | Helper text above the control | Context needed before typing |
| Helper text | helperText | Caption below the control | Format hints, consequences |
| Error | errorText | Alert icon + red caption, role=alert | Blocking validation failure |
| Warning | warningText | Triangle icon + amber caption | Accepted but questionable value |
| Success | successText | Check icon + green caption | Confirmed valid value |
Format: CL-00000.
Postcode could not be verified.
Why messages carry an icon
WCAG 1.4.1 forbids colour as the only carrier of meaning. Every validation message pairs a token colour with an icon and explicit wording, so the state survives greyscale, low vision and colour-blind viewing.
Sizes
Small for dense tables and toolbars, medium as the default, large for focused single-task forms.
In context
A production form built only from the components above — no one-off styles.
New resident visit
Everyone selected receives the visit summary.
Included in the family portal update.
Do / Don't
Do
- Give every field a visible label, and mark required fields with the built-in indicator.
- Use the typed inputs (EmailInput, DateInput, NumberInput) so keyboards and pickers match the data.
- Keep one size per form region — mixing sizes in one column breaks the rhythm.
- Pair error colour with the icon and a sentence that says how to fix the value.
Don't
- Don't replace a label with a placeholder — it disappears the moment typing starts.
- Don't override height, padding or border colour with utility classes on a single instance.
- Don't communicate an error with a red border only.
- Don't disable a field to make it read-only — use `readOnly` so the value stays legible and copyable.