Pantrix UI

Date Text Field

A text field for a day: the day is typed in digits, or chosen from a calendar that a button at the box's end opens.

import com.pantrixui.components.date.datetextfield.PantrixDateTextField

Examples

A birthday, typed

A birthday, typed: a day after today is not taken. The calendar opens too, but typing a far year is quicker. Once the day is whole it is written below; left half typed, the field shows its error that says the pattern.

var birthday by remember { mutableStateOf<PantrixDate?>(null) }
val strings = PantrixTheme.strings
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixDateTextField(
        value = birthday,
        onValueChange = { birthday = it },
        label = "Birthday",
        maxDate = rememberToday(),
        showClearButton = true,
        tagKey = "birthday"
    )
    PantrixText(
        text = birthday?.let { "Birthday: ${strings.date(it.day, it.month, it.year)}" } ?: "No day yet",
        style = PantrixTheme.typography.body
    )
}

A delivery day on a weekday

A delivery day on a weekday within the next month. In the calendar the weekends are faint. A Saturday typed is not taken and the field says "This day cannot be chosen"; a day past the month says the bounds.

var delivery by remember { mutableStateOf<PantrixDate?>(null) }
val today = rememberToday()
PantrixDateTextField(
    value = delivery,
    onValueChange = { delivery = it },
    label = "Delivery day",
    supportingText = "Delivered on weekdays only",
    minDate = today.plusDays(1),
    maxDate = today.plusMonths(1),
    isDateEnabled = { it.dayOfWeek <= FRIDAY },
    tagKey = "delivery"
)

private const val FRIDAY = 5

The calendar in a sheet or a dialog

The calendar opens in a sheet: near the thumb on a phone, as wide as the screen. presentation = Dialog opens it in a dialog in the middle of the window. A day chosen closes the sheet and is written into the field.

var appointment by remember { mutableStateOf<PantrixDate?>(null) }
var presentation by remember { mutableStateOf(DatePickerPresentation.Sheet) }
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixSegmentButton(
        options = listOf(
            SegmentOption(DatePickerPresentation.Popover, "Popover", tagKey = "popover"),
            SegmentOption(DatePickerPresentation.Sheet, "Sheet", tagKey = "sheet"),
            SegmentOption(DatePickerPresentation.Dialog, "Dialog", tagKey = "dialog")
        ),
        selected = presentation,
        onSelect = { presentation = it },
        fullWidth = false,
        tagKey = "presentation"
    )
    PantrixDateTextField(
        value = appointment,
        onValueChange = { appointment = it },
        label = "Appointment day",
        minDate = rememberToday(),
        presentation = presentation,
        tagKey = "appointment"
    )
}

The app's error and disabled

The app's own error comes before the field's: a day already taken is said with error and supportingText. The field under it is disabled: it takes no focus and opens no calendar.

var meeting by remember { mutableStateOf<PantrixDate?>(PantrixDate(2026, 11, 2)) }
val taken = meeting == PantrixDate(2026, 11, 2)
PantrixDateTextField(
    value = meeting,
    onValueChange = { meeting = it },
    label = "Meeting day",
    supportingText = if (taken) "This day is taken, choose another" else "A day the office is open",
    error = taken,
    tagKey = "meeting"
)
PantrixDateTextField(
    value = PantrixDate(2026, 10, 1),
    onValueChange = {},
    enabled = false,
    label = "Contract start",
    tagKey = "contract"
)

Read-only: from the calendar alone

A read-only field is not typed into: the day is chosen from the calendar alone. A press on the box, Enter or Space opens the calendar. For a place where a day must be chosen, and what is typed must not be misread.

var checkIn by remember { mutableStateOf<PantrixDate?>(null) }
PantrixDateTextField(
    value = checkIn,
    onValueChange = { checkIn = it },
    readOnly = true,
    label = "Check-in day",
    placeholder = "Choose from the calendar",
    minDate = rememberToday(),
    tagKey = "check_in"
)

Bound to a form

Bound to a form's field: with no day, "Save" shows the error and sends the focus to the field. The error shows on leaving the field too; going into the calendar is no leaving it. Digits that make no day leave the value null, and the rule catches it.

val form = rememberFormState()
val scope = rememberCoroutineScope()
val report = LocalExampleEvents.current
val start = form.field<PantrixDate?>("start", initial = null, Rules.required("Enter the start day"))
PantrixDateTextField(
    field = start,
    modifier = Modifier.fillMaxWidth(),
    form = form,
    label = "Start day",
    minDate = rememberToday()
)
PantrixButton(
    text = "Save",
    onClick = { scope.launch { form.submit { report("Saved") } } },
    form = form,
    tagKey = "save_start"
)

