Skip to content
    Style: Wireframe

    Components · Forms

    Date field

    The native date input in the field style, or a from–to range that keeps the end after the start.

    0.14% of the 2,073 sites in The UI Index have a date picker (3 sites).

    Pick a weekday; we deploy at 09:00.

    Report period

    Both previews render the same markup; the HTML tab below is that markup without a framework.

    Install

    React
    npx shadcn@latest add https://www.themes.dev/ui/r/date-field.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/date-field.json

    Or copy the files below into your project. They need clsx, tailwind-merge, and the themes.dev UI theme. The HTML needs only the compiled CSS and the script.

    date-field.tsx
    import { useState } from 'react';
    import { cn } from '@/lib/utils';
    import { dateField as c } from './date-field.variants';
    
    export interface DateFieldProps {
      id: string;
      label: string;
      defaultValue?: string;
      min?: string;
      max?: string;
      hint?: string;
      name?: string;
      required?: boolean;
      /** A from–to pair in one fieldset; label is then the legend, and defaultValue/min/max are for the "from" field. */
      range?: { toValue?: string; fromLabel?: string; toLabel?: string };
      className?: string;
    }
    
    /** A native date field in the field style, or a from–to pair whose "to" can't be before "from". */
    export function DateField({ id, label, defaultValue, min, max, hint, name, required, range, className }: DateFieldProps) {
      const [from, setFrom] = useState(defaultValue);
      if (range)
        return (
          <fieldset data-date-range="" aria-describedby={hint ? `${id}-hint` : undefined} className={cn(c.range, className)}>
            <legend className={c.legend}>{label}</legend>
            <div className={c.pair}>
              <div className={c.root}>
                <label htmlFor={`${id}-from`} className={c.label}>{range.fromLabel ?? 'From'}</label>
                <input type="date" id={`${id}-from`} name={name ? `${name}-from` : undefined} defaultValue={defaultValue} min={min} max={max} required={required} data-from="" className={c.input} onChange={(e) => setFrom(e.target.value)} />
              </div>
              <div className={c.root}>
                <label htmlFor={`${id}-to`} className={c.label}>{range.toLabel ?? 'To'}</label>
                <input type="date" id={`${id}-to`} name={name ? `${name}-to` : undefined} defaultValue={range.toValue} min={from ?? min} max={max} required={required} data-to="" className={c.input} />
              </div>
            </div>
            {hint && <p id={`${id}-hint`} className={c.hint}>{hint}</p>}
          </fieldset>
        );
      return (
        <div className={cn(c.root, className)}>
          <label htmlFor={id} className={c.label}>{label}</label>
          <input type="date" id={id} name={name} defaultValue={defaultValue} min={min} max={max} required={required} aria-describedby={hint ? `${id}-hint` : undefined} className={c.input} />
          {hint && <p id={`${id}-hint`} className={c.hint}>{hint}</p>}
        </div>
      );
    }
    date-field.variants.ts
    import { fieldClasses } from '../input/input.variants';
    
    /** A date field on the native date input: the platform's own calendar, keyboard entry per part, the user's
     *  locale, min and max. Restyled to the field (the picker button becomes a quiet calendar mark). A range is two
     *  fields in a fieldset (from, to), with the "to" field's min kept at the "from" date. */
    export const dateField = {
      root: 'grid gap-2',
      label: 'text-sm font-medium',
      // The ring is always there, transparent, and takes the ring colour on focus: focus sits in the day, month or year
      // part, and the browser applies a focus style there a frame late (forced-colours modes show the outline too).
      input: `${fieldClasses} h-9 w-full max-w-56 py-1 tabular-nums outline-2 outline-offset-0 outline-transparent [outline-style:solid] focus:border-ring focus:outline-ring [color-scheme:light] dark:[color-scheme:dark] [&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:rounded-sm [&::-webkit-calendar-picker-indicator]:opacity-60 hover:[&::-webkit-calendar-picker-indicator]:opacity-100`,
      hint: 'text-sm text-muted-foreground',
      range: 'm-0 grid min-w-0 gap-2 border-0 p-0',
      legend: 'mb-2 p-0 text-sm font-medium',
      pair: 'flex flex-wrap items-end gap-3',
    };