A Compose Multiplatform date and time picker library that provides a consistent UI across Android, iOS, Desktop, and Web platforms.
π Blog: Development Journey (Korean)
![]() TimePicker Sample |
![]() DatePicker Sample |
![]() BottomSheet Sample Date and time selection in bottom sheet |
Demo: The animation above shows the complete interaction flow for date and time selection using bottom sheets.
- π Multiplatform: Android, iOS, Desktop (JVM), and Web support
- β° TimePicker: 12-hour and 24-hour format support
- π YearMonthPicker: Year and month selection component
- π± BottomSheet Ready: Works seamlessly with Material3 ModalBottomSheet
- π¨ Fully Customizable: Colors, fonts, sizes, visible item counts, and more
- π Responsive Layout: Automatically adapts to screen sizes
- π§© Compose Native: Seamless integration with Jetpack Compose and Compose Multiplatform
- Consistent UX: Same design and behavior across all platforms
- KMP Optimized: Built on kotlinx-datetime for stable cross-platform operation
- Extensible: Build custom pickers using the provided base components
dependencies {
implementation("io.github.kez-lab:compose-date-time-picker:0.4.0")
}| Platform | Status | Minimum Version |
|---|---|---|
| Android | β Stable | API 24+ |
| iOS | π§ͺ Beta | iOS 13+ |
| Desktop (JVM) | β Stable | JVM 17+ |
| Web (JS) | Modern browsers |
import androidx.compose.runtime.*
import com.kez.picker.rememberPickerState
import com.kez.picker.time.TimePicker
import com.kez.picker.util.TimeFormat
import com.kez.picker.util.currentHour
import com.kez.picker.util.currentMinute
@Composable
fun TimePickerExample() {
// Manage picker state
val hourState = rememberPickerState(currentHour)
val minuteState = rememberPickerState(currentMinute)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth().padding(16.dp)
) {
TimePicker(
hourPickerState = hourState,
minutePickerState = minuteState,
timeFormat = TimeFormat.HOUR_24
)
Spacer(modifier = Modifier.height(16.dp))
// Display selected time
Text(
text = "Selected: %02d:%02d".format(
hourState.selectedItem,
minuteState.selectedItem
),
style = MaterialTheme.typography.titleLarge
)
}
}import com.kez.picker.util.TimePeriod
@Composable
fun TimePicker12HourExample() {
// Convert current time to 12-hour format
val hour12 = if (currentHour > 12) currentHour - 12
else if (currentHour == 0) 12
else currentHour
val period = if (currentHour >= 12) TimePeriod.PM else TimePeriod.AM
val hourState = rememberPickerState(hour12)
val minuteState = rememberPickerState(currentMinute)
val periodState = rememberPickerState(period)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth().padding(16.dp)
) {
TimePicker(
hourPickerState = hourState,
minutePickerState = minuteState,
periodPickerState = periodState,
timeFormat = TimeFormat.HOUR_12 // Enable 12-hour format
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "%02d:%02d %s".format(
hourState.selectedItem,
minuteState.selectedItem,
periodState.selectedItem // AM or PM
),
style = MaterialTheme.typography.titleLarge
)
}
}import com.kez.picker.date.YearMonthPicker
import com.kez.picker.util.currentDate
@Composable
fun YearMonthPickerExample() {
// Using kotlinx-datetime (KMP standard)
val yearState = rememberPickerState(currentDate.year)
val monthState = rememberPickerState(currentDate.monthNumber)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth().padding(16.dp)
) {
YearMonthPicker(
yearPickerState = yearState,
monthPickerState = monthState
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Selected: ${yearState.selectedItem}/${monthState.selectedItem}",
style = MaterialTheme.typography.titleLarge
)
}
}import androidx.compose.material3.*
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomSheetPickerExample() {
var showBottomSheet by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState()
val hourState = rememberPickerState(currentHour)
val minuteState = rememberPickerState(currentMinute)
val scope = rememberCoroutineScope()
Button(onClick = { showBottomSheet = true }) {
Text("Select Time")
}
if (showBottomSheet) {
ModalBottomSheet(
onDismissRequest = { showBottomSheet = false },
sheetState = sheetState
) {
Column(
modifier = Modifier.fillMaxWidth().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
TimePicker(
hourPickerState = hourState,
minutePickerState = minuteState,
timeFormat = TimeFormat.HOUR_24
)
Button(
onClick = {
scope.launch {
sheetState.hide()
showBottomSheet = false
}
}
) {
Text("Confirm")
}
}
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
modifier |
Modifier |
Modifier |
Modifier to be applied to the composable |
hourPickerState |
PickerState<Int> |
rememberPickerState(currentHour) |
State for the hour picker |
minutePickerState |
PickerState<Int> |
rememberPickerState(currentMinute) |
State for the minute picker |
periodPickerState |
PickerState<TimePeriod>? |
null |
State for AM/PM picker (12-hour format only) |
timeFormat |
TimeFormat |
TimeFormat.HOUR_24 |
Time format (HOUR_12 or HOUR_24) |
startTime |
LocalDateTime |
currentDateTime |
Initial time value |
minuteItems |
List<Int> |
0..59 |
List of minutes to display |
hourItems |
List<Int> |
1..12 (12-hour) / 0..23 (24-hour) |
List of hours to display |
periodItems |
List<TimePeriod> |
[AM, PM] |
List of periods (12-hour format only) |
visibleItemsCount |
Int |
3 |
Number of items visible at once |
itemPadding |
PaddingValues |
PaddingValues(8.dp) |
Padding for each item |
textStyle |
TextStyle |
TextStyle(fontSize = 16.sp) |
Text style for unselected items |
selectedTextStyle |
TextStyle |
TextStyle(fontSize = 22.sp) |
Text style for the selected item |
dividerColor |
Color |
LocalContentColor.current |
Color of the divider line |
fadingEdgeGradient |
Brush |
(default gradient) | Gradient for fading edges |
dividerThickness |
Dp |
1.dp |
Thickness of the divider line |
pickerWidth |
Dp |
80.dp |
Width of each picker column |
| Parameter | Type | Default | Description |
|---|---|---|---|
modifier |
Modifier |
Modifier |
Modifier to be applied to the composable |
yearPickerState |
PickerState<Int> |
rememberPickerState(currentDate.year) |
State for the year picker |
monthPickerState |
PickerState<Int> |
rememberPickerState(currentDate.monthNumber) |
State for the month picker |
startLocalDate |
LocalDate |
currentDate |
Initial date value |
yearItems |
List<Int> |
1900..2100 |
List of years to display |
monthItems |
List<Int> |
1..12 |
List of months to display |
visibleItemsCount |
Int |
3 |
Number of items visible at once |
itemPadding |
PaddingValues |
PaddingValues(8.dp) |
Padding for each item |
textStyle |
TextStyle |
TextStyle(fontSize = 16.sp) |
Text style for unselected items |
selectedTextStyle |
TextStyle |
TextStyle(fontSize = 24.sp) |
Text style for the selected item |
dividerColor |
Color |
LocalContentColor.current |
Color of the divider line |
fadingEdgeGradient |
Brush |
(default gradient) | Gradient for fading edges |
dividerThickness |
Dp |
2.dp |
Thickness of the divider line |
pickerWidth |
Dp |
100.dp |
Width of each picker column |
You can extend the library by using the Picker composable directly to create custom pickers:
@Composable
fun CustomPicker() {
Picker(
state = rememberPickerState(0),
items = listOf("Option 1", "Option 2", "Option 3"),
// ... customization parameters
)
}./gradlew :sample:installDebugThe app will be installed on your connected device or emulator.
In Android Studio or IntelliJ IDEA:
- Open
sample/src/desktopMain/kotlin/com/kez/picker/sample/Main.kt - Click the run button next to the
main()function
Or run via Gradle:
./gradlew :sample:desktopRun -DmainClass=com.kez.picker.sample.MainKt --quietOpen the iosApp/ project in Xcode and run.
Build and serve the web application:
./gradlew :sample:wasmJsBrowserDevelopmentRun Contributions are welcome! Whether it's bug reports, feature requests, or code contributions, we appreciate your help.
- Fork this repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
For bugs or feature requests, please use GitHub Issues.
Compose-DateTimePicker/
βββ datetimepicker/ # Library module
β βββ src/
β βββ commonMain/ # Shared code
β β βββ kotlin/com/kez/picker/
β β βββ date/ # Date pickers (YearMonthPicker)
β β βββ time/ # Time pickers (TimePicker)
β β βββ util/ # Utilities (currentHour, currentDate, etc.)
β βββ androidMain/ # Android platform implementation
β βββ iosMain/ # iOS platform implementation
β βββ desktopMain/ # Desktop (JVM) platform implementation
β βββ jsMain/ # Web (JS) platform implementation
βββ sample/ # Sample application
βββ src/
βββ commonMain/ # Shared sample code
βββ androidMain/ # Android entry point
βββ iosMain/ # iOS entry point
βββ jvmMain/ # Desktop entry point
βββ jsMain/ # Web entry point
Copyright 2024 KEZ Lab
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Special thanks to all contributors and the Compose Multiplatform community.
If you find this library helpful, please consider giving it a βοΈ on GitHub!


