Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Component Library (kael_ui)

Kael ships a complete, shadcn-inspired component library: kael_ui. It provides 100+ polished, accessible components so you can build rich desktop applications with Kael alone — no external component library required.

kael_ui is the continuation of adabraka-ui, now developed inside the Kael repository at crates/kael_ui.

Installation

[dependencies]
kael = "0.4"
kael_ui = "0.4"

Setup

One import gives you everything — the components plus the Kael essentials (div, px, Render, Application, …). You do not need a separate use kael::*;, and mixing the two globs is discouraged because the names collide:

use kael_ui::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Application::try_new()?.run(|cx: &mut App| {
        kael_ui::init(cx);
        install_theme(cx, Theme::dark());

        if let Err(error) = cx.open_window(WindowOptions::default(), |_, cx| {
            cx.new(|_| MyApp)
        }) {
            eprintln!("failed to open the application window: {error}");
            cx.quit();
        }
    });
    Ok(())
}

kael_ui::init(cx) registers the bundled Inter and JetBrains Mono fonts, sets up keybindings for interactive components (inputs, selects, the editor, sidebars, popovers, sheets, dialogs), and initializes the HTTP client used for remote image loading.

Using the theme

install_theme stores the active Theme in the app's global state, so the recommended way to read it is Theme::get(cx) (or the alias Theme::of(cx)), which borrows the theme out of cx with no per-render clone:

impl Render for MyApp {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let theme = Theme::of(cx);
        div()
            .bg(theme.tokens.background)
            .text_color(theme.tokens.foreground)
            .child(
                Button::new("save", "Save")
                    .variant(ButtonVariant::Default)
                    .on_click(|_, _, _| println!("Saved!")),
            )
    }
}

use_theme() is still available and returns an owned Theme; it is the legacy path (it clones the whole theme on every call and does not take a cx). Prefer Theme::get(cx) / Theme::of(cx) in new code.

Tokens follow shadcn/ui naming: background/foreground, primary, secondary, muted, accent, destructive, border, card, and so on, each with light and dark variants.

Custom themes and live switching

Eighteen presets ship in-tree (Theme::dark(), Theme::light(), Theme::tokyo_night(), Theme::catppuccin_mocha(), Theme::nord(), …), and you can brand your app with Theme::custom: start from any preset's tokens and override only what you need with struct-update syntax.

let brand = Theme::custom(ThemeTokens {
    primary: hsla(262.0 / 360.0, 0.83, 0.58, 1.0),
    primary_foreground: hsla(0.0, 0.0, 1.0, 1.0),
    radius_md: px(10.0),
    ..ThemeTokens::dark()
});
install_theme(cx, brand);

install_theme can be called again at any time — it refreshes every open window, so components re-read the new tokens immediately. Wiring a theme picker is just a button:

Button::new("theme-light", "Light").on_click(cx.listener(|_, _, _, cx| {
    install_theme(cx, Theme::light());
    cx.notify();
}))

Customizing individual components

Every component implements Kael's Styled trait, so the entire Tailwind-like styling API works directly on it — this is the className of kael_ui. User styles are applied last and override the component's defaults:

Button::new("cta", "Get started")
    .rounded(px(999.0))          // pill shape
    .px(px(28.0))                // wider padding
    .bg(rgb(0x8b5cf6))           // one-off brand color
    .shadow_lg()

Card::new()
    .content(body("Hello"))
    .w(px(360.0))
    .border_2()
    .border_color(rgb(0x10b981))

Use the theme for app-wide identity and Styled overrides for one-off adjustments. The repository-only Astryx showcase composes both approaches in one application.

What's included

ModuleComponents
componentsButton, IconButton, Input, Textarea, SearchInput, NumberInput, OtpInput, TagInput, MentionInput, HotkeyInput, Checkbox, Radio, Toggle, Switch, Slider, RangeSlider, Select, Combobox, Dropdown, DatePicker, TimePicker, Calendar, ColorPicker, Rating, FileUpload, Avatar, AvatarGroup, Progress, Spinner, Skeleton, Stepper, Pagination, Carousel, Timeline, QrCode, CopyButton, InlineEdit, code Editor with tree-sitter syntax highlighting, audio/video players, and many more
displayTable, DataTable, DataGrid, Card, Badge, Accordion, RichText, Markdown and HTML rendering (feature-gated)
navigationSidebar, Menu, AppMenu, Tabs, Breadcrumbs, Toolbar, StatusBar, Tree, FileTree, VirtualList
overlaysDialog, AlertDialog, ConfirmDialog, Sheet, BottomSheet, Popover, PopoverMenu, HoverCard, ContextMenu, Toast, Tooltip, CommandPalette
chartsLineChart, AreaChart, BarChart, PieChart, DonutChart, RadarChart, Gauge, Heatmap, Treemap, Sparkline
layoutVStack, HStack, Grid, ScrollContainer, responsive breakpoint helpers
animationsEasing presets, springs, transitions, animated presence/state, shimmer, confetti, and other motion effects

