# Controlled fields

> This document is the Markdown version of [formisch.dev/qwik/guides/controlled-fields/](https://formisch.dev/qwik/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.

### Text input example

For a text input field you simply add the `value` attribute and pass the value of the field:

```tsx
<Field
  of={loginForm}
  path={['firstName']}
  render$={(field) => (
    <input {...field.props} type="text" value={field.input.value} />
  )}
/>
```

### Exception for files

The HTML `<input type="file" />` element is an exception because it cannot be controlled. However, you have the possibility to control the UI around it. For inspiration you can use the code of our [`FileInput`](https://github.com/open-circle/formisch/blob/main/playgrounds/qwik/src/components/FileInput.tsx) component from our [playground](/playground/special/).

## Numbers and dates

Fields defined with a `v.number()` or `v.date()` schema need extra steps to be controlled, because the `<input />` element natively understands only strings as value.

### Number input example

An `<input type="number" />` only reads and writes strings. So if you spread `field.props` onto it, `field.input.value` holds a string like `"123"`, even though it is typed as `number`. To store a real number, override the input handler with one that converts the value via `valueAsNumber` and forwards it to `field.onInput`.

You also have to handle `NaN`. While typing a floating point number, the value can briefly be `NaN`, for example right after typing `1.`. If you don't catch this for the displayed value, the input is cleared. It is best to encapsulate both in a separate component as described in the [input components](/qwik/guides/input-components.md) guide.

```tsx
import type { FieldElementProps } from '@formisch/qwik';
import {
  component$,
  type QRL,
  type ReadonlySignal,
  useSignal,
  useTask$,
} from '@qwik.dev/core';

interface NumberInputProps extends Omit<FieldElementProps, 'onInput$'> {
  type: 'number';
  label?: string;
  placeholder?: string;
  input: ReadonlySignal<number | undefined>;
  errors: ReadonlySignal<[string, ...string[]] | null>;
  required?: boolean;
  onInput$: QRL<(value: number | undefined) => void>;
}

export const NumberInput = component$<NumberInputProps>(
  ({ input, label, errors, name, onInput$, ...inputProps }) => {
    // Keep the last displayed value that is not `NaN`
    const value = useSignal<number>();
    useTask$(({ track }) => {
      if (!Number.isNaN(track(input))) {
        value.value = input.value;
      }
    });

    return (
      <div>
        {label && <label for={name}>{label}</label>}
        <input
          {...inputProps}
          id={name}
          name={name}
          type="number"
          value={value.value}
          // Convert string to number before storing
          onInput$={(_, element) =>
            onInput$(element.value === '' ? undefined : element.valueAsNumber)
          }
          aria-invalid={!!errors.value}
          aria-errormessage={`${name}-error`}
        />
        {errors.value && <div id={`${name}-error`}>{errors.value[0]}</div>}
      </div>
    );
  }
);
```

Pass `field.onInput` after spreading `field.props` so it overrides the native handler.

```tsx
<Field
  of={form}
  path={['age']}
  render$={(field) => (
    <NumberInput
      {...field.props}
      type="number"
      label="Age"
      input={field.input}
      errors={field.errors}
      onInput$={field.onInput}
    />
  )}
/>
```

### Date input example

The same applies to dates. An `<input type="date" />` works with `yyyy-mm-dd` strings, while you usually want to store a `Date`. So you convert in both directions: format the `Date` for display, and parse the entered string back with `valueAsDate` before storing it.

```tsx
import type { FieldElementProps } from '@formisch/qwik';
import {
  component$,
  type QRL,
  type ReadonlySignal,
  useComputed$,
} from '@qwik.dev/core';

interface DateInputProps extends Omit<FieldElementProps, 'onInput$'> {
  type: 'date';
  label?: string;
  placeholder?: string;
  input: ReadonlySignal<Date | undefined>;
  errors: ReadonlySignal<[string, ...string[]] | null>;
  required?: boolean;
  onInput$: QRL<(value: Date | undefined) => void>;
}

export const DateInput = component$<DateInputProps>(
  ({ input, label, errors, name, onInput$, ...inputProps }) => {
    // Transform date to string
    const value = useComputed$(() =>
      input.value && !Number.isNaN(input.value.getTime())
        ? input.value.toISOString().split('T', 1)[0]
        : ''
    );

    return (
      <div>
        {label && <label for={name}>{label}</label>}
        <input
          {...inputProps}
          id={name}
          name={name}
          type="date"
          value={value.value}
          // Convert string to date before storing
          onInput$={(_, element) => onInput$(element.valueAsDate ?? undefined)}
          aria-invalid={!!errors.value}
          aria-errormessage={`${name}-error`}
        />
        {errors.value && <div id={`${name}-error`}>{errors.value[0]}</div>}
      </div>
    );
  }
);
```

As with the number input, pass `field.onInput` after the spread to override the native handler.

```tsx
<Field
  of={form}
  path={['birthday']}
  render$={(field) => (
    <DateInput
      {...field.props}
      type="date"
      label="Birthday"
      input={field.input}
      errors={field.errors}
      onInput$={field.onInput}
    />
  )}
/>
```

## Custom inputs and component libraries

Some component libraries don't expose the underlying native HTML element, which means you cannot spread `field.props` onto them. For these cases, use `field.onInput` to set the value programmatically.

```tsx
import { DatePicker } from 'some-component-library';

<Field
  of={form}
  path={['date']}
  render$={(field) => (
    <DatePicker
      value={field.input.value}
      onChange$={(newDate) => field.onInput(newDate)}
    />
  )}
/>;
```

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

The `field.onInput` method updates the field value and triggers validation, just like a native input would.

## Next steps

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