The Editor component is now implemented! Check it out.
v2.1.16
/
  • Get Started
  • Components
  • Composables
  • Typography
  • GitHub
  • Layout
  • App
  • Container
  • Error
  • SidebarLayout
  • Element
  • Advice
  • Alert
  • Avatar
  • AvatarGroup
  • Badge
  • Banner
  • Button
  • Calendar
  • Card
  • Chip
  • Collapsible
  • Countdown
  • FieldGroup
  • Kbd
  • Progress
  • Separator
  • Skeleton
  • Form
  • Checkbox
  • CheckboxGroup
  • ColorPicker
  • FileUpload
  • Form
  • FormField
  • Input
  • InputDate
  • InputMenu
  • InputNumber
  • InputTags
  • InputTime
  • PinInput
  • RadioGroup
  • Range
  • Select
  • SelectMenu
  • Switch
  • Textarea
  • Data
  • Accordion
  • DescriptionList
  • Empty
  • Table
  • TableWrapper
  • Timeline
  • User
  • Navigation
  • Breadcrumb
  • CommandPalette
  • Link
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Overlay
  • ContextMenu
  • DropdownMenu
  • Modal
  • Popover
  • Slideover
  • Toast
  • Tooltip
  • Page
  • PageCard
  • PageColumns
  • PageGrid
  • PageLinks
  • PageList
  • Dashboard
  • DashboardGroup
  • DashboardSearch
  • DashboardSearchButton
  • AI Chat
  • soonChatMessage
  • soonChatMessages
  • soonChatPalette
  • soonChatPrompt
  • soonChatPromptSubmit
  • Editor
  • NewEditor
  • NewEditorDragHandle
  • NewEditorEmojiMenu
  • NewEditorMentionMenu
  • NewEditorSuggestionMenu
  • NewEditorToolbar
  • Content
  • ContentSearch
  • ContentSearchButton
  • ContentSurround
  • ContentToc
  • Color Mode
  • ColorModeAvatar
  • ColorModeButton
  • ColorModeImage
  • ColorModeSelect
  • ColorModeSwitch
  • i18n
  • LocaleSelect
  • b24icons
  • b24jssdk
Use our Nuxt starter
v2.1.16
  • Docs
  • Components
  • Composables
  • Typography

Form

A form component designed for validation and handling submissions.
GitHub
Demo
Nuxt UI

Usage

Use the Form component to validate form data using any validation library supporting Standard Schema such as Valibot, Zod, Regle, Yup, Joi or Superstruct or your own validation logic.

It works with the FormField component to display error messages around form elements automatically.

Schema validation

It requires two props:

  • state - a reactive object holding the form's state.
  • schema - any Standard Schema or Superstruct.
No validation library is included by default, ensure you install the one you need.
<script setup lang="ts">
import * as v from 'valibot'
import type { FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const schema = v.object({
  email: v.pipe(v.string(), v.email('Invalid email')),
  password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
})

type Schema = v.InferOutput<typeof schema>

const state = reactive({
  email: '',
  password: ''
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'air-primary-success' })
  console.log(event.data)
}
</script>

<template>
  <B24Form :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <B24FormField label="Email" name="email">
      <B24Input v-model="state.email" />
    </B24FormField>

    <B24FormField label="Password" name="password">
      <B24Input v-model="state.password" type="password" />
    </B24FormField>

    <B24Button color="air-primary" type="submit">
      Submit
    </B24Button>
  </B24Form>
</template>
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const schema = z.object({
  email: z.email('Invalid email'),
  password: z.string('Password is required').min(8, 'Must be at least 8 characters')
})

type Schema = z.output<typeof schema>

