component — conform

Conform Date Picker

The quebi DatePicker wired to Conform. Binds a date field's name, required, default, and validity from field metadata and renders inline errors.

  • form
  • conform
  • date
  • date-picker
  • calendar
  • validation

01 example

Bound to a Conform form

Submit with a past or empty date to see the validation error wired from field metadata.

Event date

source

conform-date-picker.tsx

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

"use client"

import type { FieldMetadata } from "@conform-to/react"
import { type CalendarDate, parseDate } from "@internationalized/date"
import { composeRenderProps } from "react-aria-components"
import type { DateValue } from "react-aria-components"
import { cn } from "@/lib/utils"
import { DatePicker, type DatePickerProps, DatePickerTrigger } from "@/components/date-picker"
import { Description, FieldError, fieldStyles, Label } from "@/components/field"

export interface ConformDatePickerProps
  extends Omit<
    DatePickerProps<DateValue>,
    "children" | "name" | "form" | "value" | "defaultValue" | "isRequired" | "isInvalid"
  > {
  /**
   * A date field: the wire value is an ISO `YYYY-MM-DD` string that Conform
   * coerces to a Date, so the metadata carries `Date | string`.
   */
  field: FieldMetadata<Date | string>
  label?: string
  description?: string
}

/** Parse a Conform field's string default (ISO `YYYY-MM-DD`) into a CalendarDate.
 *  Returns undefined for empty/invalid values so the field starts blank. */
function toDefaultValue(value: unknown): CalendarDate | undefined {
  if (typeof value !== "string" || value === "") return undefined
  try {
    return parseDate(value)
  } catch {
    return undefined
  }
}

/**
 * ConformDatePicker — DatePicker wired to Conform.
 *
 * Binds a date Conform field to the quebi DatePicker: derives name, id, form,
 * required, default, and validity from the field metadata and renders inline
 * errors. The control submits an ISO `YYYY-MM-DD` string via the hidden input
 * react-aria renders for it.
 */
export function ConformDatePicker({
  field,
  label,
  description,
  className,
  ...props
}: ConformDatePickerProps) {
  const hasErrors = !field.valid && !!field.errors
  const isRequired = field.required ?? false

  return (
    <DatePicker
      {...props}
      id={field.id}
      name={field.name}
      form={field.formId}
      defaultValue={toDefaultValue(field.initialValue)}
      isRequired={isRequired}
      isInvalid={hasErrors}
      className={composeRenderProps(className, (resolved) => cn(fieldStyles, resolved))}
    >
      {label && (
        <Label className={cn(hasErrors && "text-quebi-danger")}>
          {label}
          {isRequired && <span className="ml-1 text-quebi-fg">*</span>}
        </Label>
      )}
      <DatePickerTrigger />
      {/* No ids and no aria-describedby here: this is a react-aria field, so it
          generates the description and error ids and already points the control
          at them. Setting id={field.errorId} would not break that — on mount
          react-aria re-points the control at whatever id the element actually
          carries — it would just duplicate wiring that is already correct.
          Outside a react-aria field the ids are yours: see ConformSwitch. */}
      {description && <Description>{description}</Description>}
      {hasErrors && <FieldError>{field.errors?.join(", ")}</FieldError>}
    </DatePicker>
  )
}