Props and Event
| Key | Type | Default | Description |
|---|---|---|---|
value | FormValue<Form> | {} | Object value that you want to edit. |
form | Form | [] | Form structure with validation config. |
disabled | boolean | false | Prop to disable all fields. |
mode | 'input' | 'show' | 'input' |
| Event | |||
onChange | FormValue<Form> | {} | Return the new object value. |
onError | boolean | false | Define if form has error. |
Form prop
Form
A form is a list of Section with a list of Fields inside.
type Form = Section[][
{
// Form already open without collapse
fields: [
// ...
]
},
{
name: 'Section name',
header: {
label: 'Section label',
open: true
},
fields: [
// ...
]
}
// ...
]Section
Required parameters:
fields: Fields section contains a list of row and column with field inside. All details are described below.
Optional parameters:
name: Section name, if you put section name here, it will appear as a collapsed section.header: Section header, you can use it to set the label of the section and if it is open or not. If present the section is collapsable. You can specifyopentrue/false to set the section open or closed by default.header.label: Label of the section header. If not set, it will be generated automatically from the name.header.open: If true, the section will be open by default.
You can check the interface below:
type SectionHeader = {
label?: string
collapsable?: {
open?: boolean
}
}
type Section = {
header?: SectionHeader
fields: Fields
}Fields
Fields is a list of rows with a list of column inside. In every column you can put a field.
type Fields = Field[][]Eg: If you want to create a form with 2 fields in the first row and 1 field in the second row you can do:
fields: [
[
{
// FIELD
},
{
// FIELD
}
],
[
{
// FIELD
}
],
]
}Field
Field must have a type and a name and others optional parameters described below:
Required parameters:
type: Type of field. You can use one of the type described in the interface below or implement your own custom component and pass the instance of it.name: Name of the field. It will be used to get the value of the field. This field supports dot syntax, so you can use it to create nested fields likeuser.nameoruser.address.street.
Optional parameters:
className: Class name of the field. It will be used to set the class name of the field, you can use it to add custom styles or classes.size: Size of the field. You can use one of the size described in the interface below to set the size of the field.label: Label of the field. It will be used to override the label of the field, if not set the label will be generated automatically from the name.caption: Caption of the field. It will be used to show a caption under the field if needed.disabled: If true, the field will be disabled.visible: If true, the field will be visible. If false, the field will be hidden.
Custom parameters:
options: Options for the field. It will be used to set the options of the field.validation: Validation rules for the field. It will be used to validate the field value through Zod schema.render: Custom render function for the field. It will be used to render the field if you use ashowmode.private: If the callback is set, it will be used to render the field in a private mode, meaning that the field will not be visible in the form but can be used to store data.
Every type has its own parameters, basically they can have an options, validation, render and private.
Options
Options are used to set the options of the field, like items for a select or options for a checkbox. The options are different for every type of field, you can check the interface below for more details.
Validation
Validation is handled via Zod schema. Below you can find practical examples for each common Zod schema type, both simple and advanced usage.
Validation could be a zod rule or a function that returns a zod rule. The function will be called with the form value and should return a zod rule.
In this way you can create dynamic validation rules based on the form value.
[
{
type: 'number',
name: 'min',
},
{
type: 'text',
name: 'name',
validation: z.number(),
// or
validation: (v) => {
const min = v?.min ? (v.min as number) : 0
return z.number().int().min(min).max(99)
}
}
]ZodString
// simple
z.string()
// advanced
z.string().min(5).max(20).email({ message: 'Invalid email' })ZodNumber
// simple
z.number()
// advanced
z.number().int().min(0).max(100)ZodBoolean
// simple
z.boolean()
// advanced
z.boolean().refine(val => val === true, { message: 'Must be true' })ZodDate
// simple
z.date()
// advanced
z.date().min(new Date('2024-01-01'), { message: 'Date too old' })ZodArray
// simple
z.array(z.string())
// advanced
z.array(z.number().min(1)).min(2, { message: 'At least 2 numbers' })ZodObject
// simple
z.object({ name: z.string(), age: z.number() })
// advanced
z.object({
user: z.object({
email: z.string().email(),
roles: z.array(z.string())
})
})ZodEnum
// simple
z.enum(['A', 'B', 'C'])
// advanced
z.enum(['admin', 'user', 'guest'])ZodLiteral
// simple
z.literal('ok')
// advanced
z.union([z.literal('yes'), z.literal('no')])ZodNull
// simple
z.null()
// advanced
z.union([z.string(), z.null()])ZodUndefined
// simple
z.undefined()
// advanced
z.union([z.string(), z.undefined()])ZodAny
// simple
z.any()
// advanced
z.any().refine(val => typeof val === 'object', { message: 'Must be an object' })ZodUnknown
// simple
z.unknown()
// advanced
z.unknown().refine(val => Array.isArray(val), { message: 'Must be an array' })ZodNever
// simple
z.never()
// advanced
z.union([z.string(), z.never()])ZodRecord
// simple
z.record(z.string())
// advanced
z.record(z.string(), z.number())ZodMap
// simple
z.map(z.string(), z.number())
// advanced
z.map(z.string(), z.object({ id: z.number() }))ZodSet
// simple
z.set(z.number())
// advanced
z.set(z.string()).min(2)ZodPromise
// simple
z.promise(z.string())
// advanced
z.promise(z.object({ id: z.number() }))ZodTuple
// simple
z.tuple([z.string(), z.number()])
// advanced
z.tuple([z.string(), z.number(), z.boolean()])ZodUnion
// simple
z.union([z.string(), z.number()])
// advanced
z.union([
z.object({ type: z.literal('a'), value: z.string() }),
z.object({ type: z.literal('b'), value: z.number() })
])Nullable
// simple
z.string().nullable()
// advanced
z.number().min(0).nullable().default(null)Optional
// simple
z.string().optional()
// advanced
z.object({ name: z.string().optional(), age: z.number() })Regex
// simple
z.string().regex(/^abc/)
// advanced
z.string().regex(/^[A-Z]{3}-\d{4}$/)Predefined functions (e.g. email, uuid)
// email
z.string().email()
// uuid
z.string().uuid()Render
Render is a custom render function for the field. It will be used to render the field if you use a show mode. You can use it to create a custom view of the field, like a custom component or a custom HTML element.
{
type: 'text',
name: 'text',
render: (value: string) => (
<span className="size-32 bg-red-200">{value}</span>
),
}Private
Private is a boolean that indicates if the field is private or not. If true, the field will not be visible in the form but can be used to store data. This is useful for fields that you want to keep hidden from the user but still need to store data.
{
type: 'text',
name: 'text',
private: async () => {
const res = await fetch(`/api/string`)
const data = await res.json()
return data.data
},
}Field interface
export type Size =
| '1/2'
| '1/3'
| '1/4'
| '1/6'
| '2/3'
| '3/4'
| '5/6'
| 'full'
interface CommonField {
type: string
name: string
className?: string
size?: Size
label?: string | JSX.Element
caption?: string | JSX.Element
disabled?: boolean
visible?: boolean
validation?: z.ZodTypeAny
}Field examples
{
type: 'select',
name: 'select',
size: '1/4',
caption: 'Select your favorite language',
disabled: false,
visible: true,
options: {
placeholder: 'Select a language',
items: [
{ value: 'javascript', label: 'JavaScript' },
{ value: 'typescript', label: 'TypeScript' },
{ value: 'python', label: 'Python' }
]
},
validation: z.union([
z.literal('javascript'),
z.literal('typescript'),
z.literal('python')
])
}