Date Picker
A calendar to choose a day from, a month at a time: the month's title between the arrows that turn it, the days of the week over its columns, and its days.
import com.pantrixui.components.date.datepicker.PantrixDatePicker
- Navigation. The arrows turn the month; pressing the title shows the years in place of the days, and choosing a year turns the calendar to the same month of it. Where the chosen day is not on show, the calendar goes to its month.
- Days that cannot be chosen. The days outside
minDateandmaxDate, and thoseisDateEnabledleaves out, are drawn faint and cannot be pressed; the arrows do not go past the ends. - Stretch. The first press chooses the stretch's first day and the second its last; a press on a day before the first starts the stretch again there. While the last is chosen, the days to the one under the pointer are shown in a lighter colour.
- Week. It starts on the language's day: Sunday in English, Monday in Turkish, Saturday in Arabic;
firstDayOfWeekchanges it. - Today. The ringed day is the today of the screen's clock and turns by itself at midnight. The screen's clock is
LocalPantrixClock, the device's by default (PantrixClock.System); another place's day is given bytoday, or for a whole screen byLocalPantrixClock. - Platform. A day's room is the platform's least touch target on a phone (48dp on Android, 44pt on iOS), and 36dp on the desktop and the web. In a narrow place the days shrink until they fit.
- Date type. Days are
PantrixDates, and no date library is needed; docs/guide/date-time/DATE_TIME_GUIDE.md turns them intokotlinx-datetime's orjava.time's.
Examples
One day in a popover
A button shows the chosen day and opens the calendar under it in a popover; choosing a day closes the popover. The days before today cannot be chosen.
var due by remember { mutableStateOf<PantrixDate?>(null) }
var open by remember { mutableStateOf(false) }
val strings = PantrixTheme.strings
PantrixPopover(
visible = open,
onDismissRequest = { open = false },
anchor = {
PantrixButton(
text = due?.let(strings::written) ?: "Choose a due date",
onClick = { open = !open },
variant = ButtonVariant.Outline,
leadingIcon = { PantrixIcon(PantrixIcons.Calendar, contentDescription = null) },
tagKey = "due"
)
},
alignment = PopoverAlignment.Start,
tagKey = "due"
) {
PantrixDatePicker(
selected = due,
onSelect = {
due = it
open = false
},
minDate = rememberToday(),
tagKey = "due"
)
}
private fun PantrixStrings.written(day: PantrixDate): String = date(day.day, day.month, day.year)
A weekday stretch in the page
A stretch of leave from a calendar that stands in the page: only weekdays can be its ends, and the week starts on Monday in every language. The chosen stretch is written under the calendar.
var leave by remember { mutableStateOf<PantrixDateRange?>(null) }
val strings = PantrixTheme.strings
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PantrixDatePicker(
selectedRange = leave,
onRangeSelect = { leave = it },
isDateEnabled = { it.dayOfWeek <= FRIDAY },
firstDayOfWeek = MONDAY,
tagKey = "leave"
)
PantrixText(
text = leave?.let { "Leave: ${strings.written(it.start)} – ${strings.written(it.end)}" }
?: "Choose the first and the last day of the leave",
style = PantrixTheme.typography.body
)
}
private fun PantrixStrings.written(day: PantrixDate): String = date(day.day, day.month, day.year)
private const val MONDAY = 1
private const val FRIDAY = 5
Limited days in a dialog
One of the next thirty days, in a dialog. The choice in the calendar is a draft: OK takes it, and Cancel or a press outside the dialog leaves it.
var appointment by remember { mutableStateOf<PantrixDate?>(null) }
var draft by remember { mutableStateOf<PantrixDate?>(null) }
var open by remember { mutableStateOf(false) }
val today = rememberToday()
val strings = PantrixTheme.strings
val report = LocalExampleEvents.current
PantrixButton(
text = appointment?.let { "Appointment: ${strings.written(it)}" } ?: "Choose the appointment day",
onClick = {
draft = appointment
open = true
},
variant = ButtonVariant.Outline,
tagKey = "appointment"
)
PantrixDialog(
visible = open,
onDismissRequest = { open = false },
title = "Appointment day",
actions = {
PantrixButton(text = "Cancel", onClick = { open = false }, variant = ButtonVariant.Text, tagKey = "cancel")
PantrixButton(
text = "OK",
onClick = {
appointment = draft
open = false
report("Appointment chosen")
},
enabled = draft != null,
tagKey = "accept"
)
},
tagKey = "appointment"
) {
PantrixDatePicker(
selected = draft,
onSelect = { draft = it },
minDate = today,
maxDate = today.plusDays(DAYS_AHEAD),
tagKey = "appointment"
)
}
private fun PantrixStrings.written(day: PantrixDate): String = date(day.day, day.month, day.year)
private const val DAYS_AHEAD = 30
A birthday in a sheet, with the years
A birthday in a sheet: no day after today can be chosen. A year far off is chosen from the years the title opens; the calendar turns to the same month of that year. The calendar fills the sheet's width (fillMaxWidth): its columns widen, and in a very wide sheet it stands in the middle.
var birthday by remember { mutableStateOf<PantrixDate?>(null) }
var open by remember { mutableStateOf(false) }
val strings = PantrixTheme.strings
PantrixButton(
text = birthday?.let { "Birthday: ${strings.written(it)}" } ?: "Choose the birthday",
onClick = { open = true },
variant = ButtonVariant.Outline,
tagKey = "birthday"
)
PantrixSheet(
visible = open,
onDismissRequest = { open = false },
title = "Birthday",
tagKey = "birthday"
) {
PantrixDatePicker(
selected = birthday,
onSelect = {
birthday = it
open = false
},
modifier = Modifier.fillMaxWidth(),
maxDate = rememberToday(),
tagKey = "birthday"
)
}
private fun PantrixStrings.written(day: PantrixDate): String = date(day.day, day.month, day.year)
Another place's today
The calendar's today by a hotel's clock, not the device's: today gives the ring of today, and minDate with it the first day that can be chosen. Kiritimati (UTC+14) and Pago Pago (UTC-11) are 25 hours apart, so the two are always on different days: as the place changes, the ring and the first day move by a day. Where today is not given it is the day of the screen's clock (rememberToday()) and turns by itself at midnight; this example reads the hotel's day as the place is chosen.
var hotel by remember { mutableStateOf(Hotel.Kiritimati) }
var arrival by remember { mutableStateOf<PantrixDate?>(null) }
val today = remember(hotel) { PantrixDate.of(Clock.System.now(), offset = hotel.offset) }
val strings = PantrixTheme.strings
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PantrixSegmentButton(
options = listOf(
SegmentOption(Hotel.Kiritimati, "Kiritimati", tagKey = "kiritimati"),
SegmentOption(Hotel.PagoPago, "Pago Pago", tagKey = "pago_pago")
),
selected = hotel,
onSelect = {
hotel = it
arrival = null
},
fullWidth = false,
tagKey = "hotel"
)
PantrixDatePicker(
selected = arrival,
onSelect = { arrival = it },
minDate = today,
today = today,
tagKey = "arrival"
)
PantrixText(
text = "Today at the hotel: ${strings.written(today)} (${hotel.zone})",
style = PantrixTheme.typography.body
)
PantrixText(
text = arrival?.let { "Arrival: ${strings.written(it)}" } ?: "Choose the day of arrival",
style = PantrixTheme.typography.body
)
}
private enum class Hotel(
val zone: String,
val offset: Duration
) {
Kiritimati("UTC+14", 14.hours),
PagoPago("UTC-11", (-11).hours)
}
private fun PantrixStrings.written(day: PantrixDate): String = date(day.day, day.month, day.year)
The screen's clock
A whole screen's clock: LocalPantrixClock gives the hotel's clock, and everything inside it reads it. The calendar is given no today; its ring, rememberToday() and the time itself are by the hotel's clock. A preview or a test shows a screen on a day and at an hour this way: PantrixClock(clock, utcOffset) takes a Clock of its own as well.
var hotel by remember { mutableStateOf(Hotel.Kiritimati) }
val clock = remember(hotel) { PantrixClock(Clock.System) { hotel.offset } }
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PantrixSegmentButton(
options = listOf(
SegmentOption(Hotel.Kiritimati, "Kiritimati", tagKey = "kiritimati"),
SegmentOption(Hotel.PagoPago, "Pago Pago", tagKey = "pago_pago")
),
selected = hotel,
onSelect = { hotel = it },
fullWidth = false,
tagKey = "screen_hotel"
)
CompositionLocalProvider(LocalPantrixClock provides clock) {
var arrival by remember { mutableStateOf<PantrixDate?>(null) }
PantrixDatePicker(selected = arrival, onSelect = { arrival = it }, tagKey = "screen_arrival")
val today = rememberToday()
val now = LocalPantrixClock.current.time()
val strings = PantrixTheme.strings
PantrixText(
text = "Today on the screen: ${strings.written(today)}, at ${strings.clock(now.hour, now.minute)}",
style = PantrixTheme.typography.body
)
}
}
private enum class Hotel(
val zone: String,
val offset: Duration
) {
Kiritimati("UTC+14", 14.hours),
PagoPago("UTC-11", (-11).hours)
}
private fun PantrixStrings.written(day: PantrixDate): String = date(day.day, day.month, day.year)
Its own colours and disabled
The calendar's own colours and measure, and a switch that disables it. The ends of a stretch are in the success colour, the days between in its light fill, and the ring of today in the warning colour; every colour not given is the theme's. The calendar has no padding of its own (padding = 0.dp): it stands in the card's. With the switch off the calendar is faint: no day is chosen and the month does not turn, and the chosen stretch stays.
var trip by remember { mutableStateOf<PantrixDateRange?>(null) }
var enabled by remember { mutableStateOf(true) }
val colors = PantrixTheme.colors
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PantrixSwitchButton(
checked = enabled,
onCheckedChange = { enabled = it },
label = "Can be chosen",
tagKey = "trip_enabled"
)
PantrixDatePicker(
selectedRange = trip,
onRangeSelect = { trip = it },
enabled = enabled,
style = DatePickerStyle(padding = 0.dp),
colors = DatePickerColors(
selectedContainer = colors.success.color,
rangeContainer = colors.success.container,
today = colors.warning.color
),
tagKey = "trip"
)
}
API
selected
A calendar to choose a day from, a month at a time: the month's title between the arrows that turn it, the days of the week over its columns, and its days.
| Parameter | Type | Default | What it is |
|---|---|---|---|
selected | PantrixDate? | the chosen day, or null while none is. | |
onSelect | (PantrixDate) -> Unit | handed the day a press chose: one that can be chosen. | |
modifier | Modifier | Modifier | the calendar's modifier. The calendar is seven days wide, and narrower where it is given less room. Given a width, as Modifier.fillMaxWidth() gives it the width of a sheet, it fills it: its columns grow as far as DatePickerStyle.maxCellWidth, and past that it stands in the middle. |
minDate | PantrixDate? | null | the first day that can be chosen, or null for none so early. |
maxDate | PantrixDate? | null | the last day that can be chosen, or null for none so late. |
isDateEnabled | ((PantrixDate) -> Boolean)? | null | whether a day between them can be chosen, or null where every one can. |
firstDayOfWeek | Int? | null | the day the weeks start on, as ISO 8601 numbers the days: 1 is Monday and 7 is Sunday. Null is the language's, PantrixLocale.firstDayOfWeek: Sunday in English, Monday in Turkish, Saturday in Arabic. |
enabled | Boolean | true | whether a day can be chosen at all. A calendar that takes no choice is drawn faint. |
today | PantrixDate | rememberToday() | the day marked as today: by default the one the screen's clock shows (rememberToday), which turns at midnight. A test gives a day of its own. |
style | DatePickerStyle? | null | this call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform. |
colors | DatePickerColors? | null | this calendar's own colours: the ones it sets, with the theme's for the rest. |
tagKey | String? | null | the calendar's automation key: it is DatePicker_<tagKey>, and its days are DatePicker_<tagKey>.Day_2026_10_17. |
selectedRange
A calendar to choose a stretch of days from: a stay, a leave, the days a report is of. It is the calendar that chooses a day, pressed twice: its first press chooses the first day of the stretch and its second the last.
| Parameter | Type | Default | What it is |
|---|---|---|---|
selectedRange | PantrixDateRange? | the chosen stretch, or null while none is. | |
onRangeSelect | (PantrixDateRange) -> Unit | handed the stretch two presses chose. | |
modifier | Modifier | Modifier | the calendar's modifier. The calendar is seven days wide, and narrower where it is given less room; given a width, it fills it as far as DatePickerStyle.maxCellWidth lets its columns grow. |
minDate | PantrixDate? | null | the first day that can be chosen, or null for none so early. |
maxDate | PantrixDate? | null | the last day that can be chosen, or null for none so late. |
isDateEnabled | ((PantrixDate) -> Boolean)? | null | whether a day between them can be the first or the last day of a stretch, or null where every one can. |
firstDayOfWeek | Int? | null | the day the weeks start on, as ISO 8601 numbers the days: 1 is Monday and 7 is Sunday. Null is the language's, PantrixLocale.firstDayOfWeek. |
enabled | Boolean | true | whether a stretch can be chosen at all. A calendar that takes no choice is drawn faint. |
today | PantrixDate | rememberToday() | the day marked as today: by default the one the screen's clock shows (rememberToday), which turns at midnight. A test gives a day of its own. |
style | DatePickerStyle? | null | this call's own style: over every enclosing one and under the density. |
colors | DatePickerColors? | null | this calendar's own colours: the ones it sets, with the theme's for the rest. |
tagKey | String? | null | the calendar's automation key: it is DatePicker_<tagKey>, and its days are DatePicker_<tagKey>.Day_2026_10_17. |
DatePickerStyle
PantrixDatePicker's geometry: the room of a day, the round mark a chosen day is drawn in, and the room around the month.
| Parameter | Type | Default | What it is |
|---|---|---|---|
cellSize | Dp | Dp.Unspecified | The width and the height of a day's room: the column and the row. The title and the year's rooms are as tall. |
daySize | Dp | Dp.Unspecified | The width and the height of the round mark a chosen day, today and the day under a pointer are drawn in, in the middle of its room. A stretch of days is a band as tall. |
padding | Dp | Dp.Unspecified | The room between the calendar's edge and what it holds. |
gap | Dp | Dp.Unspecified | Between the title and the days of the week over the month. |
maxCellWidth | Dp | Dp.Unspecified | The widest a column grows to where the calendar is given more width than seven days' rooms: half as wide again as cellSize. |
DatePickerColors
What a PantrixDatePicker is painted with, in place of the theme's colours: a colour that is set is used, and one that is left out stays the theme's.
| Parameter | Type | Default | What it is |
|---|---|---|---|
content | Color | Color.Unspecified | The days' numbers, the title and the arrows. The theme's textPrimary. |
weekday | Color | Color.Unspecified | The days of the week over the columns. The theme's textSecondary. |
selectedContainer | Color | Color.Unspecified | The round mark of a chosen day, and of a stretch's first and last. The primary role's color. |
selectedContent | Color | Color.Unspecified | The number on it. The primary role's onColor, or the colour a role's fill given as selectedContainer gives what stands on it. |
rangeContainer | Color | Color.Unspecified | The band under the days between a stretch's first and last. The primary role's container. While the last is being chosen under a pointer, the band to the day under it is drawn in half of it. |
rangeContent | Color | Color.Unspecified | The numbers on the band. The primary role's onContainer. |
today | Color | Color.Unspecified | The ring around today, and its number. The primary role's color. |
hover | Color | Color.Unspecified | The round mark under a pointer: which day a press would choose. A share of content. |
disabled | Color | Color.Unspecified | The numbers of the days that cannot be chosen, and everything of a calendar that takes no choice. The theme's textDisabled. |
Accessibility
- Keyboard. The calendar is one Tab stop. The arrows walk a day or a week, Page Up and Page Down a month, with Shift a year, Home and End to the week's first and last day; Enter and Space choose.
- Screen reader. Each day is read in full with the day of the week ("Saturday, October 17, 2026"); chosen, today, and the start, the middle and the end of a stretch are said. When the month changes, the new month is announced politely.