# How to Implement Form Validation with Astryx FormLayout and Field Components

> Learn to implement form validation with Astryx FormLayout and Field components. Easily trigger error UI and manage layout for accessible forms.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-08-06

---

**Use the `Field` component inside a `FormLayout` and pass `validationState` and `validationMessage` props to trigger accessible error UI, while `FormLayoutContext` automatically handles directional layout for labels and inputs.**

Form validation in the Astryx design system relies on a composable architecture where layout and validation concerns remain separate. In `facebook/astryx`, the `FormLayout` component provides directional context to its children, while the `Field` component consumes that context to render labels, controls, and validation feedback. This pattern allows you to implement robust, accessible form validation across vertical, horizontal, or mixed layouts without duplicating layout logic.

## Understanding the FormLayout Architecture

### FormLayout and Direction Context

The `FormLayout` component acts as a container that establishes a visual direction for all nested fields. According to the source code in [[`FormLayout.tsx`](https://github.com/facebook/astryx/blob/main/FormLayout.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/FormLayout/FormLayout.tsx), the component accepts a `direction` prop with values `'vertical'`, `'horizontal'`, or `'horizontal-labels'`. It renders a semantic container with `role="group"` and passes the direction value into `FormLayoutContext`.

### FormLayoutContext Propagation

[[`FormLayoutContext.tsx`](https://github.com/facebook/astryx/blob/main/FormLayoutContext.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/FormLayout/FormLayoutContext.tsx) defines the React context that carries the `FormLayoutDirection` enum to descendant components. When you wrap fields in `FormLayout`, each `Field` component automatically accesses this context via `use(FormLayoutContext)` to determine whether to stack labels above inputs or align them side-by-side.

### Field Component Validation Interface

The [[`Field.tsx`](https://github.com/facebook/astryx/blob/main/Field.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/Field/Field.tsx) source file exposes the primary interface for validation. The component accepts `validationState` (`'error' | 'warning' | 'success'`) and `validationMessage` props. When `validationState` is set to `'error'`, the component injects `aria-invalid="true"` and associates the message via `aria-describedby`. The [[`FieldStatus.tsx`](https://github.com/facebook/astryx/blob/main/FieldStatus.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/FieldStatus/FieldStatus.tsx) sub-component renders visual indicators and manages live-region announcements for screen readers.

## Implementing Validation with Field Components

### Basic Vertical Form with Required Field Validation

The most common pattern uses `direction="vertical"` to stack fields. Here is a complete example showing client-side validation logic passed to the `Field` component:

```tsx
import {FormLayout} from '@astryxdesign/core/FormLayout';
import {Field} from '@astryxdesign/core/Field';
import {TextInput} from '@astryxdesign/core/TextInput';
import {useState} from 'react';

export function SimpleForm() {
  const [name, setName] = useState('');
  const [error, setError] = useState<string | null>(null);

  const validate = () => {
    if (!name.trim()) {
      setError('Name is required');
    } else {
      setError(null);
    }
  };

  return (
    <FormLayout direction="vertical" data-testid="my-form" role="group">
      <Field
        label="Name"
        validationState={error ? 'error' : undefined}
        validationMessage={error}
      >
        <TextInput
          value={name}
          onChange={(e) => setName(e.target.value)}
          onBlur={validate}
        />
      </Field>
    </FormLayout>
  );
}

```

**Key implementation details:**
- **Parent-controlled validation:** The `validate` function runs on blur, updating React state that flows back into `validationState` and `validationMessage`.
- **Automatic ARIA attributes:** When `error` is truthy, `Field` automatically links the `TextInput` to the error message via `aria-describedby` and sets `aria-invalid`.
- **Live region announcements:** The validation message appears in an assertive live region managed by `FieldStatus`, ensuring screen readers announce errors immediately.

### Horizontal-Labels Layout with Mixed Field Types

For denser forms, use `direction="horizontal-labels"` to place labels to the left of inputs while maintaining vertical stacking of field groups:

```tsx
import {FormLayout} from '@astryxdesign/core/FormLayout';
import {Field} from '@astryxdesign/core/Field';
import {TextInput, NumberInput, Select} from '@astryxdesign/core';
import {useState} from 'react';

export function MixedForm() {
  const [age, setAge] = useState('');
  const [ageError, setAgeError] = useState<string | null>(null);

  const validateAge = () => {
    const num = Number(age);
    if (Number.isNaN(num) || num < 0) {
      setAgeError('Enter a valid age');
    } else {
      setAgeError(null);
    }
  };

  return (
    <FormLayout direction="horizontal-labels" style={{maxWidth: 600}}>
      <Field label="First name">
        <TextInput placeholder="John" />
      </Field>

      <Field
        label="Age"
        validationState={ageError ? 'error' : undefined}
        validationMessage={ageError}
      >
        <NumberInput
          value={age}
          onChange={(e) => setAge(e.target.value)}
          onBlur={validateAge}
        />
      </Field>

      <Field label="Country">
        <Select options={['US', 'CA', 'UK']} />
      </Field>
    </FormLayout>
  );
}

```

**Layout considerations:**
- The `FormLayoutContext` value `'horizontal-labels'` causes `Field` to render labels and inputs in a side-by-side grid without additional CSS.
- Each `Field` operates independently, allowing different validation states (error, warning, success) to coexist in the same layout.

## Handling Complex Layouts and Nested Forms

You can nest `FormLayout` components to create sectioned forms with varying directions. Because each `FormLayout` provides its own context value, nested fields adapt their layout while outer containers preserve group semantics:

```tsx
<FormLayout direction="vertical">
  {/* Section 1: Contact details */}
  <FormLayout direction="horizontal-labels">
    <Field label="Email" validationState={emailError ? 'error' : undefined} validationMessage={emailError}>
      <TextInput type="email" />
    </Field>
    <Field label="Phone">
      <TextInput type="tel" />
    </Field>
  </FormLayout>

  {/* Section 2: Address */}
  <FormLayout direction="horizontal">
    <Field label="City">...</Field>
    <Field label="State">...</Field>
    <Field label="ZIP" validationState={zipError ? 'error' : undefined} validationMessage={zipError}>
      <TextInput />
    </Field>
  </FormLayout>
</FormLayout>

```

**Nesting behavior:**
- Inner layouts override the direction for their immediate children only.
- Validation props function identically at every nesting level because `Field` always reads the nearest `FormLayoutContext` for layout while receiving validation state via props.

## Accessibility and Screen Reader Support

The validation system in Astryx is designed to meet WCAG standards through the following mechanisms implemented in the source:

- **`aria-invalid`:** Set automatically when `validationState="error"` is passed to `Field`.
- **`aria-describedby`:** Links the input control to the validation message container rendered by `FieldStatus`.
- **Live regions:** `FieldStatus` uses `aria-live="assertive"` to announce validation changes without requiring focus movement, as verified in [[`Field.test.tsx`](https://github.com/facebook/astryx/blob/main/Field.test.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/Field/Field.test.tsx).
- **Semantic grouping:** `FormLayout` renders `role="group"` by default, giving screen reader users context about related fields.

## Summary

- **FormLayout** provides direction context via `FormLayoutContext` to arrange labels and inputs consistently.
- **Field** consumes the layout context and accepts `validationState` and `validationMessage` props to render accessible validation UI.
- **FieldStatus** handles visual indicators and live-region announcements for screen readers.
- Validation logic remains in parent components; `Field` is strictly presentational, allowing integration with any validation library (Yup, Zod, etc.).
- Nested `FormLayout` components support complex, multi-section forms while preserving accessibility attributes.

## Frequently Asked Questions

### How does FormLayout know which direction to apply to nested fields?

`FormLayout` wraps its children in a `FormLayoutContext.Provider` that passes the `direction` prop value. When a `Field` component renders, it calls `use(FormLayoutContext)` to read the current direction and adjusts its internal CSS grid accordingly. This mechanism is defined in [[`FormLayoutContext.tsx`](https://github.com/facebook/astryx/blob/main/FormLayoutContext.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/FormLayout/FormLayoutContext.tsx) and consumed in [[`Field.tsx`](https://github.com/facebook/astryx/blob/main/Field.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/Field/Field.tsx).

### Can I use custom input components with Astryx Field validation?

Yes. The `Field` component uses React composition via its children prop. As long as the child component accepts standard HTML attributes like `aria-invalid` and `aria-describedby`, it will inherit accessibility properties automatically. Pass your custom input as a child of `Field` and supply `validationState` and `validationMessage` props to the `Field` wrapper.

### What happens if I don't provide a validationMessage but set validationState to error?

The `Field` component will still mark the input as invalid with `aria-invalid="true"`, but screen reader users will not hear a descriptive error message. It is recommended to always provide a `validationMessage` when `validationState` is `'error'` or `'warning'` to ensure compliance with accessibility guidelines.

### Does Astryx FormLayout handle form submission or prevent invalid submissions?

No. `FormLayout` and `Field` are strictly presentational and do not manage form state or submission logic. You must implement form-level validation and submission handling in your parent component (e.g., using React Hook Form, Formik, or native form events) and pass the resulting validation states down to individual `Field` components.