const state = reactive<Partial<Schema>>({
  email: undefined,
  password: undefined
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <B24Form :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <B24FormField label="Email" name="email">
      <B24Input v-model="state.email" />
    </B24FormField>

    <B24FormField label="Password" name="password">
      <B24Input v-model="state.password" type="password" />
    </B24FormField>

    <B24Button color="air-primary" type="submit">
      Submit
    </B24Button>
  </B24Form>
</template>
<script setup lang="ts">
import type { FormSubmitEvent } from '@bitrix24/b24ui-nuxt'
import { useRegle, type InferInput } from '@regle/core'
import { required, email, minLength, withMessage } from '@regle/rules'

const { r$ } = useRegle({ email: '', password: '' }, {
  email: { required, email: withMessage(email, 'Invalid email') },
  password: { required, minLength: withMessage(minLength(8), 'Must be at least 8 characters') }
})

type Schema = InferInput<typeof r$>

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <B24Form :schema="r$" :state="r$.$value" class="space-y-4" @submit="onSubmit">
    <B24FormField label="Email" name="email">
      <B24Input v-model="r$.$value.email" />
    </B24FormField>

    <B24FormField label="Password" name="password">
      <B24Input v-model="r$.$value.password" type="password" />
    </B24FormField>

    <B24Button type="submit">
      Submit
    </B24Button>
  </B24Form>
</template>
<script setup lang="ts">
import { object, string } from 'yup'
import type { InferType } from 'yup'
import type { FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const schema = object({
  email: string().email('Invalid email').required('Required'),
  password: string()
    .min(8, 'Must be at least 8 characters')
    .required('Required')
})

type Schema = InferType<typeof schema>

const state = reactive({
  email: undefined,
  password: undefined
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <B24Form :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <B24FormField label="Email" name="email">
      <B24Input v-model="state.email" />
    </B24FormField>

    <B24FormField label="Password" name="password">
      <B24Input v-model="state.password" type="password" />
    </B24FormField>

    <B24Button color="air-primary" type="submit">
      Submit
    </B24Button>
  </B24Form>
</template>
<script setup lang="ts">
import Joi from 'joi'
import type { FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const schema = Joi.object({
  email: Joi.string().required(),
  password: Joi.string()
    .min(8)
    .required()
})

const state = reactive({
  email: undefined,
  password: undefined
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'air-primary-success' })
  console.log(event.data)
}
</script>

<template>
  <B24Form :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <B24FormField label="Email" name="email">
      <B24Input v-model="state.email" />
    </B24FormField>

    <B24FormField label="Password" name="password">
      <B24Input v-model="state.password" type="password" />
    </B24FormField>

    <B24Button color="air-primary" type="submit">
      Submit
    </B24Button>
  </B24Form>
</template>
<script setup lang="ts">
import { object, string, nonempty, refine } from 'superstruct'
import type { Infer } from 'superstruct'
import type { FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const schema = object({
  email: nonempty(string()),
  password: refine(string(), 'Password', (value) => {
    if (value.length >= 8) return true
    return 'Must be at least 8 characters'
  })
})

const state = reactive({
  email: '',
  password: ''
})

type Schema = Infer<typeof schema>

async function onSubmit(event: FormSubmitEvent<Schema>) {
  console.log(event.data)
}
</script>

<template>
  <B24Form :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <B24FormField label="Email" name="email">
      <B24Input v-model="state.email" />
    </B24FormField>

    <B24FormField label="Password" name="password">
      <B24Input v-model="state.password" type="password" />
    </B24FormField>

    <B24Button color="air-primary" type="submit">
      Submit
    </B24Button>
  </B24Form>
</template>

Errors are reported directly to the FormField component based on the name or error-pattern prop. This means the validation rules defined for the email attribute in your schema will be applied to <FormField name="email">.

Nested validation rules are handled using dot notation. For example, a rule like { user: z.object({ email: z.string() }) } will be applied to <FormField name="user.email">.

Custom validation

Use the validate prop to apply your own validation logic.

The validation function must return a list of errors with the following attributes:

  • message - the error message to display.
  • name - the name of the FormField to send the error to.
It can be used alongside the schema prop to handle complex use cases.
<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const state = reactive({
  email: undefined,
  password: undefined
})

type Schema = typeof state

function validate(state: Partial<Schema>): FormError[] {
  const errors = []
  if (!state.email) errors.push({ name: 'email', message: 'Required' })
  if (!state.password) errors.push({ name: 'password', message: 'Required' })
  return errors
}

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'air-primary-success' })
  console.log(event.data)
}
</script>

<template>
  <B24Form :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
    <B24FormField label="Email" name="email">
      <B24Input v-model="state.email" />
    </B24FormField>

    <B24FormField label="Password" name="password">
      <B24Input v-model="state.password" type="password" />
    </B24FormField>

    <B24Button color="air-primary" type="submit">
      Submit
    </B24Button>
  </B24Form>
</template>

Input events

The Form component automatically triggers validation when an input emits an input, change, or blur event.

  • Validation on input occurs as you type.
  • Validation on change occurs when you commit to a value.
  • Validation on blur happens when an input loses focus.

You can control when validation happens this using the validate-on prop.

The form always validates on submit.
Radio group
Checkbox group
Drop your image here
PNG (max. 1MB)
You can use the useFormField composable to implement this inside your own components.

Error event

You can listen to the @error event to handle errors. This event is triggered when the form is submitted and contains an array of FormError objects with the following fields:

  • id - the input's id.
  • name - the name of the FormField
  • message - the error message to display.

Here's an example that focuses the first input element with an error after the form is submitted:

<script setup lang="ts">
import type { FormError, FormErrorEvent, FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const state = reactive({
  email: undefined,
  password: undefined
})

type Schema = typeof state

function validate(state: Partial<Schema>): FormError[] {
  const errors = []
  if (!state.email) errors.push({ name: 'email', message: 'Required' })
  if (!state.password) errors.push({ name: 'password', message: 'Required' })
  return errors
}

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'air-primary-success' })
  console.log(event.data)
}

async function onError(event: FormErrorEvent) {
  if (event?.errors?.[0]?.id) {
    const element = document.getElementById(event.errors[0].id)
    element?.focus()
    element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
  }
}
</script>

<template>
  <B24Form :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
    <B24FormField label="Email" name="email">
      <B24Input v-model="state.email" />
    </B24FormField>

    <B24FormField label="Password" name="password">
      <B24Input v-model="state.password" type="password" />
    </B24FormField>

    <B24Button color="air-primary" type="submit">
      Submit
    </B24Button>
  </B24Form>
</template>

Nesting forms

Use the nested prop to nest multiple Form components and link their validation functions. In this case, validating the parent form will automatically validate all the other forms inside it.

Nested forms directly inherit their parent's state, so you don't need to define a separate state for them. You can use the name prop to target a nested attribute within the parent's state.

It can be used to dynamically add fields based on user's input:

<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const schema = z.object({
  name: z.string().min(2),
  news: z.boolean().default(false)
})

type Schema = z.output<typeof schema>

const nestedSchema = z.object({
  email: z.email()
})

type NestedSchema = z.output<typeof nestedSchema>

const state = reactive<Partial<Schema & NestedSchema>>({ })

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'air-primary-success' })
  console.log(event.data)
}
</script>

