Skip to content

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'
}