v0.1.6·How-To
Build a Form with Validation
Add client-side validation to forms with error messages and visual feedback.
Published: 12/17/2025

Build a Form with Validation

This guide shows how to build a form with validation feedback and error display using UI Lab components.

Setup

You need:

  • React hooks (useState, useCallback)
  • UI Lab components (Input, Label, Button, Card)
  • Basic regex for validation patterns

Basic form with validation

Create a component that validates email and password:

import { useState, useCallback } from 'react';
import { Button, Card, Input, Label } from 'ui-lab-components';

export function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
  const [submitted, setSubmitted] = useState(false);

  const validateForm = useCallback(() => {
    const newErrors: typeof errors = {};

    if (!email.includes('@')) {
      newErrors.email = 'Enter a valid email';
    }
    if (password.length < 8) {
      newErrors.password = 'Password must be at least 8 characters';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  }, [email, password]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (validateForm()) {
      setSubmitted(true);
      // Send to API
    }
  };

  return (
    <Card className="w-full max-w-sm">
      <Card.Header>
        <Card.Title>Sign in</Card.Title>
      </Card.Header>
      <form onSubmit={handleSubmit}>
        <Card.Content className="space-y-4">
          <div>
            <Label htmlFor="email">Email</Label>
            <Input
              id="email"
              type="email"
              value={email}
              onChange={(e) => setEmail(e.currentTarget.value)}
              placeholder="you@example.com"
              aria-invalid={!!errors.email}
            />
            {errors.email && (
              <p className="text-red-600 text-sm mt-1">{errors.email}</p>
            )}
          </div>

          <div>
            <Label htmlFor="password">Password</Label>
            <Input
              id="password"
              type="password"
              value={password}
              onChange={(e) => setPassword(e.currentTarget.value)}
              aria-invalid={!!errors.password}
            />
            {errors.password && (
              <p className="text-red-600 text-sm mt-1">{errors.password}</p>
            )}
          </div>
        </Card.Content>
        <Card.Footer>
          <Button type="submit" className="w-full">
            {submitted ? 'Signing in...' : 'Sign in'}
          </Button>
        </Card.Footer>
      </form>
    </Card>
  );
}

Field-level validation

Validate as users type, not just on submit:

const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const value = e.currentTarget.value;
  setEmail(value);

  // Real-time validation
  if (value && !value.includes('@')) {
    setErrors({ ...errors, email: 'Invalid email' });
  } else {
    const newErrors = { ...errors };
    delete newErrors.email;
    setErrors(newErrors);
  }
};

<Input
  id="email"
  type="email"
  value={email}
  onChange={handleEmailChange}
  aria-invalid={!!errors.email}
/>

Display validation states

Use Input's accessibility attributes to indicate errors:

<Input
  aria-invalid={!!error}
  aria-describedby={error ? 'error-message' : undefined}
/>
{error && (
  <p id="error-message" className="text-red-600 text-sm mt-1">
    {error}
  </p>
)}

Common validation patterns

// Email
const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

// Password strength
const isStrongPassword = (password: string) => {
  return password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password);
};

// Phone
const isValidPhone = (phone: string) => /^\d{3}-?\d{3}-?\d{4}$/.test(phone);

// URL
const isValidUrl = (url: string) => {
  try {
    new URL(url);
    return true;
  } catch {
    return false;
  }
};

Next step

Now that you have a working form, learn how to style error states with Dark Mode Setup.

© 2025 UI Lab • Built for humans and machines