Date picker
One day picked from a month. The calendar loads on its first open, and every month name and digit comes from Intl.
client - size not measured yet, lazy
Date picker
client - size not measured yet, lazyOne day picked from a month. The calendar loads on its first open, and every month name and digit comes from Intl.
Try it
size
invalid
fullWidth
placement
firstDayOfWeek
The code
date-picker.tsx
import { DatePicker } from 'topclassui/date-picker'
<div className="grid justify-items-start gap-1.5">
<label htmlFor="date-picker-playground" className="text-sm text-body">Check-in</label>
<DatePicker id="date-picker-playground" defaultValue="2026-09-22" />
</div>The value is an ISO date string, YYYY-MM-DD; the shown format comes from the provider's locale.
Every state
3 statesPick a weekday.
Import from the package
topclassui/date-picker
import { DatePicker } from 'topclassui/date-picker'Install once:npm i topclassui@beta zod
Examples
valueanddefaultValueare an ISO date or null; a string that is not a real day, such as 2026-02-30, counts as null.- With a
name, a hidden input sends the ISO date, and an empty value with nothing chosen. Before the page hydrates it sends the default. minDateandmaxDateare ISO dates; a day outside them is dimmed and cannot be chosen, and the month buttons stop at them.- The week starts on
firstDayOfWeek, 1 (Monday) to 7 (Sunday); left out, on the locale's first day. - The calendar is a chunk of its own, loaded on the first open. The grid is always Gregorian, with no date library.
basic
basic.tsx
import { DatePicker } from 'topclassui/date-picker'
/** A label that points at the trigger by its id: the server prints the day in the provider's locale, and the form
* input holds it as an ISO date. */
export default function Demo() {
return (
<div className="grid justify-items-start gap-1.5">
<label htmlFor="date-picker-basic" className="text-sm text-body">Check-in</label>
<DatePicker id="date-picker-basic" defaultValue="2026-09-22" />
</div>
)
}
in-field
Rooms are ready from 15:00
in-field.tsx
import { Field } from 'topclassui/field'
import { DatePicker } from 'topclassui/date-picker'
/** The card's sample: the Field's control object spreads onto the DatePicker as it is, so the label names the trigger
* and the hint describes it after the day. */
export default function Demo() {
return (
<Field name="checkIn" label="Check-in" hint="Rooms are ready from 15:00">
{(control) => (
<DatePicker
{...control}
defaultValue="2026-10-02"
minDate="2026-09-25"
maxDate="2027-03-31"
/>
)}
</Field>
)
}
limits
limits.tsx
import { DatePicker } from 'topclassui/date-picker'
/** The card's dates: a day from 25 September 2026 to 31 March 2027. The days outside are dimmed and cannot be chosen,
* and the previous button stops at September. */
export default function Demo() {
return (
<div className="grid justify-items-start gap-1.5">
<label htmlFor="date-picker-limits" className="text-sm text-body">Check-in</label>
<DatePicker id="date-picker-limits" name="checkIn" defaultValue="2026-10-02" minDate="2026-09-25" maxDate="2027-03-31" />
</div>
)
}
week-start
week-start.tsx
import { TopClassProvider } from 'topclassui/provider'
import { DatePicker } from 'topclassui/date-picker'
/** The first column: Monday, then Saturday, then left to the locale, which for British English is Monday too. */
export default function Demo() {
return (
<div className="grid justify-items-start gap-3">
<DatePicker aria-label="Monday first" firstDayOfWeek={1} defaultValue="2026-09-22" />
<DatePicker aria-label="Saturday first" firstDayOfWeek={6} defaultValue="2026-09-22" />
<TopClassProvider direction="ltr" locale="en-GB">
<DatePicker aria-label="The locale's first day" defaultValue="2026-09-22" />
</TopClassProvider>
</div>
)
}
form
form.tsx
import { Button } from 'topclassui/button'
import { DatePicker } from 'topclassui/date-picker'
/** The DatePicker carries the name and the default. It sends checkIn=2026-10-02, before any script runs and after. */
export default function Demo() {
return (
<form className="grid justify-items-start gap-3">
<label htmlFor="date-picker-form" className="text-sm text-body">Check-in</label>
<DatePicker id="date-picker-form" name="checkIn" defaultValue="2026-10-02" required />
<Button type="submit">Save</Button>
</form>
)
}
sizes
sizes.tsx
import { DatePicker } from 'topclassui/date-picker'
const SIZES = [
{ size: 'xs', label: 'Extra small' },
{ size: 'sm', label: 'Small' },
{ size: 'md', label: 'Medium' },
{ size: 'lg', label: 'Large' },
{ size: 'xl', label: 'Extra large' },
] as const
/** The five sizes of the trigger: the height, the padding and the type step change, and the width follows the type step. */
export default function Demo() {
return (
<div className="grid justify-items-start gap-3">
{SIZES.map(({ size, label }) => (
<DatePicker key={size} aria-label={label} size={size} defaultValue="2026-09-22" />
))}
</div>
)
}
In Arabic, right to left
rtl.tsx
import { TopClassProvider } from 'topclassui/provider'
import { DatePicker } from 'topclassui/date-picker'
/** Arabic, inside TopClassProvider as the root layout would mount it: Arabic month names, the week from Saturday,
* the icon at the right, the calendar lined up with the right edge of the trigger, and ArrowLeft the next day. */
export default function Demo() {
return (
<TopClassProvider direction="rtl" locale="ar">
<div dir="rtl" lang="ar" className="grid justify-items-start gap-1.5">
<label htmlFor="date-picker-rtl" className="text-sm text-body">تاريخ الوصول</label>
<DatePicker id="date-picker-rtl" name="checkIn" defaultValue="2026-09-22" />
</div>
</TopClassProvider>
)
}
Props
Keyboard and accessibility
- A name is required:
idfor a<label htmlFor>, oraria-label, oraria-labelledby. The label names the button and the day is its description. - The calendar follows the date picker dialog of the ARIA Authoring Practices; while it is open the focus is in it, named by its month.
requireddescribes the button as required, since a button cannot carry aria-required.invalidturns its line to danger and thickens it.
Right to left
Month and day names come in Arabic, the week starts on Saturday in ar, and the calendar lines up with the right edge of the button. ArrowLeft is the next day.
Server and client
A client component: it renders on the server and hydrates in the browser.
Files: index.tsx, client.tsx.
On the chip: client - size not measured yet, lazy.