Calendar

A standalone calendar component that provides a rich interface for date selection and navigation. The Calendar component supports single date selection, date range selection, multiple month views, custom disabled dates, and comprehensive keyboard navigation.

Install

#Import

use gpui_kit::component::{
    calendar::{Calendar, CalendarState, CalendarEvent, Date, Matcher},
};
// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Calendar } from "calendar.slint";
use iced::widget::{button, column, container, mouse_area, row, scrollable, svg, text};
use iced::{Alignment, Element, Length, Task, Theme};

#Usage

#Calendar

let state = cx.new(|cx| CalendarState::new(window, cx));
Calendar::new(&state)
Calendar {
    year: 2026;
    month: 9;
    selected-day: 26;
}
// One month grid with the 26th picked: the component page's first documented
// sample, and the Slint example's first `GroupBox`.
let single = body_box(
    Calendar::new(app.year, app.month)
        .selected(Some(app.single))
        .block_weekends(app.block_weekends)
        .on_select(Message::Pick)
        .on_shift(Message::Shift),
);

#Two columns

use chrono::Local;

let state = cx.new(|cx| {
    let mut state = CalendarState::new(window, cx);
    state.set_date(Local::now().naive_local().date(), window, cx);
    state
});

Calendar::new(&state)
Calendar {
    year: 2026;
    month: 9;
    selected-day: 26;
    columns: 2;
}
// The same month twice, side by side: the Slint example's `columns: 2`.
let two = body_box(
    Calendar::new(app.year, app.month)
        .selected(Some(app.single))
        .columns(2)
        .block_weekends(app.block_weekends)
        .on_select(Message::Pick)
        .on_shift(Message::Shift),
);

#Three columns

use chrono::{Local, Days};

let state = cx.new(|cx| {
    let mut state = CalendarState::new(window, cx);
    let now = Local::now().naive_local().date();
    state.set_date(
        Date::Range(Some(now), now.checked_add_days(Days::new(7))),
        window,
        cx
    );
    state
});

Calendar::new(&state)
Calendar {
    year: 2026;
    month: 9;
    selected-day: 26;
    columns: 3;
}
// The widest the component draws: three months, the Slint example's
// `columns: 3`.
let three = body_box(
    Calendar::new(app.year, app.month)
        .selected(Some(app.single))
        .columns(3)
        .block_weekends(app.block_weekends)
        .on_select(Message::Pick)
        .on_shift(Message::Shift),
);

#Multiple Months Display

// Show 2 months side by side
Calendar::new(&state)
    .number_of_months(2)

// Show 3 months
Calendar::new(&state)
    .number_of_months(3)
SlintNo Slint version of this example yet.

#Calendar Sizes

Calendar::new(&state).large()
Calendar::new(&state) // medium (default)
Calendar::new(&state).small()
SlintNo Slint version of this example yet.

#Event Planning Calendar

let event_calendar = cx.new(|cx| {
    let mut state = CalendarState::new(window, cx);
    // Disable past dates and weekends
    state = state.disabled_matcher(Matcher::custom(|date| {
        let now = Local::now().naive_local().date();
        *date < now || matches!(date.weekday(), Weekday::Sat | Weekday::Sun)
    }));
    state
});

Calendar::new(&event_calendar)
    .large() // Easier to see and interact with
SlintNo Slint version of this example yet.

#Vacation Booking Calendar

let vacation_calendar = cx.new(|cx| {
    let mut state = CalendarState::new(window, cx);
    state.set_date(Date::Range(None, None), window, cx); // Range mode
    state
});

Calendar::new(&vacation_calendar)
    .number_of_months(2) // Show 2 months for range selection
SlintNo Slint version of this example yet.

#Report Date Range Selector

let report_calendar = cx.new(|cx| {
    let mut state = CalendarState::new(window, cx)
        .year_range((2020, 2025)); // Limit to business years

    state.set_date(Date::Range(None, None), window, cx);
    state
});

Calendar::new(&report_calendar)
    .number_of_months(3)
    .small() // Compact for dashboard use
SlintNo Slint version of this example yet.

#Availability Calendar

use std::collections::HashSet;

let unavailable_dates: HashSet<NaiveDate> = get_unavailable_dates();

let availability_calendar = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(Matcher::custom(move |date| {
            unavailable_dates.contains(date)
        }))
});

Calendar::new(&availability_calendar)
    .number_of_months(2)
SlintNo Slint version of this example yet.

