Dioxus 0.7 component library. Atomic design, pure Tailwind, 26 themes, 45 components.
eq_ui_0.1.1_showcase-preview.mp4
| Component | Category | ARIA | Description |
|---|---|---|---|
| EqText | Atom | native | Semantic text with variant-based tag selection (h1-h3, body, caption, muted) |
| EqLabel | Atom | native | Form label with for_id binding |
| EqLink | Atom | native | Anchor link with color theming |
| EqInput | Atom | native | Input/textarea with kind variants (text, email, password, number, textarea) |
| EqIcon | Atom | full | Icon wrapper with size variants |
| EqImage | Atom | full | Image with sizing, aspect ratio, and object-fit control |
| EqCheckbox | Atom | full | Checkbox with checked/unchecked/indeterminate states |
| EqButton | Atom | native | Button with 5 variants, 3 sizes, gradient transitions |
| EqDivider | Atom | full | Separator with solid/dashed/dotted/spacer variants |
| EqScrollableSpace | Atom | full | Scrollable container with themed scrollbar |
| EqVideo | Atom | full | Video with poster overlay, autoplay, controls |
| EqProgress | Atom | full | Progress bar with determinate/indeterminate, 4 variants, gradient fill |
| EqTab | Atom | full | Tab bar with underline, pill, and card variants; badges; disabled state |
| EqRadioGroup | Atom | full | Radio button group with mutually exclusive selection, three sizes, vertical/horizontal layout |
| EqSwitch | Atom | full | Toggle switch with pill track and sliding thumb, three sizes |
| EqSlider | Atom | native | Range slider with accent-color theming, three sizes, optional value label |
| EqAvatar | Atom | full | User avatar with image/initials/icon fallback, four sizes, status dot |
| EqTooltip | Atom | full | Hover/focus tooltip, four positions, pure CSS, keyboard accessible |
| EqSelect | Atom | full | Dropdown select with search, keyboard nav, WAI-ARIA combobox |
| EqCard | Molecule | full | Card with header/body/footer slots |
| EqImageCard | Molecule | full | Image card with caption modes (below/overlay) |
| EqCarousel | Molecule | full | Generic content carousel with arrows and dots |
| EqTree | Molecule | full | Collapsible tree view with select and expand |
| EqAccordion | Molecule | full | Collapsible panels with single/multi-expand modes |
| EqNavItem | Molecule | full | Navigation item with icon, label, active state, size variants |
| EqCta | Molecule | full | Call-to-action with title, description, action slot, inline/centered layout |
| EqModal | Molecule | full | Modal dialog with backdrop, five sizes, close-on-Escape, focus trap |
| EqToastList | Molecule | full | Toast notification stack, four severity levels, auto-dismiss, six positions |
| EqDropdown | Molecule | full | Dropdown menu with items, separators, keyboard navigation, two positions |
| EqDatePicker | Molecule | full | Date picker with calendar popup, month navigation, formatted display |
| EqCalendar | Molecule | full | Calendar with month & week views, events, month/year drill-down |
| EqVirtualList | Molecule | full | Windowed list rendering only visible items, sticky headers, scroll-to-index |
| EqDeviceFrame | Molecule | full | Static iPhone 16 / 16 Pro chrome with Dynamic Island for showcasing mobile-only components |
| EqHeader | Organism | native | Sticky header with brand, nav, and backdrop blur |
| EqFooter | Organism | native | Footer with link groups and copyright |
| EqHeroShell | Organism | full | Hero banner with background image, overlay, custom colors |
| EqPageSection | Organism | native | Titled content section |
| EqAppShell | Organism | native | Full page layout (header + main + footer) |
| EqNavbar | Organism | native | Horizontal nav bar |
| EqDrawer | Organism | full | Slide-in panel from any edge, four sizes, header/body/footer slots |
| EqGrid | Organism | full | Data grid with sorting, filtering, pagination, virtualization, DnD, export |
| EqFilePicker | Organism | full | File/folder picker with drag-drop, thumbnails, progress, backend trait |
| EqToolbar | Organism | full | Mobile header with start/title/end slots and an optional secondary row |
| EqBottomNav | Organism | full | Bottom-anchored mobile tab bar with icon+label items, badges, and active state |
| EqMobileAppShell | Organism | full | Three-region mobile layout (toolbar + scrollable body + bottom nav) with iOS safe-area padding |
| Getting Started Guide | Guide | - | In-app developer guide for the playground |
| Theme Showcase | Theming | - | Theme color and gradient swatch viewer |
ARIA legend: full = roles + attributes + keyboard, native = semantic HTML, pending = planned, - = non-interactive.
Planned (not yet built):
| Component | Category | ARIA | Notes |
|---|---|---|---|
| Skeleton | Atom | - | CSS keyframes |
| Badge | Atom | - | Status indicator |
| ContextMenu | Molecule | planned | Via dioxus-primitives, positioning via eval |
| HoverCard | Molecule | planned | Via dioxus-primitives, positioning via eval |
| RichTextEditor | Organism | - | JS editor init via eval |
| Signature | Atom | - | Canvas drawing via eval |
Tier 1 = works as-is, Tier 2 = needs small fix, Tier 3 = needs significant work.
| Component | Tier | Needs eval | Notes |
|---|---|---|---|
| Getting Started Guide | 1 | no | Playground-only, feature-gated |
| EqText | 1 | no | |
| EqLabel | 1 | no | |
| EqLink | 1 | no | |
| EqInput | 1 | no | |
| EqIcon | 1 | no | |
| EqImage | 1 | no | object-fit support on Blitz needs verification |
| EqCheckbox | 1 | no | |
| EqButton | 2 | no | @property gradient transition needs Blitz fallback |
| EqDivider | 1 | no | |
| EqScrollableSpace | 2 | no | Custom scrollbar CSS cosmetic-only on Blitz |
| EqVideo | 1 | no | Video playback on Blitz depends on media support |
| EqProgress | 1 | no | Indeterminate animation needs Blitz fallback |
| EqTab | 1 | no | |
| EqRadioGroup | 1 | no | |
| EqSwitch | 2 | no | CSS transition degrades to instant toggle on Blitz |
| EqSlider | 1 | no | Native range input |
| EqAvatar | 1 | no | |
| EqTooltip | 2 | no | CSS hover positioning may degrade on Blitz |
| EqSelect | 3 | yes | Uses document::eval for positioning and outside-click |
| EqCard | 1 | no | |
| EqImageCard | 1 | no | |
| EqCarousel | 2 | no | Slide transition needs Blitz fallback |
| EqTree | 1 | no | Full WAI-ARIA tree pattern |
| EqAccordion | 1 | no | Full WAI-ARIA accordion pattern |
| EqNavItem | 1 | no | |
| EqCta | 1 | no | |
| EqModal | 2 | yes | Uses document::eval for focus trap |
| EqToastList | 3 | yes | Uses JS setTimeout for auto-dismiss |
| EqDropdown | 3 | yes | Uses document::eval for outside-click detection |
| EqDatePicker | 3 | yes | Uses document::eval for popup positioning |
| EqCalendar | 1 | no | |
| EqVirtualList | 3 | yes | Uses document::eval for scroll-to-index |
| EqDeviceFrame | 1 | no | Pure CSS + inline SVG, no JS |
| EqHeader | 2 | no | backdrop-filter needs Blitz fallback |
| EqFooter | 1 | no | |
| EqHeroShell | 2 | no | Decorative aria-hidden, optional role prop |
| EqPageSection | 1 | no | |
| EqAppShell | 1 | no | |
| EqNavbar | 1 | no | |
| EqDrawer | 2 | no | CSS transition degrades on Blitz |
| EqGrid | 3 | yes | Full ARIA, virtualization, DnD all use eval |
| EqFilePicker | 3 | yes | Uses document::eval for file input and drop handling |
| EqToolbar | 1 | no | Pure layout, no JS |
| EqBottomNav | 1 | no | Pure layout, no JS |
| EqMobileAppShell | 1 | no | Pure layout, uses env(safe-area-inset-*) which Blitz may need to support |
| Theme Showcase | 1 | no | Playground-only, feature-gated |
Theming - 26 built-in themes, custom CSS, runtime switching. The theme module also exports shared Tailwind constants you can use in your own layouts.
Add the crate to your Cargo.toml. Check
crates.io/crates/eq_ui for the
current version before pinning; this README drifts.
[dependencies]
# From crates.io:
eq_ui = "0.4.2"
# Or from GitHub:
eq_ui = { git = "https://github.com/equidevium/eq_ui", branch = "main" }
# Or from a local path:
eq_ui = { path = "../eq_ui" }Wire up CSS and theming in your root component:
use eq_ui::{UI_TAILWIND_CSS, UI_INDEX_CSS, UI_BUTTONS_CSS};
use eq_ui::eq_theme::EqTheme;
#[component]
fn App() -> Element {
// Initialize the theme context
let _theme = EqTheme::use_theme_provider();
rsx! {
document::Link { rel: "stylesheet", href: UI_TAILWIND_CSS }
document::Link { rel: "stylesheet", href: UI_INDEX_CSS }
document::Link { rel: "stylesheet", href: UI_BUTTONS_CSS }
// Injects the active theme's CSS at runtime
EqThemeRenderer {}
// ... your app layout
}
}If you're using Tailwind in your own project, add a @source so it picks up eq_ui's classes:
@import "tailwindcss";
@source "../path/to/eq_ui/src/**/*.rs";When you push changes to eq_ui and want your consuming project to pick them up:
rm -rf ~/.cargo/git/checkouts/eq_ui-*
rm -rf ~/.cargo/git/db/eq_ui-*
cargo update -p eq_uiuse eq_ui::atoms::{
EqText, TextVariant, EqInput, InputKind, EqLabel, EqLink,
EqIcon, IconSize, EqImage, AtomImageSize, AspectRatio, ObjectFit,
EqCheckbox, CheckboxState, EqButton, ButtonVariant, ButtonSize,
EqDivider, EqScrollableSpace, EqVideo,
EqProgress, ProgressVariant, ProgressSize,
EqTab, TabItem, TabVariant, TabSize,
EqRadioGroup, RadioItem, RadioSize, RadioLayout,
EqSwitch, SwitchSize,
EqSlider, SliderSize,
EqAvatar, AvatarSize, AvatarStatus,
EqTooltip, TooltipPosition,
EqSelect, SelectOption, SelectPosition,
};
use eq_ui::molecules::{
EqCard, EqCardBody, EqCardFooter, EqCardHeader,
EqImageCard, CaptionMode,
EqCarousel, CarouselMode,
EqTree, TreeNode,
EqAccordion, AccordionItem, AccordionMode,
EqNavItem, NavItemSize,
EqCta, CtaLayout,
EqModal, ModalSize,
EqToastList, ToastData, ToastSeverity, ToastPosition,
EqDropdown, DropdownItem, DropdownPosition,
EqDatePicker, DateValue, DatePickerPosition,
EqCalendar, CalendarEvent, CalendarMode, EventColor,
EqVirtualList, VirtualListDirection, StickyHeader,
EqDeviceFrame, DeviceModel,
};
use eq_ui::organisms::{
EqAppShell, EqHeader, EqFooter, EqHeroShell, EqPageSection, EqNavbar,
EqGrid, EqColumnDef, GridNavigation, GridDensity, RowSelection,
ColumnAlign, ExportFormat, GridDragPayload,
EqDrawer, DrawerSide, DrawerSize,
EqFilePicker, FilePickerMode, PickedFile, FilePickerBackend, WebFilePickerBackend,
EqToolbar,
EqBottomNav, BottomNavItem, BottomNavBadge,
EqMobileAppShell,
};
use eq_ui::theme; // shared constants like CONTAINER_LAYOUT, BTN_PRIMARY, etc.// Text with semantic HTML - picks the right tag automatically
EqText { variant: TextVariant::H1, "Page title" }
EqText { "Just a paragraph." } // defaults to Body
// Form label tied to an input
EqLabel { for_id: "email", "Email address" }
// Plain anchor link
EqLink { href: "/about".into(), "About us" }
// Input with kind variants
EqInput {
kind: InputKind::Email,
placeholder: "you@example.com",
name: "email",
oninput: move |e| { /* handle it */ },
}
// Icon wrapper - pass an SVG or image as children
EqIcon { size: IconSize::Lg, muted: true,
// your svg or img here
}
// Image with sizing, aspect ratio, and object-fit control
EqImage {
src: "https://example.com/photo.jpg",
alt: "A photo",
size: AtomImageSize::Lg,
aspect_ratio: AspectRatio::Ratio16_9,
object_fit: ObjectFit::Cover,
rounded: true,
}
// Checkbox with three visual states
EqCheckbox {
state: CheckboxState::Checked,
label: "I agree to the terms",
on_change: move |next| agreed.set(next),
}
// Button with five variants and three sizes
EqButton {
variant: ButtonVariant::Primary,
size: ButtonSize::Lg,
on_click: move |_| save(),
"Save Changes"
}
EqButton { variant: ButtonVariant::Ghost, "Cancel" }
EqButton { variant: ButtonVariant::Danger, disabled: true, "Delete" }
// Solid (no gradient) with custom text color
EqButton { gradient: false, color: "#fbbf24", "Solid Button" }
// Divider with variants
EqDivider { variant: DividerVariant::Dashed }
// Scrollable container
EqScrollableSpace {
div { class: "p-4",
for item in items { p { "{item}" } }
}
}
// Progress bar - determinate, indeterminate, variants
EqProgress { value: 0.65 }
EqProgress { value: 0.3, variant: ProgressVariant::Warning, label: true }
EqProgress { indeterminate: true, size: ProgressSize::Lg }
// Tab bar - three visual variants, badges, disabled tabs
let mut active = use_signal(|| 0usize);
EqTab {
tabs: vec![
TabItem::new("Overview"),
TabItem::new("Inbox").badge(12),
TabItem::new("Settings"),
TabItem::new("Archived").disabled(true),
],
variant: TabVariant::Underline, // or Pill, Card
size: TabSize::Md,
active: active(),
on_change: move |idx| active.set(idx),
}
// Range slider
EqSlider {
value: 50.0,
min: 0.0,
max: 100.0,
size: SliderSize::Md,
show_value: true,
on_change: move |val| slider_val.set(val),
}
// Avatar with fallbacks
EqAvatar {
src: "https://example.com/avatar.jpg",
alt: "Jane Doe",
size: AvatarSize::Lg,
status: AvatarStatus::Online,
}
// Tooltip
EqTooltip {
text: "More information here",
position: TooltipPosition::Top,
span { "Hover me" }
}
// Select dropdown with search
EqSelect {
options: vec![
SelectOption::new("us", "United States"),
SelectOption::new("uk", "United Kingdom"),
SelectOption::new("de", "Germany"),
],
placeholder: "Choose a country",
searchable: true,
on_change: move |val| country.set(val),
}// Card with slots
EqCard {
EqCardHeader { "Card title" }
EqCardBody { "Some content here." }
EqCardFooter { "Footer info" }
}
// Image card with caption below or as overlay
EqImageCard {
src: "https://example.com/photo.jpg",
alt: "Mountain landscape",
mode: CaptionMode::Overlay,
size: AtomImageSize::Lg,
aspect_ratio: AspectRatio::Ratio16_9,
rounded: true,
title: "Alpine Meadow",
description: "A serene landscape captured during the golden hour.",
}
// Generic carousel - pass any elements as slides
EqCarousel {
slides: vec![
rsx! { EqImageCard { src: "...", alt: "Slide 1", /* ... */ } },
rsx! { EqImageCard { src: "...", alt: "Slide 2", /* ... */ } },
rsx! { div { "Any content works as a slide" } },
],
}
// Collapsible tree view
EqTree {
nodes: vec![
TreeNode::new("id-1", "Item One"),
TreeNode::new_with_children("id-2", "Parent", vec![
TreeNode::new("id-3", "Child"),
]),
],
selected: selected_id(),
on_select: move |id: String| selected_id.set(Some(id)),
}
// Accordion with single-expand or multi-expand modes
EqAccordion {
items: vec![
AccordionItem::new("faq-1", "What is eq_ui?", rsx! { p { "A Dioxus component library." } }),
AccordionItem::new("faq-2", "How many themes?", rsx! { p { "25 built-in themes." } }),
],
mode: AccordionMode::Single,
}
// Modal dialog
let mut show_modal = use_signal(|| false);
EqModal {
open: show_modal(),
on_close: move |_| show_modal.set(false),
size: ModalSize::Md,
title: "Confirm Action",
div { "Are you sure you want to proceed?" }
}
// Toast notifications
let mut toasts = use_signal(Vec::<ToastData>::new);
EqToastList {
toasts: toasts(),
position: ToastPosition::TopRight,
on_dismiss: move |id| toasts.write().retain(|t| t.id != id),
}
// Dropdown menu
EqDropdown {
items: vec![
DropdownItem::new("edit", "Edit"),
DropdownItem::separator(),
DropdownItem::new("delete", "Delete").disabled(true),
],
on_select: move |id| handle_action(id),
EqButton { variant: ButtonVariant::Outline, "Actions" }
}
// Date picker
EqDatePicker {
value: date_val(),
on_change: move |d| date_val.set(Some(d)),
placeholder: "Select a date",
}
// Calendar with month or week view
EqCalendar {
events: vec![
CalendarEvent::new("1", "Meeting", 2026, 5, 4),
CalendarEvent::timed("2", "Standup", 2026, 5, 4, 9, 0, 9, 30),
],
mode: CalendarMode::Month, // or Week
on_date_click: move |date| selected.set(Some(date)),
}
// Virtual list for large datasets
EqVirtualList {
item_count: 10_000,
item_size: 40.0,
viewport_size: 400.0,
render_item: Callback::new(move |idx| rsx! { div { "Row {idx}" } }),
}
// iPhone device frame for showcasing mobile-only components
EqDeviceFrame {
model: DeviceModel::IPhone16,
div { class: "p-6 text-center",
h1 { class: "text-xl font-semibold", "My Mobile App" }
p { class: "text-sm opacity-70", "Mobile-only content lives inside the frame." }
}
}The three mobile organisms compose together. EqMobileAppShell owns
the layout; EqToolbar and EqBottomNav slot into it.
let mut active = use_signal(|| "home".to_string());
let nav_items = vec![
BottomNavItem::new("home", "Home", rsx! { /* icon */ }),
BottomNavItem::new("inbox", "Inbox", rsx! { /* icon */ })
.badge(BottomNavBadge::Count(3)),
BottomNavItem::new("profile", "Profile", rsx! { /* icon */ }),
];
EqDeviceFrame {
model: DeviceModel::IPhone16,
EqMobileAppShell {
toolbar: rsx! {
EqToolbar {
title: rsx! { "Inbox" },
end: rsx! { EqButton { variant: ButtonVariant::Ghost, "Edit" } },
}
},
bottom_nav: rsx! {
EqBottomNav {
items: nav_items,
active: active(),
on_change: move |id| active.set(id),
}
},
div { class: "p-4",
// Page body scrolls independently
}
}
}Organisms take Element props instead of depending on a specific router, so they work across web, desktop, and mobile.
// App shell - pass your own header, footer, and page content
EqAppShell {
header: rsx! { EqHeader { site_title: "My App", nav: rsx! { /* your nav items */ } } },
footer: rsx! { EqFooter {} },
// children become the main content area
p { "Hello world" }
}
// Hero section with background image and custom colors
EqHeroShell {
title: "Welcome",
subtitle: "Something cool goes here",
title_color: "var(--my-custom-color)", // optional color override
subtitle_color: "#ff6b6b", // accepts any CSS color value
background: rsx! {
EqImage {
src: "/assets/hero-bg.jpg",
alt: "Hero background",
size: AtomImageSize::Full,
object_fit: ObjectFit::Cover,
}
},
actions: rsx! { button { "Get started" } },
}
// Page section with optional title/description
EqPageSection {
id: "features",
title: "Features",
description: "Everything you need to build fast.",
// children go in the body
}
// Drawer - slide-in panel from any edge
let mut show_drawer = use_signal(|| false);
EqDrawer {
open: show_drawer(),
on_close: move |_| show_drawer.set(false),
side: DrawerSide::Right,
size: DrawerSize::Md,
title: "Settings",
div { "Drawer content here" }
}
// File picker with drag-and-drop
EqFilePicker {
mode: FilePickerMode::Multiple,
accept: ".png,.jpg,.pdf",
max_size: 10 * 1024 * 1024, // 10 MB
on_change: move |files: Vec<PickedFile>| handle_files(files),
}For the header specifically, you provide the nav items as <li> elements and EqHeader wraps them in the right markup:
EqHeader {
site_title: "My Site",
nav: rsx! {
li { a { href: "/", "Home" } }
li { a { href: "/about", "About" } }
},
}EqGrid handles sorting, filtering, pagination, virtualization, row selection, bulk actions, column resizing, drag-and-drop, and export.
#[derive(Clone, PartialEq)]
struct Employee {
name: String,
role: String,
salary: f64,
}
let columns = vec![
EqColumnDef::new("name", "Name", |e: &Employee| e.name.clone())
.filterable(true),
EqColumnDef::new("role", "Role", |e: &Employee| e.role.clone())
.filterable(true),
EqColumnDef::new("salary", "Salary", |e: &Employee| e.salary.to_string())
.with_formatter(|e: &Employee| format!("${:.0}", e.salary))
.align(ColumnAlign::Right)
.comparator(|a: &Employee, b: &Employee| {
a.salary.partial_cmp(&b.salary).unwrap_or(std::cmp::Ordering::Equal)
}),
];
EqGrid {
data: employees,
columns: columns,
navigation: GridNavigation::Paginate, // or Standard, Virtualize
page_size: 10,
row_selection: RowSelection::Multi,
density: GridDensity::Normal,
quick_filter: true,
striped: true,
export: true,
on_selection_change: move |rows| { /* handle selection */ },
on_delete: move |rows| { /* handle delete */ },
}Virtualization renders only visible rows plus a small buffer (split-table layout with measured row heights). See the EqGrid README for details.
26 built-in themes, custom CSS themes at runtime.
Built-in: Unghosty, Burgundy, Gold, PurplePink, Monochrome, Watermelon, Sunset, Ocean, Spacetime, Gruvbox, Monokai, Hellas, Egypt, Dometrain, Catppuccin, Dracula, Nord, OneDark, RosePine, SolarizedDark, TokyoNight, Warcraft, SweetRush, Cloud, Synthwave, Limbotron (default).
- Call
EqTheme::use_theme_provider()in your rootAppcomponent. - Place
EqThemeRenderer {}before your layout - this replaces the staticUI_COLORS_CSSlink. - Optionally add a theme switcher UI for user-facing selection.
use eq_ui::eq_theme::EqTheme;
// Set a built-in theme
EqTheme::set_theme(EqTheme::Ocean);
// Load a custom theme from a CSS string
EqTheme::set_custom_theme(r#"
:root {
--color-primary-dark: #1a0a0a;
--color-accent-primary: #ff6600;
/* ... all your CSS variables ... */
}
"#.to_string());use eq_ui::eq_theme::EqTheme;
#[component]
fn ThemeSwitcher() -> Element {
let mut theme = EqTheme::use_theme();
rsx! {
select {
onchange: move |evt: Event<FormData>| {
let new_theme = match evt.value().as_str() {
"Ocean" => EqTheme::Ocean,
"Burgundy" => EqTheme::Burgundy,
// ... other themes
_ => EqTheme::Unghosty,
};
theme.set(new_theme);
},
for (name, _variant) in EqTheme::built_in_variants() {
option { value: "{name}", "{name}" }
}
}
}
}src/
lib.rs - crate root, CSS asset exports
theme.rs - shared Tailwind class constants
eq_theme.rs - theme enum, context, and runtime switching
playground_enum_trait.rs - PlaygroundEnum trait for prop iteration
playground/ - feature-gated interactive component showcase
eq_playground.rs - EqPlayground organism (self-contained with CSS/theme)
playground_types.rs - ComponentDescriptor, ComponentCategory, UsageExample
playground_helpers.rs - DemoSection, CodeBlock, StyleInfo, prop controls
playground_guide.rs - Getting Started in-app guide
theme_showcase.rs - theme color/gradient swatch viewer
atoms/
eq_text.rs - text with semantic variants (h1-h3, body, muted, etc.)
eq_label.rs - form label
eq_link.rs - anchor link
eq_input.rs - input/textarea with kind variants
eq_icon.rs - icon wrapper with size variants
eq_icon_paths.rs - SVG path data constants (Phosphor icons)
eq_image.rs - image with sizing, aspect ratio, object-fit
eq_checkbox.rs - checkbox with checked/unchecked/indeterminate states
eq_button.rs - button with 5 variants, 3 sizes, gradient transitions
eq_scrollable_space.rs - scrollable container with themed scrollbar
eq_divider.rs - separator with solid/dashed/dotted/spacer variants
eq_video.rs - video with poster overlay, autoplay, controls
eq_progress.rs - progress bar with determinate/indeterminate modes
eq_tab.rs - tab bar with underline, pill, and card variants
eq_radio_group.rs - radio button group with three sizes
eq_switch.rs - toggle switch with three sizes
eq_slider.rs - range slider with accent-color theming
eq_avatar.rs - user avatar with image/initials/icon fallback
eq_tooltip.rs - hover/focus tooltip with four positions
eq_select.rs - dropdown select with search and keyboard nav
*_styles.rs - co-located style constants for each atom
molecules/
eq_card.rs - card with header/body/footer slots
eq_image_card.rs - image card with caption modes (below/overlay)
eq_carousel.rs - generic content carousel with arrows and dots
eq_tree.rs - collapsible tree view with select and expand
eq_accordion.rs - collapsible panels with single/multi-expand modes
eq_nav_item.rs - navigation item with icon and label
eq_cta.rs - call-to-action section with inline/centered layout
eq_modal.rs - modal dialog with focus management
eq_toast.rs - toast notification stack with auto-dismiss
eq_dropdown.rs - dropdown menu with keyboard navigation
eq_date_picker.rs - date picker with calendar popup
eq_calendar.rs - calendar with month & week views
eq_virtual_list.rs - windowed list rendering with sticky headers
eq_device_frame.rs - static iPhone 16 / 16 Pro chrome for mobile-only previews
*_styles.rs - co-located style constants for each molecule
organisms/
eq_app_shell.rs - full page layout (header + main + footer)
eq_header.rs - sticky site header with brand + nav + backdrop blur
eq_footer.rs - footer with link groups + copyright
eq_hero_shell.rs - hero banner with background image, overlay, custom colors
eq_page_section.rs - titled content section
eq_navbar.rs - horizontal nav bar
eq_drawer.rs - slide-in panel from any screen edge
eq_file_picker.rs - file/folder picker with drag-drop and backend trait
eq_toolbar.rs - mobile header with start/title/end slots and a secondary row
eq_bottom_nav.rs - bottom-anchored mobile tab bar
eq_mobile_app_shell.rs - three-region mobile layout (toolbar + body + bottom nav)
eq_grid/ - feature-rich data grid organism
grid.rs - main orchestration component
types.rs - shared enums (GridNavigation, RowSelection, GridDensity, etc.)
column_def.rs - column definition builder
header.rs - sortable header with column filters and resize handles
body.rs - row rendering with selection and drag support
pagination.rs - page controls
quick_filter.rs - global search bar
bulk_actions.rs - selection toolbar (delete, export, status, clipboard)
export.rs - CSV, JSON, TXT, ODS export
styles.rs - co-located style constants
*_styles.rs - co-located style constants for each organism
eq_ui_macros/ - proc-macro crate (#[playground], PlaygroundEnum derive)
assets/
icons/ - Phosphor SVG icons (square, check-square, etc.)
theme/ - base CSS + 25 theme color files
styling/ - component-specific CSS (navbar)
tailwind.css - Tailwind entry point with @source directives
Each component keeps its Tailwind classes in a sibling _styles.rs file (e.g. eq_text.rs + eq_text_styles.rs). Shared constants (spacing, borders, surfaces, button variants) live in theme.rs.
Colors use CSS custom properties (--color-primary-dark, --color-label-primary, etc.) defined in assets/theme/. Swap the palette by switching themes or providing your own CSS variables. Some components also accept per-instance color overrides (e.g. title_color on EqHeroShell) - any CSS color value works.
Runtime: dioxus = "=0.7.3", serde + serde_json (for document::eval result parsing), eq_ui_macros (proc-macro crate, workspace member).
Build: eq_ui_build (workspace member).
Dev (examples and playground only): web-sys, wasm-bindgen, wasm-bindgen-futures.
The dioxus version is currently pinned with = because Dioxus 0.7 is still moving; loosen the pin once the upstream API stabilizes.
Dioxus desktop uses Wry (webview) for rendering, but compiles to a native binary - not WASM. web_sys/wasm-bindgen are only available on wasm32.
Where browser APIs are needed (element measurement, focus, scroll position), we use document::eval() instead of web_sys. This works on both web and desktop via Wry's webview, no wasm-bindgen required.
All existing components work cross-platform: web, desktop (Wry), mobile. See the Blitz readiness table in the Components section for native renderer status. See the Planned table for components not yet built.
Interactive playground for browsing and testing all 45 components. Enable with the playground feature:
dx serve --example playground --features playground --platform web
# if deno interferes with dx command
~/.cargo/bin/dx serve --example playground --features playground --platform web --port 3030Two-panel layout: component tree on the left, live demo on the right. Prop controls, style tokens, code examples, and a theme switcher in the header.
The playground is self-contained (bundles its own CSS/theme). You can inject your own components by pushing ComponentDescriptor entries:
use eq_ui::{all_component_descriptors, EqPlayground};
let mut descs = all_component_descriptors();
descs.push(my_component::descriptor());
rsx! { EqPlayground { descriptors: descs } }See playground.md for the full architecture specification.
A Rust crate has three test pools, and cargo test routes to them via flags. Each pool tests a different thing in eq_ui.
cargo test # everything: unit + integration + doctests
cargo test --lib # only the per-component smoke tests under src/*.rs
cargo test --tests # only unit + integration tests, no doctests
cargo test --doc # only the runnable code examples in /// and //! commentsWhat each pool covers here:
- Unit tests live in
#[cfg(test)] mod testsblocks inside every component file. One smoke test per registered component plus pure-function tests on enums, builders, and formatters where they exist. Fast. - Integration tests under a top-level
tests/folder. None at the moment; planned for future releases. - Doctests are the
no_runexamples inside///and//!doc comments. Each compiles into its own tiny binary, so doctests are slower per-test than unit tests. Catches API drift in the documentation when prop names or types change.
For day-to-day work cargo test --lib is fastest. Before tagging a release, run plain cargo test (or the full pre-publish checklist in ROADMAP.md).
If you add a new component, add a smoke test in the same file using the established pattern:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke_renders() {
let mut dom = VirtualDom::new(|| rsx! { EqYourComponent { /* required props */ } });
dom.rebuild_in_place();
}
}See ROADMAP.md for what's coming next.