Slint Kit Documentation

Dates

Date Range Picker for Slint

KitDateRangePicker · Slint Kit 0.3.0

Calendar for selecting a start date and an end date.

Calendar for selecting a start date and an end date.
Native app built from the example below.

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

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
Calendar for selecting a start date and an end date.
Native screenshot of the same example, rendered with Skia.
// 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; }
    }
}

Download this example

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

See also