TanStack Form

Integrate Ark Preset components with TanStack Form for powerful, type-safe form state management in Solid.js.

Docs

TanStack Form provides a headless form library with first-class Solid.js support. Combined with Ark Preset’s accessible form components, you get a complete form solution with proper styling, validation, and accessibility.

Plan
Priority
{"name":"","email":"","age":0,"role":"","country":"","bio":"","plan":"free","rating":0,"priority":"medium","volume":50,"notifications":false,"skills":[],"password":"","accepted":false,"startDate":"2026-06-01","bold":false,"alignment":"left"}
import { createForm } from "@tanstack/solid-form";
import { createListCollection } from "@ark-ui/solid";
import { Index, Show } from "solid-js";
import { parseDate } from "@internationalized/date";
import { z } from "zod";

import {
  Input,
  Button,
  Textarea,
  NumberInput,
  Select,
  SelectLabel,
  SelectTrigger,
  SelectContent,
  SelectItem,
  Checkbox,
  CheckboxLabel,
  Switch,
  SwitchLabel,
  Slider,
  SliderLabel,
  SliderControl,
  SliderThumb,
  RatingGroup,
  RatingGroupLabel,
  Combobox,
  ComboboxLabel,
  ComboboxInputTrigger,
  ComboboxContent,
  ComboboxItem,
  TagsInput,
  RadioGroup,
  RadioGroupItem,
  SegmentGroup,
  SegmentGroupItem,
  Toggle,
  ToggleGroup,
  ToggleGroupItem,
  PasswordInput,
  DatePicker,
} from "~/components/tanstack-form";
import { labelVariants } from "@ark-preset/core";

// ── Zod schema ──

const formSchema = z.object({
  name: z.string().min(2, "Name must be at least 2 characters"),
  email: z.string().min(1, "Email is required").email("Invalid email format"),
  age: z.number().min(13, "Must be at least 13 years old"),
  role: z.string().min(1, "Please select a role"),
  country: z.string(),
  bio: z.string().max(200, "Bio must be under 200 characters"),
  plan: z.string(),
  rating: z.number(),
  priority: z.string(),
  volume: z.number(),
  notifications: z.boolean(),
  skills: z.array(z.string()).max(10, "Maximum 10 skills allowed"),
  password: z
    .string()
    .refine((v) => v.length === 0 || v.length >= 6, "Password must be at least 6 characters"),
  accepted: z.boolean().refine((v) => v, { message: "You must accept the terms" }),
  startDate: z.string().min(1, "Start date is required"),
  bold: z.boolean(),
  alignment: z.string(),
});

// ── Field helpers ──

function fieldError(field: any): string | undefined {
  const err = field.state.meta.errors[0];
  if (!err) return undefined;
  if (typeof err === "string") return err;
  return (err as { message?: string }).message;
}

function FieldHint(props: { field: any }) {
  return (
    <Show when={fieldError(props.field)}>
      {(msg) => <div class="text-destructive text-sm">{msg()}</div>}
    </Show>
  );
}

// ── Data ──

const countries = createListCollection({
  items: [
    { label: "Indonesia", value: "id" },
    { label: "Malaysia", value: "my" },
    { label: "Singapore", value: "sg" },
    { label: "Thailand", value: "th" },
    { label: "Vietnam", value: "vn" },
    { label: "Japan", value: "jp" },
    { label: "South Korea", value: "kr" },
    { label: "United States", value: "us" },
    { label: "United Kingdom", value: "gb" },
    { label: "Australia", value: "au" },
  ],
});

const roles = createListCollection({
  items: [
    { label: "Developer", value: "developer" },
    { label: "Designer", value: "designer" },
    { label: "Product Manager", value: "pm" },
    { label: "QA Engineer", value: "qa" },
    { label: "Data Analyst", value: "analyst" },
    { label: "DevOps Engineer", value: "devops" },
    { label: "Tech Lead", value: "tech-lead" },
    { label: "Engineering Manager", value: "eng-manager" },
    { label: "CTO", value: "cto" },
    { label: "VP of Engineering", value: "vp-eng" },
  ],
});

// ── Component ──

