Sidebar
A flexible sidebar component that provides navigation structure for applications. Features collapsible states, nested menu items, header and footer sections, and responsive design. Perfect for creating application navigation panels, admin dashboards, and complex hierarchical interfaces.
#Import
use gpui_kit::component::sidebar::{
Sidebar, SidebarHeader, SidebarFooter, SidebarGroup,
SidebarMenu, SidebarMenuItem, SidebarToggleButton
};// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { SidebarEntry, SidebarGroup, Sidebar } from "sidebar.slint";use iced::widget::{button, column, container, mouse_area, row, svg, text};
use iced::{Alignment, Color, Element, Length, Padding, Shadow, Task, Theme};#Usage
#Basic Sidebar
use gpui_kit::component::{sidebar::*, Side};
Sidebar::new()
.header(
SidebarHeader::new()
.child("My Application")
)
.child(
SidebarGroup::new("Navigation")
.child(
SidebarMenu::new()
.child(
SidebarMenuItem::new("Dashboard")
.icon(IconName::LayoutDashboard)
.on_click(|_, _, _| println!("Dashboard clicked"))
)
.child(
SidebarMenuItem::new("Settings")
.icon(IconName::Settings)
.on_click(|_, _, _| println!("Settings clicked"))
)
)
)
.footer(
SidebarFooter::new()
.child("User Profile")
)Rectangle {
width: 100%;
height: 320px;
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
clip: true;
HorizontalLayout {
width: 100%;
height: 100%;
// The section lays a fixed-width child out centred; a
// sidebar belongs against the left edge.
alignment: start;
Sidebar {
height: 100%;
groups: [
{
title: "",
entries: [
{ label: "Home", icon: "house", suffix: "", trailing-icon: "", muted: false },
{ label: "Inbox", icon: "inbox", suffix: "", trailing-icon: "", muted: false },
{ label: "Settings", icon: "settings", suffix: "", trailing-icon: "", muted: false },
],
},
];
selected-entry: 0;
}
}
}// One untitled group, the first row current. The brand mark is `box` and the
// footer keeps the component's defaults, which is the Slint example's own
// binding.
let basic = card(
0,
Sidebar::new()
.header_icon(BOX)
.group(SidebarGroup::new("").entries(platform_entries()))
.selected(app.selected[0].0, app.selected[0].1)
.collapsed(app.collapsed[0])
.on_select(|group, entry| Message::Select {
section: 0,
group,
entry,
})
.on_toggle(|collapsed| Message::Toggle {
section: 0,
collapsed,
}),
app.collapsed[0],
CARD,
);#Collapsible Sidebar
let mut collapsed = false;
Sidebar::new()
.collapsed(collapsed)
.collapsible(true)
.header(
SidebarHeader::new()
.child(
h_flex()
.child(Icon::new(IconName::Home))
.when(!collapsed, |this| this.child("Home"))
)
)
.child(
SidebarGroup::new("Menu")
.child(
SidebarMenu::new()
.child(
SidebarMenuItem::new("Files")
.icon(IconName::Folder)
)
)
)
// Toggle button
SidebarToggleButton::new()
.collapsed(collapsed)
.on_click(|_, _, _| {
collapsed = !collapsed;
})Rectangle {
width: 100%;
height: 320px;
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
clip: true;
HorizontalLayout {
width: 100%;
height: 100%;
// The section lays a fixed-width child out centred; a
// sidebar belongs against the left edge.
alignment: start;
Sidebar {
height: 100%;
collapsed: true;
groups: [
{
title: "Platform",
entries: [
{ label: "Home", icon: "house", suffix: "", trailing-icon: "", muted: false },
{ label: "Inbox", icon: "inbox", suffix: "", trailing-icon: "", muted: false },
{ label: "Settings", icon: "settings", suffix: "", trailing-icon: "", muted: false },
],
},
];
selected-entry: 1;
}
}
}// The same group, drawn as the 56px rail. The rails' headings, labels, suffix
// and footer name leave; the icons and the brand mark stay. The application's
// `panel-left` trigger sits beside the rail, because the rail has no room for
// its own.
let collapsible = card(
1,
Sidebar::new()
.header_icon(BOX)
.group(SidebarGroup::new("Platform").entries(platform_entries()))
.selected(app.selected[1].0, app.selected[1].1)
.collapsed(app.collapsed[1])
.on_select(|group, entry| Message::Select {
section: 1,
group,
entry,
})
.on_toggle(|collapsed| Message::Toggle {
section: 1,
collapsed,
}),
app.collapsed[1],
CARD,
);#Nested Menu Items
SidebarMenuItem::new("Projects")
.icon(IconName::FolderOpen)
.active(true)
.children([
SidebarMenuItem::new("Web App")
.active(false)
.on_click(|_, _, _| println!("Web App selected")),
SidebarMenuItem::new("Mobile App")
.active(true)
.on_click(|_, _, _| println!("Mobile App selected")),
SidebarMenuItem::new("Desktop App")
.on_click(|_, _, _| println!("Desktop App selected")),
])
.on_click(|_, _, _| {
// Toggle project group
})SlintNo Slint version of this example yet.
#Multiple Groups
Sidebar::new()
.child(
SidebarGroup::new("Main")
.child(
SidebarMenu::new()
.child(SidebarMenuItem::new("Dashboard").icon(IconName::Home))
.child(SidebarMenuItem::new("Analytics").icon(IconName::BarChart))
)
)
.child(
SidebarGroup::new("Content")
.child(
SidebarMenu::new()
.child(SidebarMenuItem::new("Posts").icon(IconName::FileText))
.child(SidebarMenuItem::new("Media").icon(IconName::Image))
.child(SidebarMenuItem::new("Comments").icon(IconName::MessageCircle))
)
)
.child(
SidebarGroup::new("Settings")
.child(
SidebarMenu::new()
.child(SidebarMenuItem::new("General").icon(IconName::Settings))
.child(SidebarMenuItem::new("Users").icon(IconName::Users))
)
)Rectangle {
width: 100%;
height: 340px;
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
clip: true;
HorizontalLayout {
width: 100%;
height: 100%;
// The section lays a fixed-width child out centred; a
// sidebar belongs against the left edge.
alignment: start;
Sidebar {
height: 100%;
header-title: "Acme";
groups: [
{
title: "Overview",
entries: [
{ label: "Dashboard", icon: "layout-dashboard", suffix: "", trailing-icon: "", muted: false },
{ label: "Reports", icon: "chart-column", suffix: "", trailing-icon: "", muted: false },
],
},
{
title: "Management",
entries: [
{ label: "Customers", icon: "users", suffix: "1,234", trailing-icon: "", muted: false },
{ label: "Billing", icon: "database", suffix: "", trailing-icon: "", muted: false },
{ label: "Support", icon: "message-circle", suffix: "", trailing-icon: "", muted: false },
],
},
];
selected-group: 1;
selected-entry: 0;
}
}
}// Groups are what carry the headings; an entry is the same shape whether it
// sits in a titled group or an untitled one. The heading names the workspace
// and the second group's first row is current, which is the Slint example's
// binding.
let multiple = card(
2,
Sidebar::new()
.header_icon(BOX)
.header_title("Acme")
.group(SidebarGroup::new("Overview").entries([
SidebarEntry::new("Dashboard").icon(LAYOUT_DASHBOARD),
SidebarEntry::new("Reports").icon(CHART_COLUMN),
]))
.group(SidebarGroup::new("Management").entries([
SidebarEntry::new("Customers").icon(USERS).suffix("1,234"),
SidebarEntry::new("Billing").icon(DATABASE),
SidebarEntry::new("Support").icon(MESSAGE_CIRCLE),
]))
.selected(app.selected[2].0, app.selected[2].1)
.collapsed(app.collapsed[2])
.on_select(|group, entry| Message::Select {
section: 2,
group,
entry,
})
.on_toggle(|collapsed| Message::Toggle {
section: 2,
collapsed,
}),
app.collapsed[2],
TALL_CARD,
);#With Badges and Suffixes
use gpui_kit::component::{Badge, Switch};
SidebarMenuItem::new("Notifications")
.icon(IconName::Bell)
.suffix(
Badge::new()
.count(5)
.child("5")
)
SidebarMenuItem::new("Dark Mode")
.icon(IconName::Moon)
.suffix(
Switch::new("dark-mode")
.checked(true)
.xsmall()
)
SidebarMenuItem::new("Settings")
.icon(IconName::Settings)
.suffix(IconName::ChevronRight)SlintNo Slint version of this example yet.
// The workspace from GPUI's sidebar story: the same rail, with a header
// swap, two section labels, a disclosed Playground group, trailing chevrons
// on the top-level rows, a dotted badge, a muted Travel and the user footer.
// The disclosure is the component's own `collapsible::reveal`, so the arrow
// swaps and the children move over the accordion's 150ms line.
let workspace = card(
3,
Sidebar::new()
.header_icon(LAYERS)
.header_title("Company Name")
.header_subtitle("Enterprise")
.header_action(CHEVRONS_UP_DOWN, Message::SwapWorkspace)
.group(
SidebarGroup::new("Platform").entries([
SidebarEntry::new("Playground")
.icon(SQUARE_TERMINAL)
.children([
SidebarEntry::new("History")
.destructive(true)
.switch(app.history_on),
SidebarEntry::new("Starred"),
SidebarEntry::new("Settings"),
])
.open(app.playground_open),
SidebarEntry::new("Models")
.icon(BOT)
.trailing_icon(CHEVRON_RIGHT),
SidebarEntry::new("Documentation")
.icon(BOOK_OPEN)
.trailing_icon(CHEVRON_RIGHT),
SidebarEntry::new("Settings")
.icon(SETTINGS)
.trailing_icon(CHEVRON_RIGHT),
]),
)
.group(
SidebarGroup::new("Projects").entries([
SidebarEntry::new("Design Engineering")
.icon(FRAME)
.trailing_icon(BELL)
.badge(true),
SidebarEntry::new("Sales and Marketing")
.icon(CHART_COLUMN)
.trailing_icon(SETTINGS),
SidebarEntry::new("Travel").icon(GLOBE).muted(true),
]),
)
.selected(app.selected[3].0, app.selected[3].1)
.collapsed(app.collapsed[3])
.footer_initials("JL")
.footer_name("Jason Lee")
.on_select(|group, entry| match (group, entry) {
// The row with children reports the same press a leaf does; the
// caller is the one that decides it means "disclose".
(0, 0) => Message::TogglePlayground,
_ => Message::Select {
section: 3,
group,
entry,
},
})
.on_entry_switch(|_, _, _, checked| Message::SetHistory(checked)),
app.collapsed[3],
WORKSPACE_CARD,
);#Right-Side Placement
Sidebar::new()
.side(Side::Right)
.width(300)
.header(
SidebarHeader::new()
.child("Right Panel")
)
.child(
SidebarGroup::new("Tools")
.child(
SidebarMenu::new()
.child(SidebarMenuItem::new("Inspector").icon(IconName::Search))
.child(SidebarMenuItem::new("Console").icon(IconName::Terminal))
)
)SlintNo Slint version of this example yet.
#Context Menus
Add right-click context menus to sidebar menu items for additional actions:
use gpui_kit::component::menu::PopupMenu;
SidebarMenuItem::new("Project Files")
.icon(IconName::Folder)
.context_menu(|menu, _, _| {
menu.link("Open in Editor", "https://editor.example.com")
.separator()
.menu_with_description("Rename", "Rename this project", Box::new(RenameAction))
.menu_with_description("Delete", "Delete this project", Box::new(DeleteAction))
.separator()
.submenu("Share", |submenu| {
submenu.menu("Copy Link", Box::new(CopyLinkAction))
.menu("Send via Email", Box::new(EmailAction))
})
})
// Multiple items with context menus
SidebarMenu::new()
.child(
SidebarMenuItem::new("Documentation")
.icon(IconName::BookOpen)
.context_menu(|menu, _, _| {
menu.menu("View Online", Box::new(ViewOnlineAction))
.menu("Download PDF", Box::new(DownloadPdfAction))
})
)
.child(
SidebarMenuItem::new("Settings")
.icon(IconName::Settings)
.children([
SidebarMenuItem::new("General")
.context_menu(|menu, _, _| {
menu.menu("Reset to Defaults", Box::new(ResetAction))
}),
SidebarMenuItem::new("Advanced")
.context_menu(|menu, _, _| {
menu.menu("Export Settings", Box::new(ExportAction))
.menu("Import Settings", Box::new(ImportAction))
})
])
)SlintNo Slint version of this example yet.
#Custom Width and Styling
Sidebar::new()
.width(280) // Custom width in pixels
.border_width(2) // Custom border width
.header(
SidebarHeader::new()
.p_4() // Custom padding
.rounded(cx.theme().radius)
.child("Custom Styled Sidebar")
)SlintNo Slint version of this example yet.
#Interactive Header with Popup Menu
use gpui_kit::component::menu::DropdownMenu;
SidebarHeader::new()
.child(
h_flex()
.gap_2()
.child(Icon::new(IconName::Building))
.child("Company Name")
.child(Icon::new(IconName::ChevronsUpDown))
)
.dropdown_menu(|menu, _, _| {
menu.menu("Acme Corp", Box::new(SelectCompany("acme")))
.menu("Tech Solutions", Box::new(SelectCompany("tech")))
.separator()
.menu("Switch Organization", Box::new(SwitchOrg))
})SlintNo Slint version of this example yet.
#Footer with User Information
SidebarFooter::new()
.justify_between()
.child(
h_flex()
.gap_2()
.child(Icon::new(IconName::User))
.when(!collapsed, |this| {
this.child(
v_flex()
.child("John Doe")
.child(div().text_xs().text_color(cx.theme().muted_foreground).child("john@example.com"))
)
})
)
.when(!collapsed, |this| {
this.child(Icon::new(IconName::MoreHorizontal))
})SlintNo Slint version of this example yet.
#Responsive Sidebar
let is_mobile = window_width < 768;
Sidebar::new()
.collapsed(is_mobile || manually_collapsed)
.width(if is_mobile { 60 } else { 240 })
.header(
SidebarHeader::new()
.child(
div()
.when(!is_mobile, |this| this.child("Full App Name"))
.when(is_mobile, |this| this.child(Icon::new(IconName::Menu)))
)
)SlintNo Slint version of this example yet.
#File Explorer Sidebar
Sidebar::new()
.header(
SidebarHeader::new()
.child(
h_flex()
.gap_2()
.child(IconName::Folder)
.child("Explorer")
)
)
.child(
SidebarGroup::new("Folders")
.child(
SidebarMenu::new()
.child(
SidebarMenuItem::new("src")
.icon(IconName::FolderOpen)
.active(true)
.children([
SidebarMenuItem::new("components")
.icon(IconName::Folder),
SidebarMenuItem::new("utils")
.icon(IconName::Folder),
SidebarMenuItem::new("main.rs")
.icon(IconName::FileCode)
.active(true),
])
)
.child(
SidebarMenuItem::new("tests")
.icon(IconName::Folder)
)
.child(
SidebarMenuItem::new("Cargo.toml")
.icon(IconName::FileText)
)
)
)SlintNo Slint version of this example yet.
#Admin Dashboard Sidebar
Sidebar::new()
.header(
SidebarHeader::new()
.child(
h_flex()
.gap_2()
.child(
div()
.size_8()
.rounded_full()
.bg(cx.theme().primary)
.child(Icon::new(IconName::Crown))
)
.child("Admin Panel")
)
)
.child(
SidebarGroup::new("Overview")
.child(
SidebarMenu::new()
.child(
SidebarMenuItem::new("Dashboard")
.icon(IconName::LayoutDashboard)
.active(true)
)
.child(
SidebarMenuItem::new("Analytics")
.icon(IconName::TrendingUp)
.suffix(Badge::new().count(2))
)
)
)
.child(
SidebarGroup::new("Management")
.child(
SidebarMenu::new()
.child(
SidebarMenuItem::new("Users")
.icon(IconName::Users)
.suffix("1,234")
)
.child(
SidebarMenuItem::new("Orders")
.icon(IconName::ShoppingCart)
.suffix(Badge::new().dot().variant_destructive())
)
.child(
SidebarMenuItem::new("Products")
.icon(IconName::Package)
)
)
)
.footer(
SidebarFooter::new()
.child(
h_flex()
.gap_2()
.child(IconName::User)
.child("Administrator")
)
.child(IconName::LogOut)
)SlintNo Slint version of this example yet.
#Settings Sidebar
Sidebar::new()
.width(300)
.header(
SidebarHeader::new()
.child("Settings")
)
.child(
SidebarGroup::new("General")
.child(
SidebarMenu::new()
.child(
SidebarMenuItem::new("Appearance")
.icon(IconName::Palette)
.active(true)
)
.child(
SidebarMenuItem::new("Notifications")
.icon(IconName::Bell)
.suffix(
Switch::new("notifications")
.checked(true)
.xsmall()
)
)
.child(
SidebarMenuItem::new("Privacy")
.icon(IconName::Shield)
)
)
)
.child(
SidebarGroup::new("Advanced")
.child(
SidebarMenu::new()
.child(
SidebarMenuItem::new("Developer")
.icon(IconName::Code)
.children([
SidebarMenuItem::new("Debug Mode")
.suffix(
Switch::new("debug")
.checked(false)
.xsmall()
),
SidebarMenuItem::new("Console")
.on_click(|_, _, _| println!("Open console")),
])
)
.child(
SidebarMenuItem::new("Performance")
.icon(IconName::Zap)
)
)
)SlintNo Slint version of this example yet.
#Theming
The sidebar uses dedicated theme colors:
// Theme colors used by sidebar
cx.theme().sidebar // Background
cx.theme().sidebar_foreground // Text color
cx.theme().sidebar_border // Border color
cx.theme().sidebar_accent // Hover/active background
cx.theme().sidebar_accent_foreground // Hover/active text
cx.theme().sidebar_primary // Primary elements
cx.theme().sidebar_primary_foreground // Primary textSlintNo Slint version of this example yet.