For native desktop code editors, markdown editors, log viewers, SQL consoles, and prompt builders, prefer the native Editor before embedding Monaco, CodeMirror, or a WebView textarea. Position::to_text(), Selection::to_text(), FoldRange::to_text(), EditorDiagnostic::to_text(), EditorState::to_text(), and Editor::to_text() expose language, line/content byte counts, cursor and selection geometry, modified/file-path presence, undo/redo depth, syntax and highlight readiness, search counts/options, fold state, readonly mode, diagnostic counts, and visual override coverage without logging document text, file paths, selected text, search terms, diagnostic messages, or style callback internals.

For native desktop dashboards, admin tools, file managers, and data-heavy workspaces, prefer native Table, DataTable, and DataGrid components before reaching for browser tables. ColumnDef::to_text(), DataTableState::to_text(), DataTable::to_text(), RowAction::to_text(), GridColumnDef::to_text(), DataGridState::to_text(), and DataGrid::to_text() expose column/row counts, virtual backing, cached rows, sort and selection state, editable columns, active edit buffers, search presence, edit/row-action handlers, and load-more/fetch-page wiring without logging headers, row values, ids, labels, queries, edit text, dimensions, or callback internals.

For native desktop media players, timelines, podcast tools, video review surfaces, and lightweight editors, prefer native VideoPlayer, AudioPlayer, and Waveform before embedding a browser player. VideoPlayer::to_text(), VideoPlayerState::to_text(), VideoCaptionStyle::to_text(), AudioPlayer::to_text(), AudioPlayerState::to_text(), and Waveform::to_text() expose source kind, route, player size, controls/captions/poster/source/title presence, progress and volume buckets, handler counts, and waveform sample shape without logging media URLs, file paths, titles, caption text, exact seek times, volume or rate values, waveform amplitudes, or colors.

For native desktop dialogs, sheets, custom menus, context menus, command palettes, and omniboxes, prefer the native Dialog, Sheet, BottomSheet, Menu, ContextMenu, MenuBar, and CommandPalette stacks instead of hosted browser overlays. Their to_text() helpers expose size/purpose/dismissal policy, header/content/footer presence, item/result counts, nesting, disabled state, shortcut coverage, query presence/length, selection state, and handler coverage without logging ids, labels, titles, descriptions, categories, shortcut strings, user queries, coordinates, dimensions, child contents, or callback internals.

Icons

Components render Lucide icons by name. kael_ui bundles the compact set used by its built-in components, so published-crate consumers do not need to copy framework assets into their application. An application asset source is checked first, preserving branded overrides.

The complete 1,600+ SVG catalog remains repository-only under crates/kael_ui/assets/icons for discovery and the Astryx showcase. Point the resolver at your own icon directory to replace the bundled set:

kael_ui::set_icon_base_path("assets/icons");

Feature flags

FeatureDefaultEnables
httpyesRemote image loading (Avatar, image components)
markdownnodisplay::markdown rendering
html-rendernodisplay::html rendering
audionoAudioPlayer playback via rodio
image-avif, image-exrnoOpt-in AVIF (libdav1d) and OpenEXR decoding
editor-languagesnoTree-sitter grammars for 20+ languages in the editor

Showcase

The repository keeps one comprehensive, sectioned showcase instead of a large collection of small examples:

cargo run -p kael_ui --example astryx_showcase \
  --features "markdown html-render audio media editor-languages"

The showcase is not part of the kael_ui crate package.

Template apps

Three complete starter applications live in templates/ — copy one as the skeleton of your own app:

cargo run -p dashboard-app    # analytics: sidebar, stat cards, charts, data table
cargo run -p messaging-app    # chat: conversation list, message bubbles, composer
cargo run -p workspace-app    # IDE shell: file tree, syntax-highlighted editor, status bar