---
title: "Form"
description: "Schema-driven, auto-generating form components and state management."
---

> Documentation Index
> Fetch the complete documentation index at: https://areia.ilha.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Form

import { Demo1, Demo2, Demo3 } from "./form.demos";

# Form

Schema-driven, auto-generating form components for Areia.

Works with any [Standard Schema](https://standardschema.dev) library (Zod, Valibot, ArkType, etc.). Built on ilha signals and Areia field controls.

## Installation

```bash
bun add @areia/form
```

Peer dependencies: `areia`, `ilha`, and `@areia/slots`.

## Import

```ts
import {
  Form,
  FloatingForm,
  createFormState,
  renderForm,
} from "@areia/form";
```

## Usage

`Form` is a factory: call it with your schema, default values, and options. It returns an ilha island with the schema captured in its closure — so the schema is never serialized through `data-ilha-props`.

 console.log(values) }\n);\n\nexport default ilha.render(() => );'
  }
  lang="tsx"
>

## Use with any framework via `.define()`

Because `Form` returns a normal ilha island, you can register it as a [custom element](https://ilha.build/guide/island/define/) with [`.define()`](https://ilha.build/guide/island/define/). That mounts and unmounts itself via `connectedCallback` / `disconnectedCallback`, so it works from React, Vue, Svelte, plain HTML, or a CMS template — without calling `ilha.mount()`.

Schema and callbacks stay in the factory closure (they cannot round-trip through HTML attributes). Register once at module scope, then drop the tag anywhere:

```ts
// settings-form.ts
import { Form } from "@areia/form";
import { z } from "zod";

const schema = z.object({
  name: z.string(),
  active: z.boolean(),
});

export const SettingsForm = Form(
  schema,
  { name: "", active: true },
  {
onSubmit: (values) => console.log(values),
onChange: (values) => console.log("changed:", values),
  },
);

// Safe during SSR — no-ops where customElements is unavailable.
SettingsForm.define("areia-settings-form");
```

### Plain HTML

```html
<script type="module" src="./settings-form.ts"></script>
<areia-settings-form></areia-settings-form>
```

### React

```tsx
import "./settings-form";

export function Page() {
  return <areia-settings-form />;
}
```

### Vue

```vue
<script setup>
import "./settings-form";
</script>

<template>
  <areia-settings-form />
</template>
```

`FloatingForm` works the same way — call `.define("areia-scene-controls")` on the island returned by the factory.

For rich runtime updates (title, width, etc.) after mount, set the host's [`props`](https://ilha.build/guide/island/define/) property. Prefer capturing submit/change handlers in the factory options so they stay typed and outside the attribute surface.

## Examples

### UI Overrides

Manually override field types, labels, or extra props with `uiOverrides`.

 console.log(values),\n  }\n);\n\nexport default ilha.render(() => );'
  }
  lang="tsx"
>

### Floating Form (DevTools)

`FloatingForm` is also a factory. It creates a draggable, collapsible overlay panel — useful for generative art, 3D scenes, or internal control panels.

 console.log("changed:", values),\n  }\n);\n\nexport default ilha.render(() => (\n  <div class="relative w-full h-[400px]">\n    \n  </div>\n));'
  }
  lang="tsx"
>

### Headless State Manager

If you only need reactive state for your own UI, use `createFormState`. Values and `field(path)` accessors are ilha signals — usable with `bind:value` / `bind:checked`, or observed from any framework via `effect`.

```ts
import { z } from "zod";
import { effect } from "ilha";
import { createFormState } from "@areia/form";

const schema = z.object({
  username: z.string().min(3),
});

const state = createFormState(schema, { username: "" });

effect(() => {
  console.log("Current values:", state.values());
  console.log("Validation errors:", state.errors());
  console.log("Dirty:", state.isDirty());
});

// Programmatic updates
state.setValue("username", "admin");

// Or bind a field accessor directly in ilha JSX:
// <Input bind:value={state.field("username")} />

await state.validate();
```

