You are viewing docs for an older version
This page documents v0.2. The latest version is v0.3.
DatePicker
Pick a date or a date range — type it in or use the calendar, with ready-made presets.
Yakshanba — dam olish kuni
'use client';
import { formatDate } from '@akhatmirzo/core';
import { DatePicker, Field } from '@akhatmirzo/ui';
import { useState } from 'react';
const isSunday = (date: Date) => date.getDay() === 0;
export default function DatePickerBasic() {
const [date, setDate] = useState<Date | null>(null);
return (
<div className="w-full max-w-xs">
<Field
label="Yetkazish sanasi"
description={date ? formatDate(date, 'd MMMM yyyy, EEEE') : 'Yakshanba — dam olish kuni'}
>
<DatePicker
value={date}
onValueChange={setDate}
minDate={new Date()}
disabledDates={isSunday}
presets
clearable
/>
</Field>
</div>
);
}
Import
import { Calendar, DatePicker, DateRangePicker } from '@akhatmirzo/ui';Three components share one calendar:
| Component | What it does |
|---|---|
DatePicker | A single date: an Input-style field plus a calendar in a popover |
DateRangePicker | A date range: a button trigger plus a two-month calendar |
Calendar | The 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.presetsgive 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-describedbyand 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
'use client';
import { formatDate } from '@akhatmirzo/core';
import { DatePicker, Field, type DatePickerError } from '@akhatmirzo/ui';
import { useState } from 'react';
const errorText: Record<DatePickerError, string> = {
invalid: "Bunday sana yo'q",
unavailable: "O'tgan sanani tanlab bo'lmaydi",
};
export default function DatePickerTyping() {
const [date, setDate] = useState<Date | null>(null);
const [error, setError] = useState<DatePickerError | null>(null);
return (
<div className="flex w-full max-w-xs flex-col gap-3">
<Field
label="Uchrashuv sanasi"
required
error={error ? errorText[error] : undefined}
description="Sinab ko'ring: 31022026"
>
<DatePicker
value={date}
onValueChange={setDate}
onErrorChange={setError}
minDate={new Date()}
/>
</Field>
<p className="text-sm text-text-muted">
Qiymat: <code>{date ? formatDate(date, 'yyyy-MM-dd') : 'null'}</code>
</p>
</div>
);
}
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
'use client';
import { DatePicker, Field } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function DatePickerBirthday() {
const [birthday, setBirthday] = useState<Date | null>(new Date(1995, 4, 17));
return (
<div className="w-full max-w-xs">
<Field label="Tug'ilgan sana" description="Sarlavhani bosing — oy va yilni tanlash">
<DatePicker
value={birthday}
onValueChange={setBirthday}
minDate={new Date(1920, 0, 1)}
maxDate={new Date()}
/>
</Field>
</div>
);
}
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.
'use client';
import { DatePicker } from '@akhatmirzo/ui';
export default function DatePickerSizes() {
const today = new Date();
return (
<div className="flex w-full max-w-xs flex-col gap-3">
<DatePicker size="sm" aria-label="Kichik" defaultValue={today} />
<DatePicker size="md" aria-label="O'rta" defaultValue={today} />
<DatePicker size="lg" aria-label="Katta" defaultValue={today} />
</div>
);
}
States
disabled, readOnly, an error and a custom format. A readOnly field never opens the
calendar.
Sanani tanlang
d MMMM yyyy
'use client';
import { DatePicker, Field } from '@akhatmirzo/ui';
export default function DatePickerStates() {
const today = new Date();
return (
<div className="grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<Field label="O'chirilgan" disabled>
<DatePicker defaultValue={today} />
</Field>
<Field label="Faqat o'qish">
<DatePicker defaultValue={today} readOnly />
</Field>
<Field label="Muddat" error="Sanani tanlang" required>
<DatePicker />
</Field>
<Field label="Boshqa format" description="d MMMM yyyy">
<DatePicker format="d MMMM yyyy" defaultValue={today} />
</Field>
</div>
);
}
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
'use client';
import { formatDate } from '@akhatmirzo/core';
import { DateRangePicker, Field, type DateRangeValue } from '@akhatmirzo/ui';
import { useState } from 'react';
const daysAgo = (n: number) => {
const d = new Date();
return new Date(d.getFullYear(), d.getMonth(), d.getDate() - n);
};
export default function DateRangePickerBasic() {
const [range, setRange] = useState<DateRangeValue | null>({ from: daysAgo(6), to: daysAgo(0) });
return (
<div className="w-full max-w-sm">
<Field
label="Hisobot davri"
description={
range?.from && range.to
? `${formatDate(range.from, 'd MMM')} – ${formatDate(range.to, 'd MMM yyyy')}`
: 'Davr tanlanmagan'
}
>
<DateRangePicker value={range} onValueChange={setRange} presets maxDate={new Date()} />
</Field>
</div>
);
}
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
'use client';
import { DateRangePicker, Field, type DateRangeValue } from '@akhatmirzo/ui';
import { useState } from 'react';
const addDays = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
export default function DateRangePickerPresets() {
const [vacation, setVacation] = useState<DateRangeValue | null>(null);
return (
<div className="w-full max-w-sm">
<Field label="Ta'til" description="Yakshanbalar yopiq">
<DateRangePicker
value={vacation}
onValueChange={setVacation}
minDate={new Date()}
disabledDates={(d) => d.getDay() === 0}
clearable
name="tatil"
presets={[
{ label: 'Bir hafta', range: (t) => ({ from: addDays(t, 1), to: addDays(t, 7) }) },
{ label: 'Ikki hafta', range: (t) => ({ from: addDays(t, 1), to: addDays(t, 14) }) },
]}
/>
</Field>
</div>
);
}
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.
| Du | Se | Ch | Pa | Ju | Sh | Ya |
|---|---|---|---|---|---|---|
'use client';
import { formatDate } from '@akhatmirzo/core';
import { Calendar } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function CalendarInline() {
const [date, setDate] = useState<Date | null>(new Date());
return (
<div className="flex flex-col items-center gap-2">
<Calendar
value={date}
onValueChange={setDate}
disabledDates={(d) => d.getDay() === 0}
className="rounded-xl border border-border bg-surface p-3"
/>
<span className="text-sm text-text-muted">
{date ? formatDate(date, 'd MMMM yyyy, EEEE') : 'Sana tanlanmagan'}
</span>
</div>
);
}
| Du | Se | Ch | Pa | Ju | Sh | Ya |
|---|---|---|---|---|---|---|
| Du | Se | Ch | Pa | Ju | Sh | Ya |
|---|---|---|---|---|---|---|
'use client';
import { Calendar, type DateRangeValue } from '@akhatmirzo/ui';
import { useState } from 'react';
const addDays = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
export default function CalendarRange() {
const today = new Date();
const [range, setRange] = useState<DateRangeValue>({
from: addDays(today, -3),
to: addDays(today, 4),
});
return (
<div className="flex flex-col items-center gap-2">
<Calendar
mode="range"
numberOfMonths={2}
value={range}
onValueChange={setRange}
className="rounded-xl border border-border bg-surface p-3"
/>
<span className="text-sm text-text-muted">
{range.from && !range.to ? 'Tugash sanasini tanlang' : 'Oraliq tanlandi'}
</span>
</div>
);
}
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).
| Prop | Type | Default | Description |
|---|---|---|---|
value | Date | null | — | Selected date (controlled) |
defaultValue | Date | null | — | Initial value |
onValueChange | (value: Date | null) => void | — | Called when a date is picked, typed correctly or cleared |
format | string | 'dd.MM.yyyy' | Display and input format (core formatDate tokens) |
minDate | Date | — | Earliest allowed date |
maxDate | Date | — | Latest allowed date |
disabledDates | (date: Date) => boolean | — | Return true to block a day |
presets | boolean | DatePreset[] | — | Quick options under the calendar |
size | 'sm' | 'md' | 'lg' | 'md' | Height |
invalid | boolean | — | Error state (automatic inside a Field with an error) |
clearable | boolean | false | Shows a ✕ button when there is a value |
open | boolean | — | Whether the calendar is open (controlled) |
defaultOpen | boolean | false | |
onOpenChange | (open: boolean) => void | — | |
today | Date | — | Today's date (defaults to new Date()) |
numberOfMonths | number | 1 | Months shown |
onErrorChange | (error: DatePickerError | null) => void | — | Typed-text error changed: 'invalid' or 'unavailable' |
showError | boolean | — | Built-in error text. By default shown only when there is no outside error |
labels | DatePickerLabels | — | UI strings (see below) |
inputClassName | string | — | Class for the inner <input> |
contentClassName | string | — | Class for the popover panel |
DateRangePicker
Accepts the remaining <button> attributes (applied to the trigger). className goes on the
root, triggerClassName on the button.
| Prop | Type | Default | Description |
|---|---|---|---|
value | DateRangeValue | null | — | { from, to } (controlled) |
defaultValue | DateRangeValue | null | — | |
onValueChange | (value: DateRangeValue | null) => void | — | Only with a complete range, or null when cleared |
format | string | 'dd.MM.yyyy' | |
minDate | Date | — | |
maxDate | Date | — | |
disabledDates | (date: Date) => boolean | — | |
presets | boolean | DateRangePreset[] | — | true — today, last 7 days, this month, last month |
numberOfMonths | 1 | 2 | — | Defaults to 2, or 1 on phones (< 640px) |
size | 'sm' | 'md' | 'lg' | 'md' | |
invalid | boolean | — | |
clearable | boolean | false | |
open | boolean | — | |
defaultOpen | boolean | false | |
onOpenChange | (open: boolean) => void | — | |
today | Date | — | |
name | string | — | Adds hidden `${name}From` and `${name}To` form fields |
labels | DateRangePickerLabels | — | |
triggerClassName | string | — | |
contentClassName | string | — |
Calendar
Accepts the remaining <div> attributes. The type of value depends on mode.
| Prop | Type | Default | Description |
|---|---|---|---|
mode | 'single' | 'range' | 'single' | One date or a range |
value | Date | null / DateRangeValue | null | — | single / range |
defaultValue | Date | null / DateRangeValue | null | — | |
onValueChange | (value: Date | null) => void / (value: DateRangeValue) => void | — | |
minDate | Date | — | |
maxDate | Date | — | |
disabledDates | (date: Date) => boolean | — | |
numberOfMonths | number | 1 | |
month | Date | — | Displayed month (controlled) |
defaultMonth | Date | — | |
onMonthChange | (month: Date) => void | — | |
view | 'days' | 'months' | 'years' | — | Current view (controlled) |
defaultView | 'days' | 'months' | 'years' | 'days' | |
onViewChange | (view: CalendarView) => void | — | |
today | Date | — | |
showOutsideDays | boolean | — | Days from adjacent months. Defaults to true for one month, false for several |
autoFocus | boolean | false | Focus the selected day (or today's) |
footer | ReactNode | — | Content under the calendar |
labels | CalendarLabels | — |
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
| Attribute | Element | Value |
|---|---|---|
data-size | field container | sm · md · lg |
data-state | field container | open · closed |
data-invalid | field container | present when invalid |
data-disabled | field container | present when disabled |
data-readonly | DatePicker field | present when readOnly |
Keyboard
| Key | Action |
|---|---|
↓ (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 / End | Start / end of the week |
Enter / Space | Select the day |
Esc | Closes 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">witharia-haspopup="dialog",aria-expanded, andaria-controlspointing 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 througharia-describedbyand announced from anaria-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 fullaria-label, and the current one is marked witharia-current. - DateRangePicker: after the first day, "Tugash sanasini tanlang" ("pick the end date") is
announced from an
aria-liveregion; preset buttons carryaria-pressed. - Icons are
aria-hidden; the "open calendar" and "clear" buttons have anaria-label.