export default function TanstackFormDemo() {
  const form = createForm(() => ({
    defaultValues: {
      name: "",
      email: "",
      age: 0,
      role: "",
      country: "",
      bio: "",
      plan: "free",
      rating: 0,
      priority: "medium",
      volume: 50,
      notifications: false,
      skills: [] as string[],
      password: "",
      accepted: false,
      startDate: "2026-06-01",
      bold: false,
      alignment: "left",
    },
    validators: {
      onBlur: formSchema,
      onSubmit: (values: unknown) => {
        console.log("[dbg] onSubmit validator called");
        const r = formSchema.safeParse(values);
        console.log("[dbg] onSubmit validator success =", r.success);
        return r.success ? undefined : r.error;
      },
    },
    onSubmit: async ({ value }) => {
      console.log("[dbg] form onSubmit called, accepted =", value.accepted);
      alert(JSON.stringify(value, null, 2));
    },
  }));

  return (
    <form
      onSubmit={(e) => {
        console.log("[dbg] native submit event");
        e.preventDefault();
        e.stopPropagation();
        try {
          const r = form.handleSubmit() as any;
          if (r && typeof r.catch === "function") {
            r.catch((err: unknown) => console.log("[dbg] handleSubmit rejected", err));
          }
        } catch (err) {
          console.log("[dbg] handleSubmit threw", err);
        }
      }}
      class="flex flex-col gap-5"
    >
      {/* ── Name ── */}
      <form.Field
        name="name"
        children={(field) => (
          <Input
            name={field().name}
            value={field().state.value}
            label="Name"
            placeholder="Enter your name"
            error={fieldError(field())}
            onBlur={field().handleBlur}
            onInput={(e) => field().handleChange(e.currentTarget.value)}
          />
        )}
      />

      {/* ── Email ── */}
      <form.Field
        name="email"
        children={(field) => (
          <Input
            name={field().name}
            value={field().state.value}
            label="Email"
            placeholder="email@example.com"
            error={fieldError(field())}
            onBlur={field().handleBlur}
            onInput={(e) => field().handleChange(e.currentTarget.value)}
          />
        )}
      />

      {/* ── Age ── */}
      <form.Field
        name="age"
        children={(field) => (
          <NumberInput
            name={field().name}
            label="Age"
            value={String(field().state.value)}
            error={!!fieldError(field())}
            min={0}
            max={150}
            onValueChange={(e) => field().handleChange(e.valueAsNumber)}
            onBlur={field().handleBlur}
          />
        )}
      />

      {/* ── Password ── */}
      <form.Field
        name="password"
        children={(field) => (
          <PasswordInput
            name={field().name}
            value={field().state.value}
            label="Password"
            placeholder="Enter password"
            error={!!fieldError(field())}
            onInput={(e: InputEvent) => field().handleChange((e.target as HTMLInputElement).value)}
            onBlur={field().handleBlur}
          />
        )}
      />

      {/* ── Role ── */}
      <form.Field
        name="role"
        children={(field) => (
          <div class="not-prose flex flex-col gap-1">
            <Select
              name={field().name}
              collection={roles}
              value={field().state.value ? [field().state.value] : []}
              error={!!fieldError(field())}
              onValueChange={(e) => {
                field().handleChange(e.value[0]);
                field().handleBlur();
              }}
              onOpenChange={(details) => {
                if (!details.open) field().handleBlur();
              }}
            >
              <SelectLabel>Role</SelectLabel>
              <SelectTrigger placeholder="Select a role" />
              <SelectContent>
                <Index each={roles.items}>
                  {(item) => <SelectItem item={item()}>{item().label}</SelectItem>}
                </Index>
              </SelectContent>
            </Select>
            <FieldHint field={field()} />
          </div>
        )}
      />

      {/* ── Country (Combobox) ── */}
      <form.Field
        name="country"
        children={(field) => (
          <div class="not-prose flex flex-col gap-1">
            <Combobox
              name={field().name}
              collection={countries}
              value={field().state.value ? [field().state.value] : []}
              onValueChange={(e) => field().handleChange(e.value[0])}
              onOpenChange={(details) => {
                if (!details.open) field().handleBlur();
              }}
            >
              <ComboboxLabel>Country</ComboboxLabel>
              <ComboboxInputTrigger placeholder="Search country..." />
              <ComboboxContent>
                <Index each={countries.items}>
                  {(item) => <ComboboxItem item={item()}>{item().label}</ComboboxItem>}
                </Index>
              </ComboboxContent>
            </Combobox>
          </div>
        )}
      />

      {/* ── Plan (RadioGroup) ── */}
      <form.Field
        name="plan"
        children={(field) => (
          <fieldset>
            <div class="not-prose flex flex-col gap-2">
              <legend class={labelVariants()}>Plan</legend>
              <RadioGroup
                name={field().name}
                value={field().state.value}
                onValueChange={(e) => field().handleChange(e.value ?? "")}
                onBlur={field().handleBlur}
              >
                <RadioGroupItem value="free">Free</RadioGroupItem>
                <RadioGroupItem value="pro">Pro</RadioGroupItem>
                <RadioGroupItem value="enterprise">Enterprise</RadioGroupItem>
              </RadioGroup>
            </div>
          </fieldset>
        )}
      />

      {/* ── Priority (SegmentGroup) ── */}
      <form.Field
        name="priority"
        children={(field) => (
          <fieldset>
            <div class="not-prose flex flex-col gap-2">
              <legend class={labelVariants()}>Priority</legend>
              <SegmentGroup
                name={field().name}
                value={field().state.value}
                onValueChange={(e) => field().handleChange(e.value ?? "")}
                onBlur={field().handleBlur}
              >
                <SegmentGroupItem value="low">Low</SegmentGroupItem>
                <SegmentGroupItem value="medium">Medium</SegmentGroupItem>
                <SegmentGroupItem value="high">High</SegmentGroupItem>
              </SegmentGroup>
            </div>
          </fieldset>
        )}
      />

      {/* ── Rating ── */}
      <form.Field
        name="rating"
        children={(field) => (
          <RatingGroup
            name={field().name}
            value={field().state.value}
            count={5}
            orientation="vertical"
            onValueChange={(e) => field().handleChange(e.value)}
            onBlur={field().handleBlur}
          >
            <RatingGroupLabel>Rating</RatingGroupLabel>
          </RatingGroup>
        )}
      />

      {/* ── Volume (Slider) ── */}
      <form.Field
        name="volume"
        children={(field) => (
          <Slider
            name={field().name}
            value={[field().state.value]}
            min={0}
            max={100}
            step={1}
            onValueChange={(e) => field().handleChange(e.value[0])}
            onBlur={field().handleBlur}
          >
            <SliderLabel>Volume: {field().state.value}%</SliderLabel>
            <SliderControl>
              <SliderThumb index={0} />
            </SliderControl>
          </Slider>
        )}
      />

      {/* ── Start Date (DatePicker) ── */}
      <form.Field
        name="startDate"
        children={(field) => (
          <DatePicker
            name={field().name}
            label="Start Date"
            placeholder="Pick a date"
            value={field().state.value ? [parseDate(field().state.value)] : []}
            error={!!fieldError(field())}
            onValueChange={(e) => field().handleChange(e.value[0] ? String(e.value[0]) : "")}
          />
        )}
      />

      {/* ── Bio ── */}
      <form.Field
        name="bio"
        children={(field) => (
          <Textarea
            name={field().name}
            value={field().state.value}
            label="Bio"
            placeholder="Tell us about yourself"
            error={fieldError(field())}
            onBlur={field().handleBlur}
            onInput={(e) => field().handleChange(e.currentTarget.value)}
          />
        )}
      />

      {/* ── Skills (TagsInput) ── */}
      <form.Field
        name="skills"
        children={(field) => (
          <div class="not-prose flex flex-col gap-1">
            <TagsInput
              name={field().name}
              value={field().state.value}
              label="Skills"
              placeholder="Type skill and press Enter"
              onValueChange={(e) => field().handleChange(e.value)}
            />
          </div>
        )}
      />

      {/* ── Notifications (Switch) ── */}
      <form.Field
        name="notifications"
        children={(field) => (
          <Switch
            name={field().name}
            checked={field().state.value}
            onCheckedChange={(e) => field().handleChange(!!e.checked)}
            onBlur={field().handleBlur}
          >
            <SwitchLabel>Enable email notifications</SwitchLabel>
          </Switch>
        )}
      />

      {/* ── Bold (Toggle) ── */}
      <form.Field
        name="bold"
        children={(field) => (
          <Toggle
            name={field().name}
            pressed={field().state.value}
            onPressedChange={(pressed) => field().handleChange(!!pressed)}
            onBlur={field().handleBlur}
          >
            Bold
          </Toggle>
        )}
      />

      {/* ── Alignment (ToggleGroup) ── */}
      <form.Field
        name="alignment"
        children={(field) => (
          <ToggleGroup
            value={[field().state.value]}
            onValueChange={(e) => field().handleChange(e.value[0])}
            onBlur={field().handleBlur}
          >
            <ToggleGroupItem value="left">Left</ToggleGroupItem>
            <ToggleGroupItem value="center">Center</ToggleGroupItem>
            <ToggleGroupItem value="right">Right</ToggleGroupItem>
          </ToggleGroup>
        )}
      />

      {/* ── Accepted (Checkbox) ── */}
      <form.Field
        name="accepted"
        children={(field) => (
          <div class="not-prose flex flex-col gap-1">
            <Checkbox
              name={field().name}
              checked={field().state.value}
              invalid={!!fieldError(field())}
              onCheckedChange={(e) => {
                field().handleChange(!!e.checked);
              }}
              onBlur={field().handleBlur}
            >
              <CheckboxLabel>I accept the terms and conditions</CheckboxLabel>
            </Checkbox>
            <FieldHint field={field()} />
          </div>
        )}
      />

      {/* ── Submit ── */}
      <form.Subscribe
        selector={(state) => ({
          isSubmitting: state.isSubmitting,
        })}
        children={(state) => (
          <Button type="submit" disabled={state().isSubmitting}>
            {state().isSubmitting ? "Submitting..." : "Submit"}
          </Button>
        )}
      />

      {/* DEBUG: live form values (temporary) */}
      <form.Subscribe
        selector={(s) => s.values}
        children={(v) => <pre class="text-xs opacity-50">{JSON.stringify(v())}</pre>}
      />
    </form>
  );
}

