@akhatmirzo/ui

DatePicker

Pick a date or a date range — type it in or use the calendar, with ready-made presets.

Yakshanba — dam olish kuni

Import

import { Calendar, DatePicker, DateRangePicker } from '@akhatmirzo/ui';

Three components share one calendar:

ComponentWhat it does
DatePickerA single date: an Input-style field plus a calendar in a popover
DateRangePickerA date range: a button trigger plus a two-month calendar
CalendarThe calendar on its own — place it on a page or in your own UI

When to use

  • When you need an exact day — a delivery date, a meeting, a deadline. If you only need a month or a year, a plain Select is enough.
  • Far-away dates like birthdays work well too: users can type the date, and clicking the calendar caption switches to month and year selection.
  • Report periods, vacations, filters call for DateRangePicker. presets give the most common ranges in one click.
  • Set limits up front: minDate, maxDate, disabledDates. Users see the blocked days but can't pick them — better than showing an error afterwards.
  • Need a label? Wrap the field in Field: id, aria-describedby and the error state are wired up for you.

Typing a date

Users can type straight into the field. The __.__.____ mask adds the dots, and the date is checked as soon as the last digit is in. A date that doesn't exist (31.02.2026) is invalid; a date outside your limits is unavailable. Read the error type with onErrorChange.

Sinab ko'ring: 31022026

Qiymat: null

One error message, not two

By default DatePicker shows its own error under the field. When the surrounding Field has an error (or you pass invalid), the built-in message is hidden so the user doesn't see two lines. Use showError to force it on or off.

The mask only works with numeric formats (dd.MM.yyyy, MM/dd/yyyy). With a word-based format such as format="d MMMM yyyy", the field is parsed as plain text.

Month and year view

Click the calendar caption ("Oktabr 2026") to jump to a month grid, then to a year grid. It's the fastest way to reach a date decades away. Months and years outside minDate / maxDate are disabled.

Sarlavhani bosing — oy va yilni tanlash

Presets

presets adds quick buttons under the calendar. Pass true for "Today", "Tomorrow" and "In a week" (labelled in Uzbek by default). For your own, pass a DatePreset[]:

import { DatePicker, type DatePreset } from '@akhatmirzo/ui';

const presets: DatePreset[] = [
  {
    label: 'In 3 days',
    date: (today) => new Date(today.getFullYear(), today.getMonth(), today.getDate() + 3),
  },
  { label: 'New Year', date: new Date(2027, 0, 1) },
];

<DatePicker presets={presets} />;

If a preset lands on a blocked day (say, a closed Sunday), the next allowed day is picked instead (it looks up to 31 days ahead).

Sizes

sm, md, lg — 32, 40 and 48px, the same heights as Input.

States

disabled, readOnly, an error and a custom format. A readOnly field never opens the calendar.

Sanani tanlang

d MMMM yyyy

DateRangePicker

The range is confirmed on the second click. After the first day, moving the pointer previews the range. Desktop shows two months, phones show one. With presets, the defaults are "Today", "Last 7 days", "This month" and "Last month".

16 Okt – 22 Okt 2026

onValueChange only fires with a complete range (both from and to) or with null when cleared — a half-picked range never leaks out.

Custom presets and forms

In a DateRangePreset, range is either an object or a function that receives today. Pass name and the form gets two hidden fields: ${name}From and ${name}To (yyyy-MM-dd).

Yakshanbalar yopiq

Typing is DatePicker-only

The DateRangePicker trigger is a button, so there's no text to type. From the keyboard, press Enter, Space or ↓ — the calendar opens with focus already on a day.

Calendar

Use the calendar without a popover, right on the page — a booking screen, for example. Set mode="range" to select a range.

Oktabr 2026
22 Oktabr 2026, Payshanba
Oktabr 2026
Noyabr 2026
Oraliq tanlandi

Props

DatePicker

Accepts the remaining <input> attributes (name, id, placeholder, required, readOnly, disabled, aria-* ...) and ref. className goes on the root, inputClassName on the inner <input>. With name, a hidden form field is added (yyyy-MM-dd).

PropTypeDefaultDescription
valueDate | null—Selected date (controlled)
defaultValueDate | null—Initial value
onValueChange(value: Date | null) => void—Called when a date is picked, typed correctly or cleared
formatstring'dd.MM.yyyy'Display and input format (core formatDate tokens)
minDateDate—Earliest allowed date
maxDateDate—Latest allowed date
disabledDates(date: Date) => boolean—Return true to block a day
presetsboolean | DatePreset[]—Quick options under the calendar
size'sm' | 'md' | 'lg''md'Height
invalidboolean—Error state (automatic inside a Field with an error)
clearablebooleanfalseShows a ✕ button when there is a value
openboolean—Whether the calendar is open (controlled)
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void—
todayDate—Today's date (defaults to new Date())
numberOfMonthsnumber1Months shown
onErrorChange(error: DatePickerError | null) => void—Typed-text error changed: 'invalid' or 'unavailable'
showErrorboolean—Built-in error text. By default shown only when there is no outside error
labelsDatePickerLabels—UI strings (see below)
inputClassNamestring—Class for the inner <input>
contentClassNamestring—Class for the popover panel

