DatePicker
A text field with a calendar popover for picking a single date.
Playground
<script lang="ts">
import { DatePicker } from '@urbicon-ui/blocks';
const label = 'Date';
const placeholder = 'Select a date';
</script>
<DatePicker
{label}
{placeholder}
/>01 Examples
Basic DatePicker
<script lang="ts">
import { DatePicker } from '@urbicon-ui/blocks';
let selectedDate = $state<Date | undefined>(undefined);
</script>
<div class="max-w-xs">
<DatePicker
bind:value={selectedDate}
label="Event date"
placeholder="Select a date"
defaultMonth={2}
defaultYear={2026}
/>
{#if selectedDate}
<div class="bg-surface-elevated border-border-subtle mt-3 rounded-lg border p-3">
<p class="text-text-secondary text-sm">
<span class="text-text-primary font-medium">Selected:</span>
{selectedDate.toLocaleDateString('en-US', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</p>
</div>
{/if}
</div>
DateRangePicker
<script lang="ts">
import { DateRangePicker } from '@urbicon-ui/blocks';
let value = $state<{ start: Date; end: Date } | undefined>(undefined);
</script>
<div class="max-w-xs">
<DateRangePicker
bind:value
label="Travel dates"
placeholder="Select a range"
defaultMonth={2}
defaultYear={2026}
/>
{#if value}
<div class="bg-surface-elevated border-border-subtle mt-3 rounded-lg border p-3">
<p class="text-text-secondary text-sm">
<span class="text-text-primary font-medium">From:</span>
{value.start.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</p>
<p class="text-text-secondary text-sm">
<span class="text-text-primary font-medium">To:</span>
{value.end.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</p>
<p class="text-text-secondary mt-1 text-xs">
{Math.ceil((value.end.getTime() - value.start.getTime()) / (1000 * 60 * 60 * 24)) + 1} days
</p>
</div>
{/if}
</div>
With Constraints
<script lang="ts">
import { DatePicker } from '@urbicon-ui/blocks';
let selectedDate = $state<Date | undefined>(undefined);
const minDate = new Date(2026, 2, 1);
const maxDate = new Date(2026, 2, 31);
/** Disable weekends */
function isWeekend(date: Date): boolean {
const day = date.getDay();
return day === 0 || day === 6;
}
/** Specific holidays / blocked dates */
const holidays = [new Date(2026, 2, 6), new Date(2026, 2, 20)];
</script>
<div class="max-w-xs">
<DatePicker
bind:value={selectedDate}
label="Appointment"
placeholder="Pick a weekday"
helper="Weekdays in March 2026 only, no holidays."
{minDate}
{maxDate}
isDateDisabled={isWeekend}
disabledDates={holidays}
defaultMonth={2}
defaultYear={2026}
/>
{#if selectedDate}
<div class="bg-surface-elevated border-border-subtle mt-3 rounded-lg border p-3">
<p class="text-text-secondary text-sm">
<span class="text-text-primary font-medium">Appointment:</span>
{selectedDate.toLocaleDateString('en-US', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</p>
</div>
{/if}
</div>
02 Accessibility
ARIA Roles
The trigger input carries aria-haspopup="dialog" and aria-expanded, so the popover's state is announced.
The embedded calendar is a role="grid" of day cells,
with the same keyboard model as Calendar.
Keyboard Navigation
Enter, Space or ArrowDown open the calendar, Escape closes it. Inside, the arrow keys move between days and weeks and PageUp/PageDown between months. Focus rings use focus-visible:, so they appear for the keyboard only.
Screen Reader Labels
The label reaches the screen reader through the input. Every day cell carries an aria-label with the full date ("Thursday, 12 March
2026"), and error and helper text are linked through aria-describedby.
Internationalisation
Formatting goes through the native Intl.DateTimeFormat with the configured locale, so weekday names, month names and the input
format follow the language without further configuration.
03 API Reference
38 propsProp | Type | Default | Description | |
|---|---|---|---|---|
calendarVariant | defaultborderedghost | 'default' | Visual style of the calendar popup. | |
class | string | — | class property | |
clearable | boolean | true | Allow clearing the selected date. | |
closeOnClickOutside | boolean | true | Whether the popover closes on outside click. | |
closeOnEscape | boolean | true | Whether the popover closes on Escape key. | |
closeOnSelect | boolean | true | Close popover after selecting a date. | |
defaultMonth | MonthIndex | — | Default month shown when the picker opens without a value. 0–11. | |
defaultYear | number | — | Default year shown when the picker opens without a value. | |
disabled | boolean | false | Disable the entire picker. | |
disabledDates | Date[] | — | Specific dates that are disabled. | |
displayFormat | DateFormatOptions | — | Intl.DateTimeFormat options for the displayed date. | |
error | string | — | Error message shown below the input. | |
fixedWeeks | boolean | true | Always show 6 week rows, so the overlay keeps its height while paging
months. Set false to let it shrink to 4 or 5 rows. | |
helper | string | — | Helper text shown below the input. | |
inputVariant | outlinedfilledghostunderline | 'outlined' | Input variant. | |
isDateDisabled | (date: Date) => boolean | — | Predicate that disables specific dates. Errors thrown by the predicate are caught and logged; the date is then treated as allowed so a faulty consumer callback can't take the picker down. | |
label | string | — | Label above the input. | |
locale | string | 'auto' | BCP 47 locale tag for date formatting — month names, weekday names and the
header title. Defaults to 'auto', which follows the active
<I18nProvider> locale, so an app that already declares its language does
not have to repeat it here. SSR-safe: the locale comes from context, so the
server and the client resolve the same tag (Intl with undefined would
follow the runtime and disagree across hydration). Falls back to the base
locale (en) when no provider is mounted. Pass an explicit tag
(e.g. 'de-DE', 'ja-JP') to override. | |
maxDate | Date | — | Latest selectable date. | |
minDate | Date | — | Earliest selectable date. | |
mint | MintProp | 'none' | Micro-interaction preset forwarded to the inner Input. | |
name | string | — | Shared name for native form submission. When set, a hidden input
is rendered carrying the serialized date — matching what the user
picked, so the visible input's locale-formatted display string is
never submitted instead.
Empty / unset values submit as "" so the field still appears in
the FormData payload. | |
onClickOutside | () => void | — | Fires after an outside click closes the popover. Notification only —
does NOT govern whether close happens. That is controlled by
closeOnClickOutside. | |
onEscape | () => void | — | Fires after Escape closes the popover. Notification only — does NOT
govern whether close happens. That is controlled by closeOnEscape. | |
onOpenChange | (open: boolean) => void | — | Fires when the popover opens or closes. | |
onValueChange | (value: Date | undefined) => void | — | Fires when the selected date changes. | |
placeholder | string | — | Placeholder when no date is selected. | |
preset | string | — | Apply a named preset registered on <BlocksProvider>. | |
required | boolean | false | Mark input as required. | |
showOutsideDays | boolean | true | Show days from adjacent months. | |
showWeekNumbers | boolean | false | Show ISO week numbers. | |
size | xssmmdlg +1 more | 'md' | Component size. | |
slotClasses | Partial<Record<DatePickerSlots, string>> | — | Per-slot class overrides. base is the positioning wrapper the field and
the popover anchor sit in, iconButton the clear / open-calendar buttons
in the field's right-icon area. The field itself is an Input and the
overlay a Calendar; restyle those under their own names. | |
unstyled | boolean | — | Strip the default tv() classes of the wrapper AND of the Input, Popover and Calendar it renders; slotClasses and class then stand alone. | |
value | Date | string | null | — | Currently selected date. Supports bind:value.
Accepts a Date, an ISO timestamp string, or null / undefined.
Internally coerced to a Date; the picker emits Date instances
via DatePickerProps.onValueChange. When both bind:value and
onValueChange are wired, both fire on every user-driven change —
pick one to drive side-effects (saves, analytics) to avoid duplicates. | |
valueFormat | dateiso | 'date' | Format used to serialise the date for the hidden form input.
- 'date' (default): YYYY-MM-DD in the local timezone — matches
the native <input type="date"> payload and Zod schemas like
z.string().regex(/^\d{4}-\d{2}-\d{2}$/).transform((v) => new Date(v)).
- 'iso': full ISO-8601 with Z suffix (UTC). Use this when the
downstream schema expects a parseable timestamp string (e.g. a
Drizzle timestamp({ withTimezone: true, mode: 'date' }) column).
Only relevant when DatePickerProps.name is set. | |
weekStartsOn | WeekdayIndex | 1 | First day of the week. 0 = Sunday, 1 = Monday. | |
...HTMLAttributes<HTMLDivElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') |
04 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
DatePickerProps | interface | props | 0 | — | |
DateRangePickerProps | interface | props | 0 | — | |
DatePickerPreset | interface | helper | 0 | — | |
DateRangePreset | interface | helper | 0 | — | |
DatePickerVariants | type | variant | 0 | — | |
DatePickerSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
MintProp | type | helper | 1 | — | |
MonthIndex | type | helper | 1 | Month index used by Date#getMonth(). | |
WeekdayIndex | type | helper | 1 | Weekday index used by Date#getDay(). | |
DateRange | interface | helper | 0 | An inclusive start/end date pair: a selected range, a visible window, the
range onNavigate reports. One type for Calendar, Planner and
ResourceTimeline alike.
ResourceTimeline.getRange is not this type: it also accepts local date
strings ('2026-06-16'), which a selection value must not. | |
DateFormatOptions | type | helper | 1 | — | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
BuiltinMintName | type | helper | 0 | Built-in mint names as a literal union, so the mint prop autocompletes
across every component — the single list the hand-curated playground knobs
and docs used to drift away from. |
05 Installation
Import
import { DatePicker, DateRangePicker } from '@urbicon-ui/blocks';
import type { DatePickerProps, DateRangePickerProps } from '@urbicon-ui/blocks';