### Static render (no island)

`renderForm` produces markup without wiring submit/validation. Pass an existing `state` when you own the store yourself:

```tsx
import { renderForm, createFormState } from "@areia/form";

const state = createFormState(schema, defaults);

renderForm({
  schema,
  defaultValues: defaults,
  state,
  submitLabel: "Save",
});
```

## API Reference

### Form(schema, defaultValues, options?)

| Parameter       | Type                      | Description                                          |
| --------------- | ------------------------- | ---------------------------------------------------- |
| `schema`        | `StandardSchemaV1`        | The validation schema (Zod, Valibot, ArkType, etc.). |
| `defaultValues` | `Record<string, unknown>` | Initial field values.                                |
| `options`       | `FormOptions`             | Optional configuration.                              |

Returns an ilha island. Call [`.define(tagName)`](https://ilha.build/guide/island/define/) to expose it as a custom element.

**FormOptions**

| Option        | Type                                | Default    | Description                                         |
| ------------- | ----------------------------------- | ---------- | --------------------------------------------------- |
| `uiOverrides` | `UIOverrides`                       | -          | Manual overrides for field type, labels, options.   |
| `onSubmit`    | `(values: T, event: Event) => void` | -          | Called when the form passes validation and submits. |
| `onChange`    | `(values: T) => void`               | -          | Fired when values diverge from the defaults.        |
| `submitLabel` | `string`                            | `"Submit"` | Text rendered on the submit button.                 |
| `validateOn`  | `"change" \| "blur" \| "submit"`    | -          | When validation rules are checked.                  |

### FloatingForm(schema, defaultValues, options?)

| Parameter       | Type                      | Description             |
| --------------- | ------------------------- | ----------------------- |
| `schema`        | `StandardSchemaV1`        | The validation schema.  |
| `defaultValues` | `Record<string, unknown>` | Initial values.         |
| `options`       | `FloatingFormOptions`     | Optional configuration. |

Also returns an ilha island — use `.define()` the same way as `Form`.

**FloatingFormOptions**

| Option        | Type                       | Default            | Description                         |
| ------------- | -------------------------- | ------------------ | ----------------------------------- |
| `uiOverrides` | `UIOverrides`              | -                  | Overrides.                          |
| `title`       | `string`                   | `"Controls"`       | Title rendered in the drag handle.  |
| `collapsed`   | `boolean`                  | `false`            | Whether the panel starts collapsed. |
| `position`    | `{ x: number; y: number }` | `{ x: 20, y: 20 }` | Initial position.                   |
| `width`       | `string \| number`         | `"320px"`          | Fixed width of the floating panel.  |
| `onClose`     | `() => void`               | -                  | Shows a close button when set.      |
| `onChange`    | `(values: T) => void`      | -                  | Called when values become dirty.    |
| `onSubmit`    | `(values: T) => void`      | -                  | Called on form submit.              |

### createFormState(schema, defaultValues)

| Return     | Type                                     | Description                                              |
| ---------- | ---------------------------------------- | -------------------------------------------------------- |
| `values`   | `() => T`                                | Current form values (ilha signal).                       |
| `isDirty`  | `() => boolean`                          | `true` when any top-level key differs from the defaults. |
| `isValid`  | `() => boolean`                          | `true` when there are no validation errors.              |
| `errors`   | `() => Record<string, string>`           | Path → message map.                                      |
| `setValue` | `(path: string, value: unknown) => void` | Write a field by dotted path.                            |
| `field`    | `(path: string) => SignalAccessor`       | Bindable slice for `bind:value` / `bind:checked`.        |
| `validate` | `() => Promise<boolean>`                 | Run Standard Schema validation.                          |
| `reset`    | `() => void`                             | Restore defaults and clear errors.                       |

Source: https://areia.ilha.build/components/form/index.mdx
