Skip to content

Date field

Native date, time, month and datetime entry on the shared control surface.

Formsstarterformdatetimecalendarscheduling

Live preview

full widthLive preview — open it in a new tab for the full-height version.
Open the preview in a new tab

Source

The exact file rendered in the preview above.

import { DateField } from '@/components/ui/date-field'
import { Field } from '@/components/ui/field'
import { DemoColumn, DemoGrid, DemoStage } from './_kit'

export default function DateFieldDemo() {
  return (
    <DemoStage>
      <DemoColumn width="lg">
        <DemoGrid columns={2}>
          <Field name="starts" label="Starts" required hint="Uses the visitor's locale format.">
            {(field) => <DateField {...field} defaultValue="2026-03-14" />}
          </Field>
          <Field name="ends" label="Ends" error="End date must be after the start date.">
            {(field) => <DateField {...field} defaultValue="2026-03-01" />}
          </Field>
        </DemoGrid>

        <DemoGrid columns={2}>
          <Field name="window" label="Maintenance window">
            {(field) => <DateField {...field} kind="time" defaultValue="02:30" />}
          </Field>
          <Field name="cutover" label="Cutover">
            {(field) => (
              <DateField {...field} kind="datetime-local" defaultValue="2026-03-14T02:30" />
            )}
          </Field>
        </DemoGrid>

        <Field name="period" label="Billing period">
          {(field) => <DateField {...field} kind="month" defaultValue="2026-03" disabled />}
        </Field>
      </DemoColumn>
    </DemoStage>
  )
}

components/demos/date-field.tsx

import { DateField } from '@/components/ui/date-field'
import { Field } from '@/components/ui/field'
import { DemoColumn, DemoGrid, DemoStage } from './_kit'

export default function DateFieldDemo() {
  return (
    <DemoStage>
      <DemoColumn width="lg">
        <DemoGrid columns={2}>
          <Field name="starts" label="Starts" required hint="Uses the visitor's locale format.">
            {(field) => <DateField {...field} defaultValue="2026-03-14" />}
          </Field>
          <Field name="ends" label="Ends" error="End date must be after the start date.">
            {(field) => <DateField {...field} defaultValue="2026-03-01" />}
          </Field>
        </DemoGrid>

        <DemoGrid columns={2}>
          <Field name="window" label="Maintenance window">
            {(field) => <DateField {...field} kind="time" defaultValue="02:30" />}
          </Field>
          <Field name="cutover" label="Cutover">
            {(field) => (
              <DateField {...field} kind="datetime-local" defaultValue="2026-03-14T02:30" />
            )}
          </Field>
        </DemoGrid>

        <Field name="period" label="Billing period">
          {(field) => <DateField {...field} kind="month" defaultValue="2026-03" disabled />}
        </Field>
      </DemoColumn>
    </DemoStage>
  )
}

components/ui/date-field.tsx

import type { InputHTMLAttributes } from 'react'
import { Calendar } from 'lucide-react'
import { cn } from '@/lib/cn'
import { controlSurface } from './input'

/**
 * DateField
 *
 * A native `type="date"` control with Foundry chrome. Building a custom date
 * picker means re-implementing locale formats, keyboard grids and screen-reader
 * announcements — the native control already ships all of that, plus the
 * platform picker on mobile.
 *
 * The only additions are the shared control surface and a decorative icon,
 * hidden on WebKit where the engine draws its own indicator.
 */
export interface DateFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
  fieldSize?: 'sm' | 'md' | 'lg'
  /** `time` and `datetime-local` share identical chrome. */
  kind?: 'date' | 'time' | 'datetime-local' | 'month'
}

const heights = {
  sm: 'h-control-sm text-xs',
  md: 'h-control text-sm',
  lg: 'h-control-lg text-base',
} as const

export function DateField({
  fieldSize = 'md',
  kind = 'date',
  className,
  ...props
}: DateFieldProps) {
  return (
    <div className="relative flex items-center">
      <input
        type={kind}
        className={cn(
          controlSurface,
          heights[fieldSize],
          'px-3',
          '[&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:opacity-60',
          '[&::-webkit-calendar-picker-indicator]:hover:opacity-100',
          className,
        )}
        {...props}
      />
      <Calendar
        className="pointer-events-none absolute right-3 size-4 text-ink-subtle [@supports(-webkit-appearance:none)]:hidden"
        aria-hidden="true"
      />
    </div>
  )
}

Demo source — adapt to your project. Foundry is not published as a package.

Usage

Native on purpose. A custom picker means re-implementing locale formats, keyboard grids and screen-reader announcements — and losing the platform picker on mobile. Foundry adds chrome, nothing else.

  • Values are always ISO (`YYYY-MM-DD`); display formatting is the browser’s job.
  • Use `min` and `max` to express real constraints rather than validating after submission.
  • For a date range, use two fields and validate the relationship between them.

Variants and states

Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.

  • Date
  • Time
  • Datetime
  • Month
  • Invalid
  • Disabled

Accessibility

Platform behaviour
Segment-by-segment keyboard editing and the mobile picker come from the native control.
Decoration
The calendar icon is hidden on engines that draw their own indicator, so the two never collide.

Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.