Key Features

  • Type-safe form state — Fully typed fields, values, and validation
  • Schema validation — Works with Zod, Valibot, ArkType, and Effect
  • Fine-grained reactivity — Solid.js signals for efficient re-renders
  • Async validation — Debounced and async validators built-in
  • Custom field components — Compose reusable field patterns with createFormHook

Installation

Install TanStack Form:

npm install @tanstack/solid-form

Optionally install a schema validation library:

npm install zod
# or
npm install valibot
# or
npm install arktype

Basic Usage

Create a form with createForm and use form.Field to wire up Ark Preset components:

import { createForm } from "@tanstack/solid-form";
import { Input } from "~/components/input";
import { Button } from "~/components/button";

function SignupForm() {
  const form = createForm(() => ({
    defaultValues: {
      name: "",
      email: "",
    },
    onSubmit: async ({ value }) => {
      console.log("Submit", value);
    },
  }));

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        e.stopPropagation();
        form.handleSubmit();
      }}
      class="flex flex-col gap-4 max-w-sm"
    >
      <form.Field
        name="name"
        children={(field) => (
          <Input
            name={field().name}
            value={field().state.value}
            label="Name"
            placeholder="Enter your name"
            onBlur={field().handleBlur}
            onInput={(e) => field().handleChange(e.currentTarget.value)}
          />
        )}
      />

      <form.Field
        name="email"
        children={(field) => (
          <Input
            name={field().name}
            value={field().state.value}
            label="Email"
            placeholder="Enter your email"
            onBlur={field().handleBlur}
            onInput={(e) => field().handleChange(e.currentTarget.value)}
          />
        )}
      />

      <Button type="submit">Submit</Button>
    </form>
  );
}

