Date Range Picker for Slint
KitDateRangePicker · Slint Kit 0.3.0
Calendar for selecting a start date and an end date.

Try the component
Download the complete project, extract it and run cargo run --locked in its folder. Rust 1.92+; Slint Kit 0.3.0 from crates.io. You can also copy the four files below.
Navigate months and choose a real calendar day. Rust validates civil dates and rebuilds the calendar; a range is accepted only after Apply.
ui/app.slint
export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
import { KitDateRangePicker,KitThemes,CalendarDay,CalendarNavigation,CivilDateView,DateRangePhase } from "@kit/kit.slint";
export {CalendarDay,CalendarNavigation,CivilDateView,DateRangePhase}
export component App inherits Window {
title:"date-range-picker";preferred-width:480px;preferred-height:560px;max-width:100000px;max-height:100000px;background:KitThemes.light.canvas;
in property <[CalendarDay]> days;in property <int> active;in property <string> calendar-title;in property <string> accepted;
callback navigate(CalendarNavigation);callback choose(CalendarDay);callback cancel();in property <DateRangePhase> phase;in property <string> summary;in property <string> error;callback apply();
VerticalLayout {padding:24px;spacing:16px;alignment:start;
control:=KitDateRangePicker{days:root.days;weekdays:["M","T","W","T","F","S","S"];active-index:root.active;title:root.calendar-title;navigate(step)=>{root.navigate(step);}choose(day)=>{root.choose(day);}cancelled=>{root.cancel();}phase:root.phase;summary:root.summary;error:root.error;apply-requested=>{root.apply();}}
Text{text:"Accepted: "+root.accepted;color:KitThemes.light.text-primary;wrap:word-wrap;}
}
}
src/main.rs
use slint::ComponentHandle;
use slint_kit::{
CalendarController, CalendarMove, CivilDate, DateRangeController, RangeSelection, WeekStart,
};
use std::{cell::RefCell, rc::Rc};
mod ui {
slint::include_modules!();
}
struct State {
calendar: CalendarController,
accepted: String,
range: DateRangeController,
}
fn movement(step: ui::CalendarNavigation) -> CalendarMove {
match step {
ui::CalendarNavigation::PreviousDay => CalendarMove::PreviousDay,
ui::CalendarNavigation::NextDay => CalendarMove::NextDay,
ui::CalendarNavigation::PreviousWeek => CalendarMove::PreviousWeek,
ui::CalendarNavigation::NextWeek => CalendarMove::NextWeek,
ui::CalendarNavigation::WeekStart => CalendarMove::WeekStart,
ui::CalendarNavigation::WeekEnd => CalendarMove::WeekEnd,
ui::CalendarNavigation::PreviousMonth => CalendarMove::PreviousMonth,
ui::CalendarNavigation::NextMonth => CalendarMove::NextMonth,
ui::CalendarNavigation::PreviousYear => CalendarMove::PreviousYear,
ui::CalendarNavigation::NextYear => CalendarMove::NextYear,
}
}
fn publish(app: &ui::App, state: &State) {
let slots = state.calendar.grid_slots();
let days = slots
.iter()
.map(|slot| {
slot.map_or_else(ui::CalendarDay::default, |cell| ui::CalendarDay {
year: cell.date.year(),
month: cell.date.month() as i32,
day: cell.date.day() as i32,
label: cell.date.day().to_string().into(),
accessible_label: cell.date.iso_string().into(),
enabled: cell.enabled,
selected: cell.selected,
in_range: false,
today: false,
})
})
.collect::<Vec<_>>();
let mut days = days;
match state.range.draft() {
RangeSelection::ChoosingStart => {
app.set_phase(ui::DateRangePhase::ChoosingStart);
app.set_summary("Choose a start date".into());
}
RangeSelection::ChoosingEnd { start } => {
app.set_phase(ui::DateRangePhase::ChoosingEnd);
app.set_summary(format!("{} → choose an end date", start.iso_string()).into());
for day in &mut days {
day.selected = day.year == start.year()
&& day.month == start.month() as i32
&& day.day == start.day() as i32;
}
}
RangeSelection::Complete(range) => {
app.set_phase(ui::DateRangePhase::Complete);
app.set_summary(
format!(
"{} → {}",
range.start().iso_string(),
range.end().iso_string()
)
.into(),
);
for day in &mut days {
if let Ok(date) = CivilDate::new(day.year, day.month as u32, day.day as u32) {
day.selected = date == range.start() || date == range.end();
day.in_range = range.contains(date);
}
}
}
}
app.set_days(slint::ModelRc::new(slint::VecModel::from(days)));
app.set_active(
slots
.iter()
.position(|s| s.is_some_and(|cell| cell.focused))
.unwrap_or(0) as i32,
);
let month = state.calendar.displayed();
app.set_calendar_title(format!("{:04}-{:02}", month.year(), month.month()).into());
app.set_accepted(state.accepted.clone().into());
}
fn main() -> Result<(), slint::PlatformError> {
let app = ui::App::new()?;
slint_kit::init_form_validation!(ui, app);
let date = CivilDate::new(2026, 10, 17).unwrap();
let state = Rc::new(RefCell::new(State {
calendar: CalendarController::new(Some(date), date.year_month(), None, WeekStart::Monday),
accepted: "No range accepted".into(),
range: DateRangeController::new(None),
}));
publish(&app, &state.borrow());
let weak = app.as_weak();
let shared = state.clone();
app.on_navigate(move |step| {
if let Some(app) = weak.upgrade() {
let mut state = shared.borrow_mut();
if state.calendar.navigate(movement(step)).is_ok() {
publish(&app, &state);
}
}
});
let weak = app.as_weak();
let shared = state.clone();
app.on_choose(move |day| {
if !day.enabled {
return;
}
if let (Some(app), Ok(date)) = (
weak.upgrade(),
CivilDate::new(day.year, day.month as u32, day.day as u32),
) {
let mut state = shared.borrow_mut();
if state.calendar.choose(date).is_ok() {
let error = if state.range.choose(date).is_ok() {
""
} else {
"End must follow start"
};
app.set_error(error.into());
publish(&app, &state);
}
}
});
let weak = app.as_weak();
let shared = state.clone();
app.on_cancel(move || {
if let Some(app) = weak.upgrade() {
let mut state = shared.borrow_mut();
state.range.cancel();
app.set_error("".into());
publish(&app, &state);
}
});
let weak = app.as_weak();
let shared = state.clone();
app.on_apply(move || {
if let Some(app) = weak.upgrade() {
let mut state = shared.borrow_mut();
if let Some(range) = state.range.apply() {
state.accepted = format!(
"{} → {}",
range.start().iso_string(),
range.end().iso_string()
);
app.set_accepted(state.accepted.clone().into());
}
}
});
app.run()
}
Cargo.toml and build.rs
Cargo.toml
[package]
name = "slint-kit-date-range-picker-example"
version = "0.1.0"
edition = "2024"
rust-version = "1.92"
license = "GPL-3.0-or-later"
publish = false
[dependencies]
slint-kit = "=0.3.0"
slint = { version = "=1.18.1", default-features = false, features = ["std", "compat-1-18", "backend-winit", "renderer-skia", "accessibility"] }
[build-dependencies]
slint-kit-build = "=0.3.0"
[workspace]
build.rs
fn main() {
slint_kit_build::compile("ui/app.slint").expect("compile component example");
}
Files: Cargo.toml · build.rs · ui/app.slint · src/main.rs
Connect day requests to DateRangeController and publish its selection phase. Apply commits a complete range; Cancel restores it.
The example highlights both endpoints and the dates between them. The end must follow the start.
Choose between built-in options and custom composition
Shared options
error— built-in property; custom composition example
Visual composition
The file below produces the screenshot. For controller-driven components, it shows the displayed state without supplying the Rust wiring. Create a project.
Show the screenshot source

