Guides
Forms

Forms

A guide to building forms with Ark Ripple components.

Ark Ripple provides the Field and Fieldset components for integrating with native form element or any form library.

Field Context

Form components in Ark Ripple automatically integrate with Field through context. When nested inside a Field.Root, they inherit disabled, invalid, required, and readOnly states automatically.

import { Field } from 'ark-ripple/field'
import { NumberInput } from 'ark-ripple/number-input'

component Demo() {
  <Field.Root disabled>
    <NumberInput.Root />  // NumberInput will be disabled
  </Field.Root>
}

Accessible Labels

When building accessible forms, you need to ensure that they are properly labeled and described.

  • Field.Label: Used to provide an accessible label the input.
  • Field.HelperText: Used to provide additional instructions about the input.

These components are automatically linked to the input element via the aria-describedby attribute.

Best practice: Make sure that labels are visible (and not just used as placeholders) for screen readers to read them.

import { Field } from 'ark-ripple/field'

component Demo() {
  <form>
    <Field.Root>
      <Field.Label>{"Username"}</Field.Label>
      <Field.Input placeholder="Enter your username" />
      <Field.HelperText>{"This will be your public display name."}</Field.HelperText>
    </Field.Root>
  </form>
}

Error Handling and Validation

When the input is invalid, you can use the Field.ErrorText component to provide an error message for the input, and pass the invalid prop to the Field.Root component.

Best practice: Make sure to provide clear, specific error messages that are easy to understand and fix.

import { Field } from 'ark-ripple/field'

component Demo() {
  <form>
    <Field.Root invalid>
      <Field.Label>{"Username"}</Field.Label>
      <Field.Input placeholder="Enter your username" />
      <Field.ErrorText>{"Username is required."}</Field.ErrorText>
    </Field.Root>
  </form>
}

Required Fields

To indicate that a field is required, you can pass the required prop to the Field.Root component. Optionally, you can use the Field.RequiredIndicator component to indicate that the field is required.

Best practice: Don't rely solely on color to indicate required status

import { Field } from 'ark-ripple/field'

export component Demo() {
  <form>
    <Field.Root required>
      <Field.Label>
        {"Username"}
        <Field.RequiredIndicator>{"(required)"}</Field.RequiredIndicator>
      </Field.Label>
      <Field.Input placeholder="Enter your username" />
      <Field.ErrorText>{"Username is required."}</Field.ErrorText>
    </Field.Root>
  </form>
}

To indicate that a field is optional, use the fallback prop on the Field.RequiredIndicator component.

<Field.RequiredIndicator fallback="Optional">{"(required)"}</Field.RequiredIndicator>

Native Controls

Field supports native HTML form controls including input, textarea, and select:

import { Field } from 'ark-ripple/field'

export component Demo(){
  <form>
    // Input
    <Field.Root>
      <Field.Label>{"Email"}</Field.Label>
      <Field.Input type="email" placeholder="you@example.com" />
    </Field.Root>

    // Textarea
    <Field.Root>
      <Field.Label>{"Bio"}</Field.Label>
      <Field.Textarea placeholder="Tell us about yourself" />
    </Field.Root>

    // Select
    <Field.Root>
      <Field.Label>{"Country"}</Field.Label>
      <Field.Select>
        <option value="us">{"United States"}</option>
        <option value="uk">{"United Kingdom"}</option>
        <option value="de">{"Germany"}</option>
      </Field.Select>
    </Field.Root>
  </form>
}

Form Reset

When the reset event is triggered on a form, all Ark Ripple components automatically sync their internal state with the form's reset values.

Note: For this to work correctly, always include the HiddenInput component in your form controls. The hidden input participates in the native form reset mechanism, and Ark Ripple listens for this to sync the component state.

import { Checkbox } from 'ark-ripple/checkbox'

component Demo() {
  <form>
    <Checkbox.Root name="terms" defaultChecked>
      <Checkbox.Label>{"I agree to the terms"}</Checkbox.Label>
      <Checkbox.Control>
        <Checkbox.Indicator />
      </Checkbox.Control>
      <Checkbox.HiddenInput />
    </Checkbox.Root>

    // Clicking reset will restore checkbox to defaultChecked state
    <button type="reset">{"Reset"}</button>
    <button type="submit">{"Submit"}</button>
  </form>
}

Fieldset Context

When you have multiple fields in a form or a component that renders multiple input elements, you can use the Fieldset component to group them together.

Common use cases checkbox group, radio group, input + select composition, etc.

Checkbox Group

import { Fieldset } from 'ark-ripple/fieldset'

const items = [
  { label: 'React', value: 'react' },
  { label: 'Solid', value: 'solid' },
  { label: 'Vue', value: 'vue' },
  { label: 'Svelte', value: 'svelte' },
]

component Demo() {
  <Fieldset.Root>
    <Fieldset.Legend>{"Frameworks"}</Fieldset.Legend>
    <Checkbox.Group name="framework">
      for (const item of items; key item.value) {
        <Checkbox.Root value={item.value} />
      }
    </Checkbox.Group>
    <Fieldset.HelperText>{"Choose your preferred frameworks"}</Fieldset.HelperText>
  </Fieldset.Root>
}

Radio Group

import { Fieldset } from 'ark-ripple/fieldset'
import { RadioGroup } from 'ark-ripple/radio-group'

const items = [
  { label: 'React', value: 'react' },
  { label: 'Solid', value: 'solid' },
  { label: 'Vue', value: 'vue' },
  { label: 'Svelte', value: 'svelte' },
]

component Demo() {
  <Fieldset.Root>
    <Fieldset.Legend>{"Frameworks"}</Fieldset.Legend>
    <RadioGroup.Root name="framework">
      for (const item of items; key item.value) {
        <RadioGroup.Item value={item.value} key={item.value} />
      }
    </RadioGroup.Root>
    <Fieldset.HelperText>{"Choose your preferred framework"}</Fieldset.HelperText>
  </Fieldset.Root>
}