| title | Plugin Form |
|---|
import { InteractiveDemo } from '@/app/components/InteractiveDemo'; import { PluginLoader } from '@/app/components/PluginLoader';
Advanced form components with validation, multi-step forms, and comprehensive field support.
npm install @object-ui/plugin-form<PluginLoader plugins={['form']}>
- Form Builder - Create complex forms from schemas
- Validation - Built-in validation with custom rules
- Multi-Step Forms - Wizard-style multi-step forms
- Field Types - All standard HTML5 input types
- Form State - Automatic state management
- Error Handling - Field-level and form-level errors
{
type: 'form',
fields: FormField[],
submitLabel?: string,
cancelLabel?: string,
onSubmit?: (data) => void,
onCancel?: () => void,
className?: string
}
interface FormField {
name: string;
type: string; // 'input', 'select', 'checkbox', etc.
label: string;
placeholder?: string;
required?: boolean;
validation?: ValidationRule[];
defaultValue?: any;
disabled?: boolean;
className?: string;
}
A sectioned form renders as ONE grid. The form view's columns (spec
FormView.columns) sets how wide that grid is; a section's columns sets how
densely that section fills it. The view's value wins; without it the grid takes
the widest section's density, and without either it is single-column.
ObjectForm (simple), ModalForm, TabbedForm and SplitForm all resolve it
that way, so the same metadata lays out identically in every host. The grid is
applied to the field container inside the form, never wrapped around the <form>.
A sectioned form stays one form: declare the tabs on it and the renderer distributes the fields into panels, instead of rendering a form per section (which strands every section but the first outside the submit, and lets an inactive tab unmount along with its values).
{
type: 'form',
fields: [/* every tab's fields, in one flat list */],
fieldTabs: [
{ key: 'basics', label: 'Basics', fields: ['subject', 'status'] },
{ key: 'detail', label: 'Detail', description: 'Anything else', fields: ['description'] },
],
defaultFieldTab?: 'basics', // defaults to the first tab
fieldTabsPosition?: 'top', // 'top' | 'bottom' | 'left' | 'right'
}
- Panels are force-mounted and only CSS-hidden, so a tab the user leaves keeps its values and its validation.
- A failed submit activates the tab holding the first offending field and marks
every tab with a rejected field — client rules and server
fields[]alike. - Fields no tab claims render above the tab strip rather than disappearing.
- Needs at least two tabs; ignored when the form uses
children.
ModalForm (contentLayout: 'tabbed') and TabbedForm build on this.
Side-by-side panels follow the same rule: the <form> wraps the whole panel
group and each pane holds only fields, so one react-hook-form instance spans the
divider.
{
type: 'form',
fields: [/* every pane's fields, in one flat list */],
fieldPanes: [
{ key: 'primary', fields: ['subject'], defaultSize: 50 },
{ key: 'secondary', fields: ['status', 'priority'], defaultSize: 50, minSize: 20 },
],
fieldPanesOrientation?: 'horizontal', // 'horizontal' | 'vertical'
fieldPanesResizable?: true, // false pins the divider
}
- A submit from anywhere carries every pane's values, and a field rule in one pane can read a field in another — neither works with a form per panel.
defaultSize/minSizeare percentages of the group.- Each pane is its own
@container, so a multi-column group collapses as the divider is dragged narrower. - Fields no pane claims render above the panel group rather than disappearing.
- Needs at least two panes; ignored when the form uses
childrenorfieldTabs.
SplitForm builds on this: section 1 becomes the primary pane, the rest stack in
the secondary one behind inline section headers.
import '@object-ui/plugin-form';
import { formComponents } from '@object-ui/plugin-form';
import { ComponentRegistry } from '@object-ui/core';
Object.entries(formComponents).forEach(([type, component]) => {
ComponentRegistry.register(type, component);
});
{
"type": "form",
"fields": [
{
"name": "username",
"type": "input",
"label": "Username",
"required": true,
"validation": [
{ "type": "minLength", "value": 3, "message": "Min 3 characters" },
{ "type": "maxLength", "value": 20, "message": "Max 20 characters" }
]
},
{
"name": "password",
"type": "input",
"inputType": "password",
"label": "Password",
"required": true,
"validation": [
{ "type": "minLength", "value": 8, "message": "Min 8 characters" }
]
}
],
"submitLabel": "Sign Up"
}{
"type": "multi-step-form",
"steps": [
{
"title": "Personal Info",
"fields": [
{ "name": "firstName", "type": "input", "label": "First Name", "required": true },
{ "name": "lastName", "type": "input", "label": "Last Name", "required": true }
]
},
{
"title": "Contact Info",
"fields": [
{ "name": "email", "type": "input", "inputType": "email", "label": "Email", "required": true },
{ "name": "phone", "type": "input", "inputType": "tel", "label": "Phone" }
]
}
]
}The plugin supports these field types:
- input - Text, email, password, number, tel, url, etc.
- textarea - Multi-line text input
- select - Dropdown select
- checkbox - Single checkbox
- radio-group - Radio button group
- date-picker - Date selection
- file-upload - File upload
import type { FormSchema, FormField } from '@object-ui/plugin-form';
const loginForm: FormSchema = {
type: 'form',
fields: [...],
submitLabel: 'Sign In'
};
MIT