<template>
  <B24Form
    ref="form"
    :state="state"
    :schema="schema"
    class="gap-4 flex flex-col w-60"
    @submit="onSubmit"
  >
    <B24FormField label="Name" name="name">
      <B24Input v-model="state.name" placeholder="John Lennon" />
    </B24FormField>

    <div>
      <B24Checkbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
    </div>

    <B24Form v-if="state.news" :schema="nestedSchema" nested>
      <B24FormField label="Email" name="email">
        <B24Input v-model="state.email" placeholder="john@lennon.com" />
      </B24FormField>
    </B24Form>

    <div>
      <B24Button color="air-primary" type="submit">
        Submit
      </B24Button>
    </div>
  </B24Form>
</template>

Or to validate list inputs:

<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@bitrix24/b24ui-nuxt'

const schema = z.object({
  customer: z.string().min(2)
})

type Schema = z.output<typeof schema>

const itemSchema = z.object({
  description: z.string().min(1),
  price: z.number().min(0)
})

type ItemSchema = z.output<typeof itemSchema>

const state = reactive<Partial<Schema & { items: Partial<ItemSchema>[] }>>({ })

function addItem() {
  if (!state.items) {
    state.items = []
  }
  state.items.push({})
}

function removeItem() {
  if (state.items) {
    state.items.pop()
  }
}

const toast = useToast()

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'air-primary-success' })
  console.log(event.data)
}
</script>

