# Create your form

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

Formisch consists of hooks, components and methods. To create a form you use the [`useForm`](/honox/api/useForm.md) hook.

## Form hook

The [`useForm`](/honox/api/useForm.md) hook initializes and returns the store of your form. The store contains the state of the form and can be used with other Formisch hooks, components and methods to build your form.

```tsx
// app/islands/LoginForm.tsx
import { Field, Form, useForm } from '@formisch/honox';
import * as v from 'valibot';

const LoginSchema = v.object({
  email: v.pipe(v.string(), v.email()),
  password: v.pipe(v.string(), v.minLength(8)),
});

export default function LoginForm() {
  const loginForm = useForm({
    schema: LoginSchema,
  });

  return (
    <Form of={loginForm} onSubmit={(output) => console.log(output)}>
      {/* Form fields will go here */}
    </Form>
  );
}
```

### Configuration options

The [`useForm`](/honox/api/useForm.md) hook accepts a configuration object with the following options:

- `schema`: Your Valibot schema that defines the form
- `initialInput`: Initial values for your form fields (optional)
- `emptyInput`: The empty value each field type starts at when a required field has no initial input (optional, defaults to `{ string: '' }`)
- `validate`: When validation first occurs (optional, defaults to `'submit'`)
- `revalidate`: When revalidation occurs after initial validation (optional, defaults to `'input'`)

```tsx
const loginForm = useForm({
  schema: LoginSchema,
  initialInput: {
    email: 'user@example.com',
  },
  validate: 'initial',
  revalidate: 'input',
});
```

Formisch tracks two inputs for every field: the **initial input** (baseline for dirty tracking) and the **current input** (what the user is editing). In many apps, the initial input represents the server state while the current input represents the client state.

`isDirty` becomes `true` when a field's current input differs from its initial input. Use [`setInput`](/methods/api/setInput.md) to update the current input (client state), and use [`reset`](/methods/api/reset.md) to update the initial input (baseline) when your server data changes or is refreshed.

### Empty input

By default, a required string field starts as an empty string (`''`) instead of `undefined`, matching an empty text input. This way an empty field shows the validation message you defined for it (for example from `v.nonEmpty()`) without you setting an `initialInput` for every field.

You can configure the empty value per field type, or opt out by setting a type to `undefined`:

```tsx
const loginForm = useForm({
  schema: LoginSchema,
  emptyInput: {
    string: '', // the default
    number: 0, // required numbers start at 0 instead of undefined
    boolean: false, // checkboxes start unchecked
  },
});
```

Optional and nullable fields are never affected and keep starting as `undefined`, since they accept it. The supported types are `string`, `number`, `boolean` and `date`, and the default is `{ string: '' }`.

## Render in an island

Forms are interactive, so the component that creates the form must be a HonoX island. Place it in the `app/islands` directory and export it as default, as in the example above. You can then render it from any route like a regular component:

```tsx
// app/routes/login/index.tsx
import { createRoute } from 'honox/factory';
import LoginForm from '../../islands/LoginForm';

export default createRoute((c) => c.render(<LoginForm />));
```

HonoX renders the island on the server and hydrates it on the client. During server-side rendering, effects do not run and form state is not tracked, so the form simply renders its initial state and becomes interactive after hydration. Props passed to an island are serialized as JSON, so pass plain data, such as the initial input of your form, rather than functions.

Until the island is hydrated, the browser submits the form natively. By default, this sends a GET request with all field values in the URL. For forms with sensitive data such as passwords, set `method="post"` on the `<Form />` component to keep the values out of the URL, and make sure that the route handles the POST request, for example by exporting a `POST` handler created with `createRoute`. After hydration, Formisch handles the submission and the `method` has no effect.

## Multiple forms

When a page contains multiple forms, you can create separate form stores for each one:

```tsx
import { Form, useForm } from '@formisch/honox';
import * as v from 'valibot';

const LoginSchema = v.object({
  email: v.pipe(v.string(), v.email()),
  password: v.pipe(v.string(), v.minLength(8)),
});

const RegisterSchema = v.object({
  username: v.pipe(v.string(), v.minLength(3)),
  email: v.pipe(v.string(), v.email()),
  password: v.pipe(v.string(), v.minLength(8)),
});

export default function App() {
  const loginForm = useForm({ schema: LoginSchema });
  const registerForm = useForm({ schema: RegisterSchema });

  return (
    <>
      <Form of={loginForm} onSubmit={(output) => console.log(output)}>
        {/* … */}
      </Form>
      <Form of={registerForm} onSubmit={(output) => console.log(output)}>
        {/* … */}
      </Form>
    </>
  );
}
```

If you need a multi-step form (wizard), see the [multi-step form discussion](https://github.com/open-circle/formisch/discussions/108) for patterns and approaches.

## Next steps

Now that you know how to create a form, continue to the [add form fields](/honox/guides/add-form-fields.md) guide to learn how to connect your input elements to the form using the [`Field`](/honox/api/Field.md) component.
