Skip to content

InputsField group

Field group

A label, optional description, and one or more inputs. The container for any form field on the site.

Stack or rowRequired markerError state

Demo

Default is stack. The label is a label element set in ds-label style. The description sits below the label. The error replaces the description when present.

Default

We'll only contact you about the design system.

Error state

Error

Please enter a valid email address.

Row orientation

Row

Implementation

field-group.tsx
import { type ReactNode } from "react"
import { cn } from "@/lib/utils"

interface FieldGroupProps {
  label: string
  htmlFor?: string
  description?: string
  error?: string
  required?: boolean
  orientation?: "stack" | "row"
  children: ReactNode
}

export function FieldGroup({
  label,
  htmlFor,
  description,
  error,
  required,
  orientation = "stack",
  children,
}: FieldGroupProps) {
  return (
    <div className={cn("ds-field", orientation === "row" && "ds-field--row")}>
      <label htmlFor={htmlFor} className="ds-label">
        {label}
        {required ? <span aria-hidden="true" className="ds-field__required">*</span> : null}
      </label>
      {children}
      {error ? (
        <p className="ds-field__error" role="alert">{error}</p>
      ) : description ? (
        <p className="ds-field__description">{description}</p>
      ) : null}
    </div>
  )
}

Props

PropTypeDefaultDescription
labelstringNoneThe fieldset legend
descriptionstringNoneHelper text below the legend
errorstringNoneError text; takes precedence over description
requiredbooleanfalseAdds a required marker to the legend
orientation"stack" | "row""stack"Field layout

Accessibility

  • The label is a real label, not placeholder-as-label.
  • Error messages are announced via role="alert".
  • Description is associated with the field via aria-describedby (in the consumer's input).
  • Required fields are marked with a visible asterisk and the required attribute on the input.