The Calendar component provides a foundation for any date-related UI requirements, from simple date pickers to complex scheduling interfaces.

#Date Restrictions

#Disabled Weekends

let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(vec![0, 6]) // Sunday=0, Saturday=6
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Disabled Specific Weekdays

// Disable Sundays, Wednesdays, and Saturdays
let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(vec![0, 3, 6])
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Disabled Date Range

use chrono::{Local, Days};

let now = Local::now().naive_local().date();

let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(Matcher::range(
            Some(now),
            now.checked_add_days(Days::new(7)),
        ))
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Disabled Date Interval

// Disable dates outside the interval (before/after specified dates)
let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(Matcher::interval(
            Some(now.checked_sub_days(Days::new(30)).unwrap()),
            now.checked_add_days(Days::new(30))
        ))
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Custom Disabled Dates

// Disable first 5 days of each month
let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(Matcher::custom(|date| {
            date.day0() < 5 // day0() returns 0-based day
        }))
});

Calendar::new(&state)

// Disable all Mondays
let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(Matcher::custom(|date| {
            date.weekday() == chrono::Weekday::Mon
        }))
});

Calendar::new(&state)

// Disable past dates
let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(Matcher::custom(|date| {
            *date < Local::now().naive_local().date()
        }))
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Month/Year Navigation

The Calendar automatically provides navigation controls:

  • Previous/Next Month: Arrow buttons in the header
  • Month Selection: Click on month name to open month picker
  • Year Selection: Click on year to open year picker
  • Year Pages: Navigate through 20-year pages in year view

#Custom Year Range

let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .year_range((2020, 2030)) // Limit to specific year range
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Handle Selection Events

let state = cx.new(|cx| CalendarState::new(window, cx));

cx.subscribe(&state, |view, _, event, _| {
    match event {
        CalendarEvent::Selected(date) => {
            match date {
                Date::Single(Some(selected_date)) => {
                    println!("Date selected: {}", selected_date);
                }
                Date::Range(Some(start), Some(end)) => {
                    println!("Range selected: {} to {}", start, end);
                }
                Date::Range(Some(start), None) => {
                    println!("Range start: {}", start);
                }
                _ => {
                    println!("Selection cleared");
                }
            }
        }
    }
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Advanced Examples

#Business Days Only Calendar

use chrono::Weekday;

let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(Matcher::custom(|date| {
            matches!(date.weekday(), Weekday::Sat | Weekday::Sun)
        }))
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Holiday Calendar

use chrono::NaiveDate;
use std::collections::HashSet;

// Define holidays
let holidays: HashSet<NaiveDate> = [
    NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(), // New Year
    NaiveDate::from_ymd_opt(2024, 7, 4).unwrap(), // Independence Day
    NaiveDate::from_ymd_opt(2024, 12, 25).unwrap(), // Christmas
].into_iter().collect();

let state = cx.new(|cx| {
    CalendarState::new(window, cx)
        .disabled_matcher(Matcher::custom(move |date| {
            holidays.contains(date)
        }))
});

Calendar::new(&state)
SlintNo Slint version of this example yet.

#Multi-Month Range Selector

let state = cx.new(|cx| {
    let mut state = CalendarState::new(window, cx);
    state.set_date(Date::Range(None, None), window, cx); // Range mode
    state
});

Calendar::new(&state)
    .number_of_months(3) // Show 3 months for easier range selection
SlintNo Slint version of this example yet.

#Quarterly View Calendar

let state = cx.new(|cx| CalendarState::new(window, cx));

// Update to show current quarter's months
Calendar::new(&state)
    .number_of_months(3)
SlintNo Slint version of this example yet.

#Custom Styling

use gpui_kit::{px, relative};

Calendar::new(&calendar)
    .p_4() // Custom padding
    .bg(cx.theme().secondary) // Custom background
    .border_2() // Custom border
    .border_color(cx.theme().primary) // Custom border color
    .rounded(px(12.)) // Custom border radius
    .w(px(400.)) // Custom width
    .h(px(350.)) // Custom height
SlintNo Slint version of this example yet.

#API Reference

#Calendar

A month grid. Mirrors GPUI Component's `Calendar`. `columns` is 1 for a single month, or 2–3 for multiple months side by side with spacing between them. The grid stays at the width of its day cells.

NameKindTypeDefault
yearin-outint2026
monthin-outint9
selected-dayin-outint26
columnsinint1
range-frominint
range-toinint
block-weekendsinbool
changedcallback(year: int, month: int, day: int)