IndiaCN UI

Date Picker

Date, range, time and date-time pickers behind a field or icon trigger.

Introduction

Four pickers over one Calendar and one Time Picker: a single date, a span across two months, a time of day, and both on one surface.

UX4G gives every picker two triggers — an input-shaped field with a trailing icon, or a bare 32px icon button. Both are here as trigger='field' and trigger='icon'.

The field is a button, not a text input. It looks identical to the kit's field, but a real input invites typing and then has to guess whether "03/04" is March or April. The button says what it is — aria-haspopup='dialog', the chosen date as its accessible name — and the grid stays the one way in.

Installation

npx shadcn@latest add https://indiacn.in/r/date-picker.json
import {
DatePicker,
DateRangePicker,
DateTimePicker,
TimePickerField,
} from '@/components/ui/date-picker';

Usage

const [date, setDate] = useState<Date | null>(null);

return <DatePicker value={date} onValueChange={setDate} />;

Examples

Default

Picking a day commits it and closes the surface — there is nothing left to confirm.

DatePickerDefault
'use client';

import { useState } from 'react';

import { DatePicker } from '@/components/ui/date-picker';

export default function Component() {
  const [date, setDate] = useState<Date | null>(null);

  return <DatePicker value={date} onValueChange={setDate} />;
}

Triggers

DatePickerTriggers
'use client';

import { useState } from 'react';

import { IDateRange } from '@/components/ui/calendar';
import {
  DatePicker,
  DateRangePicker,
  DateTimePicker,
  TimePickerField,
} from '@/components/ui/date-picker';
import { ITimeValue } from '@/components/ui/time-picker';
import { Label1 } from '@/components/ui/typography';

export function DatePickerDefault() {
  const [date, setDate] = useState<Date | null>(null);

  return (
    <div className='w-full max-w-[320px]'>
      <DatePicker value={date} onValueChange={setDate} />
    </div>
  );
}

export function DatePickerTriggers() {
  const [field, setField] = useState<Date | null>(null);
  const [icon, setIcon] = useState<Date | null>(null);

  return (
    <div className='flex w-full max-w-[420px] items-end gap-4'>
      <div className='flex-1'>
        <DatePicker value={field} onValueChange={setField} />
      </div>
      <DatePicker value={icon} onValueChange={setIcon} trigger='icon' />
    </div>
  );
}

export function DateRangePickerDefault() {
  const [range, setRange] = useState<IDateRange>({ from: null, to: null });

  return (
    <div className='w-full max-w-[360px]'>
      <DateRangePicker value={range} onValueChange={setRange} />
    </div>
  );
}

export function TimePickerFieldDefault() {
  const [time, setTime] = useState<ITimeValue | null>(null);

  return (
    <div className='w-full max-w-[320px]'>
      <TimePickerField value={time} onValueChange={setTime} />
    </div>
  );
}

export function DateTimePickerDefault() {
  const [value, setValue] = useState<Date | null>(null);

  return (
    <div className='flex w-full max-w-[360px] flex-col gap-2'>
      <Label1 className='text-neutral'>Appointment</Label1>
      <DateTimePicker value={value} onValueChange={setValue} showSeconds={false} />
    </div>
  );
}

Range

The surface stays open until both ends are set, because closing on the first click would make the second one impossible.

DateRangePickerDefault
'use client';

import { useState } from 'react';

import { IDateRange } from '@/components/ui/calendar';
import { DateRangePicker } from '@/components/ui/date-picker';

export default function Component() {
  const [range, setRange] = useState<IDateRange>({ from: null, to: null });

  return <DateRangePicker value={range} onValueChange={setRange} />;
}

Time

Ok commits the draft, so scrolling past a value costs nothing.

TimePickerFieldDefault
'use client';

import { useState } from 'react';

import { TimePickerField } from '@/components/ui/date-picker';
import { ITimeValue } from '@/components/ui/time-picker';

export default function Component() {
  const [time, setTime] = useState<ITimeValue | null>(null);

  return <TimePickerField value={time} onValueChange={setTime} />;
}

Date and time

Appointment
DateTimePickerDefault
'use client';

import { useState } from 'react';

import { DateTimePicker } from '@/components/ui/date-picker';

export default function Component() {
  const [value, setValue] = useState<Date | null>(null);

  return <DateTimePicker value={value} onValueChange={setValue} showSeconds={false} />;
}

Accessibility

  • The trigger's accessible name carries the current value — "Choose date: 21 Oct 2021" — so it is never just "Choose date" with the answer only visible.
  • Dates are formatted as 21 Oct 2021, with the month spelled out. 21/10/2021 and 10/21/2021 are the same eleven characters and different days.
  • The surface is a popover: Esc closes it and focus returns to the trigger.
  • Inside, the calendar keeps its full grid keyboard model and each time column its listbox model.
  • The pickers that hold a draft — time and date-time — commit only on Ok, so a keyboard user passing through values does not fire a change per keystroke.

API Reference

DatePicker

PropTypeDefaultDescription
valueDate | nullCurrent date
onValueChange(date: Date) => voidFires when a day is chosen
trigger'field' | 'icon''field'Which trigger to render
placeholderstring'Select Date'Field text while empty
format(date: Date) => string21 Oct 2021Overrides the trigger text
showViewControlsbooleantrueMonth and year selects inside the surface
min / maxDateSelectable bounds
disabledbooleanfalseDisables the trigger

DateRangePicker

Takes value: IDateRange and onValueChange: (range: IDateRange) => void, plus trigger, placeholder, min, max and disabled.

TimePickerField

Takes value: ITimeValue | null and onValueChange: (value: ITimeValue) => void, plus trigger, placeholder, showSeconds and disabled.

DateTimePicker

Takes value: Date | null and onValueChange: (date: Date) => void, plus trigger, placeholder, showSeconds, min, max and disabled. Ok commits the day and the time together.

The module also exports formatDate(date) and formatRange(range).

On this page