// SPDX-License-Identifier: GPL-3.0-or-later
// Display projections are fixtures. Validate accepted domain values in Rust.
import { KitDateRangePicker, KitTheme, KitThemes } from "@kit/kit.slint";
export component Example inherits Window {
title: "KitDateRangePicker example";
preferred-width: 420px;
preferred-height: 520px;
max-width: 100000px;
max-height: 100000px;
background: root.theme.canvas;
in-out property <KitTheme> theme: KitThemes.light;
in-out property <string> status;
VerticalLayout {
padding: 24px; spacing: 16px; alignment: start;
Text { text: "Date Range Picker"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
KitDateRangePicker { theme: root.theme; title: "October 2026"; days: [{ year: 2026, month: 9, day: 28, label: "28", accessible-label: "September 28, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 9, day: 29, label: "29", accessible-label: "September 29, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 9, day: 30, label: "30", accessible-label: "September 30, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 1, label: "1", accessible-label: "October 1, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 2, label: "2", accessible-label: "October 2, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 3, label: "3", accessible-label: "October 3, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 4, label: "4", accessible-label: "October 4, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 5, label: "5", accessible-label: "October 5, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 6, label: "6", accessible-label: "October 6, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 7, label: "7", accessible-label: "October 7, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 8, label: "8", accessible-label: "October 8, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 9, label: "9", accessible-label: "October 9, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 10, label: "10", accessible-label: "October 10, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 11, label: "11", accessible-label: "October 11, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 12, label: "12", accessible-label: "October 12, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 13, label: "13", accessible-label: "October 13, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 14, label: "14", accessible-label: "October 14, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 15, label: "15", accessible-label: "October 15, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 16, label: "16", accessible-label: "October 16, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 17, label: "17", accessible-label: "October 17, 2026", enabled: true, selected: true, today: true, in-range: false }, { year: 2026, month: 10, day: 18, label: "18", accessible-label: "October 18, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 19, label: "19", accessible-label: "October 19, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 20, label: "20", accessible-label: "October 20, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 21, label: "21", accessible-label: "October 21, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 22, label: "22", accessible-label: "October 22, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 23, label: "23", accessible-label: "October 23, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 24, label: "24", accessible-label: "October 24, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 25, label: "25", accessible-label: "October 25, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 26, label: "26", accessible-label: "October 26, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 27, label: "27", accessible-label: "October 27, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 28, label: "28", accessible-label: "October 28, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 29, label: "29", accessible-label: "October 29, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 30, label: "30", accessible-label: "October 30, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 10, day: 31, label: "31", accessible-label: "October 31, 2026", enabled: true, selected: false, today: false, in-range: false }, { year: 2026, month: 11, day: 1, label: "1", accessible-label: "November 1, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 11, day: 2, label: "2", accessible-label: "November 2, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 11, day: 3, label: "3", accessible-label: "November 3, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 11, day: 4, label: "4", accessible-label: "November 4, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 11, day: 5, label: "5", accessible-label: "November 5, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 11, day: 6, label: "6", accessible-label: "November 6, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 11, day: 7, label: "7", accessible-label: "November 7, 2026", enabled: false, selected: false, today: false, in-range: false }, { year: 2026, month: 11, day: 8, label: "8", accessible-label: "November 8, 2026", enabled: false, selected: false, today: false, in-range: false }]; summary: "October 17–21, 2026"; choose(day) => { root.status = "Requested day " + day.day; } }
if root.status != "": Text { text: root.status; color: root.theme.text-secondary; font-size: 12px * root.theme.text-scale; wrap: word-wrap; }
}
}
Public API
Inherits Rectangle.
Properties
| Name | Type | Direction |
|---|---|---|
theme |
KitTheme |
in |
control-size |
ControlSize |
in |
access |
FieldAccess |
in |
phase |
DateRangePhase |
in |
days |
[CalendarDay] |
in |
weekdays |
[string] |
in |
active-index |
int |
in |
title |
string |
in |
summary |
string |
in |
error |
string |
in |
Types and variants
| Type | Declaration |
|---|---|
struct CalendarDay |
year: int, month: int, day: int, label: string, accessible-label: string, enabled: bool, selected: bool, in-range: bool, today: bool, |
enum CalendarNavigation |
previous-day, next-day, previous-week, next-week, week-start, week-end, previous-month, next-month, previous-year, next-year |
enum ControlSize |
compact, medium, comfortable |
enum DateRangePhase |
choosing-start, choosing-end, complete |
enum FieldAccess |
editable, read-only, disabled |
Callbacks
navigate(CalendarNavigation)choose(CalendarDay)apply-requested()cancelled()