On a state, as ISO 8601 writes a day

On a state: state.date is the field's day, state.text the digits typed. Together they tell an empty field from a half-typed one. ISO 8601 reads the same in every language. The button changes the state's day, and the field writes it in itself.

val state = rememberDateTextFieldState()
val today = rememberToday()
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixDateTextField(
        state = state,
        label = "Report day",
        format = DateFieldFormat.Iso,
        tagKey = "report_day"
    )
    PantrixText(
        text = when {
            state.date != null -> "Day: ${state.date}"
            state.text.isEmpty() -> "The field is empty"
            else -> "Half typed: ${state.text}"
        },
        style = PantrixTheme.typography.body
    )
    PantrixButton(
        text = "Write today",
        onClick = { state.date = today },
        variant = ButtonVariant.Outline,
        tagKey = "write_today"
    )
}

An own format

The field's format comes from the theme's language; format says another. The formats of most languages are ready in DateFieldFormat (DayMonthYearDots, MonthDayYearSlashes, Iso, …); an own format is the order of the parts (DateFieldOrder) and a mark: the first here puts the year first, with slashes, as Japanese does. Change the format: the field's day stays the same, only the way it is written changes.

var shipping by remember { mutableStateOf<PantrixDate?>(PantrixDate(2026, 10, 18)) }
var format by remember { mutableStateOf(DateFieldFormat(DateFieldOrder.YearMonthDay, '/')) }
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixSegmentButton(
        options = listOf(
            SegmentOption(DateFieldFormat(DateFieldOrder.YearMonthDay, '/'), "YYYY/MM/DD", tagKey = "year_first"),
            SegmentOption(DateFieldFormat.MonthDayYearSlashes, "MM/DD/YYYY", tagKey = "month_first"),
            SegmentOption(DateFieldFormat(DateFieldOrder.DayMonthYear, '-'), "DD-MM-YYYY", tagKey = "day_first")
        ),
        selected = format,
        onSelect = { format = it },
        fullWidth = false,
        tagKey = "format"
    )
    PantrixDateTextField(
        value = shipping,
        onValueChange = { shipping = it },
        label = "Shipping day",
        format = format,
        tagKey = "shipping"
    )
}

A sheet in a narrow window

The default presentation = DatePickerPresentation.Adaptive: in a narrow window the calendar opens in a sheet from the bottom, near the finger and as wide as the window; in a wider window in a popover under the box. Pick "Narrow" in the card's window selector and open the calendar. It is chosen as the calendar opens: it does not change while it is open, even if the window does.

var day by remember { mutableStateOf<PantrixDate?>(null) }
// The card's window is as tall as the example: the example is kept tall so that a sheet opened in a narrow window has room.
Column(Modifier.fillMaxWidth().height(460.dp)) {
    PantrixDateTextField(
        value = day,
        onValueChange = { day = it },
        modifier = Modifier.fillMaxWidth(),
        label = "Meeting day",
        minDate = rememberToday(),
        presentation = DatePickerPresentation.Adaptive,
        tagKey = "adaptive_day"
    )
}

API

value

A text field for a day: the day is typed in digits, or chosen from a calendar that a button at the box's end opens.