Validation

Inline Validators

Pass synchronous or asynchronous validators directly to form.Field:

<form.Field
  name="email"
  validators={{
    onChange: ({ value }) => {
      if (!value) return "Email is required";
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
        return "Invalid email format";
      return undefined;
    },
    onChangeAsyncDebounceMs: 500,
    onChangeAsync: async ({ value }) => {
      // Simulate async check (e.g., email uniqueness)
      await new Promise((r) => setTimeout(r, 300));
      return value === "taken@example.com"
        ? "Email is already in use"
        : undefined;
    },
  }}
  children={(field) => (
    <Input
      name={field().name}
      value={field().state.value}
      label="Email"
      placeholder="Enter your email"
      error={
        field().state.meta.isTouched && field().state.meta.errors.length > 0
          ? field().state.meta.errors.join(", ")
          : undefined
      }
      onBlur={field().handleBlur}
      onInput={(e) => field().handleChange(e.currentTarget.value)}
    />
  )}
/>

Schema Validation with Zod

Use Zod schemas for declarative validation:

import { z } from "zod";
import { createForm } from "@tanstack/solid-form";

const signupSchema = z.object({
  name: z.string().min(3, "Name must be at least 3 characters"),
  email: z.string().email("Invalid email address"),
  age: z.coerce.number().min(18, "Must be at least 18"),
});

function SignupForm() {
  const form = createForm(() => ({
    defaultValues: {
      name: "",
      email: "",
      age: 0,
    },
    validators: {
      onChange: signupSchema,
    },
    onSubmit: async ({ value }) => {
      console.log("Validated data:", value);
    },
  }));

  return (
    <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
      <form.Field
        name="name"
        children={(field) => (
          <Input
            name={field().name}
            value={field().state.value}
            label="Name"
            error={
              field().state.meta.isTouched && field().state.meta.errors[0]
                ? field().state.meta.errors[0]
                : undefined
            }
            onBlur={field().handleBlur}
            onInput={(e) => field().handleChange(e.currentTarget.value)}
          />
        )}
      />
      {/* ... more fields */}
    </form>
  );
}

