Forms libraryStructure · Validate · Submit
Forms that guide people from input to completion
Build forms with clear labels, useful validation and predictable submission. Combine Kamod’s Preact controls with native form semantics, then add a schema and coordinated state when the task needs them. Your application supplies the service calls and business rules; the interface should make every step understandable.
Start with a small working form, explore the Formisch integration for @formisch/preact and valibot, and keep field styling aligned with the shared CSS foundation. The guidance below covers field choice, state, errors and recovery, with copyable examples and checks for real content, keyboard use and both color schemes.
In this guide 7 sections
Find your form starting point
Use the integration that matches the complexity of the form. A small native form may need only Input, Label and Button. The Formisch guide adds schema-backed state, validation modes, custom controls and dynamic fields, with examples you can adapt to your project.
Browse the available guide below, or start with the focused examples on this page. Keep your existing form library when it already meets your needs; the Kamod controls are the presentation layer, not a requirement to change how your application validates data.
Design the task before the fields
Ask for the smallest set of information that completes the task. Give the form a clear purpose and make the primary action describe the result. A short profile update and a multi-step application have different needs; neither benefits from collecting fields that the application will not use.
Order fields the way people think about the information. Group related choices, explain unfamiliar requirements before the input, and distinguish optional fields consistently. Keep labels, instructions and errors close enough to read as one unit.
| The input | Start with | Design detail |
|---|---|---|
| A short answer | Input | Choose the right type and autocomplete hint. Keep the label visible after the person starts typing. |
| A longer answer | Textarea | Explain length requirements before submission and leave enough room to review what was written. |
| One choice | Radio Group | Show a short set of meaningful options together. For a longer list, consider Select or Native Select. |
| Independent choices | Checkbox | Use separate values for choices that can be combined. Explain whether at least one selection is required. |
| An on/off preference | Switch | Clarify whether the change applies immediately or needs Save. Do not silently mix both models in one form. |
| Related fields | Field | Keep labels, descriptions and errors with the control. Use a fieldset and legend for a named group. |
Keep the structure native
Start with form, a visible label, a named input and a submit button. The browser already understands keyboard submission, required fields and many common input types. Kamod’s Input, Label and Button let you retain those semantics while using your app’s theme.
Give every submitted control a name. Match htmlFor to a unique id, connect helper text with aria-describedby, and use explicit button types. A secondary action inside a form should usually be type="button"; use type="reset" only when clearing the form is intentional and understandable.
Prefer a single column on small screens. Two fields can share a row when their relationship is clear, but the reading and keyboard order should remain predictable. Long error messages must wrap without pushing controls beyond the screen edge.
Build from a small, working example
These examples assume your Preact app already has Kamod UI and its styles configured. Start with the native version, introduce a schema when rules need to be reused, then use Formisch for coordinated values, validation and submission. The schema and Formisch examples additionally require valibot and @formisch/preact; follow the Formisch installation guide.
The preview is a safe place to try empty input, an invalid email and a successful check. It does not save data or contact a service. Copy the source into your own project and supply the application callback when you are ready to integrate it.
Make validation understandable
Choose when feedback appears
For many forms, validating on submit avoids showing errors before someone has had a chance to answer. After an error appears, revalidating while editing helps people see when they have fixed it. Validation on blur can suit longer forms, but it should not interrupt the flow of entering a value.
Formisch exposes validate and revalidate settings. Choose them deliberately for the task, and avoid duplicate custom validation paths that disagree with the schema. If you turn off browser validation with noValidate, make sure the form library provides the complete feedback path instead.
Explain the fix, not just the failure
“Enter a valid email address” is useful; “Invalid input” is not enough.Describe the requirement in plain language, retain what was entered and associate the message with its field. Pair an error color with text and aria-invalid; color alone does not explain what happened.
For several errors, an error summary can point to the affected fields. Decide where focus should go after submission and check that the first invalid field is reachable. Avoid repeatedly moving focus while someone is typing or announcing every keystroke.
Client rules are a convenience, not an authorization boundary. Validate again on the server and return errors that the form can map to a field or a general message. Treat a conflict, an expired session and a network failure as different situations when their recovery steps differ.
Connect submission and recovery
Separate editing, saving and saved state
Keep the current field values separate from the last confirmed result. When saving starts, show a concise pending label and prevent duplicate submissions. Await the actual operation before showing success. A resolved client handler does not mean the server accepted the change unless your integration checks the response.
Let the owning page supply the request, authentication and routing. A reusable form should accept a callback rather than hard-code an endpoint. Return its promise to the form library so pending state follows the work, and translate a rejected request into a useful message.
Keep a path back to progress
On failure, keep useful input and make retry possible. On success, decide whether the form stays open, resets or navigates away; show what happened before removing the context. An optimistic update needs a defined rollback path if the request fails.
For dynamic fields, use stable keys and meaningful labels. A removed row should not make keyboard focus disappear. For custom Select, Checkbox or Switch controls, connect the documented value/change props to Formisch explicitly. Use the full integration examples for field arrays, reset behavior and supported control adapters.
Make it your own
Set up once, reuse everywhere. These guides apply to individual components and complete blocks. Start with the global stylesheet and Tailwind CSS source detection, then choose your theme and finish with consistent icons. Following this order makes it easier to tell a setup issue from a change you want to make to the design.
Keep your app’s existing conventions and connect the shared foundation before overriding local classes. Each guide below focuses on one part of that setup and includes a quick visual check. Once the basics look right, use real content to review spacing, contrast and keyboard behavior in both light and dark modes.
Work with the source
A useful next step: read the piece you want to change. These two repositories cover the components and visual details behind the library. Check the README and the version in your package.json when comparing an example with your installed API.
- Components & blockskamod-uiFollow a component into
packages/coreor explore complete compositions inpackages/blocks. Read the implementation before changing shared behavior.@kamod-ch/ui(opens in a new tab) - Icons & visual detailskamod-iconsExplore the icon families, import paths and usage examples. Choose one consistent style for related actions and let icons inherit your theme with
currentColor.@kamod-ch/icons(opens in a new tab)
Review every path through the form
Test the complete task with realistic input and service responses. A form that looks correct with one valid value may still be difficult to use when data is missing, a request is slow or a label is translated.
- Keyboard and labels. Tab through every control, submit with Enter where appropriate, and verify focus after errors, reset and dynamic field changes. Check that every control has an accessible name.
- Boundaries. Try empty input, surrounding whitespace, long text and values at each minimum or maximum. Verify that the server and client rules agree.
- Request states. Simulate pending, success, validation failure, offline behavior and retry. Confirm repeated clicks cannot trigger duplicate writes.
- Presentation. Check both color modes, enlarged text and narrow screens. Error text should wrap, the primary action should stay reachable and focus should remain visible.
- Production. Run the relevant tests and build from
package.json. Open the form route directly in the production output and verify styles, initial values and any restored draft behavior.
Keep a small regression test for the most important success and failure paths. If a pattern repeats, extract the field composition or submit adapter after its behavior is clear. Reuse the interaction, not just the appearance.