Docs / Components / 01

ComposeDatePicker

A month calendar for picking one date, a date range or several dates. Swipe or use the arrows to change month, and tap the month title to jump to another year.

  • State-driven
  • Single · Range · Multiple
  • Dialog
  • minSdk 24, no desugaring

Preview

How it works

  • The state holds everything. rememberComposeDatePickerState() keeps the selection mode, the selected date(s), the displayed month and the rules for which dates can be picked. The composable only draws it, so you read results from the state (selectedDate, selectedRange, selectedDates) and can drive it from code.
  • Three selection modes. DateSelectionMode.Single picks one date, Range picks a start and an end (first tap, second tap), and Multiple toggles any number of dates, optionally capped with maxSelectionCount.
  • Rules decide what can be tapped. minDate / maxDate are hard bounds the calendar won't page past. SelectableDates rules (weekdays only, except a list, your own lambda) disable individual days, and they combine with and / or.
  • Decorate or redraw days. decoration adds dots, a small label or a tint to chosen days. dayContent replaces what's drawn inside a day, while the picker still draws the selection, today's outline and the range band.
  • Its own date types. CalendarDate and CalendarMonth replace java.time, so the picker runs on minSdk 24 without core library desugaring.

Setup

Add the library once (see Getting started), then import from com.nameisjayant.components.datepicker:

Imports
import com.nameisjayant.components.datepicker.*

Quick start

ComposeDatePicker.kt
val state = rememberComposeDatePickerState()          // Single by default

ComposeDatePicker(state = state)

state.selectedDate                                     // CalendarDate? e.g. 2026-10-22

Selection modes

ComposeDatePicker.kt
// One date
val single = rememberComposeDatePickerState(
    selectionMode = DateSelectionMode.Single,
    initialSelectedDate = CalendarDate.today(),
)
single.selectedDate                    // CalendarDate?

// A start and an end. The first tap sets the start, the second the end.
val range = rememberComposeDatePickerState(selectionMode = DateSelectionMode.Range)
range.selectedRange                    // ClosedRange<CalendarDate>?, null until both ends are picked
range.rangeStart; range.rangeEnd       // each end on its own

// Any number of dates. Tapping a selected date removes it.
val multiple = rememberComposeDatePickerState(
    selectionMode = DateSelectionMode.Multiple,
    maxSelectionCount = 5,             // optional cap
)
multiple.selectedDates                 // Set<CalendarDate>

The state can also be driven from code: select(date), setRange(start, end), clearSelection(), isSelected(date), isSelectable(date), hasSelection, and displayedMonth (read it, or set it to jump to a month).

Disabling dates

ComposeDatePicker.kt
val today = CalendarDate.today()
val state = rememberComposeDatePickerState(
    selectionMode = DateSelectionMode.Range,
    minDate = today,                                    // hard bounds: the picker won't page past them
    maxDate = today.plusMonths(6),
    selectableDates = SelectableDates.WeekdaysOnly and
        SelectableDates.except(publicHolidays),         // combine rules with `and` / `or`
    allowDisabledDatesInRange = false,                  // a range can't jump over disabled days
)