TanStack Form also supports Valibot, ArkType, and Effect schemas via Standard Schema — no adapter needed.

Subscribe to Form State

Use form.Subscribe to reactively track form-level state:

<form.Subscribe
  selector={(state) => ({
    canSubmit: state.canSubmit,
    isSubmitting: state.isSubmitting,
  })}
  children={(state) => (
    <Button type="submit" disabled={!state().canSubmit}>
      {state().isSubmitting ? "Submitting..." : "Submit"}
    </Button>
  )}
/>

Working with Non-Input Components

Select

import { createListCollection } from "@ark-ui/solid";
import { Index } from "solid-js";
import { Select, SelectLabel, SelectTrigger, SelectContent, SelectItem } from "~/components/select";

const frameworks = createListCollection({
  items: [
    { label: "React", value: "react" },
    { label: "Solid", value: "solid" },
    { label: "Vue", value: "vue" },
  ],
});

// Inside your form:
<form.Field
  name="framework"
  validators={{
    onBlur: ({ value }) => (!value ? "Please select a framework" : undefined),
  }}
  children={(field) => (
    <>
      <Select
        name={field().name}
        collection={frameworks}
        value={field().state.value ? [field().state.value] : []}
        onValueChange={(e) => field().handleChange(e.value[0])}
        onOpenChange={(details) => {
          if (!details.open) field().handleBlur();
        }}
      >
        <SelectLabel>Framework</SelectLabel>
        <SelectTrigger placeholder="Select a framework" />
        <SelectContent>
          <Index each={frameworks.items}>
            {(item) => <SelectItem item={item()}>{item().label}</SelectItem>}
          </Index>
        </SelectContent>
      </Select>
      {field().state.meta.errors[0] && (
        <div class="text-destructive text-sm mt-1">{field().state.meta.errors[0]}</div>
      )}
    </>
  )}
/>

Checkbox

import { Checkbox, CheckboxLabel } from "~/components/checkbox";

<form.Field
  name="accepted"
  validators={{
    onChange: ({ value }) => !value ? "You must accept the terms" : undefined,
    onBlur: ({ value }) => !value ? "You must accept the terms" : undefined,
  }}
  children={(field) => (
    <>
      <Checkbox
        name={field().name}
        checked={field().state.value}
        invalid={!!field().state.meta.errors[0]}
        onCheckedChange={(e) => field().handleChange(!!e.checked)}
        onBlur={field().handleBlur}
      >
        <CheckboxLabel>I accept the terms and conditions</CheckboxLabel>
      </Checkbox>
      {field().state.meta.errors[0] && (
        <div class="text-destructive text-sm mt-1">{field().state.meta.errors[0]}</div>
      )}
    </>
  )}
/>

Switch

import { Switch } from "~/components/switch";

<form.Field
  name="notifications"
  children={(field) => (
    <Switch
      name={field().name}
      checked={field().state.value}
      onCheckedChange={(e) => field().handleChange(!!e.checked)}
      onBlur={field().handleBlur}
    >
      Enable notifications
    </Switch>
  )}
/>

Textarea

import { Textarea } from "~/components/textarea";

<form.Field
  name="bio"
  validators={{
    onChange: ({ value }) =>
      value.length > 500 ? "Bio must be under 500 characters" : undefined,
  }}
  children={(field) => (
    <Textarea
      name={field().name}
      value={field().state.value}
      label="Bio"
      placeholder="Tell us about yourself"
      error={
        field().state.meta.errors[0]
          ? field().state.meta.errors[0]
          : undefined
      }
      onBlur={field().handleBlur}
      onInput={(e) => field().handleChange(e.currentTarget.value)}
    />
  )}
/>

Radio Group

import { RadioGroup } from "~/components/radio-group";

<form.Field
  name="plan"
  validators={{
    onChange: ({ value }) => (!value ? "Please select a plan" : undefined),
    onBlur: ({ value }) => (!value ? "Please select a plan" : undefined),
  }}
  children={(field) => (
    <RadioGroup
      name={field().name}
      value={field().state.value}
      label="Subscription Plan"
      error={field().state.meta.errors[0]}
      onChange={(e) => field().handleChange(e.value)}
      onBlur={field().handleBlur}
      options={[
        { value: "free", label: "Free" },
        { value: "pro", label: "Pro" },
        { value: "enterprise", label: "Enterprise" },
      ]}
    />
  )}
