Get Started
Introduction
Components
Accordion
Badge
Button
Card
Checkbox
Command
Dialog
Dropdown Menu
Input
Select
Switch
Table
Tabs
Toast
Tooltip
Forms
Introduction
Validation
Field Arrays

Introduction

Forms in BarefootJS — start with createSignal for the simple cases, reach for @barefootjs/form when things get more involved.

#Simple Form

For a small form, the built-in primitives are usually enough. Pair createSignal with value and onInput for two-way binding — no extra dependencies required.

#Controlled input with createSignal

Current value:

1import { createSignal } from '@barefootjs/client'2import { Input } from '@/components/ui/input'34const [text, setText] = createSignal('')56<Input7  value={text()}8  onInput={(e) => setText(e.target.value)}9  placeholder="Type something..."10/>11<p>Current value: {text()}</p>

#When to Reach for @barefootjs/form

Wiring signals by hand stays pleasant for a single input, but starts to add up once a form needs:

  • Schema-based validation across multiple fields
  • Per-field touched / dirty state
  • Different timing for first validation vs. revalidation (blur vs. input)
  • Server-side errors mapped back onto specific fields
  • Submission state, reset, and clean default-value tracking

At that point, @barefootjs/form bundles all of it behind a small, declarative API.

#Features

@barefootjs/form is built around Standard Schema, so any compliant validator works out of the box — swap the schema and the rest of the component stays exactly the same.

Validator-agnostic via Standard Schema

Use Zod, Valibot, ArkType, or any other Standard Schema implementation. Migrate between them without touching component code.

Configurable validation timing

validateOn picks when validation first runs ("submit" | "blur" | "input"); revalidateOn picks how it behaves after the first error.

Field controllers

form.field(name) returns a memoized controller exposing value(), error(), touched(), dirty(), and matching handlers.

Server errors and dirty tracking

form.setError() surfaces server-side errors on specific fields; form.isDirty() compares current values against defaults.

Installation

1# With Zod2bun add @barefootjs/form zod34# With Valibot5bun add @barefootjs/form valibot67# With ArkType8bun add @barefootjs/form arktype

#Basic Example

#Profile form with Zod

This is your public display name.

1"use client"23import { createForm } from '@barefootjs/form'4import { z } from 'zod'56function ProfileForm() {7  const form = createForm({8    schema: z.object({9      username: z.string()10        .min(1, 'Username is required')11        .max(30, 'Username must be at most 30 characters'),12    }),13    defaultValues: { username: '' },14    onSubmit: async (data) => {15      await fetch('/api/profile', {16        method: 'POST',17        body: JSON.stringify(data),18      })19    },20  })2122  const username = form.field('username')2324  return (25    <form onSubmit={form.handleSubmit}>26      <label>Username</label>27      <input28        value={username.value()}29        onInput={username.handleInput}30        onBlur={username.handleBlur}31      />32      <p>{username.error()}</p>33      <button type="submit" disabled={form.isSubmitting()}>34        {form.isSubmitting() ? 'Submitting...' : 'Submit'}35      </button>36    </form>37  )38}

Same component, different validator

Because createForm accepts any Standard Schema validator, swapping the schema definition is the only change needed.

Valibot

1import { createForm } from '@barefootjs/form'2import * as v from 'valibot'34// Just swap the schema — everything else stays the same5const form = createForm({6  schema: v.object({7    username: v.pipe(8      v.string(),9      v.minLength(1, 'Username is required'),10      v.maxLength(30, 'Username must be at most 30 characters'),11    ),12  }),13  defaultValues: { username: '' },14  onSubmit: async (data) => { /* ... */ },15})

ArkType

1import { createForm } from '@barefootjs/form'2import { type } from 'arktype'34const form = createForm({5  schema: type({6    username: '1 <= string <= 30',7  }),8  defaultValues: { username: '' },9  onSubmit: async (data) => { /* ... */ },10})

#Next Steps

  • Validation — error timing, multi-field forms, and custom rules.
  • Field Arrays — dynamic add / remove / reorder for repeated fields.