ParameterTypeDefaultWhat it is
valuePantrixDate?the day the field shows, or null.
onValueChange(PantrixDate?) -> Unittold the field's day each time the user changes it: a day, or null for none.
modifierModifierModifierthe modifier of the whole field: its label, its box and what is under it.
enabledBooleantruewhether the field can be used. One that cannot takes no focus and opens no calendar.
readOnlyBooleanfalsewhether the day can only be chosen from the calendar, and not typed.
labelString?nullwhat the field is for, written above the box: the field's name for a screen reader, and its calendar's.
placeholderString?nullshown in the box while it is empty, in the place of the pattern the field shows by itself.
supportingTextString?nullhelp under the box. With error it is the message that says what to put right; without it, the field's own error takes its place while there is one.
errorBooleanfalsewhether the caller finds the day wrong, as a day already taken: the field is drawn in error, and supportingText is the message.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text. A slot does not size what is put in it.
showClearButtonBooleanfalseshows a button that empties the field, while it can be typed into and is not empty.
minDatePantrixDate?nullthe first day the field takes, or null for no first. Not after maxDate.
maxDatePantrixDate?nullthe last day the field takes, or null for no last.
isDateEnabled((PantrixDate) -> Boolean)?nullwhether the field takes a day between the two, or null for every day: a weekday, a day not yet booked.
firstDayOfWeekInt?nullthe day the calendar's weeks start on, 1 for Monday to 7 for Sunday as ISO 8601 numbers them, or null for the language's.
todayPantrixDaterememberToday()the day the calendar marks as today: by default the one the screen's clock shows (rememberToday), which turns at midnight.
formatDateFieldFormatDateFieldFormat.of(PantrixTheme.locale)the order the parts are typed in and the mark between them: the theme's language's and region's by default. of: The role of the theme this tone is drawn in.
presentationDatePickerPresentationDatePickerPresentation.Adaptivewhat the calendar opens in: by default a sheet in a narrow window and a popover under the box in a wider one, chosen as it opens; or always a popover, a sheet or a dialog.
styleTextFieldStyle?nullthis call's own style of the field: over every enclosing one and under the density.
colorsTextFieldColors?nullthis call's own colours of the field, in place of the theme's roles.
pickerStyleDatePickerStyle?nullthis call's own style of the calendar.
pickerColorsDatePickerColors?nullthis call's own colours of the calendar.
contentDescriptionString?nullwhat a screen reader says for the field where it has no label.
tagKeyString?nullthe field's automation key: the field is DateTextField_<tagKey>, its button DateTextFieldCalendar_<tagKey>, its clear button DateTextFieldClear_<tagKey> and its calendar DatePicker_<tagKey>. The field reports that the calendar opened and that a day was chosen in it, and never the day.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted: its focus, its hover and its presses.

state

A date field on a state its caller keeps, as PantrixDateTextField of a value is in every other way: the state's date is the field's day, setting it writes the day in, and its text tells a field that is half typed from an empty one.

ParameterTypeDefaultWhat it is
stateDateTextFieldStatethe digits typed and the day they make. Create it with rememberDateTextFieldState().
modifierModifierModifierthe modifier of the whole field: its label, its box and what is under it.
enabledBooleantruewhether the field can be used. One that cannot takes no focus and opens no calendar.
readOnlyBooleanfalsewhether the day can only be chosen from the calendar, and not typed.
labelString?nullwhat the field is for, written above the box: the field's name for a screen reader, and its calendar's.
placeholderString?nullshown in the box while it is empty, in the place of the pattern the field shows by itself.
supportingTextString?nullhelp under the box. With error it is the message; without it, the field's own error takes its place while there is one.
errorBooleanfalsewhether the caller finds the day wrong: the field is drawn in error, and supportingText is the message.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text.
showClearButtonBooleanfalseshows a button that empties the field, while it can be typed into and is not empty.
minDatePantrixDate?nullthe first day the field takes, or null for no first. Not after maxDate.
maxDatePantrixDate?nullthe last day the field takes, or null for no last.
isDateEnabled((PantrixDate) -> Boolean)?nullwhether the field takes a day between the two, or null for every day.
firstDayOfWeekInt?nullthe day the calendar's weeks start on, 1 for Monday to 7 for Sunday, or null for the language's.
todayPantrixDaterememberToday()the day the calendar marks as today: by default the one the screen's clock shows (rememberToday).
formatDateFieldFormatDateFieldFormat.of(PantrixTheme.locale)the order the parts are typed in and the mark between them: the theme's locale's by default. of: The role of the theme this tone is drawn in.
presentationDatePickerPresentationDatePickerPresentation.Adaptivewhat the calendar opens in: by default a sheet in a narrow window and a popover under the box in a wider one, chosen as it opens; or always a popover, a sheet or a dialog.
styleTextFieldStyle?nullthis call's own style of the field.
colorsTextFieldColors?nullthis call's own colours of the field.
pickerStyleDatePickerStyle?nullthis call's own style of the calendar.
pickerColorsDatePickerColors?nullthis call's own colours of the calendar.
contentDescriptionString?nullwhat a screen reader says for the field where it has no label.
tagKeyString?nullthe field's automation key, as PantrixDateTextField of a value takes it.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted.

field

A date field bound to a form's field: its day, its error and its name come from field.