/>

Custom Field Components

For reusable field patterns, use createFormHookContexts and createFormHook:

// src/hooks/form-context.tsx
import { createFormHookContexts } from "@tanstack/solid-form";

export const { fieldContext, formContext, useFieldContext } =
  createFormHookContexts();
// src/components/text-field.tsx
import { useFieldContext } from "../hooks/form-context";
import { Input } from "~/components/input";

export function TextField(props: { label: string; placeholder?: string }) {
  const field = useFieldContext<string>();
  return (
    <Input
      name={field().name}
      value={field().state.value}
      label={props.label}
      placeholder={props.placeholder}
      error={
        field().state.meta.isTouched && field().state.meta.errors[0]
          ? field().state.meta.errors[0]
          : undefined
      }
      onBlur={field().handleBlur}
      onInput={(e) => field().handleChange(e.currentTarget.value)}
    />
  );
}
// src/hooks/form.ts
import { createFormHook } from "@tanstack/solid-form";
import { fieldContext, formContext } from "./form-context";
import { TextField } from "../components/text-field";

export const { useAppForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: { TextField },
  formComponents: {},
});
// Usage
import { useAppForm } from "../hooks/form";

function ProfileForm() {
  const form = useAppForm(() => ({
    defaultValues: { firstName: "", lastName: "" },
    onSubmit: async ({ value }) => console.log(value),
  }));

  return (
    <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
      <form.AppField
        name="firstName"
        children={(field) => <field.TextField label="First Name" />}
      />
      <form.AppField
        name="lastName"
        children={(field) => <field.TextField label="Last Name" />}
      />
    </form>
  );
}

Full Example

Here’s a complete form with Zod schema validation — this is the same code used in the live demo at the top of this page:

import { createForm } from "@tanstack/solid-form";
import { createListCollection } from "@ark-ui/solid";
import { Index, Show } from "solid-js";
import { parseDate } from "@internationalized/date";
import { z } from "zod";

import {
  Input, Button, Textarea, NumberInput,
  Select, SelectLabel, SelectTrigger, SelectContent, SelectItem,
  Checkbox, CheckboxLabel,
  Switch, SwitchLabel,
  Slider, SliderLabel, SliderControl, SliderThumb,
  RatingGroup, RatingGroupLabel,
  Combobox, ComboboxLabel, ComboboxInputTrigger, ComboboxContent, ComboboxItem,
  TagsInput,
  RadioGroup, RadioGroupItem,
  SegmentGroup, SegmentGroupItem,
  Toggle, ToggleIndicator,
  ToggleGroup, ToggleGroupItem,
  PasswordInput, DatePicker,
} from "@ark-preset/solid";
import { labelVariants } from "@ark-preset/core";

// ── Zod schema ──

const formSchema = z.object({
  name: z.string().min(2, "Name must be at least 2 characters"),
  email: z.string().min(1, "Email is required").email("Invalid email format"),
  age: z.number().min(13, "Must be at least 13 years old"),
  role: z.string().min(1, "Please select a role"),
  country: z.string(),
  bio: z.string().max(200, "Bio must be under 200 characters"),
  plan: z.string(),
  rating: z.number(),
  priority: z.string(),
  volume: z.number(),
  notifications: z.boolean(),
  skills: z.array(z.string()).max(10, "Maximum 10 skills allowed"),
  password: z
    .string()
    .refine(
      (v) => v.length === 0 || v.length >= 6,
      "Password must be at least 6 characters",
    ),
  accepted: z.boolean().refine((v) => v, { message: "You must accept the terms" }),
  startDate: z.string().min(1, "Start date is required"),
  bold: z.boolean(),
  alignment: z.string(),
});

// ── Field helpers ──

function fieldError(field: any): string | undefined {
  const err = field.state.meta.errors[0];
  if (!err) return undefined;
  if (typeof err === "string") return err;
  return (err as { message?: string }).message;
}

function FieldHint(props: { field: any }) {
  return (
    <Show when={fieldError(props.field)}>
      {(msg) => <div class="text-destructive text-sm">{msg()}</div>}
    </Show>
  );
}

// ── Data ──

