# Halo Field

> Form field layout for React and shadcn/ui with label, description, and error slots for Halo Input and Halo Textarea.

Source: https://www.cult-ui.com/docs/components/halo-field

## Example

```tsx title="halo-field-demo.tsx"
"use client"

import { useId, useState } from "react"
import { Mail } from "lucide-react"

import {
  HaloField,
  HaloFieldContent,
  HaloFieldDescription,
  HaloFieldError,
  HaloFieldLabel,
} from "@/components/ui/halo-field"
import { HaloInput, HaloTextarea } from "@/components/ui/halo-input"

const EMAIL_LIKE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/

export default function HaloFieldDemo() {
  const emailId = useId()
  const bioId = useId()
  const [email, setEmail] = useState("")
  const [showEmailError, setShowEmailError] = useState(false)
  const emailInvalid = showEmailError && !EMAIL_LIKE.test(email)

  return (
    <main className="flex w-full flex-col items-center justify-center">
      <div className="flex w-full max-w-md flex-col gap-12">
        <section className="space-y-3">
          <h2 className="font-semibold text-foreground text-lg tracking-tight">
            Field + input
          </h2>
          <p className="text-pretty text-muted-foreground text-sm">
            Groups a label with an input, helper text, and optional validation
            messages in a frosted shell with an animated rim.
          </p>
          <HaloField>
            <HaloFieldLabel htmlFor={emailId}>Email</HaloFieldLabel>
            <HaloFieldContent>
              <HaloInput
                aria-invalid={emailInvalid}
                autoComplete="email"
                id={emailId}
                invalid={emailInvalid}
                leadingSlot={<Mail aria-hidden />}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="you@example.com"
                type="email"
                value={email}
              />
              <HaloFieldDescription>
                We’ll only use this for account updates.
              </HaloFieldDescription>
              {emailInvalid ? (
                <HaloFieldError>Enter a valid email address.</HaloFieldError>
              ) : null}
            </HaloFieldContent>
          </HaloField>
          <button
            className="rounded-md border border-border px-3 py-1.5 font-medium text-foreground text-sm fine-hover:hover:bg-muted"
            onClick={() => setShowEmailError((v) => !v)}
            type="button"
          >
            Toggle validation (demo)
          </button>
        </section>

        <section className="space-y-3">
          <h2 className="font-semibold text-foreground text-lg tracking-tight">
            Textarea
          </h2>
          <HaloField>
            <HaloFieldLabel htmlFor={bioId}>Bio</HaloFieldLabel>
            <HaloFieldContent>
              <HaloTextarea
                id={bioId}
                placeholder="A few lines about you…"
                rows={5}
              />
              <HaloFieldDescription>
                Optional — shown on your profile.
              </HaloFieldDescription>
            </HaloFieldContent>
          </HaloField>
        </section>
      </div>
    </main>
  )
}
```

Halo Field is a form field layout component for shadcn/ui built on the shadcn Field primitives. Use it to build consistently spaced fields in settings pages, sign-up forms, and other generic forms.

## Installation

### CLI

```bash
npx shadcn@latest add @cult-ui/halo-field
```

### Manual

**Add the shadcn `field` component if you do not already have it.**

**Copy and paste the following code into your project.**

```tsx title="halo-field.tsx"
"use client"

/**
 * Form field layout aligned with shadcn `Field` — label, description, error, and control
 * slots for {@link HaloInput} / {@link HaloTextarea} (general-purpose; not tied to
 * search or composer).
 */
import type { ComponentProps } from "react"

import { cn } from "@/lib/utils"
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldError,
  FieldLabel,
  FieldTitle,
} from "@/components/ui/field"

export type HaloFieldProps = ComponentProps<typeof Field>

/** Root group; pairs with {@link HaloFieldLabel}, {@link HaloFieldContent}, etc. */
export function HaloField({ className, ...props }: HaloFieldProps) {
  return (
    <Field
      className={cn("group/halo-field w-full max-w-full gap-2", className)}
      data-slot="halo-field"
      {...props}
    />
  )
}

export const HaloFieldContent = FieldContent
export const HaloFieldDescription = FieldDescription
export const HaloFieldError = FieldError
export const HaloFieldLabel = FieldLabel
export const HaloFieldTitle = FieldTitle
```

**Pair with `halo-input` for controls. Update the import paths to match your project setup.**
