Time Picker for Slint
KitTimePicker · Slint Kit 0.3.0
Local-time input with hour and minute adjustment controls.

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.
Type a 24-hour local time, or use the hour/minute buttons. Rust validates the draft before adjusting and accepting it; no time zone is inferred.
ui/app.slint
export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
import { KitTimePicker, KitButton, KitThemes, ValidationPresentation, TimeSegment, TimeAdjustment } from "@kit/kit.slint";
export { TimeSegment, TimeAdjustment }
export component App inherits Window {
title: "Local time"; preferred-width: 520px; preferred-height: 400px;
max-width: 100000px; max-height: 100000px; background: KitThemes.light.canvas;
in-out property <string> draft; in property <string> error; in property <string> accepted;
callback edited(string); callback commit(); callback blurred(); callback cancel();
callback adjust(TimeSegment,TimeAdjustment);
VerticalLayout {
padding: 24px; spacing: 16px; alignment: start;
control := KitTimePicker {
required: true;
label: "Local time"; text <=> root.draft; error: root.error;
edited(value) => { root.edited(value); }
commit-requested(value) => { root.commit(); }
cancelled => { root.cancel(); }
adjust(segment,direction)=>{root.adjust(segment,direction);}
}
KitButton { text: "Accept"; activated => { root.commit(); } }
Text { text: "Accepted: " + root.accepted; color: KitThemes.light.text-primary; wrap: word-wrap; }
}
}
src/main.rs
use slint::ComponentHandle;
use slint_kit::*;
slint::include_modules!();
type Value = LocalTime;
fn parse(text: &str) -> DraftValue<Value> {
LocalTime::parse(text, slint_kit::TimePrecision::Minute)
}
fn format(value: &Value) -> String {
value.format(
slint_kit::TimePrecision::Minute,
slint_kit::ClockFormat::TwentyFourHour,
)
}
fn main() -> Result<(), slint::PlatformError> {
let app = App::new()?;
slint_kit::init_form_validation!(self, app);
let initial = LocalTime::new(9, 30, 0).unwrap();
app.set_accepted(format(&initial).into());
let weak = app.as_weak();
let field = FieldBinding::<Value, ()>::new(
Some(initial),
Ownership::Local,
CommitPolicy::OnBlur,
AbsencePolicy::Required,
parse,
format,
|_| String::new(),
move |view| {
if let Some(app) = weak.upgrade() {
app.set_draft(view.draft.into());
app.set_error(view.error.into());
}
},
);
field.set_html_constraints(slint_kit::HtmlConstraints {
kind: slint_kit::HtmlInputKind::Time, required: true,
step: Some("60".into()),
..Default::default()
});
let weak = app.as_weak();
field.on_intent(move |intent| {
if let Some(app) = weak.upgrade() {
if let FieldIntent::Commit(value) = intent {
app.set_accepted(format(&value).into());
}
}
});
let binding = field.clone();
app.on_edited(move |text| {
let _ = binding.edited(&text);
});
let binding = field.clone();
app.on_commit(move || {
let _ = binding.commit(CommitTrigger::Submit);
});
let binding = field.clone();
app.on_blurred(move || {
let _ = binding.commit(CommitTrigger::Blur);
});
let binding = field.clone();
app.on_cancel(move || binding.cancel());
let binding = field.clone();
app.on_adjust(move |segment, direction| {
let DraftValue::Parsed(value) = parse(&binding.snapshot().draft) else {
return;
};
let delta = match segment {
TimeSegment::Hour => 3600,
TimeSegment::Minute => 60,
TimeSegment::Second => 1,
} * if direction == TimeAdjustment::Increment {
1
} else {
-1
};
let seconds = (value.hour() as i32 * 3600
+ value.minute() as i32 * 60
+ value.second() as i32
+ delta)
.rem_euclid(86400) as u32;
let next = LocalTime::new(seconds / 3600, seconds / 60 % 60, seconds % 60).unwrap();
let _ = binding.edited(&format(&next));
let _ = binding.commit(CommitTrigger::Explicit);
});
app.run()
}
Cargo.toml and build.rs
Cargo.toml
[package]
name = "slint-kit-time-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 text commits to LocalTime and adjustment requests to hour/minute arithmetic. The example uses 24-hour HH:MM.
Adjustments wrap within one day. An invalid text draft must be corrected before a step can be accepted.
Choose between built-in options and custom composition
Shared options
label— built-in property; custom composition exampleerror— 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 { KitTheme, KitThemes, KitTimePicker } from "@kit/kit.slint";
export component Example inherits Window {
title: "KitTimePicker example";
preferred-width: 620px;
preferred-height: 300px;
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: "Time Picker"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
KitTimePicker { theme: root.theme; label: "Start time"; text: "09:30"; accepted-time: { hour: 9, minute: 30, second: 0 }; }
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 |
|---|---|---|
required |
bool |
in |
min |
string |
in |
max |
string |
in |
step |
string |
in |
custom-validity |
string |
in-out |
validity |
KitValidityState |
out |
will-validate |
bool |
out |
validation-message |
string |
out |
theme |
KitTheme |
in |
control-size |
ControlSize |
in |
access |
FieldAccess |
in |
accepted-time |
LocalTimeView |
in |
clock-format |
ClockFormat |
in |
precision |
TimePrecision |
in |
label |
string |
in |
error |
string |
in |
text |
string |
in-out |
Types and variants
| Type | Declaration |
|---|---|
enum ClockFormat |
twenty-four-hour, twelve-hour |
enum ControlSize |
compact, medium, comfortable |
enum FieldAccess |
editable, read-only, disabled |
struct KitValidityState |
valid: bool, value-missing: bool, type-mismatch: bool, pattern-mismatch: bool, too-long: bool, too-short: bool, range-underflow: bool, range-overflow: bool, step-mismatch: bool, bad-input: bool, custom-error: bool, |
struct LocalTimeView |
hour: int, minute: int, second: int |
enum TimeAdjustment |
increment, decrement |
enum TimePrecision |
minute, second |
enum TimeSegment |
hour, minute, second |
Callbacks
invalid()edited(string)commit-requested(string)adjust(TimeSegment,TimeAdjustment)cancelled()
Functions
check-validity() → boolreport-validity() → boolset-custom-validity(message: string)reset-validation()take-focus()