# Form field with validation

> Native form field with a required check, an error, reset and the submitted value.

A shadcn/ui block from the [@reui](https://reui.io) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/reui/c-color-picker-15).

- Page, with a live preview: https://shadcn.exchange/r/reui/c-color-picker-15
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/reui/c-color-picker-15-light-cce8fc2a76def8fe.png
- Registry item: https://reui.io/r/new-york-v4/c-color-picker-15.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://reui.io/r/new-york-v4/c-color-picker-15.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `button`, `@reui/color-picker`, `field`

## Files

### `c-color-picker-15.tsx`

```tsx
"use client"

import { useId, useState } from "react"
import {
  ColorPicker,
  ColorPickerContent,
  ColorPickerInput,
} from "@/components/reui/color-picker"

import { Button } from "@/components/ui/button"
import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field"

export function Pattern() {
  const id = useId()
  const [invalid, setInvalid] = useState(false)
  const [saved, setSaved] = useState<string | null>(null)

  return (
    /* `noValidate`: the Field draws the error, not the browser bubble. */
    <form
      noValidate
      className="mx-auto w-full max-w-sm"
      onSubmit={(event) => {
        event.preventDefault()
        /* The picker submits its value under `name`, or "" while empty. */
        const color = String(
          new FormData(event.currentTarget).get("brandColor") ?? ""
        )
        setInvalid(!color)
        setSaved(color || null)
      }}
      onReset={() => {
        /* The picker returns to its default (empty) on the same reset. */
        setInvalid(false)
        setSaved(null)
      }}
    >
      <FieldGroup>
        <Field data-invalid={invalid || undefined}>
          <FieldLabel htmlFor={id}>Brand color</FieldLabel>
          <ColorPicker
            id={id}
            name="brandColor"
            required
            invalid={invalid}
            aria-describedby={`${id}-hint`}
            onValueChange={(next) => {
              if (next) setInvalid(false)
            }}
          >
            <ColorPickerInput placeholder="Type or pick a color" />
            <ColorPickerContent />
          </ColorPicker>
          {/* One id for both, so aria-describedby follows whichever shows. */}
          {invalid ? (
            <FieldError id={`${id}-hint`}>
              Choose a brand color to continue.
            </FieldError>
          ) : (
            <FieldDescription id={`${id}-hint`}>
              Used on the login page, emails and invoices.
            </FieldDescription>
          )}
        </Field>
        <div className="flex items-center gap-2">
          <Button type="submit">Save</Button>
          <Button type="reset" variant="outline">
            Reset
          </Button>
          <p
            role="status"
            className="text-muted-foreground ms-auto min-w-0 truncate text-sm"
          >
            {saved && `Saved ${saved.toUpperCase()}`}
          </p>
        </div>
      </FieldGroup>
    </form>
  )
}
```
