# Controlled fields

> This document is the Markdown version of [formisch.dev/vue/guides/controlled-fields/](https://formisch.dev/vue/guides/controlled-fields/). For the complete documentation index, see [llms.txt](https://formisch.dev/llms.txt).

By default, all form fields are uncontrolled because that's the default behavior of the browser. For a simple login or contact form this is quite sufficient.

## Why controlled?

As soon as your forms become more complex, for example you set initial values or change the values of a form field via [`setInput`](/methods/api/setInput.md), it becomes necessary that you control your fields yourself. For example, depending on which HTML form field you use, you may need to set the `value`, `checked` or `selected` attributes.

## Simple text inputs

For a text input you simply add the `v-model` directive:

```vue
<template>
  <Field :of="loginForm" :path="['firstName']" v-slot="field">
    <input v-model="field.input" v-bind="field.props" type="text" />
  </Field>
</template>
```

## Numbers and dates

In Vue you don't wire up an input handler — `v-model` reads and writes the value through the `field.input` setter. That makes numbers and dates mostly automatic.

### Number inputs

For `<input type="number" />`, the `.number` modifier converts the value to a real number that matches a `v.number()` schema. Vue applies it automatically for number inputs, but you can also add it explicitly:

```vue
<template>
  <Field :of="form" :path="['age']" v-slot="field">
    <input v-model.number="field.input" v-bind="field.props" type="number" />
  </Field>
</template>
```

### Date inputs

An `<input type="date" />` exposes its value as a `yyyy-mm-dd` string, which `v-model` stores as-is. This pairs directly with a `v.string()` schema:

```vue
<template>
  <Field :of="form" :path="['birthday']" v-slot="field">
    <input v-model="field.input" v-bind="field.props" type="date" />
  </Field>
</template>
```

If your schema expects a real `Date` (`v.date()`), Vue has no modifier for this, so convert in both directions yourself:

```vue
<script setup lang="ts">
function parseDate(event: Event) {
  return (event.target as HTMLInputElement).valueAsDate ?? undefined;
}
</script>

<template>
  <Field :of="form" :path="['birthday']" v-slot="field">
    <input
      v-bind="field.props"
      type="date"
      :value="field.input?.toISOString().split('T', 1)[0] ?? ''"
      @input="field.input = parseDate($event)"
    />
  </Field>
</template>
```

## Checkboxes

For checkboxes, you bind the value with `v-model`, which handles both boolean and array values automatically:

**Single checkbox** (boolean):

```vue
<template>
  <Field :of="form" :path="['acceptTerms']" v-slot="field">
    <input type="checkbox" v-bind="field.props" v-model="field.input" />
  </Field>
</template>
```

**Multiple checkboxes** (array of strings):

```vue
<template>
  <Field :of="form" :path="['interests']" v-slot="field">
    <label v-for="option in options" :key="option.value">
      <input
        type="checkbox"
        :value="option.value"
        v-bind="field.props"
        v-model="field.input"
      />
      {{ option.label }}
    </label>
  </Field>
</template>
```

## Select elements

For select elements, you control the value with `v-model`:

**Single select**:

```vue
<template>
  <Field :of="form" :path="['country']" v-slot="field">
    <select v-bind="field.props" v-model="field.input">
      <option
        v-for="option in options"
        :key="option.value"
        :value="option.value"
      >
        {{ option.label }}
      </option>
    </select>
  </Field>
</template>
```

**Multiple select**:

```vue
<template>
  <Field :of="form" :path="['languages']" v-slot="field">
    <select multiple v-bind="field.props" v-model="field.input">
      <option
        v-for="option in options"
        :key="option.value"
        :value="option.value"
      >
        {{ option.label }}
      </option>
    </select>
  </Field>
</template>
```

## File inputs

The HTML `<input type="file" />` element is an exception because it cannot be controlled in the traditional way. However, you can control the UI around it. For inspiration, check out our [`FileInput`](https://github.com/open-circle/formisch/blob/main/playgrounds/vue/src/components/FileInput.vue) component from the [playground](/playground/special/).

## Custom inputs and component libraries

Component libraries don't expose their underlying native element, so `v-bind="field.props"` does not work on them. Vue treats the `ref` entry inside that object as a real template ref, which would register the component instance instead of a DOM element. Bind the entries individually and let `v-model` write the value:

```vue
<template>
  <Field :of="form" :path="['date']" v-slot="field">
    <DatePicker
      :ref="(instance) => field.props.ref(instance?.$el ?? instance)"
      :name="field.props.name"
      :autofocus="field.props.autofocus"
      v-model="field.input"
      @focus="field.props.onFocus"
      @blur="field.props.onBlur"
    />
  </Field>
</template>
```

This is useful for:

- **Component libraries** that wrap native elements without exposing them
- **Complex custom inputs** like date pickers, rich text editors, or color pickers

Assigning `field.input` updates the field value and triggers validation, just like a native input would. Registering the element keeps [`focus`](/methods/api/focus.md) working, and the focus handler is what marks the field as touched. Unwrapping `$el` only helps when the component's root is the focusable element; if it wraps its control in a container, reach for the library's own input ref instead.

For ready-made wiring recipes, check out our integration guides:

- [shadcn-vue](/vue/guides/shadcn-vue.md)
- [Ark UI](/vue/guides/ark-ui.md)
- [Reka UI](/vue/guides/reka-ui.md)
- [PrimeVue](/vue/guides/primevue.md)

## Next steps

Now that you understand controlled fields, you can explore more advanced topics like [nested fields](/vue/guides/nested-fields.md) and [field arrays](/vue/guides/field-arrays.md) to handle complex form structures.
