Pantrix UI

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

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.

ParameterTypeDefaultWhat it is
selectedPantrixDate?the chosen day, or null while none is.
onSelect(PantrixDate) -> Unithanded the day a press chose: one that can be chosen.
modifierModifierModifierthe 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.
minDatePantrixDate?nullthe first day that can be chosen, or null for none so early.
maxDatePantrixDate?nullthe last day that can be chosen, or null for none so late.
isDateEnabled((PantrixDate) -> Boolean)?nullwhether a day between them can be chosen, or null where every one can.
firstDayOfWeekInt?nullthe 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.
enabledBooleantruewhether a day can be chosen at all. A calendar that takes no choice is drawn faint.
todayPantrixDaterememberToday()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.
styleDatePickerStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsDatePickerColors?nullthis calendar's own colours: the ones it sets, with the theme's for the rest.
tagKeyString?nullthe 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.

ParameterTypeDefaultWhat it is
selectedRangePantrixDateRange?the chosen stretch, or null while none is.
onRangeSelect(PantrixDateRange) -> Unithanded the stretch two presses chose.
modifierModifierModifierthe 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.
minDatePantrixDate?nullthe first day that can be chosen, or null for none so early.
maxDatePantrixDate?nullthe last day that can be chosen, or null for none so late.
isDateEnabled((PantrixDate) -> Boolean)?nullwhether a day between them can be the first or the last day of a stretch, or null where every one can.
firstDayOfWeekInt?nullthe 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.
enabledBooleantruewhether a stretch can be chosen at all. A calendar that takes no choice is drawn faint.
todayPantrixDaterememberToday()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.
styleDatePickerStyle?nullthis call's own style: over every enclosing one and under the density.
colorsDatePickerColors?nullthis calendar's own colours: the ones it sets, with the theme's for the rest.
tagKeyString?nullthe 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.

ParameterTypeDefaultWhat it is
cellSizeDpDp.UnspecifiedThe width and the height of a day's room: the column and the row. The title and the year's rooms are as tall.
daySizeDpDp.UnspecifiedThe 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.
paddingDpDp.UnspecifiedThe room between the calendar's edge and what it holds.
gapDpDp.UnspecifiedBetween the title and the days of the week over the month.
maxCellWidthDpDp.UnspecifiedThe 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.

ParameterTypeDefaultWhat it is
contentColorColor.UnspecifiedThe days' numbers, the title and the arrows. The theme's textPrimary.
weekdayColorColor.UnspecifiedThe days of the week over the columns. The theme's textSecondary.
selectedContainerColorColor.UnspecifiedThe round mark of a chosen day, and of a stretch's first and last. The primary role's color.
selectedContentColorColor.UnspecifiedThe number on it. The primary role's onColor, or the colour a role's fill given as selectedContainer gives what stands on it.
rangeContainerColorColor.UnspecifiedThe 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.
rangeContentColorColor.UnspecifiedThe numbers on the band. The primary role's onContainer.
todayColorColor.UnspecifiedThe ring around today, and its number. The primary role's color.
hoverColorColor.UnspecifiedThe round mark under a pointer: which day a press would choose. A share of content.
disabledColorColor.UnspecifiedThe numbers of the days that cannot be chosen, and everything of a calendar that takes no choice. The theme's textDisabled.

Accessibility