OTP Field for Slint
KitOtpField · Slint Kit 0.3.0
Native text input for a fixed-length one-time code.

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.
Enter six ASCII digits, then accept. Incomplete or invalid codes remain editable and do not replace the accepted code.
ui/app.slint
export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
import { KitOtpField, KitButton, KitThemes, ValidationPresentation } from "@kit/kit.slint";
export component App inherits Window {
title: "Six-digit code"; 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();
VerticalLayout {
padding: 24px; spacing: 16px; alignment: start;
control := KitOtpField {
required: true;
label: "Six-digit code"; text <=> root.draft; error: root.error;
validation: root.error != "" ? ValidationPresentation.invalid : ValidationPresentation.pristine;
placeholder: "123456";
edited(value) => { root.edited(value); }
commit-requested(value) => { root.commit(); }
cancelled => { root.cancel(); }
focus-observed(focused) => { if (!focused) { root.blurred(); } }
}
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 = OtpValue;
fn parse(text: &str) -> DraftValue<Value> {
OtpValue::parse(text, OtpLength::new(6).unwrap(), OtpAlphabet::AsciiDigits)
}
fn format(value: &Value) -> String {
value.as_str().into()
}
fn main() -> Result<(), slint::PlatformError> {
let app = App::new()?;
slint_kit::init_form_validation!(self, app);
let initial = match parse("123456") {
DraftValue::Parsed(value) => value,
_ => unreachable!(),
};
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::Text, required: true,
..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());
app.run()
}
Cargo.toml and build.rs
Cargo.toml
[package]
name = "slint-kit-otp-field-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
Set the expected length and parse with OtpValue. The example requires six ASCII digits.
Typing updates the draft; only a complete code replaces the accepted value. Verification with a server belongs to the application.
Choose between built-in options and custom composition
Shared options
label— built-in property; custom composition exampledescription— 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
import { KitOtpField, KitTheme, KitThemes } from "@kit/kit.slint";
export component Example inherits Window {
title: "KitOtpField 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: "Otp Field"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
KitOtpField { theme: root.theme; label: "Verification code"; text: "482913"; 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; }
}
}
Public API
Inherits KitTextField.
Available inherited properties
| Name | Type | Defined on |
|---|---|---|
theme |
KitTheme |
KitTextField |
control-size |
ControlSize |
KitTextField |
access |
FieldAccess |
KitTextField |
validation |
ValidationPresentation |
KitTextField |
purpose |
TextPurpose |
KitTextField |
input-type |
InputType |
KitTextField |
input-kind |
HtmlInputKind |
KitTextField |
validation-twelve-hour |
bool |
KitTextField |
number-format |
NumberInputFormat |
KitTextField |
required |
bool |
KitTextField |
multiple |
bool |
KitTextField |
min-length |
int |
KitTextField |
max-length |
int |
KitTextField |
pattern |
string |
KitTextField |
pattern-enabled |
bool |
KitTextField |
min |
string |
KitTextField |
max |
string |
KitTextField |
step |
string |
KitTextField |
default-value |
string |
KitTextField |
validation-timing |
ValidationTiming |
KitTextField |
custom-validity |
string |
KitTextField |
required-message |
string |
KitTextField |
email-message |
string |
KitTextField |
url-message |
string |
KitTextField |
pattern-message |
string |
KitTextField |
too-short-message |
string |
KitTextField |
too-long-message |
string |
KitTextField |
underflow-message |
string |
KitTextField |
overflow-message |
string |
KitTextField |
step-message |
string |
KitTextField |
bad-input-message |
string |
KitTextField |
validity |
KitValidityState |
KitTextField |
will-validate |
bool |
KitTextField |
validation-ready |
bool |
KitTextField |
validation-message |
string |
KitTextField |
pattern-error |
string |
KitTextField |
value |
string |
KitTextField |
dirty |
bool |
KitTextField |
touched |
bool |
KitTextField |
secret-visibility |
SecretVisibility |
KitTextField |
line-mode |
TextLineMode |
KitTextField |
resize-policy |
ResizePolicy |
KitTextField |
min-lines |
int |
KitTextField |
max-lines |
int |
KitTextField |
label |
string |
KitTextField |
accessible-name |
string |
KitTextField |
input-method-hints |
InputMethodHints |
KitTextField |
show-validation-message |
bool |
KitTextField |
validation-visible |
bool |
KitTextField |
description |
string |
KitTextField |
error |
string |
KitTextField |
placeholder |
string |
KitTextField |
text-letter-spacing |
length |
KitTextField |
text-alignment |
TextHorizontalAlignment |
KitTextField |
prefix |
string |
KitTextField |
suffix |
string |
KitTextField |
text |
string |
KitTextField |
show-character-count |
bool |
KitTextField |
character-count |
int |
KitTextField |
reveal-label |
string |
KitTextField |
hide-label |
string |
KitTextField |
clear-label |
string |
KitTextField |
character-count-label |
string |
KitTextField |
checking-label |
string |
KitTextField |
has-focus |
bool |
KitTextField |