Skip to content
Areia
Esc
↑↓navigate↵open⌘Jpreview
On this page

Form

Schema-driven, auto-generating form components and state management.

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

Works with any Standard Schema library (Zod, Valibot, ArkType, etc.). Built on ilha signals and Areia field controls.

Installation

npm install @areia/form
pnpm add @areia/form
yarn add @areia/form
bun add @areia/form

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

Import

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

Usage

Give every field a schema default (zod .default()) and call Form with just the schema — initial values come straight from the schema. Pass { defaultValues } in options when you need explicit ones. The factory returns an ilha island with the schema captured in its closure, so the schema is never serialized through data-ilha-props.

import { ilha } from "ilha";
import { z } from "zod";
import { Form } from "@areia/form";

const schema = z.object({
  name: z.string().default("John"),
  age: z.number().min(0).max(120).default(30),
  theme: z.enum(["light", "dark", "system"]).default("system"),
  active: z.boolean().default(true),
});

const MyForm = Form(schema, {
  onSubmit: (values) => console.log(values),
});

export default ilha(() => <MyForm />);

Use with any framework via .define()

Because Form returns a normal ilha island, you can register it as a custom element with .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:

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

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

export const SettingsForm = Form(schema, {
  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

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

React

import "./settings-form";

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

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 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.

import { ilha } from "ilha";
import { z } from "zod";
import { Form } from "@areia/form";

const ColorForm = Form(
  z.object({ brandColor: z.string() }),
  { brandColor: "#ff0000" },
  {
    uiOverrides: { brandColor: { type: "color", label: "Brand Color" } },
    onSubmit: (values) => console.log(values),
  }
);

export default ilha.render(() => <ColorForm />);

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.

Scene Controls
Settings
import { ilha } from "ilha";
import { z } from "zod";
import { FloatingForm } from "@areia/form";

const SceneControls = FloatingForm(
  z.object({
    speed: z.number().min(0.1).max(10),
    wireframe: z.boolean(),
    color: z.string(),
  }),
  { speed: 1.0, wireframe: false, color: "#ff00ff" },
  {
    title: "Scene Controls",
    uiOverrides: { color: { type: "color" } },
    onChange: (values) => console.log("changed:", values),
  }
);

export default ilha.render(() => (
  <div class="relative w-full h-[400px]">
    <SceneControls />
  </div>
));

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.

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:

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

const state = createFormState(schema, defaults);

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

API Reference

Form(schema, options?)

Parameter Type Description
schema StandardSchemaV1 The validation schema (Zod, Valibot, ArkType, etc.).
options FormOptions Optional configuration.

Returns an ilha island. Call .define(tagName) to expose it as a custom element.

FormOptions

Option Type Default Description
defaultValues Record<string, unknown> from schema Initial values. Omit to derive from schema .default().
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, options?)

Parameter Type Description
schema StandardSchemaV1 The validation schema.
options FloatingFormOptions Optional configuration.

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

FloatingFormOptions

Option Type Default Description
defaultValues Record<string, unknown> from schema Initial values. Omit to derive from schema .default().
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.

Was this page helpful?