Skip to main contentSkip to docs navigation

Form Processing

A complete example of building a validated, accessible form with submit handling, inline feedback, and a success notification.

On this page

This pattern combines a registration form with controlled inputs, real-time validation feedback, and a success notification on submit.

Please enter your full name (at least 2 characters).
Please enter a valid email address.
Please select a role.
Password must be at least 8 characters.
JSX
import { useState } from 'react'
import {
  Button,
  Form,
  FormFeedback,
  FormLabel,
  Grid,
  Notification,
  Select,
  TextInput
} from '@chassis-ui/react'

export const Example = () => {
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [role, setRole] = useState('')
  const [password, setPassword] = useState('')
  const [submitted, setSubmitted] = useState(false)
  const [success, setSuccess] = useState(false)
  const validName = name.trim().length >= 2
  const validEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
  const validRole = role !== ''
  const validPassword = password.length >= 8
  const allValid = validName && validEmail && validRole && validPassword
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    setSubmitted(true)
    if (allValid) setSuccess(true)
  }
  const handleReset = () => {
    setName('')
    setEmail('')
    setRole('')
    setPassword('')
    setSubmitted(false)
    setSuccess(false)
  }
  const roleOptions = [
    { label: 'Administrator', value: 'admin' },
    { label: 'Editor', value: 'editor' },
    { label: 'Viewer', value: 'viewer' }
  ]
  return (
    <div>
      {success && (
        <Notification color="success" dismissible onClose={handleReset} className="mb-lg">
          <strong>Account created!</strong> Welcome aboard, {name}.
        </Notification>
      )}
      <Form onSubmit={handleSubmit} onReset={handleReset} validated={false}>
        <Grid columns={2} gap="md" className="mb-md">
          <div>
            <FormLabel htmlFor="reg-name" id="reg-name-label">
              Full name
            </FormLabel>
            <TextInput
              id="reg-name"
              aria-labelledby="reg-name-label"
              placeholder="Jane Smith"
              value={name}
              onChange={setName}
              valid={submitted && validName}
              invalid={submitted && !validName}
            />
            <FormFeedback invalid>
              Please enter your full name (at least 2 characters).
            </FormFeedback>
          </div>
          <div>
            <FormLabel htmlFor="reg-email" id="reg-email-label">
              Email address
            </FormLabel>
            <TextInput
              id="reg-email"
              aria-labelledby="reg-email-label"
              type="email"
              placeholder="jane@example.com"
              value={email}
              onChange={setEmail}
              valid={submitted && validEmail}
              invalid={submitted && !validEmail}
            />
            <FormFeedback invalid>Please enter a valid email address.</FormFeedback>
          </div>
        </Grid>
        <Grid columns={2} gap="md" className="mb-md">
          <div>
            <FormLabel htmlFor="reg-role">Role</FormLabel>
            <Select
              id="reg-role"
              placeholder="Select a role…"
              value={role}
              onChange={(e) => setRole(e.target.value)}
              valid={submitted && validRole}
              invalid={submitted && !validRole}
              options={roleOptions}
            />
            <FormFeedback invalid>Please select a role.</FormFeedback>
          </div>
          <div>
            <FormLabel htmlFor="reg-pw" id="reg-pw-label">
              Password
            </FormLabel>
            <TextInput
              autoComplete="new-password"
              id="reg-pw"
              aria-labelledby="reg-pw-label"
              type="password"
              placeholder="Min. 8 characters"
              value={password}
              onChange={setPassword}
              valid={submitted && validPassword}
              invalid={submitted && !validPassword}
            />
            <FormFeedback invalid>Password must be at least 8 characters.</FormFeedback>
          </div>
        </Grid>
        <div className="d-flex gap-sm">
          <Button type="submit" color="primary">
            Create account
          </Button>
          <Button type="reset" color="secondary" variant="outline">
            Reset
          </Button>
        </div>
      </Form>
    </div>
  )
}

Key points

  • Validation runs on every render but feedback only shows after the first submit attempt (submitted flag).
  • valid and invalid props on inputs add visual state and expose <FormFeedback> messages.
  • The success <Notification> is dismissible — closing it calls handleReset which clears all state.
  • noValidate on <Form> disables the browser's built-in validation UI so custom feedback is shown instead.