ComposeDatePicker(state, strikeThroughDisabledDays = true)
Built-in ruleSelects
SelectableDates.Allevery date (the default)
SelectableDates.WeekdaysOnly / WeekendsOnlyMonday to Friday / Saturday and Sunday
SelectableDates.notBefore(date) / notAfter(date)on or after / on or before a date
SelectableDates.except(dates)everything except a collection of dates
SelectableDates.exceptDaysOfWeek(WeekDay.Sunday, ...)everything except some days of the week
SelectableDates { date -> ... }your own rule (it's a fun interface)

state.selectableDates is mutable, so you can swap the rule at runtime, for example once booked dates have loaded.

Decorating days

Return a DayDecoration for the days that need one: up to three dots, a short label, a tinted background, or a different number color. Mix them however you like.

ComposeDatePicker.kt
ComposeDatePicker(
    state = state,
    decoration = { date ->
        when {
            date in holidays -> DayDecoration(containerColor = Color(0xFFFBE4DC), contentColor = Color(0xFFB4472B))
            date in events -> DayDecoration(dots = listOf(Color.Unspecified, Color.Red)) // Unspecified = colors.decorationColor
            else -> prices[date]?.let { DayDecoration(label = "$$it", labelColor = Color(0xFF0F8A66)) }
        }
    },
)

DayDecoration.dot()                    // shorthand for a single dot

Drawing days yourself

dayContent replaces what's drawn inside each day. The picker still draws the selection background, today's outline and the range band, and LocalContentColor is set to the color the default text would use.

ComposeDatePicker.kt
ComposeDatePicker(
    state = state,
    dayContent = { info: DayInfo ->
        Column(horizontalAlignment = Alignment.CenterHorizontally) {
            Text("${info.date.dayOfMonth}", fontWeight = if (info.isToday) FontWeight.Bold else null)
            info.decoration?.label?.let { Text(it, fontSize = 8.sp) }
        }
    },
)

DayInfo has date, isCurrentMonth, isEnabled, isSelected, isToday, isRangeStart, isRangeEnd, isInRange and decoration.

In a dialog

ComposeDatePicker.kt
if (showPicker) {
    val state = rememberComposeDatePickerState(initialSelectedDate = booking.date)
    ComposeDatePickerDialog(
        onDismissRequest = { showPicker = false },
        confirmButton = {
            TextButton(onClick = { booking = booking.copy(date = state.selectedDate); showPicker = false }) { Text("OK") }
        },
        dismissButton = { TextButton(onClick = { showPicker = false }) { Text("Cancel") } },
    ) {
        ComposeDatePicker(state)
    }
}

Look and feel

ComposeDatePicker.kt
ComposeDatePicker(
    state = state,
    colors = ComposeDatePickerDefaults.accentColors(Color(0xFF0F8A66)),  // or .colors(...) / .copy(...)
    shape = RoundedCornerShape(28.dp),
    dayShape = ComposeDatePickerDefaults.RoundedDayShape,               // CircleShape by default
    firstDayOfWeek = WeekDay.Monday,                                    // the locale's first day by default
    showAdjacentMonthDays = true,
    highlightWeekends = true,
    title = null,                                                       // hide the small label…
    headline = { Text("When are you flying?") },                        // …or replace the big summary
)

Working with dates

The picker uses its own small CalendarDate / CalendarMonth types instead of java.time, so it works on minSdk 24 without desugaring.

ComposeDatePicker.kt
val date = CalendarDate(2026, 10, 22)           // or CalendarDate.today(), CalendarDate.parse("2026-10-22")
date.plusDays(7); date.minusMonths(1); date.daysUntil(other)
date.dayOfWeek; date.isWeekend; date.calendarMonth
date.format("yMMMd")                            // "Oct 22, 2026", ordered for the user's locale

date.toEpochMillisUtc()                         // ↔ CalendarDate.ofEpochMillisUtc(millis)
date.epochDay                                   // ↔ CalendarDate.ofEpochDay(days)
CalendarDate.from(calendar)                     // from java.util.Calendar

val month = CalendarMonth(2026, 10)             // or CalendarMonth.current()
month.firstDay; month.lastDay; month.lengthOfMonth; date in month
month.format("MMMMy")                           // "October 2026"

All parameters

ComposeDatePicker

ParameterDefaultDescription
stateSelection, displayed month and rules, from rememberComposeDatePickerState
colorsComposeDatePickerDefaults.colors()Colors of every part of the picker
shapeRectangleShapeOutline of the picker; also clips its background
dayShapeCircleShapeShape of the selection, today's outline and decoration backgrounds
firstDayOfWeeklocale'sLeftmost column
showAdjacentMonthDaysfalseShow neighbouring months' days in empty cells (not tappable)
highlightTodaytrueOutline today's date
highlightWeekendsfalseColor Saturday and Sunday with weekendContentColor
strikeThroughDisabledDaysfalseStrike through disabled days as well as dimming them
yearSelectionEnabledtrueTapping the month title opens a year grid
decorationnull(CalendarDate) -> DayDecoration?
title"Select date"Small label at the top; null hides it
headlineselection summaryLarge text under the title; null hides it (hiding both also hides the divider)
dayContentnull@Composable (DayInfo) -> Unit to draw each day yourself

rememberComposeDatePickerState: selectionMode, initialSelectedDate, initialSelectedRange, initialSelectedDates, initialDisplayedMonth, minDate (1900-01-01), maxDate (2100-12-31), selectableDates, maxSelectionCount, allowDisabledDatesInRange.

ComposeDatePickerDialog: onDismissRequest, confirmButton, dismissButton, shape, containerColor (match it to the picker's containerColor), tonalElevation, properties, content.

ComposeDatePickerColors: containerColor, titleContentColor, headlineContentColor, navigationContentColor, weekdayContentColor, weekendContentColor, dayContentColor, disabledDayContentColor, adjacentMonthDayContentColor, todayContentColor, todayBorderColor, selectedDayContainerColor, selectedDayContentColor, rangeContainerColor, rangeContentColor, decorationColor, dividerColor, yearContentColor, currentYearContentColor, selectedYearContainerColor, selectedYearContentColor.

accentColors(accent, onAccent, containerColor) derives the selection, today, range and year colors from one accent.

API

The public signatures, straight from the library sources:

com.nameisjayant.components.datepicker
@Composable
public fun ComposeDatePicker(
    state: ComposeDatePickerState,
    modifier: Modifier = Modifier,
    colors: ComposeDatePickerColors = ComposeDatePickerDefaults.colors(),
    shape: Shape = RectangleShape,
    dayShape: Shape = ComposeDatePickerDefaults.DayShape,
    firstDayOfWeek: WeekDay = ComposeDatePickerDefaults.firstDayOfWeek(),
    showAdjacentMonthDays: Boolean = false,
    highlightToday: Boolean = true,
    highlightWeekends: Boolean = false,
    strikeThroughDisabledDays: Boolean = false,
    yearSelectionEnabled: Boolean = true,
    decoration: ((CalendarDate) -> DayDecoration?)? = null,
    title: (@Composable () -> Unit)? = { ComposeDatePickerDefaults.Title(state) },
    headline: (@Composable () -> Unit)? = { ComposeDatePickerDefaults.Headline(state) },
    dayContent: (@Composable (DayInfo) -> Unit)? = null,
)

@Composable
public fun rememberComposeDatePickerState(
    selectionMode: DateSelectionMode = DateSelectionMode.Single,
    initialSelectedDate: CalendarDate? = null,
    initialSelectedRange: ClosedRange<CalendarDate>? = null,
    initialSelectedDates: Set<CalendarDate> = emptySet(),
    initialDisplayedMonth: CalendarMonth? = null,
    minDate: CalendarDate = ComposeDatePickerDefaults.MinDate,
    maxDate: CalendarDate = ComposeDatePickerDefaults.MaxDate,
    selectableDates: SelectableDates = SelectableDates.All,
    maxSelectionCount: Int = Int.MAX_VALUE,
    allowDisabledDatesInRange: Boolean = false,
): ComposeDatePickerState

@Composable
public fun ComposeDatePickerDialog(
    onDismissRequest: () -> Unit,
    confirmButton: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    dismissButton: (@Composable () -> Unit)? = null,
    shape: Shape = ComposeDatePickerDefaults.DialogShape,
    containerColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
    tonalElevation: Dp = 6.dp,
    properties: DialogProperties = DialogProperties(usePlatformDefaultWidth = false),
    content: @Composable ColumnScope.() -> Unit,
)