Slint Kit Documentation

Virtualized data

Content list for Slint

KitContentList · Slint Kit 0.3.1

Virtualized Slint list with titles, excerpts and item selection.

Virtualized Slint list with titles, excerpts and item selection.
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.

Select an article with the pointer, or move with the arrow keys and press Enter. Rust displays the accepted article title.

ui/app.slint

export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
// SPDX-License-Identifier: GPL-3.0-or-later
import { KitContentList, KitThemes, ContentItem, NavigationStep } from "@kit/kit.slint";
export { ContentItem, NavigationStep }
export component App inherits Window {
    title: "Slint content list";
    preferred-width: 580px; preferred-height: 390px;
    max-width: 100000px; max-height: 100000px;
    background: KitThemes.light.canvas;
    in property <[ContentItem]> items;
    in property <int> active-index: -1;
    in property <string> selected-label: "Choose an article";
    callback choose(int, int);
    callback move(NavigationStep);
    VerticalLayout {
        padding: 24px; spacing: 16px;
        list := KitContentList { height: 280px; items: root.items; generation: 1;
            active-index: root.active-index;
            activate(token, generation) => { root.choose(token, generation); }
            navigate(step) => { root.move(step); }
        }
        Text { text: root.selected-label; color: KitThemes.light.text-primary; }
    }
}

src/main.rs

// SPDX-License-Identifier: GPL-3.0-or-later
use slint::{ComponentHandle, ModelRc, VecModel};
use std::{cell::RefCell, rc::Rc};
mod ui {
    slint::include_modules!();
}

fn next_index(current: Option<usize>, length: usize, step: ui::NavigationStep) -> usize {
    use ui::NavigationStep::*;
    let current = current.unwrap_or(0);
    match step {
        First => 0,
        Last => length.saturating_sub(1),
        Previous | PagePrevious => current.saturating_sub(1),
        Next | PageNext => (current + 1).min(length.saturating_sub(1)),
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum Article {
    Setup,
    State,
}
impl Article {
    fn token(self) -> i32 {
        match self {
            Self::Setup => 11,
            Self::State => 23,
        }
    }
    fn title(self) -> &'static str {
        match self {
            Self::Setup => "Install Slint Kit",
            Self::State => "Keep selection stable",
        }
    }
    fn excerpt(self) -> &'static str {
        match self {
            Self::Setup => "Import a component and connect its Rust callback.",
            Self::State => "Use an application key when the display order changes.",
        }
    }
}
const ARTICLES: [Article; 2] = [Article::Setup, Article::State];
#[derive(Default)]
struct State {
    selected: Option<Article>,
    active: Option<Article>,
}
fn publish(state: &RefCell<State>, app: &ui::App) {
    let state = state.borrow();
    let items = ARTICLES
        .iter()
        .map(|article| ui::ContentItem {
            token: article.token(),
            title: article.title().into(),
            excerpt: article.excerpt().into(),
            metadata: "Guide".into(),
            link_label: "Select article".into(),
            thumbnail: slint::Image::default(),
            selected: state.selected == Some(*article),
            disabled: false,
        })
        .collect::<Vec<_>>();
    let index = ARTICLES
        .iter()
        .position(|a| Some(*a) == state.active)
        .map_or(-1, |i| i as i32);
    let label = state.selected.map_or("Choose an article".to_owned(), |a| {
        format!("Selected: {}", a.title())
    });
    drop(state);
    app.set_items(ModelRc::new(VecModel::from(items)));
    app.set_active_index(index);
    app.set_selected_label(label.into());
}
fn main() -> Result<(), slint::PlatformError> {
    let app = ui::App::new()?;
    slint_kit::init_form_validation!(ui, app);
    let state = Rc::new(RefCell::new(State::default()));
    publish(&state, &app);
    let (model, weak) = (state.clone(), app.as_weak());
    app.on_choose(move |token, generation| {
        if generation != 1 {
            return;
        }
        let Some(article) = ARTICLES.iter().copied().find(|a| a.token() == token) else {
            return;
        };
        {
            let mut state = model.borrow_mut();
            state.selected = Some(article);
            state.active = Some(article);
        }
        if let Some(app) = weak.upgrade() {
            publish(&model, &app);
        }
    });
    let weak = app.as_weak();
    app.on_move(move |step| {
        {
            let mut state = state.borrow_mut();
            let current = ARTICLES.iter().position(|a| Some(*a) == state.active);
            state.active = Some(ARTICLES[next_index(current, ARTICLES.len(), step)]);
        }
        if let Some(app) = weak.upgrade() {
            publish(&state, &app);
        }
    });
    app.run()
}

Cargo.toml and build.rs

Cargo.toml

[package]
name = "slint-kit-content-list-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

Supply items, generation and active-index. Handle activate(token, generation) in Rust using the application key; handle navigate to move focus. Give the list a bounded height.

Each ContentItem supplies its title, description, metadata and optional thumbnail. Rows render within the viewport. Set empty-label for an empty model.

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
Virtualized Slint list with titles, excerpts and item selection.
Native screenshot of the same example, rendered with Skia.
// SPDX-License-Identifier: GPL-3.0-or-later
import { KitContentList, KitTheme, KitThemes } from "@kit/kit.slint";

export component Example inherits Window {
    title: "KitContentList example";
    preferred-width: 620px;
    preferred-height: 410px;
    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: "Content List"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
        KitContentList { theme: root.theme; height: 260px; generation: 1; items: [{ token: 101, title: "A guide to native design", excerpt: "Build interfaces that feel at home on the desktop.", metadata: "Design · 5 min read", link-label: "Read article", selected: false, disabled: false }, { token: 205, title: "Keep domain values exact", excerpt: "Use validated values and stable application identity.", metadata: "Engineering · 8 min read", link-label: "Read article", selected: false, disabled: false }]; }
        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
items [ContentItem] in
generation int in
active-index int in
label string in
empty-label string in
scroll-y length out

Types and variants

Type Declaration
struct ContentItem token: int, title: string, excerpt: string, metadata: string, link-label: string, thumbnail: image, selected: bool, disabled: bool
enum NavigationStep next, previous, first, last, page-next, page-previous

Callbacks

Functions

See also