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
| Module | Components |
|---|---|
components | Button, 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 |
display | Table, DataTable, DataGrid, Card, Badge, Accordion, RichText, Markdown and HTML rendering (feature-gated) |
navigation | Sidebar, Menu, AppMenu, Tabs, Breadcrumbs, Toolbar, StatusBar, Tree, FileTree, VirtualList |
overlays | Dialog, AlertDialog, ConfirmDialog, Sheet, BottomSheet, Popover, PopoverMenu, HoverCard, ContextMenu, Toast, Tooltip, CommandPalette |
charts | LineChart, AreaChart, BarChart, PieChart, DonutChart, RadarChart, Gauge, Heatmap, Treemap, Sparkline |
layout | VStack, HStack, Grid, ScrollContainer, responsive breakpoint helpers |
animations | Easing 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
| Feature | Default | Enables |
|---|---|---|
http | yes | Remote image loading (Avatar, image components) |
markdown | no | display::markdown rendering |
html-render | no | display::html rendering |
audio | no | AudioPlayer playback via rodio |
image-avif, image-exr | no | Opt-in AVIF (libdav1d) and OpenEXR decoding |
editor-languages | no | Tree-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