Custom renderers

Render a property with a built-in alternative or your own component.

Every property gets a field that matches its type: a text input for string, a number input for number, a switch for boolean, and so on. Set renderer on a property to choose a different component.

Built-in renderers

Some fields have an alternative in the module. Use the component name, with the S prefix:

{
  "type": "object",
  "properties": {
    "notes": {
      "title": "Notes",
      "type": "string",
      "renderer": "STextarea"
    },
    "internalId": {
      "type": "string",
      "renderer": "none"
    }
  }
}
  • STextarea renders a string as a multi-line text area.
  • SSelect renders a string with an enum as a select menu.
  • none renders nothing. The value stays in the form data.

The prefix follows the prefix module option. With prefix: 'My', use MyTextarea.

Your own component

renderer also accepts the name of a component from your app. The component is looked up by name at runtime, so it must be registered globally. Put it in components/global/, or name the file *.global.vue:

components/global/ColorField.vue
<script lang="ts" setup>
defineProps<{
  id: string
  title?: string
  description?: string
  isRequired: boolean
}>()

const value = defineModel<string>({ default: '#00dc82' })
</script>

<template>
  <UFormField :name="id" :label="title" :description="description" :required="isRequired">
    <UColorPicker v-model="value" />
  </UFormField>
</template>
{
  "type": "object",
  "properties": {
    "brandColor": {
      "title": "Brand color",
      "type": "string",
      "renderer": "ColorField"
    }
  }
}

Set name on UFormField to the id prop, so validation errors for this property show up on your field.

Props

A renderer receives every keyword of its property's schema as a prop, such as title, description, properties and required. It also receives:

PropDescription
idThe property's dot path in the form value, like guardian.email. Use it as the form field name.
modelValueThe property's value. Emit update:modelValue to change it, or use defineModel().
isRequiredWhether the parent object lists this property in required.
validateFields(fields: string[]) => Promise<boolean>. Validates the given fields by dot path and runs custom validation for them. Only a renderer on the root schema receives it.

Rendering nested fields

A renderer for an object can render its children with the module's own components. Use SComponent to render a property just as the form would:

components/global/Panel.vue
<script lang="ts" setup>
defineProps<{
  id: string
  title?: string
  properties: Record<string, Record<string, unknown>>
  required: string[]
}>()

const value = defineModel<Record<string, unknown>>({ required: true })
</script>

<template>
  <UCard>
    <template #header>{{ title }}</template>
    <SComponent
      v-for="(child, key) in properties"
      :key="key"
      v-bind="child"
      :id="id ? `${id}.${key}` : key"
      :model-value="value[key]"
      :is-required="required.includes(key)"
      @update:model-value="childValue => (value = { ...value, [key]: childValue })"
    />
  </UCard>
</template>

For a full example, see the multi-step form in the demo. Its MultiStage renderer shows one child object per step and calls validateFields before moving to the next one.