DUI is a production-ready UI component library for Leptos 0.7 CSR applications. It ships 29 components with built-in accessibility (ARIA roles, keyboard navigation, focus management), a complete design token system via CSS custom properties, and dark/light mode support out of the box.
It powers three shipping applications and is built by Dirmacs.
Why DUI?
The Rust frontend ecosystem has frameworks (Leptos, Dioxus, Yew) but lacks practical, production-tested component libraries. Existing options are either:
- Tied to a single design system (Thaw → Fluent)
- Headless-only with no styling (Radix-Leptos)
- Early-stage or unmaintained
DUI fills the gap: styled, accessible, and built for teams that ship.
| DUI | Thaw | Leptonic | Radix-Leptos | |
|---|---|---|---|---|
| Components | 29 | ~60 | ~30 | 57 |
| Styled | Yes (Tailwind) | Yes (Fluent) | Yes (custom) | No (headless) |
| Accessible | Yes | Partial | Partial | Yes |
| Dark + Light | Yes | Yes | No | N/A |
| Production apps | 3 | ? | ? | 0 |
| Approach | Practical | Design-system | Framework | Primitives |
Installation
[]
= "0.2"
Note: The crate is published as
dui-leptoson crates.io, but the Rust import is justdui. This is intentional —use dui::prelude::*is all you need.
CSS Setup
Copy css/dui.css to your project's static assets and link it:
DUI components use Tailwind CSS utility classes. Add dui's source to your Tailwind content config so classes aren't purged:
// tailwind.config.js
module.exports =
Quick Start
use *;
use *;
Components
Form Controls
Button
Multi-variant button with loading spinner, disabled state, and focus ring.
// Variants: Primary (default), Secondary, Ghost, Danger
// Sizes: Sm, Md (default), Lg
<Button variant=Primary size=Lg
loading=loading_signal
on_click=Boxnew
>
"Save Changes"
</Button>
// Ghost button for toolbar actions
<Button variant=Ghost size=Sm>
"Cancel"
</Button>
// Danger with loading state
<Button variant=Danger loading=deleting>
"Delete Account"
</Button>
Props: variant: ButtonVariant, size: ButtonSize, loading: Signal<bool>, disabled: Signal<bool>, on_click: Option<Box<dyn Fn(MouseEvent)>>, class: &'static str, children: Children
Input
Text input with label, placeholder, error state, and validation styling.
let email = new;
abel=Some
value=email
placeholder="you@example.com"
input_type="email"
error=Some
/>
Props: value: RwSignal<String>, label: Option<&'static str>, placeholder: &'static str, input_type: &'static str, error: Option<String>, disabled: bool, class: &'static str
Textarea
Multi-line text input with character count and configurable resize.
let bio = new;
alue=bio
label=Some
placeholder="Tell us about yourself..."
rows=4
max_length=Some
resize="vertical"
/>
Props: value: RwSignal<String>, label: Option<&'static str>, placeholder: &'static str, rows: u32, max_length: Option<usize>, resize: &'static str, error: Option<String>, disabled: bool, class: &'static str
Select
Dropdown select with label and placeholder.
let tier = new;
abel=Some
value=tier
placeholder="Choose a plan"
options=vec!
/>
Props: value: RwSignal<String>, label: Option<&'static str>, placeholder: &'static str, options: Vec<(String, String)>, disabled: bool, class: &'static str
Checkbox
Custom-styled checkbox with SVG checkmark, label and optional description.
let agreed = new;
hecked=agreed
label=Some
description=Some
/>
Props: checked: RwSignal<bool>, label: Option<&'static str>, description: Option<&'static str>, disabled: bool, class: &'static str
Accessibility: role="checkbox", aria-checked, aria-describedby, Enter/Space toggle
RadioGroup
Radio button group with vertical/horizontal layout and arrow key navigation.
let plan = new;
alue=plan
name="plan"
options=vec!
orientation=Vertical
/>
Props: value: RwSignal<String>, name: &'static str, options: Vec<RadioOption>, orientation: RadioOrientation, class: &'static str
Accessibility: role="radiogroup", role="radio", aria-checked, Arrow key navigation (wrapping)
Switch
Toggle switch with three sizes and keyboard support.
let notifications = new;
hecked=notifications
label=Some
size=Md
/>
Props: checked: RwSignal<bool>, label: Option<&'static str>, size: SwitchSize, disabled: bool, class: &'static str
Accessibility: role="switch", aria-checked, Enter/Space toggle
Data Display
Card
Container panel with optional glow effect.
<Card class="p-6">
<h2>"Dashboard"</h2>
<p>"Content here"</p>
</Card>
// With accent glow
<Card class="p-6" glow=true>
<p>"Featured content"</p>
</Card>
Props: class: String (with #[prop(into)]), glow: bool, children: Children
Badge
Colored label for status, categories, or counts.
<Badge color=Green>"Active"</Badge>
<Badge color=Red>"Critical"</Badge>
<Badge color=Purple>"Beta"</Badge>
Props: color: BadgeColor (Gray/Blue/Green/Yellow/Red/Purple), class: &'static str, children: Children
Table
Sortable data table with hover rows.
<Table
headers=vec!
sort_key=sort_key
sort_dir=sort_dir
on_sort=Boxnew
>
// Table rows as children
<tr class="border-b border-dm hover:bg-dm-hover">
<td class="px-4 py-3">"Agent Alpha"</td>
<td class="px-4 py-3"><Badge color=Green>"Active"</Badge></td>
<td class="px-4 py-3">"2026-03-13"</td>
</tr>
</Table>
Props: headers: Vec<TableHeader>, sort_key: RwSignal<String>, sort_dir: RwSignal<SortDirection>, on_sort: Box<dyn Fn(String)>, class: &'static str, children: Children
Avatar
User avatar with image or deterministic initial-color fallback.
<Avatar name="John Doe" size=Lg />
<Avatar name="Jane" src=Some size=Md />
Props: name: String, src: Option<String>, size: AvatarSize (Xs/Sm/Md/Lg/Xl)
StatsCard, StatusBadge, ProgressBar, Skeleton
// Metric card
<StatsCard title="Total Agents" value="29" subtitle=Some />
// Status indicator with pulse animation
<StatusBadge status=Healthy label="ARES API" />
// Progress bar with color transitions
<ProgressBar value=progress label=Some />
// Loading placeholders
<Skeleton height="h-4" width="w-48" />
<SkeletonCard />
Navigation
Tabs
Horizontal tab bar with active indicator and count badges.
let active = new;
tems=vec!
active_tab=active
/>
Accessibility: role="tablist", role="tab", aria-selected, tabindex management
Breadcrumb
Navigation trail with chevron separators.
<Breadcrumb items=vec! />
Accessibility: <nav aria-label="Breadcrumb">, aria-current="page" on last item
Sidebar
Full-height navigation sidebar with icons, sections, and user area. See source for full prop API.
Overlays
Modal
Dialog overlay with backdrop, Escape/click-outside close, and focus trap.
let open = new;
"Open Modal"</Button>
<Modal open=open title="Confirm Delete" max_width="max-w-md">
<p class="text-dm-muted mb-4">"This action cannot be undone."</p>
<div class="flex gap-3 justify-end">
<Button variant=Ghost
on_click=Boxnew>"Cancel"</Button>
<Button variant=Danger>"Delete"</Button>
</div>
</Modal>
Key behavior: Children are rendered once — visibility toggles via CSS class, not conditional rendering. The modal closes itself by writing false to the open signal.
Accessibility: role="dialog", aria-modal="true", aria-label
Dropdown
Context menu with items, separators, labels, and danger items.
let menu_open = new;
pen=menu_open
items=vec!
on_select=Boxnew
>
<Button variant=Ghost size=Sm
on_click=Boxnew
>"Actions ▾"</Button>
</Dropdown>
Accessibility: role="menu", role="menuitem", Arrow key navigation, Enter to select, Escape to close
CommandPalette
Cmd+K style search interface with fuzzy filtering, keyboard navigation, and grouped results.
let cmd_open = new;
// Open with keyboard shortcut
new;
pen=cmd_open
items=new
on_select=new
placeholder="Type a command or search..."
/>
Accessibility: role="dialog", role="combobox" on search input, role="listbox" on results, role="option" with aria-selected, Arrow key navigation, Enter to select, Escape to close
Sheet
Slide-out panel from any edge of the screen.
let sheet_open = new;
Agent Details" width="max-w-md">
<p>"Sheet content here"</p>
</Sheet>
Props: open: RwSignal<bool>, side: SheetSide (Right/Left/Top/Bottom), title: &'static str, width: &'static str, children: Children
Tooltip
Hover popup with directional arrow.
<Tooltip text="Copy to clipboard".to_string position=Top>
<button>"📋"</button>
</Tooltip>
Accessibility: role="tooltip"
Feedback
Toast
Global notification system with 4 levels and auto-dismiss.
// Step 1: Provide at app root
provide_toast;
// ... and render the container
<ToastContainer />
// Step 2: Use anywhere via context
let toast = .unwrap;
toast.push;
toast.push;
toast.push;
toast.push;
// Custom duration (ms)
toast.push_with_duration;
Important: ToastState is Clone but NOT Copy. Never capture it in a closure — always call use_context::<ToastState>() at the point of use inside spawn_local or standalone functions.
AlertBanner
Dismissible alert banner with icon and 4 severity levels.
let show_alert = new;
evel=Warning
message="Your API key expires in 3 days".to_string
visible=show_alert
/>
Accessibility: role="alert", aria-label="Dismiss alert" on close button
EmptyState
Placeholder for empty data views with icon, title, and action slot.
<EmptyState
icon="M..." // SVG path data
title="No agents yet"
description="Create your first agent to get started."
>
<Button variant=Primary>"Create Agent"</Button>
</EmptyState>
Layout
Divider
Horizontal or vertical separator with optional label.
<Divider />
<Divider label=Some />
<Divider orientation=Vertical />
Accessibility: role="separator", aria-orientation
AccordionItem
Collapsible content section with animated chevron.
<AccordionItem title="Advanced Settings" initially_open=false>
<p>"Hidden content revealed on click"</p>
</AccordionItem>
Accessibility: aria-expanded, aria-controls
Kbd / KbdShortcut
Keyboard shortcut display styled as physical keycaps.
<Kbd>"⌘"</Kbd>
<KbdShortcut keys=vec! />
Theming
DUI uses 40+ CSS custom properties prefixed with --dm-*. Override any token to customize the entire library:
}
Brand Customization Example
/* Purple brand */
}
Light Mode
<!-- Explicit light mode -->
<!-- Explicit dark mode -->
<!-- Auto-detect from OS preference (default) -->
<!-- CSS class alternative -->
Light mode provides a complete alternate palette with proper contrast ratios for all tokens.
Accessibility
Every interactive component includes:
| Feature | Implementation |
|---|---|
| ARIA roles | dialog, alert, tablist/tab, switch, checkbox, radio, radiogroup, menu/menuitem, progressbar, separator, tooltip, combobox/listbox |
| Keyboard nav | Tab focus, Enter/Space activation, Escape to close overlays, Arrow keys for radio groups, dropdowns, and command palette |
| Focus ring | Visible dm-focus-ring class on all focusable elements |
| Screen readers | aria-label, aria-selected, aria-checked, aria-expanded, aria-current, aria-describedby, aria-invalid, aria-modal |
| Reduced motion | All animations respect prefers-reduced-motion: reduce |
Animations
Built-in CSS animation utilities (all respect prefers-reduced-motion):
| Class | Effect |
|---|---|
.dm-animate-fade-in |
Opacity 0 → 1 |
.dm-animate-fade-in-up |
Slide up + fade |
.dm-animate-fade-in-down |
Slide down + fade |
.dm-animate-slide-left |
Slide from left |
.dm-animate-slide-right |
Slide from right |
.dm-animate-scale-in |
Scale 92% → 100% + fade |
.dm-animate-glow |
Pulsing accent glow |
.dm-animate-pulse |
Opacity pulse |
.dm-animate-spin |
360° rotation (loading spinners) |
FAQ
Why "DUI"? It brings character. You'll remember the name.
Does DUI work with Leptos SSR? Not yet. DUI targets CSR (Client-Side Rendering) via Trunk. SSR support is on the roadmap.
Can I use DUI without Tailwind?
The components use Tailwind utility classes internally. You need Tailwind configured in your project for the styling to work. The design tokens (--dm-*) are pure CSS custom properties though.
How is DUI different from Thaw? Thaw implements Microsoft's Fluent Design system. DUI has its own dark-first design language and is styled with Tailwind. If you want Fluent, use Thaw. If you want a practical, customizable library that looks great out of the box, use DUI.
Can I customize individual components?
Yes. Every component accepts a class prop for additional Tailwind classes. For deeper customization, override the --dm-* CSS tokens or fork the component source.
Contributing
DUI is open source under the MIT license. Contributions are welcome.
# Clone
# Check
# Run tests
# Build docs
License
MIT — see LICENSE