Example
Small Example
import React from 'react'
import z from 'zod/v4'
import { Form, FormValue } from '@/types'
export const form = [
{
fields: [
[
{
type: 'text',
name: 'text',
validation: z.string({ message: 'Invalid data.' })
}
]
]
}
] as const satisfies Form
export const data: FormValue<typeof form> = {
text: ''
}Full Example
import React from 'react'
import z from 'zod/v4'
import { Form, FormValue } from '@/types'
export const form = [
{
header: {
label: 'General fields',
collapsable: {
open: false
}
},
fields: [
[
{
type: 'text',
name: 'text',
options: {
placeholder: 'React'
},
size: '1/2',
caption: 'Your name',
validation: z
.string({ message: 'Invalid data.' })
.min(3, { message: 'Name must be at least 3 characters long' })
},
{
type: 'text',
name: 'private',
options: {
placeholder: 'React'
},
size: '1/2',
caption: 'Your name',
render: (value: string) => (
<span className="size-32 bg-red-200">{value}</span>
),
private: async () => {
const res = await fetch(`/api/string`)
const data = await res.json()
return data.data
},
validation: z
.string({ message: 'Invalid data.' })
.min(3, { message: 'Name must be at least 3 characters long' })
.optional()
},
{
type: 'select',
name: 'select',
size: '1/4',
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')
])
},
{
type: 'switch',
name: 'switch',
caption: 'Is this project open source?',
size: '1/4',
options: {
trueValue: 'Yes',
falseValue: 'No'
},
validation: z.boolean()
}
],
[
{
type: 'textarea',
name: 'textarea'
}
],
[
{
type: 'number',
name: 'number',
validation: z
.number()
.min(10, { message: 'Stars must be at least 10' })
.max(100, { message: 'Stars must be at most 100' })
},
{
type: 'checkbox',
name: 'checkbox',
options: {
items: [
{ value: 'javascript', label: 'JavaScript' },
{ value: 'typescript', label: 'TypeScript' },
{ value: 'python', label: 'Python' }
]
},
validation: z.array(z.enum(['javascript', 'typescript', 'python']))
},
{
type: 'radiobox',
name: 'radiobox',
options: {
label: 'Notify me complete...',
items: [
{ value: 'javascript', label: 'JavaScript' },
{ value: 'typescript', label: 'TypeScript' },
{ value: 'python', label: 'Python' }
]
},
validation: z.enum(['javascript', 'typescript', 'python'])
}
]
]
},
{
header: {
label: 'Formidabuild Example',
collapsable: {
open: false
}
},
fields: [
[
{
type: 'toggle-group',
name: 'toggleGroupObject',
options: {
items: [
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' }
]
}
},
{
type: 'toggle-group',
name: 'toggleGroupString',
options: {
items: ['React', 'Vue', 'Svelte']
}
}
],
[
{
type: 'otp',
name: 'otp',
options: {
length: 8,
pattern: /^\d+$/,
separator: true,
label: 'OTP Code'
}
},
{
type: 'slider',
name: 'slider'
}
],
[
{
type: 'password',
name: 'password',
options: {
placeholder: 'Enter your password'
},
validation: z
.string()
.min(6, { message: 'Password must be at least 6 characters long' })
},
{
type: 'color',
name: 'color',
options: {
label: 'Select a color',
palette: [
'#FF0000',
'#00FF00',
'#0000FF',
'#FFFF00',
'#FF00FF',
'#00FFFF',
'#FFFFFF',
'#000000'
]
// customColors: true
},
validation: z.string().optional()
}
],
[
{
type: 'text',
name: 'email',
options: {
placeholder: 'Enter your email'
},
validation: z.email({ message: 'Invalid email address' })
},
{
type: 'text',
name: 'url',
options: {
placeholder: 'Enter your url'
},
validation: z.url({ message: 'Invalid URL' })
},
{
type: 'phone',
name: 'phone',
options: {
placeholder: 'Enter your phone number',
defaultCode: '+39'
},
validation: z.string().refine(
(val) => {
const match = val.match(/^(\+\d+)\s(\d{7,9})$/)
return !!match
},
{
message:
'Invalid phone number format. Must be: +prefix 7-9 digits'
}
)
},
{
type: 'rating',
name: 'rating',
options: {
score: 5,
step: 0.5
},
validation: z.number().min(0).max(5).nullable()
},
{
type: 'text-list',
name: 'textList',
options: {
placeholder: 'Add items to the list'
},
validation: z
.array(z.string().min(1, { message: 'Item cannot be empty' }))
.min(1, { message: 'At least one item is required' })
}
],
[
{
type: 'show',
name: 'dateTimeRange',
options: {
render: (value) => <div>{JSON.stringify(value)}</div>
}
},
{
type: 'render',
name: 'render',
options: {
render: () => <div className="bg-red-50">test</div>
}
}
]
]
},
{
header: {
label: 'Dates and Times',
collapsable: {
open: false
}
},
fields: [
[
{
type: 'date',
name: 'date',
options: {
placeholder: 'Select a date'
},
validation: z
.string()
.refine(
(val) =>
!val ||
(new Date(val) instanceof Date &&
!isNaN(new Date(val).getTime()) &&
new Date(val) >= new Date('2024-01-01')),
{ message: 'Date must be after 2024-01-01 or empty' }
)
.or(z.literal(''))
}
],
[
{
type: 'date-time',
name: 'dateTime'
},
{
type: 'time',
name: 'time',
validation: z.string().refine(
(val) => {
const match = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val)
if (!match) return false
return val > '15:30:00'
},
{
message: "L'orario deve essere successivo alle 15:30:00"
}
)
}
],
[
{
type: 'date-time-range',
name: 'dateTimeRange',
options: {
placeholder: 'Select a date range',
format: 'yyyy-MM-dd'
},
validation: z
.object({
start: z.string(),
end: z.string()
})
.nullable()
.refine(
(val) => {
if (!val) return true
const start = new Date(val.start)
const end = new Date(val.end)
return (
!isNaN(start.getTime()) &&
!isNaN(end.getTime()) &&
start <= end
)
},
{
message: 'Start date must be before or equal to end date'
}
)
},
{
type: 'date-range',
name: 'dateRange',
options: {
placeholder: 'Select a date range',
format: 'yyyy-MM-dd'
},
validation: z
.object({
start: z.string(),
end: z.string()
})
.nullable()
.refine(
(val) => {
if (!val) return true
const start = new Date(val.start)
const end = new Date(val.end)
return (
!isNaN(start.getTime()) &&
!isNaN(end.getTime()) &&
start <= end
)
},
{
message: 'Start date must be before or equal to end date'
}
)
}
],
[
{
type: 'calendar',
name: 'calendar',
size: '1/6'
}
]
]
},
{
header: {
label: 'Files',
collapsable: {
open: false
}
},
fields: [
[
{
type: 'file',
name: 'singleFile',
options: {
accept: ['.jpg', '.jpeg', '.png'],
multiple: false
},
validation: z
.instanceof(File, { message: 'File non presente' })
.nullable()
.refine(
(files) => {
if (!files) return true
const validExt = (file: File) =>
['.jpg', '.jpeg', '.png'].includes(
'.' + (file.name.split('.').pop() || '').toLowerCase()
)
const validSize = (file: File) => file.size <= 1048576
return validExt(files) && validSize(files)
},
{
message:
'Only .jpg, .jpeg, .png file are allowed and max size is 1MB.'
}
)
},
{
type: 'file',
name: 'multipleFile',
options: {
accept: ['.jpg', '.jpeg', '.png'],
multiple: true
},
validation: z
.array(z.instanceof(File, { message: 'File non presente' }))
.refine((Files) => {
return Files.every(
(file) =>
['.jpg', '.jpeg', '.png'].includes(
'.' + (file.name.split('.').pop() || '').toLowerCase()
) && file.size <= 1048576
)
})
}
]
]
},
{
header: {
label: 'Combobox Example',
collapsable: {
open: false
}
},
fields: [
[
{
type: 'combobox',
name: 'comboboxAsyncString',
options: {
asyncItems: async (value: string) => {
const res = await fetch(`/api/string-list?q=${value}`)
const data = await res.json()
console.log(data.data)
return data.data
},
placeholder: 'Search for a framework'
},
validation: z.string()
},
{
type: 'combobox',
name: 'comboboxAsyncRecord',
options: {
asyncItems: async (value: string) => {
const res = await fetch(`/api/object-list?q=${value}`)
const data = await res.json()
return data.data
},
itemValue: 'id',
itemLabel: 'title',
placeholder: 'Search for a recipe'
},
validation: z.uuid().nullable()
}
}
],
[
{
type: 'combobox',
name: 'comboboxString',
options: {
items: ['React', 'Vue', 'Svelte'],
placeholder: 'Select a framework'
},
validation: z.union([
z.literal('React'),
z.literal('Vue'),
z.literal('Svelte')
])
},
{
type: 'combobox',
name: 'comboboxRecord',
options: {
items: [
{ id: 'react', label: 'React' },
{ id: 'vue', label: 'Vue' },
{ id: 'svelte', label: 'Svelte' }
],
itemValue: 'id',
itemLabel: 'label',
placeholder: 'Select a framework'
},
validation: z
.union([
z.object({ id: z.literal('react'), label: z.literal('React') }),
z.object({ id: z.literal('vue'), label: z.literal('Vue') }),
z.object({ id: z.literal('svelte'), label: z.literal('Svelte') })
])
.nullable()
}
]
]
},
{
header: {
label: 'Collection Example',
collapsable: {
open: false
}
},
fields: [
[
{
type: 'list',
name: 'list',
options: [
[
{
type: 'text',
name: 'info.name',
validation: z
.string()
.min(1, { message: 'Name must be at least 1 character long' })
},
{
type: 'text',
name: 'info.aaa',
validation: z
.string()
.min(1, { message: 'Name must be at least 1 character long' })
},
{
type: 'select',
name: 'info.language',
options: {
items: [
{ value: 'javascript', label: 'JavaScript' },
{ value: 'typescript', label: 'TypeScript' },
{ value: 'python', label: 'Python' }
],
placeholder: 'Select a language'
},
validation: z.union([
z.literal('javascript'),
z.literal('typescript'),
z.literal('python')
])
}
]
],
validation: z
.array(
z.object({
info: z.object({
name: z.string().min(1),
language: z.string()
})
})
)
.min(1, { message: 'Collection must have at least 1 items' })
}
]
]
}
] as const satisfies Form
export const data: FormValue<typeof form> = {
phone: '',
render: undefined,
url: '',
textList: [],
rating: null,
email: '',
color: '',
dateTimeRange: {
start: '2025-06-10 12:12:00',
end: '2025-06-20 12:12:00'
},
dateRange: {
start: '2025-06-10',
end: '2025-06-20'
},
singleFile: null,
password: '',
multipleFile: [],
toggleGroupObject: [],
toggleGroupString: [],
otp: '55544355',
dateTime: '2025-06-10 12:12',
time: '12:00:00',
calendar: null,
text: 'React',
select: 'javascript',
switch: true,
number: 100,
checkbox: ['javascript'],
radiobox: 'javascript',
date: '',
slider: 50,
textarea: 'A JavaScript library for building user interfaces.',
list: [
{
info: {
name: 'coan',
language: 'javascript'
}
}
],
comboboxString: 'React',
comboboxRecord: null,
comboboxAsyncString: 'React',
comboboxAsyncRecord: '76ec0c34-dc73-4861-8470-5995b7731b6d'
}