šŸ‘¾ Pixel UI
Components

Field

A pixel-art styled form field component with validation and accessibility features

Overview

The Field component provides a complete form field wrapper with labeling, validation, error handling, and accessibility features. It's built as a compound component with multiple parts that work together.

Preview

Installation

See the Installation guide for setup instructions.

Usage

Basic Field

import { Field } from '@joacod/pixel-ui'

export default function Example() {
  return (
    <Field.Root>
      <Field.Label>Email</Field.Label>
      <Field.Control type="email" placeholder="you@example.com" required />
      <Field.Description>We'll never share your email</Field.Description>
      <Field.Error match="valueMissing">Email is required</Field.Error>
      <Field.Error match="typeMismatch">Please enter a valid email</Field.Error>
    </Field.Root>
  )
}

With Custom Validation

import { Field } from '@joacod/pixel-ui'

export default function Example() {
  const validatePassword = (value: unknown) => {
    const password = value as string
    if (password.length < 8) {
      return 'Password must be at least 8 characters'
    }
    if (!/[A-Z]/.test(password)) {
      return 'Password must contain an uppercase letter'
    }
    return null
  }

  return (
    <Field.Root validate={validatePassword}>
      <Field.Label>Password</Field.Label>
      <Field.Control type="password" />
      <Field.Error match="customError">
        Password must be at least 8 characters or contain an uppercase letter
      </Field.Error>
    </Field.Root>
  )
}

Component Parts

The Field component is composed of multiple subcomponents:

  • Field.Root: Container that manages validation state
  • Field.Label: Accessible label element
  • Field.Control: Input control (can be replaced with other form controls)
  • Field.Description: Helper text for the field
  • Field.Error: Conditional error message display
  • Field.Validity: Custom validity state renderer

Validation

Validation Modes

Control when validation occurs using the validationMode prop:

<Field.Root validationMode="onBlur">  {/* default */}
<Field.Root validationMode="onChange">
<Field.Root validationMode="onSubmit">

Built-in HTML5 Validation

Field supports all native HTML5 validation attributes:

Custom Validation Function

<Field.Root
  validate={(value) => {
    if (value === 'admin') return 'Username already taken'
    if ((value as string).length < 3) return 'Too short'
    return null // Valid
  }}
>
  <Field.Label>Username</Field.Label>
  <Field.Control />
  <Field.Error match="customError">
    Please check your input
  </Field.Error>
</Field.Root>

Error Matching

The Field.Error component supports flexible error matching:

{/* Match specific HTML5 validation error */}
<Field.Error match="valueMissing">Required field</Field.Error>

{/* Match custom validation errors */}
<Field.Error match="customError">Custom validation error</Field.Error>

{/* Always show error */}
<Field.Error match={true}>Always visible error</Field.Error>

States

Disabled State

<Field.Root disabled>
  <Field.Label>Disabled Field</Field.Label>
  <Field.Control placeholder="This field is disabled" />
</Field.Root>

Invalid State

<Field.Root invalid>
  <Field.Label>Invalid Field</Field.Label>
  <Field.Control placeholder="This field has an error" />
  <Field.Error match={true}>Something went wrong</Field.Error>
</Field.Root>

Advanced Usage

Using Field.Validity for Custom Display

<Field.Root>
  <Field.Label>Password</Field.Label>
  <Field.Control type="password" minLength={8} required />
  <Field.Validity>
    {({ validity, error }) => (
      <div>
        {validity.valueMissing && <span>āŒ Required</span>}
        {validity.tooShort && <span>āŒ Too short</span>}
        {validity.valid && <span>āœ… Valid</span>}
      </div>
    )}
  </Field.Validity>
</Field.Root>

Integration with Other Form Controls

Field.Control can wrap other form components:

import { Field, Input } from '@joacod/pixel-ui'

<Field.Root>
  <Field.Label>Custom Input</Field.Label>
  <Input placeholder="Using custom Input component" />
</Field.Root>

API Reference

Field.Root

PropTypeDefaultDescription
namestring-Field name for form submission
disabledbooleanfalseDisables the field
invalidbooleanfalseForce invalid state
validatefunction-Custom validation function
validationMode'onBlur' | 'onChange' | 'onSubmit''onBlur'When to run validation
classNamestring-Additional CSS classes

Field.Label

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode-Label content

Standard HTML <label> attributes are also supported.

Field.Control

PropTypeDefaultDescription
classNamestring-Additional CSS classes

All standard HTML <input> attributes are supported.

Field.Description

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode-Description content

Field.Error

PropTypeDefaultDescription
matchboolean | ValidityState key-Error condition to match (true always shows, or match ValidityState key like 'valueMissing')
classNamestring-Additional CSS classes
childrenReactNode-Error message content

Field.Validity

PropTypeDefaultDescription
childrenfunction-Render function receiving validity state
classNamestring-Additional CSS classes

Accessibility

  • Automatic labeling: Field.Label automatically associates with the control
  • Error announcements: Error messages are announced to screen readers
  • ARIA attributes: Proper aria-describedby, aria-invalid, and aria-required attributes
  • Keyboard navigation: Full keyboard support for all interactions
  • Focus management: Proper focus handling during validation

Examples

Login Form

<form>
  <Field.Root>
    <Field.Label>Email</Field.Label>
    <Field.Control type="email" required autoComplete="email" />
    <Field.Error match="valueMissing">Email is required</Field.Error>
    <Field.Error match="typeMismatch">Invalid email address</Field.Error>
  </Field.Root>

  <Field.Root>
    <Field.Label>Password</Field.Label>
    <Field.Control type="password" required minLength={8} />
    <Field.Error match="valueMissing">Password is required</Field.Error>
    <Field.Error match="tooShort">Must be at least 8 characters</Field.Error>
  </Field.Root>

  <Button type="submit">Sign In</Button>
</form>

Registration Form with Custom Validation

<form>
  <Field.Root
    validate={(value) => {
      if ((value as string).length < 3) return 'Username too short'
      if (!/^[a-zA-Z0-9_]+$/.test(value as string)) return 'Invalid characters'
      return null
    }}
  >
    <Field.Label>Username</Field.Label>
    <Field.Control required />
    <Field.Description>Only letters, numbers, and underscores</Field.Description>
    <Field.Error match="customError">
      Username must be at least 3 characters and contain only letters, numbers, and underscores
    </Field.Error>
  </Field.Root>
</form>