DateRangePicker

Accepts the remaining <button> attributes (applied to the trigger). className goes on the root, triggerClassName on the button.

PropTypeDefaultDescription
valueDateRangeValue | null—{ from, to } (controlled)
defaultValueDateRangeValue | null—
onValueChange(value: DateRangeValue | null) => void—Only with a complete range, or null when cleared
formatstring'dd.MM.yyyy'
minDateDate—
maxDateDate—
disabledDates(date: Date) => boolean—
presetsboolean | DateRangePreset[]—true — today, last 7 days, this month, last month
numberOfMonths1 | 2—Defaults to 2, or 1 on phones (< 640px)
size'sm' | 'md' | 'lg''md'
invalidboolean—
clearablebooleanfalse
openboolean—
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void—
todayDate—
namestring—Adds hidden `${name}From` and `${name}To` form fields
labelsDateRangePickerLabels—
triggerClassNamestring—
contentClassNamestring—

Calendar

Accepts the remaining <div> attributes. The type of value depends on mode.

PropTypeDefaultDescription
mode'single' | 'range''single'One date or a range
valueDate | null / DateRangeValue | null—single / range
defaultValueDate | null / DateRangeValue | null—
onValueChange(value: Date | null) => void / (value: DateRangeValue) => void—
minDateDate—
maxDateDate—
disabledDates(date: Date) => boolean—
numberOfMonthsnumber1
monthDate—Displayed month (controlled)
defaultMonthDate—
onMonthChange(month: Date) => void—
view'days' | 'months' | 'years'—Current view (controlled)
defaultView'days' | 'months' | 'years''days'
onViewChange(view: CalendarView) => void—
todayDate—
showOutsideDaysboolean—Days from adjacent months. Defaults to true for one month, false for several
autoFocusbooleanfalseFocus the selected day (or today's)
footerReactNode—Content under the calendar
labelsCalendarLabels—

Types

interface DateRangeValue {
  from: Date | null;
  to: Date | null; // null — the range isn't finished yet
}

interface DatePreset {
  label: string;
  date: Date | ((today: Date) => Date);
}

interface DateRangePreset {
  label: string;
  range: DateRangeValue | ((today: Date) => DateRangeValue);
}

isDateSelectable(date, { minDate, maxDate, disabledDates }) is exported too, so your own validation can apply exactly the same rule.

Strings (labels)

Every string has an Uzbek default. To localize, pass only the keys you need — the rest keep their defaults:

<DatePicker
  labels={{
    placeholder: 'dd.mm.yyyy',
    openCalendar: 'Open calendar',
    clear: 'Clear',
    invalidDate: 'Invalid date',
    months: ['January', 'February' /* ... */],
    weekdaysShort: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
  }}
/>

CalendarLabels: previousMonth, nextMonth, previousYear, nextYear, previousYears, nextYears, chooseMonth, chooseYear, monthsGrid, yearsGrid, navigation, today, selected, months, monthsShort, weekdays, weekdaysShort (weeks start on Monday). DatePickerLabels adds: placeholder, openCalendar, calendar, clear, presets, invalidDate, unavailableDate, today, tomorrow, inAWeek. DateRangePickerLabels: placeholder, calendar, clear, presets, pickEnd, separator, today, last7Days, thisMonth, lastMonth.

Data attributes

AttributeElementValue
data-sizefield containersm · md · lg
data-statefield containeropen · closed
data-invalidfield containerpresent when invalid
data-disabledfield containerpresent when disabled
data-readonlyDatePicker fieldpresent when readOnly

Keyboard

KeyAction
↓ (field/trigger)Opens the calendar and moves focus to a day
Enter (in the field)Checks and confirms the typed date
← →±1 day
↑ ↓±1 week
PageUp / PageDown±1 month (Shift — ±1 year)
Home / EndStart / end of the week
Enter / SpaceSelect the day
EscCloses the calendar; in month/year view, goes back to days

In the month and year grids, arrow keys move between cells (↑ ↓ jump a row) and Home / End go to the first and last cell.

Accessibility

  • The field is a plain <input type="text"> with aria-haspopup="dialog", aria-expanded, and aria-controls pointing to the calendar while it's open. Numeric formats bring up a numeric keyboard on phones (inputMode="numeric").
  • The calendar panel is labelled "Kalendar". Opening it keeps focus in the field; ↓ or the calendar button moves focus to a day. On close, focus returns to the field.
  • Errors set aria-invalid; the message is linked through aria-describedby and announced from an aria-live="polite" region.
  • Day buttons get a full accessible name: today's date gets "Bugun" ("today"), the selected one gets "tanlangan" ("selected"). Blocked days are dimmed and struck through, and can't be picked.
  • Month and year grids use role="group", every cell has a full aria-label, and the current one is marked with aria-current.
  • DateRangePicker: after the first day, "Tugash sanasini tanlang" ("pick the end date") is announced from an aria-live region; preset buttons carry aria-pressed.
  • Icons are aria-hidden; the "open calendar" and "clear" buttons have an aria-label.

On this page