
Composable calendar component with daily, weekly, monthly and yearly views, fully state-hoisted with configurable colors and behavior, plus an animated, selectable, scrollable bar chart.
A Kotlin Multiplatform calendar component for Compose Multiplatform, with four switchable views (daily, weekly, monthly, yearly) and a generic animated bar chart.
| Target | Supported | Notes |
|---|---|---|
| Android | Yes |
minSdk 24, compileSdk 36
|
| JVM / Desktop | Yes | Compose for Desktop |
| iOS | No | Not currently configured |
All UI lives in commonMain, so adding further Compose Multiplatform targets requires no
changes to the component code.
// build.gradle.kts
dependencies {
implementation("io.github.gauravvvvvvvvvv:kmp-calendar:1.0.1")
}For a Kotlin Multiplatform module, add it to commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.gauravvvvvvvvvv:kmp-calendar:1.0.1")
}
}
}kotlinx-datetime is exposed as an api dependency, so LocalDate and DayOfWeek are
available to consumers without declaring it separately.
Built against Kotlin 2.0.21 and Compose Multiplatform 1.6.11.
import androidx.compose.runtime.*
import com.gaurav.calendar.*
import kotlinx.datetime.*
@Composable
fun CalendarScreen() {
var viewType by remember { mutableStateOf(CalendarViewType.DAILY) }
var selectedDate by remember {
mutableStateOf(
Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
)
}
var currentMonth by remember { mutableStateOf(selectedDate) }
var currentYear by remember { mutableStateOf(selectedDate.year) }
KMPCalendar(
viewType = viewType,
selectedDate = selectedDate,
currentMonth = currentMonth,
currentYear = currentYear,
onDateSelected = { selectedDate = it },
onMonthChanged = { currentMonth = it },
onYearChanged = { currentYear = it },
onViewTypeChanged = { viewType = it }
)
}KMPCalendar is fully state-hoisted — it holds no internal date state. You own
selectedDate, currentMonth, currentYear and viewType, which makes the component
straightforward to drive from a ViewModel or restore from saved state.
CalendarViewType selects which view renders. Each view has its own header control that can
switch view type via onViewTypeChanged.
| View | What it shows | Which callback fires |
|---|---|---|
DAILY |
Full month grid, single day selection | onDateSelected |
WEEKLY |
Month grid with the selected 7-day range highlighted | onDateSelected |
MONTHLY |
12-month grid for picking a month |
onDateSelected with day 1 of the chosen month |
YEARLY |
Multi-year grid for picking a year | onYearChanged |
Note that in MONTHLY view a selection is reported through onDateSelected as the first day
of the chosen month, not as a separate month callback.
KMPCalendar(
// ...
config = CalendarConfig(
weekStartDay = DayOfWeek.MONDAY,
yearRange = 2024..2030,
showTodayIndicator = true,
enablePastDates = true,
enableFutureDates = true
)
)| Property | Type | Default | Purpose |
|---|---|---|---|
weekStartDay |
DayOfWeek |
SUNDAY |
First column of the week |
yearRange |
IntRange |
2020..2035 |
Years offered in the yearly view |
colors |
CalendarColors |
CalendarColors() |
Colour overrides |
showTodayIndicator |
Boolean |
true |
Highlight today's date |
enablePastDates |
Boolean |
true |
Allow selecting dates before today |
enableFutureDates |
Boolean |
false |
Allow selecting dates after today |
enableFutureDates defaults to false, so out of the box the calendar behaves as a
date-of-record picker. Set it to true for scheduling use cases.
CalendarColors defaults every field to Color.Unspecified, which means "use the ambient
MaterialTheme". Override only what you need:
import androidx.compose.ui.graphics.Color
CalendarConfig(
colors = CalendarColors(
selectedColor = Color(0xFF3B82F6),
todayColor = Color(0xFF10B981),
weekRangeColor = Color(0xFF3B82F6).copy(alpha = 0.2f),
disabledColor = Color.Gray
)
)| Field | Applies to |
|---|---|
selectedColor |
Background of the selected date |
todayColor |
Background of today, when not selected |
weekRangeColor |
The highlighted 7-day range in WEEKLY view |
disabledColor |
Dates excluded by enablePastDates / enableFutureDates
|
Because the fallback is the Material theme rather than hardcoded values, the calendar picks up light/dark theming automatically if you pass no colours at all.
The library also ships KMPBarChart, a generic animated bar chart. It is independent of the
calendar and can be used on its own — the type parameter lets you attach any domain object to a
bar and get it back in the click callback.
import com.gaurav.calendar.charts.*
data class Sale(val id: Int, val region: String)
@Composable
fun SalesChart() {
val bars = listOf(
BarData(data = Sale(1, "North"), percentage = 82, label = "Mon"),
BarData(data = Sale(2, "South"), percentage = 45, label = "Tue"),
BarData(data = Sale(3, "East"), percentage = 18, label = "Wed"),
BarData(data = Sale(4, "West"), percentage = 67, label = "Thu")
)
KMPBarChart(
data = bars,
config = BarChartConfig(animationDuration = 700),
initialSelectedLabel = "Mon",
onBarClick = { bar -> println("tapped ${bar.data?.region}") }
)
}BarData<T> takes data: T? (optional payload), percentage: Int (0–100, drives bar height)
and label: String.
Bars are colour-coded by magnitude via the public getBarColor(percentage) function:
| Percentage | Colour |
|---|---|
| 0–25 | Red |
| 26–50 | Orange |
| 51–75 | Green |
| 76–100 | Blue |
BarChartConfig options:
| Property | Type | Default |
|---|---|---|
showValues |
Boolean |
true |
animateHeight |
Boolean |
true |
barWidth |
Dp |
24.dp |
barSlotWidth |
Dp |
64.dp |
chartHeight |
Dp |
192.dp |
animationDuration |
Int |
500 |
When there are more bars than fit the width, the chart scrolls horizontally rather than compressing the bars.
Package com.gaurav.calendar:
KMPCalendar — the unified entry pointCalendarViewType — DAILY, WEEKLY, MONTHLY, YEARLY
CalendarConfig, CalendarColors
DailyCalendarView, WeeklyCalendarView, MonthlyCalendarView, YearlyCalendarView —
the individual views, usable directly if you don't want the unified wrapperCalendarViewHeaderPackage com.gaurav.calendar.charts:
KMPBarChart, BarData, BarChartConfig, getBarColor
The project builds with JDK 17–21. Newer JDKs are not parseable by Gradle 8.13.
./gradlew build
./gradlew :library:publishToMavenLocalApache License 2.0 — see LICENSE.
A Kotlin Multiplatform calendar component for Compose Multiplatform, with four switchable views (daily, weekly, monthly, yearly) and a generic animated bar chart.
| Target | Supported | Notes |
|---|---|---|
| Android | Yes |
minSdk 24, compileSdk 36
|
| JVM / Desktop | Yes | Compose for Desktop |
| iOS | No | Not currently configured |
All UI lives in commonMain, so adding further Compose Multiplatform targets requires no
changes to the component code.
// build.gradle.kts
dependencies {
implementation("io.github.gauravvvvvvvvvv:kmp-calendar:1.0.1")
}For a Kotlin Multiplatform module, add it to commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.gauravvvvvvvvvv:kmp-calendar:1.0.1")
}
}
}kotlinx-datetime is exposed as an api dependency, so LocalDate and DayOfWeek are
available to consumers without declaring it separately.
Built against Kotlin 2.0.21 and Compose Multiplatform 1.6.11.
import androidx.compose.runtime.*
import com.gaurav.calendar.*
import kotlinx.datetime.*
@Composable
fun CalendarScreen() {
var viewType by remember { mutableStateOf(CalendarViewType.DAILY) }
var selectedDate by remember {
mutableStateOf(
Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
)
}
var currentMonth by remember { mutableStateOf(selectedDate) }
var currentYear by remember { mutableStateOf(selectedDate.year) }
KMPCalendar(
viewType = viewType,
selectedDate = selectedDate,
currentMonth = currentMonth,
currentYear = currentYear,
onDateSelected = { selectedDate = it },
onMonthChanged = { currentMonth = it },
onYearChanged = { currentYear = it },
onViewTypeChanged = { viewType = it }
)
}KMPCalendar is fully state-hoisted — it holds no internal date state. You own
selectedDate, currentMonth, currentYear and viewType, which makes the component
straightforward to drive from a ViewModel or restore from saved state.
CalendarViewType selects which view renders. Each view has its own header control that can
switch view type via onViewTypeChanged.
| View | What it shows | Which callback fires |
|---|---|---|
DAILY |
Full month grid, single day selection | onDateSelected |
WEEKLY |
Month grid with the selected 7-day range highlighted | onDateSelected |
MONTHLY |
12-month grid for picking a month |
onDateSelected with day 1 of the chosen month |
YEARLY |
Multi-year grid for picking a year | onYearChanged |
Note that in MONTHLY view a selection is reported through onDateSelected as the first day
of the chosen month, not as a separate month callback.
KMPCalendar(
// ...
config = CalendarConfig(
weekStartDay = DayOfWeek.MONDAY,
yearRange = 2024..2030,
showTodayIndicator = true,
enablePastDates = true,
enableFutureDates = true
)
)| Property | Type | Default | Purpose |
|---|---|---|---|
weekStartDay |
DayOfWeek |
SUNDAY |
First column of the week |
yearRange |
IntRange |
2020..2035 |
Years offered in the yearly view |
colors |
CalendarColors |
CalendarColors() |
Colour overrides |
showTodayIndicator |
Boolean |
true |
Highlight today's date |
enablePastDates |
Boolean |
true |
Allow selecting dates before today |
enableFutureDates |
Boolean |
false |
Allow selecting dates after today |
enableFutureDates defaults to false, so out of the box the calendar behaves as a
date-of-record picker. Set it to true for scheduling use cases.
CalendarColors defaults every field to Color.Unspecified, which means "use the ambient
MaterialTheme". Override only what you need:
import androidx.compose.ui.graphics.Color
CalendarConfig(
colors = CalendarColors(
selectedColor = Color(0xFF3B82F6),
todayColor = Color(0xFF10B981),
weekRangeColor = Color(0xFF3B82F6).copy(alpha = 0.2f),
disabledColor = Color.Gray
)
)| Field | Applies to |
|---|---|
selectedColor |
Background of the selected date |
todayColor |
Background of today, when not selected |
weekRangeColor |
The highlighted 7-day range in WEEKLY view |
disabledColor |
Dates excluded by enablePastDates / enableFutureDates
|
Because the fallback is the Material theme rather than hardcoded values, the calendar picks up light/dark theming automatically if you pass no colours at all.
The library also ships KMPBarChart, a generic animated bar chart. It is independent of the
calendar and can be used on its own — the type parameter lets you attach any domain object to a
bar and get it back in the click callback.
import com.gaurav.calendar.charts.*
data class Sale(val id: Int, val region: String)
@Composable
fun SalesChart() {
val bars = listOf(
BarData(data = Sale(1, "North"), percentage = 82, label = "Mon"),
BarData(data = Sale(2, "South"), percentage = 45, label = "Tue"),
BarData(data = Sale(3, "East"), percentage = 18, label = "Wed"),
BarData(data = Sale(4, "West"), percentage = 67, label = "Thu")
)
KMPBarChart(
data = bars,
config = BarChartConfig(animationDuration = 700),
initialSelectedLabel = "Mon",
onBarClick = { bar -> println("tapped ${bar.data?.region}") }
)
}BarData<T> takes data: T? (optional payload), percentage: Int (0–100, drives bar height)
and label: String.
Bars are colour-coded by magnitude via the public getBarColor(percentage) function:
| Percentage | Colour |
|---|---|
| 0–25 | Red |
| 26–50 | Orange |
| 51–75 | Green |
| 76–100 | Blue |
BarChartConfig options:
| Property | Type | Default |
|---|---|---|
showValues |
Boolean |
true |
animateHeight |
Boolean |
true |
barWidth |
Dp |
24.dp |
barSlotWidth |
Dp |
64.dp |
chartHeight |
Dp |
192.dp |
animationDuration |
Int |
500 |
When there are more bars than fit the width, the chart scrolls horizontally rather than compressing the bars.
Package com.gaurav.calendar:
KMPCalendar — the unified entry pointCalendarViewType — DAILY, WEEKLY, MONTHLY, YEARLY
CalendarConfig, CalendarColors
DailyCalendarView, WeeklyCalendarView, MonthlyCalendarView, YearlyCalendarView —
the individual views, usable directly if you don't want the unified wrapperCalendarViewHeaderPackage com.gaurav.calendar.charts:
KMPBarChart, BarData, BarChartConfig, getBarColor
The project builds with JDK 17–21. Newer JDKs are not parseable by Gradle 8.13.
./gradlew build
./gradlew :library:publishToMavenLocalApache License 2.0 — see LICENSE.