ParameterTypeDefaultWhat it is
fieldFormField<PantrixDate?>the form's field the date field shows and changes.
modifierModifierModifierthe modifier of the whole field.
formFormState?nullgiven, the keyboard's action key submits the form through the button registered with it.
enabledBooleantruewhether the field can be used.
readOnlyBooleanfalsewhether the day can only be chosen from the calendar.
labelString?nullwhat the field is for, written above the box.
placeholderString?nullshown in the box while it is empty, in the place of the pattern.
supportingTextString?nullhelp under the box, while the field has no error to show there.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text.
showClearButtonBooleanfalseshows a button that empties the field, while it can be typed into and is not empty.
minDatePantrixDate?nullthe first day the field takes, or null for no first. Not after maxDate.
maxDatePantrixDate?nullthe last day the field takes, or null for no last.
isDateEnabled((PantrixDate) -> Boolean)?nullwhether the field takes a day between the two, or null for every day.
firstDayOfWeekInt?nullthe day the calendar's weeks start on, 1 for Monday to 7 for Sunday, or null for the language's.
todayPantrixDaterememberToday()the day the calendar marks as today: by default the one the screen's clock shows (rememberToday).
formatDateFieldFormatDateFieldFormat.of(PantrixTheme.locale)the order the parts are typed in and the mark between them: the theme's locale's by default. of: The role of the theme this tone is drawn in.
presentationDatePickerPresentationDatePickerPresentation.Adaptivewhat the calendar opens in: by default a sheet in a narrow window and a popover under the box in a wider one, chosen as it opens; or always a popover, a sheet or a dialog.
styleTextFieldStyle?nullthis call's own style of the field.
colorsTextFieldColors?nullthis call's own colours of the field.
pickerStyleDatePickerStyle?nullthis call's own style of the calendar.
pickerColorsDatePickerColors?nullthis call's own colours of the calendar.
contentDescriptionString?nullwhat a screen reader says for the field where it has no label.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted.

TextFieldStyle

PantrixTextField's box: its shape, how tall and how wide it is at the least, the room around its text, its icons and its border.

ParameterTypeDefaultWhat it is
shapeShapeSpecShapeSpec.UnspecifiedThe box's shape. A fully round one, ShapeRole.Full, is a pill only while the field holds one line: a field of several lines takes ShapeRole.Large instead, since a capsule's curve runs into the first line of its text.
heightDpDp.UnspecifiedThe least the box is tall.
minWidthDpDp.UnspecifiedThe least the box is wide, which keeps an empty field from growing as the user types into it.
horizontalPaddingDpDp.UnspecifiedThe room between the box's edge and its text, where no icon is.
verticalPaddingDpDp.UnspecifiedThe room above the first line of the text and below the last.
iconPaddingDpDp.UnspecifiedThe room around an icon in its slot.
iconSizeDpDp.UnspecifiedThe size an icon in a slot should take. A slot does not size what is put in it.
borderWidthDpDp.UnspecifiedHow thick the box's border is.
focusedBorderWidthDpDp.UnspecifiedHow thick the border is while the field has the focus: the border is what shows it.
textRoleTypeRole?nullThe role of the type scale the text is typed in, and its placeholder set in: the body's, 16sp, on Android and iOS, as Material 3 and iOS type a field, and the label's, 14sp, on the desktop and the web, as a page under a pointer does. A select's box and the fields made on this style take it as well.

TextFieldColors

What PantrixTextField is painted with, for a field that does not take the theme's: a colour that is set is used, and one that is left out stays the theme's role. A product gives the colours it changes and no others:

ParameterTypeDefaultWhat it is
containerColorColor.UnspecifiedThe box's fill. The theme's surface.
borderColorColor.UnspecifiedThe box's border at rest. The theme's border.
hoveredBorderColorColor.UnspecifiedThe border under a pointer. The theme's borderHover, a notch stronger than border.
focusedBorderColorColor.UnspecifiedThe border while the field has the focus. The theme's primary.
errorColorColor.UnspecifiedWhat says the text is wrong: the border, the label, the message under the box, the caret, and the count once it is past its limit. The theme's danger.
textColorColor.UnspecifiedThe typed text. The theme's textPrimary.
labelColorColor.UnspecifiedThe label above the box. Left out, it is drawn as any text is where the field stands, since it is outside the box on whatever the field is put on: in textPrimary on the theme's own ground, and in the content colour of a fill.
placeholderColorColor.UnspecifiedThe placeholder. The theme's textSecondary.
supportingTextColorColor.UnspecifiedThe help under the box, and the count. The theme's textSecondary.
iconColorColor.UnspecifiedWhat an icon in a slot is drawn in unless it sets a tint of its own, and the clear button's cross. The theme's textSecondary.
cursorColorColor.UnspecifiedThe caret, and with it the handles and the highlight of a selection. The theme's primary.
scrollBarColorColor.UnspecifiedThe bar that shows how far the text of a box of several lines is scrolled. The theme's borderStrong: it is a mark beside the text, and the box's soft edge would not be seen there.