Slint Kit Documentation

Numbers and money

Number Stepper for Slint

KitNumberStepper · Slint Kit 0.3.0

Decimal input with increment and decrement requests.

Decimal input with increment and decrement requests.
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.

Edit the decimal value or click the step buttons. Rust applies exact steps of one within 0–100, then accepts the result.

ui/app.slint

export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
import { KitNumberStepper, KitButton, KitThemes, ValidationPresentation, StepDirection } from "@kit/kit.slint";
export component App inherits Window {
    title: "Quantity"; preferred-width: 520px; preferred-height: 320px;
    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 step(StepDirection);
    VerticalLayout {
        padding: 24px; spacing: 16px; alignment: start;
        control := KitNumberStepper {
            required: true;
            label: "Quantity"; text <=> root.draft; error: root.error;
            validation: root.error != "" ? ValidationPresentation.invalid : ValidationPresentation.pristine;
            placeholder: "12";
            edited(value) => { root.edited(value); }
            commit-requested(value) => { root.commit(); }
            cancelled => { root.cancel(); }
            focus-observed(focused) => { if (!focused) { root.blurred(); } }
            step-requested(direction) => { root.step(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::*;
mod ui {
    slint::include_modules!();
}
use ui::App;
type Value = DecimalValue;
fn parse(text: &str) -> DraftValue<Value> {
    DecimalRules {
        locale: NumberLocale::EnCa,
        scale: DecimalScale::new(2).unwrap(),
        sign: SignPolicy::NonNegative,
        bounds: None,
    }
    .parse(text)
}
fn format(value: &Value) -> String {
    value.to_string()
}
fn main() -> Result<(), slint::PlatformError> {
    let app = App::new()?;
    slint_kit::init_form_validation!(ui, app);
    let initial = DecimalValue::from_integer(12);
    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::Number, required: true,
        step: Some("1".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_step(move |direction| {
        let Some(value) = binding.accepted() else {
            return;
        };
        let next = DecimalStep::new(DecimalValue::from_integer(1))
            .unwrap()
            .apply(
                value,
                if direction == ui::StepDirection::Increment {
                    StepDirection::Increment
                } else {
                    StepDirection::Decrement
                },
                NumericBounds::new(DecimalValue::ZERO, DecimalValue::from_integer(100)).unwrap(),
            );
        if let Ok(next) = next {
            let _ = binding.edited(&next.to_string());
            let _ = binding.commit(CommitTrigger::Explicit);
        }
    });
    app.run()
}

Cargo.toml and build.rs

Cargo.toml

[package]
name = "slint-kit-number-stepper-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 step-requested to DecimalStep and NumericBounds. The example steps by 1 between 0 and 100.

Rust applies each step to the accepted DecimalValue. Text edits use the same parser and commit path as the other numeric fields.

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
Decimal input with increment and decrement requests.
Native screenshot of the same example, rendered with Skia.
// SPDX-License-Identifier: GPL-3.0-or-later
import { KitNumberStepper, KitTheme, KitThemes } from "@kit/kit.slint";

export component Example inherits Window {
    title: "KitNumberStepper example";
    preferred-width: 420px;
    preferred-height: 180px;
    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: "Number Stepper"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
        KitNumberStepper { theme: root.theme; label: "Guests"; text: "3"; step-requested(direction) => { root.status = "Step requested; validate in Rust"; }; edited(value) => { root.status = "Draft: " + value; } }
        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 KitField.

Properties

Name Type Direction
theme KitTheme in
control-size ControlSize in
access FieldAccess in
validation ValidationPresentation in
required bool in
min string in
max string in
step string in
number-format NumberInputFormat in
default-value string in
custom-validity string in-out
validity KitValidityState out
will-validate bool out
validation-message string out
label string in
description string in
error string in
placeholder string in
prefix string in
suffix string in
can-increment bool in
can-decrement bool in
increment-label string in
decrement-label string in
text string in-out
has-focus bool out

Types and variants

Type Declaration
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,
enum NumberInputFormat canonical, decimal-comma
enum StepDirection increment, decrement
enum ValidationPresentation pristine, checking, valid, invalid

Callbacks

Functions

See also