const countries = createListCollection({
  items: [
    { label: "Indonesia", value: "id" },
    { label: "Malaysia", value: "my" },
    { label: "Singapore", value: "sg" },
    { label: "Thailand", value: "th" },
    { label: "Vietnam", value: "vn" },
    { label: "Japan", value: "jp" },
    { label: "South Korea", value: "kr" },
    { label: "United States", value: "us" },
    { label: "United Kingdom", value: "gb" },
    { label: "Australia", value: "au" },
  ],
});

const roles = createListCollection({
  items: [
    { label: "Developer", value: "developer" },
    { label: "Designer", value: "designer" },
    { label: "Product Manager", value: "pm" },
    { label: "QA Engineer", value: "qa" },
    { label: "Data Analyst", value: "analyst" },
    { label: "DevOps Engineer", value: "devops" },
    { label: "Tech Lead", value: "tech-lead" },
    { label: "Engineering Manager", value: "eng-manager" },
    { label: "CTO", value: "cto" },
    { label: "VP of Engineering", value: "vp-eng" },
  ],
});

// ── Component ──

export default function TanstackFormDemo() {
  const form = createForm(() => ({
    defaultValues: {
      name: "", email: "", age: 0, role: "", country: "",
      bio: "", plan: "free", rating: 0, priority: "medium",
      volume: 50, notifications: false, skills: [] as string[],
      password: "", accepted: false, startDate: "2026-06-01",
      bold: false, alignment: "left",
    },
    validators: {
      onBlur: formSchema,
      onSubmit: formSchema,
    },
    onSubmit: async ({ value }) => {
      alert(JSON.stringify(value, null, 2));
    },
  }));

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        e.stopPropagation();
        form.handleSubmit();
      }}
      class="flex flex-col gap-5"
    >
      <form.Field name="name" children={(field) => (
        <Input name={field().name} value={field().state.value} label="Name"
          placeholder="Enter your name" error={fieldError(field())}
          onBlur={field().handleBlur}
          onInput={(e) => field().handleChange(e.currentTarget.value)} />
      )} />

      <form.Field name="email" children={(field) => (
        <Input name={field().name} value={field().state.value} label="Email"
          placeholder="email@example.com" error={fieldError(field())}
          onBlur={field().handleBlur}
          onInput={(e) => field().handleChange(e.currentTarget.value)} />
      )} />

      <form.Field name="age" children={(field) => (
        <NumberInput name={field().name} label="Age"
          value={String(field().state.value)} error={!!fieldError(field())}
          min={0} max={150}
          onValueChange={(e) => field().handleChange(e.valueAsNumber)}
          onBlur={field().handleBlur} />
      )} />

      <form.Field name="password" children={(field) => (
        <PasswordInput name={field().name} value={field().state.value}
          label="Password" placeholder="Enter password"
          error={!!fieldError(field())}
          onInput={(e) => field().handleChange((e.target as HTMLInputElement).value)}
          onBlur={field().handleBlur} />
      )} />

      <form.Field name="role" children={(field) => (
        <div class="not-prose flex flex-col gap-1">
          <Select name={field().name} collection={roles}
            value={field().state.value ? [field().state.value] : []}
            error={!!fieldError(field())}
            onValueChange={(e) => { field().handleChange(e.value[0]); field().handleBlur(); }}
            onOpenChange={(details) => { if (!details.open) field().handleBlur(); }}>
            <SelectLabel>Role</SelectLabel>
            <SelectTrigger placeholder="Select a role" />
            <SelectContent>
              <Index each={roles.items}>
                {(item) => <SelectItem item={item()}>{item().label}</SelectItem>}
              </Index>
            </SelectContent>
          </Select>
          <FieldHint field={field()} />
        </div>
      )} />

      <form.Field name="country" children={(field) => (
        <div class="not-prose flex flex-col gap-1">
          <Combobox name={field().name} collection={countries}
            value={field().state.value ? [field().state.value] : []}
            onValueChange={(e) => field().handleChange(e.value[0])}
            onOpenChange={(details) => { if (!details.open) field().handleBlur(); }}>
            <ComboboxLabel>Country</ComboboxLabel>
            <ComboboxInputTrigger placeholder="Search country..." />
            <ComboboxContent>
              <Index each={countries.items}>
                {(item) => <ComboboxItem item={item()}>{item().label}</ComboboxItem>}
              </Index>
            </ComboboxContent>
          </Combobox>
        </div>
      )} />

      <form.Field name="plan" children={(field) => (
        <fieldset>
          <div class="not-prose flex flex-col gap-2">
            <legend class={labelVariants()}>Plan</legend>
            <RadioGroup name={field().name} value={field().state.value}
              onValueChange={(e) => field().handleChange(e.value ?? "")}
              onBlur={field().handleBlur}>
              <RadioGroupItem value="free">Free</RadioGroupItem>
              <RadioGroupItem value="pro">Pro</RadioGroupItem>
              <RadioGroupItem value="enterprise">Enterprise</RadioGroupItem>
            </RadioGroup>
          </div>
        </fieldset>
      )} />

      <form.Field name="priority" children={(field) => (
        <fieldset>
          <div class="not-prose flex flex-col gap-2">
            <legend class={labelVariants()}>Priority</legend>
            <SegmentGroup name={field().name} value={field().state.value}
              onValueChange={(e) => field().handleChange(e.value ?? "")}
              onBlur={field().handleBlur}>
              <SegmentGroupItem value="low">Low</SegmentGroupItem>
              <SegmentGroupItem value="medium">Medium</SegmentGroupItem>
              <SegmentGroupItem value="high">High</SegmentGroupItem>
            </SegmentGroup>
          </div>
        </fieldset>
      )} />

      <form.Field name="rating" children={(field) => (
        <RatingGroup name={field().name} value={field().state.value} count={5}
          orientation="vertical"
          onValueChange={(e) => field().handleChange(e.value)}
          onBlur={field().handleBlur}>
          <RatingGroupLabel>Rating</RatingGroupLabel>
        </RatingGroup>
      )} />

      <form.Field name="volume" children={(field) => (
        <Slider name={field().name} value={[field().state.value]}
          min={0} max={100} step={1}
          onValueChange={(e) => field().handleChange(e.value[0])}
          onBlur={field().handleBlur}>
          <SliderLabel>Volume: {field().state.value}%</SliderLabel>
          <SliderControl><SliderThumb index={0} /></SliderControl>
        </Slider>
      )} />

      <form.Field name="startDate" children={(field) => (
        <DatePicker name={field().name} label="Start Date"
          placeholder="Pick a date"
          value={field().state.value ? [parseDate(field().state.value)] : []}
          error={!!fieldError(field())}
          onValueChange={(e) => field().handleChange(e.value[0] ? String(e.value[0]) : "")} />
      )} />

      <form.Field name="bio" children={(field) => (
        <Textarea name={field().name} value={field().state.value} label="Bio"
          placeholder="Tell us about yourself" error={fieldError(field())}
          onBlur={field().handleBlur}
          onInput={(e) => field().handleChange(e.currentTarget.value)} />
      )} />

      <form.Field name="skills" children={(field) => (
        <div class="not-prose flex flex-col gap-1">
          <TagsInput name={field().name} value={field().state.value}
            label="Skills" placeholder="Type skill and press Enter"
            onValueChange={(e) => field().handleChange(e.value)} />
        </div>
      )} />

      <form.Field name="notifications" children={(field) => (
        <Switch name={field().name} checked={field().state.value}
          onCheckedChange={(e) => field().handleChange(!!e.checked)}
          onBlur={field().handleBlur}>
          <SwitchLabel>Enable email notifications</SwitchLabel>
        </Switch>
      )} />

      <form.Field name="bold" children={(field) => (
        <Toggle name={field().name} pressed={field().state.value}
          onPressedChange={(pressed) => field().handleChange(!!pressed)}
          onBlur={field().handleBlur}>
          <BoldIcon />
          Bold
        </Toggle>
      )} />

      <form.Field name="alignment" children={(field) => (
        <ToggleGroup value={[field().state.value]}
          onValueChange={(e) => field().handleChange(e.value[0])}
          onBlur={field().handleBlur}>
          <ToggleGroupItem value="left">Left</ToggleGroupItem>
          <ToggleGroupItem value="center">Center</ToggleGroupItem>
          <ToggleGroupItem value="right">Right</ToggleGroupItem>
        </ToggleGroup>
      )} />

      <form.Field name="accepted" children={(field) => (
        <div class="not-prose flex flex-col gap-1">
          <Checkbox name={field().name} checked={field().state.value}
            invalid={!!fieldError(field())}
            onCheckedChange={(e) => field().handleChange(!!e.checked)}
            onBlur={field().handleBlur}>
            <CheckboxLabel>I accept the terms and conditions</CheckboxLabel>
          </Checkbox>
          <FieldHint field={field()} />
        </div>
      )} />

      <form.Subscribe
        selector={(state) => ({ isSubmitting: state.isSubmitting })}
        children={(state) => (
          <Button type="submit" disabled={state().isSubmitting}>
            {state().isSubmitting ? "Submitting..." : "Submit"}
          </Button>
        )}
      />
    </form>
  );
}