<template>
  <B24Form
    :state="state"
    :schema="schema"
    class="gap-4 flex flex-col w-60"
    @submit="onSubmit"
  >
    <B24FormField label="Customer" name="customer">
      <B24Input v-model="state.customer" placeholder="Wonka Industries" />
    </B24FormField>

    <B24Form
      v-for="(item, count) in state.items"
      :key="count"
      :name="`items.${count}`"
      :schema="itemSchema"
      class="flex gap-2"
      nested
    >
      <B24FormField :label="!count ? 'Description' : undefined" name="description">
        <B24Input v-model="item.description" />
      </B24FormField>
      <B24FormField :label="!count ? 'Price' : undefined" name="price" class="w-20">
        <B24Input v-model="item.price" type="number" />
      </B24FormField>
    </B24Form>

    <div class="flex gap-2">
      <B24Button size="sm" @click="addItem()">
        Add Item
      </B24Button>

      <B24Button size="sm" @click="removeItem()">
        Remove Item
      </B24Button>
    </div>
    <div>
      <B24Button color="air-primary" type="submit">
        Submit
      </B24Button>
    </div>
  </B24Form>
</template>

API

Props

Prop Default Type
id string | number
schema Struct<any, any> | StandardSchemaV1<object, object>

Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs.

state Partial<any>

An object representing the current state of the form.

validate (state: Partial<any>): FormError<string>[] | Promise<FormError<string>[]>

Custom validation function to validate the form state.

validateOn`['blur', 'change', 'input']` FormInputEvents[]

The list of input events that trigger the form validation.

disabledboolean

Disable all inputs inside the form.

name string

Path of the form's state within it's parent form. Used for nesting forms. Only available if nested is true.

validateOnInputDelay300 number

Delay in milliseconds before validating the form on input events.

transformtrue as Tboolean

If true, applies schema transformations on submit.

nested`false`boolean

If true, this form will attach to its parent Form and validate at the same time.

loadingAutotrueboolean

When true, all form elements will be disabled on @submit event. This will cause any focused input elements to lose their focus state.

acceptcharset string
action string
autocomplete string
enctype string
method string
novalidate false | true | "true" | "false"
target string
This component also supports all native <form> HTML attributes.

Slots

Slot Type
default{ errors: FormError<string>[]; loading: boolean; }

Emits

Event Type
submit[event: FormSubmitEvent<any>]
error[event: FormErrorEvent]

Expose

You can access the typed component instance using useTemplateRef.

<script setup lang="ts">
const form = useTemplateRef('form')
</script>

<template>
  <B24Form ref="form" />
</template>

This will give you access to the following:

NameType
submit()Promise<void>

Triggers form submission.

validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean })Promise<T>

Triggers form validation. Will raise any errors unless opts.silent is set to true.

clear(path?: keyof T | RegExp)void

Clears form errors associated with a specific path. If no path is provided, clears all form errors.

getErrors(path?: keyof T RegExp)FormError[]

Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.

setErrors(errors: FormError[], name?: keyof T RegExp)void

Sets form errors for a given path. If no path is provided, overrides all errors.

errorsRef<FormError[]>

A reference to the array containing validation errors. Use this to access or manipulate the error information.

disabledRef<boolean>
dirtyRef<boolean> true if at least one form field has been updated by the user.
dirtyFieldsDeepReadonly<Set<keyof T>> Tracks fields that have been modified by the user.
touchedFieldsDeepReadonly<Set<keyof T>> Tracks fields that the user interacted with.
blurredFieldsDeepReadonly<Set<keyof T>> Tracks fields blurred by the user.

Theme

app.config.ts
export default defineAppConfig({
  b24ui: {
    form: {
      base: ''
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import bitrix24UIPluginVite from '@bitrix24/b24ui-nuxt/vite'

export default defineConfig({
  plugins: [
    vue(),
    bitrix24UIPluginVite({
      b24ui: {
        form: {
          base: ''
        }
      }
    })
  ]
})

FileUpload

A file upload input component.

FormField

A container for form elements with built-in validation and error management.

On this page

  • Usage
    • Schema validation
    • Custom validation
    • Input events
    • Error event
    • Nesting forms
  • API
    • Props
    • Slots
    • Emits
    • Expose
  • Theme
Releases
Published under MIT License.

Copyright © 2024-present Bitrix24