Skip to content

$form()

$form is the core composable for managing form-level state, validation, and submission. It automatically integrates with fields and provides a complete form management solution.

Creates and manages a complete form instance with typed validation. Handles form-wide operations like validation, submission, and state management.

vue
<template>
  <arkform name="login" :submit="handleSubmit">
    <field name="email" />
    <field name="password" />
  </arkform>
</template>

<script setup>
const { $form } = defineArkform({
  login: { 
    schema: arktype({
      email: "string.email",
      password: "string"
    })
  }
})

const form = $form("login")
const handleSubmit = form.handleSubmit(async (result) => {
  // Handle form submission
})
</script>

✅ Signature

ts
$form<FormName>(formName: FormName): {
  state: Ref<ArkFormState<FormSchema>>
  loading: Ref<boolean>
  errors: Ref<string[]>
  messages: Ref<string[]>
  isLoading: ComputedRef<boolean>
  mode: ComputedRef<ArkMode>
  input: ComputedRef<FormType>
  set(data: Partial<FormType>): Result<Partial<FormType>>
  validate(): Result<FormType, string[]>
  clear(thingsToClear?: ("input" | "errors" | "messages")[]): Result<string>
  handleSubmit(callback: (data: Result<FormType, string[]>) => void): () => Promise<Result<null, string>>
  mount(input: Ref<ArkInputState>): void
}

📥 Parameters

NameTypeDescription
formNamestringForm identifier that must match a form defined in defineArkform

The form schema is automatically inferred from the defineArkform configuration.

🧠 Internal State

The form instance uses useState with keys:

ts
arkform:form:${formName}          // Main form state
arkform:form:${formName}:errors   // Form-level errors
arkform:form:${formName}:messages // Form-level messages  
arkform:form:${formName}:loading  // Loading state

This provides shared access across components and persists through HMR.

🧩 Returned API

state: Ref<ArkFormState>

The complete reactive form state containing:

  • name: Form name
  • id: Unique form identifier
  • mode: Current form mode
  • inputs: Record of all mounted field refs

loading: Ref<boolean>

Loading state for form operations, automatically managed by handleSubmit.

errors: Ref<string[]>

Form-level error messages (separate from field-level errors).

messages: Ref<string[]>

Form-level success or info messages.

isLoading: ComputedRef<boolean>

Computed loading state (same as loading.value).

mode: ComputedRef<ArkMode>

Reactive form mode:

  • "error" if any errors exist
  • "success" if any messages exist
  • "idle" if neither

input: ComputedRef<FormType>

Flattened object of all current field values, strongly typed based on schema:

ts
// For login form with email/password
form.input.value // { email: string, password: string }

set(data: Partial<FormType>): Result<Partial<FormType>>

Sets multiple field values at once with validation:

ts
form.set({ email: "user@example.com", password: "secret123" })

validate(): Result<FormType, string[]>

Validates all form fields and returns:

  • ok(formData) if all fields are valid
  • err(errorMessages) if validation fails

clear(thingsToClear?: ("input" | "errors" | "messages")[])

Clears form state across all fields:

  • Default: clears input, errors, and messages
  • Can specify subset: form.clear(["errors"])

handleSubmit(callback: (data: Result<FormType, string[]>) => void)

Creates a submission handler that:

  1. Automatically manages loading state
  2. Validates the form
  3. Calls your callback with the result
  4. Handles errors gracefully
ts
const submit = form.handleSubmit(async (result) => {
  if (!result.ok) {
    // Handle validation errors
    return
  }
  
  // Submit validated data
  await api.submitForm(result.data)
})

mount(input: Ref<ArkInputState>)

Registers a field with the form (called automatically by $field).

🔁 Complete Usage Example

ts
// 1. Define forms
const { $form, $field } = defineArkform({
  contact: {
    schema: arktype({
      name: "string",
      email: "string.email",
      message: "string"
    })
  }
})

// 2. Create form instance
const form = $form("contact")

// 3. Access form data
console.log(form.input.value) // { name: "", email: "", message: "" }
console.log(form.mode.value)  // "idle" | "error" | "success"

// 4. Set values
form.set({ name: "John", email: "john@example.com" })

// 5. Validate
const result = form.validate()
if (result.ok) {
  console.log("Valid form data:", result.data)
}

// 6. Submit with loading state
const handleSubmit = form.handleSubmit(async (result) => {
  if (!result.ok) {
    form.errors.value = result.message
    return
  }
  
  await submitToAPI(result.data)
  form.messages.value = ["Form submitted successfully!"]
})

Component Integration

vue
<template>
  <arkform name="contact" theme="default" :submit="handleSubmit">
    <field name="name" />
    <field name="email" />
    <field name="message" is="textarea" />
    
    <div v-if="form.mode.value === 'error'" class="errors">
      <p v-for="error in form.errors.value">{{ error }}</p>
    </div>
    
    <button type="submit" :disabled="form.isLoading.value">
      {{ form.isLoading.value ? 'Submitting...' : 'Submit' }}
    </button>
  </arkform>
</template>

🧠 Notes

  • Forms automatically discover and mount fields placed inside <arkform> components
  • The handleSubmit pattern ensures consistent loading states and error handling
  • Form state persists across component remounts and HMR
  • All operations return Result types for consistent error handling
  • Type safety is enforced throughout - you can only access fields that exist in your schema