Form
A form = a schema (zod 4) + the package's fields, on TanStack Form v1: each error shows once the field is left and goes as soon as it is fixed, in the person's language (i18n keys, never the engine's English), a value can be checked on the server as it is typed, and the server's refusals land under their fields — web and React Native, the same schema.
Web et mobile — le même composant pour React et React Native. Bêta : son API peut encore changer à une version mineure.
Quand l’utiliser
- Any form that sends something: sign-up, profile, upload details, an amount, an audience, a report, settings.
- When the server must re-check the same rules: the schema is shared (a product keeps it in
packages/core) and@krizaka/ui/form/serverparses with it in a Server Action. - When a value depends on the server (a username's availability):
validateAsyncon the field, debounced and aborted.
Quand ne pas l’utiliser
- For a single search box that filters as you type: no submit, no validation.À la place : Search field
- For a filter bar over a list: its state is the address, not a form.À la place : Filter bar
- For a setting that applies at once on its own (a theme switch): the control alone.À la place : Switch
Installation
Installer
npm install @krizaka/ui @krizaka/tailwind @krizaka/tokens tailwindcssStyles
@import "tailwindcss";
@import "@krizaka/tailwind";
@import "@krizaka/ui/tailwind.css";Importer
import { createKrizakaForm, useFormWithAction, latestBirthDate, useKrizakaField, formMessage } from "@krizaka/ui/form";Exemples
Sign-up
Orochia's sign-up: a username checked as it is typed with a free one offered, an 18+ date of birth typed as day · month · year, a server refusal on its field.
Profile
Saved by a mutation of @krizaka/query: categories, tags, a list of links that grows (form.FieldArray).
Upload
A video and what describes it — the file, a title, a description, tags, who can watch — sent together.
Amount
A tip stored in cents (Money), typed the locale's way; presets set the field from outside.
Audience
Cards to choose who watches, the price only for subscribers (form.Watch), a publication date.
Settings
Sent by a Server Action (useFormWithAction): switches, a menu, a slider, a choice.
Report
One reason among a few, details, and blocking the account in the same step.
Props
Checkbox
A box of the form (boolean): terms to accept, an option.
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | ReactNode | — | The words next to the box, clickable with it — text, or a sentence with links (terms, privacy). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
Combobox
A choice among many, typed to filter or fetched: one value (string) or several (string[]).
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
className | string | — | Classes merged on the field's root. |
creatable | boolean | — | Offers to create the query as a value. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
labels | Partial<ComboboxLabels> | — | The words of the control, passed translated. |
multiple | boolean | — | Several values (string[]), shown as chips; one (string) by default. |
onSearch | ((query: string, signal: AbortSignal) => Promise<readonly ComboboxOption[]>) | — | Fetches the options for a query (debounced, aborted by a newer one). |
options | readonly ComboboxOption[] | [] | The options, filtered as the person types. |
placeholder | string | — | The input's placeholder, passed translated. |
DateField
A date of the form (YYYY-MM-DD, "" for none): a calendar, or day · month · year typed in the locale's order.
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
autoComplete | "off" | "bday" | — | bday for a date of birth. |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
entry | "calendar" | "parts" | calendar | calendar (default: a date near today — a publication, an appointment) or parts (day · month · year typed: a
memorable date, a birth date). |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
labels | (Partial<DatePickerLabels> & Partial<Record<DatePart, string>>) | — | The words of the calendar, or the names of the boxes (day, month, year), passed translated. |
max | string | — | The latest date (ISO), for the calendar (latestBirthDate(18) for an age gate). |
min | string | — | The earliest date (ISO), for the calendar. |
FieldArray
A list of fields that grows and shrinks (links, tiers, guests): each item's fields named links[0].url.
| Prop | Type | Défaut | Description |
|---|---|---|---|
childrenrequis | (array: KrizakaFieldArray<unknown>) => ReactNode | — | Draws the items: items, push, remove, move, fieldName(i, "url") for the fields of an item. |
namerequis | string | — | The array's name in the schema (typed by the form). |
FileUpload
Files of the form (File[]): a drop zone, then the chosen files, each removable.
| Prop | Type | Défaut | Description |
|---|---|---|---|
browseLabelrequis | string | — | The button that opens the file picker, passed translated. |
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
removeLabelrequis | (fileName: string) => string | — | The name of a file's remove button ((name) => "Remove " + name), passed translated. |
titlerequis | string | — | The title in the zone ("Drop your video here"), passed translated. |
accept | string | — | The types accepted (video/*, image/png,.pdf). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | What is accepted, in words ("MP4 or MOV, up to 4 GB"). |
maxFiles | number | — | The most files. |
maxSize | number | — | The largest file, in bytes. |
multiple | boolean | — | Several files (File[], up to maxFiles); one by default (still a File[] of one). |
refusalLabel | ((rejection: FileRejection) => string) | — | The words of a refused file ((r) => r.file.name + " is too large"). |
FormError
The whole form's error — a refusal of the server that belongs to no field (a conflict, too many tries, no network).
| Prop | Type | Défaut | Description |
|---|---|---|---|
className | string | — | Classes merged on the alert. |
title | string | — | A headline above the message ("We could not create your account"), passed translated. |
FormField
A field drawn by the app (a control the package does not have): the form's value, errors and ids, any markup.
| Prop | Type | Défaut | Description |
|---|---|---|---|
childrenrequis | (field: KrizakaField<unknown>) => ReactNode | — | Draws the field: its value, setter, error and ids (useKrizakaField). |
namerequis | string | — | The field's name in the schema (typed by the form). |
asyncDebounceMs | number | — | The wait, in ms, before validateAsync. |
validateAsync | FieldCheck<unknown> | — | Checks the value on the server as it changes. |
Money
An amount of the form, stored as integer minor units (cents: 1250 = 12.50), typed the locale's way.
| Prop | Type | Défaut | Description |
|---|---|---|---|
currencyrequis | string | — | The currency (ISO 4217: USD, EUR, JPY): its sign before the amount, its decimals. |
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
placeholder | string | — | An example of an amount, passed translated. |
RadioGroup
One choice among a few, all visible.
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
optionsrequis | readonly FormOption[] | — | The choices, all visible (two to five). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
orientation | "horizontal" | "vertical" | vertical | vertical (default) or horizontal. |
variant | "list" | "cards" | list | list (a dot and its words, default) or cards (a whole card is the radio: an amount, a plan, an audience). |
Select
One choice among many, in the platform's menu (<select>: accessible and right on phones).
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
optionsrequis | readonly FormOption[] | — | The choices, in the platform's own menu (a country, a category). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
placeholder | string | — | The first line while nothing is chosen ("Choose a category"), passed translated. |
Slider
A number (or a range) of the form, chosen by feel: a share, a volume, a price range.
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
formatValue | ((value: number) => string) | — | The value as words ("15 %", "3 min"), shown and read. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
max | number | — | The highest value. Default 100. |
min | number | — | The lowest value. Default 0. |
step | number | — | The step. Default 1. |
thumbLabels | [string, string] | — | For a range ([number, number]): each thumb's name, passed translated. |
Submit
The submit button: busy while the form is sent (not pressable twice), never disabled for an invalid form.
| Prop | Type | Défaut | Description |
|---|---|---|---|
announce | string | — | A message announced politely by assistive technology when it changes (“Saved”, “Upload failed”) — passed translated. |
asChild | boolean | — | Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. |
indicator | boolean | true | Shows the glyph while loading / with a status. false: you draw your own. |
magnetic | boolean | — | The button leans a few pixels toward a fine pointer (driven by MotionObserver; never under reduced motion or on touch). |
progress | number | null | — | Between 0 and 1, while loading: the glyph's trace and a light fill across the button grow to the value (an
upload, an export). |
shape | "rounded" | "pill" | "cut" | rounded | rounded · pill · cut (45° corners, the Krizaka chamfer). |
shimmer | boolean | — | A sheen of light crosses the button on hover — for the one action a page is about. |
size | "icon" | "sm" | "md" | "lg" | md | sm · md · lg, or icon (a square). |
status | "success" | "error" | null | — | How the action ended: success draws a check, error a cross (and the button shakes its head once); a ring of
the status colour opens around it. Clear it to return to rest. |
variant | "danger" | "primary" | "gradient" | "secondary" | "outline" | "ghost" | primary | The look: primary (the accent), gradient (accent → accent-2), secondary (a raised surface), outline,
ghost, danger. |
Switch
An on/off setting of the form (boolean): the label and its hint beside the switch.
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
TagInput
Free words of the form as chips (string[]): tags, keywords.
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
labels | Partial<Pick<ComboboxLabels, "results" | "toggle" | "remove" | "empty" | "create">> | — | The words of the field, passed translated; English by default. |
max | number | — | Most tags; the field stops offering more past it. |
normalize | ((tag: string) => string) | — | Cleans a word before it is added ((t) => t.toLowerCase().replace(/^#/, "")). An empty result is ignored. |
placeholder | string | — | The input's placeholder, passed translated. |
separators | readonly string[] | — | Keys that add the word, besides Enter. Default [","]. |
suggestions | readonly string[] | — | Words offered as the person types (their own history, a curated list). |
TextArea
A longer text of the form (a bio, a description, a report).
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
maxLength | number | — | The most characters. |
placeholder | string | — | An example of a value, passed translated. |
rows | number | 4 | The visible lines. Default 4. |
TextField
A text field of the form: label, input, hint or error, an optional check on the server.
| Prop | Type | Défaut | Description |
|---|---|---|---|
labelrequis | string | — | The visible label, passed translated (also the control's accessible name). |
namerequis | string | — | The field's name in the schema: email, address.city, links[0].url (typed by the form). |
asyncDebounceMs | number | — | The wait, in ms, between the last key and validateAsync. Default 350. |
autoComplete | string | — | The browser's autofill (email, username, new-password, name). |
availableHint | string | — | The words once validateAsync accepted the value ("@eric is free"), passed translated. |
checkingLabel | string | Checking | The name of the wait while it checks ("Checking"), passed translated. |
className | string | — | Classes merged on the field's root. |
disabled | boolean | — | Not available. |
hint | string | — | A help text under the control, read with it; the error takes its place when there is one. |
inputMode | "none" | "search" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | — | The keyboard on a phone (numeric, decimal). |
maxLength | number | — | The most characters. |
placeholder | string | — | An example of a value, passed translated — never the label. |
start | ReactNode | — | Something before the text (an @, an icon). |
transform | ((raw: string) => string) | — | Cleans what is typed before it is stored ((v) => v.toLowerCase().replace(/\s+/g, "_")). |
type | "search" | "text" | "tel" | "url" | "email" | "password" | text | text (default), email, password, url, tel, search: the right keyboard on a phone. |
validateAsync | FieldCheck<string> | — | Checks the value on the server as it is typed (availability): an error key, or nothing when it is fine. |
Watch
Renders from a field's current value (a part shown only when a box is ticked, a total).
| Prop | Type | Défaut | Description |
|---|---|---|---|
childrenrequis | (value: unknown) => ReactNode | — | Draws from its current value: show a part of the form only when a box is ticked. |
namerequis | string | — | The field to follow (typed by the form). |
Accessibilité
- Every control is named by its visible label; its hint and its error are in
aria-describedby, and an invalid control hasaria-invalid. - Errors are announced (
role="alert"); a refused submit focuses the first invalid field on the web and announces the first error on native. - An availability check names its wait (
checkingLabel) and says when the value is free (availableHint): never by colour alone. - A date of birth is three labelled boxes in the locale's order with numeric keypads and
bdayautofill, not a calendar to scroll through decades.
Clavier
| Touches | Action |
|---|---|
| Enter | Sends the form from a text field (web); the keyboard's return key moves to the next field (native). |
| Tab | Leaves a field: from then on its error shows, and goes as soon as it is fixed. |
Bonnes pratiques
- Define the form once (
createKrizakaForm({ schema, defaultValues })) beside the API's contract; use it in components withform.useForm({ onSubmit }). - Name refusals with keys in the schema (
z.string().regex(re, "signUp.username.format")) and give the words inmessages/*.json; the generic ones areform.errors.*(required, tooShort {minimum}…). - Send with a mutation of @krizaka/query (
onSubmit: (v) => mutation.mutateAsync(v)) or a Server Action (useFormWithAction): a thrownKrizakaApiErroror{ ok: false, problem }puts itsfieldsunder their fields. - Never disable the submit button for an invalid form: a refused submit shows every error and focuses the first.
- Pick the field for the value:
DateField entry="parts"for a memorable date (a birth date), the calendar for a date near today;Moneystores cents;RadioGroupfor two to five visible choices,Selectbeyond. - Need a control the package does not have?
form.Fieldgives its value, setter, error and ids — never import @tanstack/* in a product.
Composants liés
Générée depuis le code de @krizaka/ui 3.1.0 : son meta.ts, ses exemples et ses types.Modifier cette documentation sur GitHub