Sidebar
Keep the main sections of a workspace in a collapsible rail.

use blinc_cn as cn;
use blinc_core::reactive::{Signal, computed};
use blinc_layout::{binding::IntoReactive, prelude::*};
pub fn sidebar_demo(choice: Signal<String>, confirmed: Signal<bool>) -> Div {
div().class("demo-wide").h(240.0).child(
cn::sidebar(confirmed)
.section("Workspace")
.item_active(
"Projects",
blinc_icons::to_svg(blinc_icons::icons::FOLDER, 18.0),
move || choice.set("projects".into()),
)
.item(
"Settings",
blinc_icons::to_svg(blinc_icons::icons::SETTINGS, 18.0),
move || choice.set("settings".into()),
)
.content(move |_| {
div()
.class("demo-panel")
.child(bound_text(computed(move |g| {
format!(
"{} workspace",
g.get(choice).filter(|v| v != "rust").unwrap_or_else(|| "Your".into())
)
})))
.child(text("Recent projects").class("demo-caption"))
.child(
div()
.class("demo-project-list")
.child(text("Component library").class("demo-project-row"))
.child(text("Website").class("demo-project-row"))
.child(text("Design system").class("demo-project-row")),
)
}),
)
}
fn bound_text(value: impl IntoReactive<String>) -> Text {
Text::bound(value.into_reactive())
}
@layer components {
.cn-sidebar,
.cn-sidebar-item {
font-family: var(--font-sans);
}
.cn-sidebar-item:focus-visible {
outline: 2px solid var(--focus-ring);
outline-offset: 2px;
}
.cn-sidebar-layout {
flex-direction: row;
align-items: stretch;
overflow: hidden;
}
.cn-sidebar {
flex-direction: column;
flex-shrink: 0;
gap: 2px;
padding: 8px 0;
overflow: hidden;
width: var(--cn-sidebar-width, 200px);
background: var(--cn-sidebar-bg, var(--surface));
border-right: 1px solid var(--border);
transition: width var(--duration-normal) var(--ease-state);
}
.cn-sidebar[data-state="collapsed"] {
width: var(--cn-sidebar-collapsed-width, 41px);
}
.cn-sidebar-toggle {
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-secondary);
cursor: pointer;
transition: background var(--duration-fast) var(--ease-state);
}
.cn-sidebar-toggle:hover {
background: var(--accent-subtle);
color: var(--text-primary);
}
.cn-sidebar-chevron {
transition: transform var(--duration-normal) var(--ease-state);
}
.cn-sidebar[data-state="collapsed"] .cn-sidebar-chevron {
transform: rotate(180deg);
}
.cn-sidebar-group {
flex-direction: column;
gap: 2px;
}
.cn-sidebar-group-label {
flex-shrink: 0;
height: 26px;
padding: 8px 12px 0;
overflow: hidden;
color: var(--text-tertiary);
transition: height var(--duration-normal) var(--ease-state),
padding-top var(--duration-normal) var(--ease-state),
opacity var(--duration-fast) var(--ease-state);
}
.cn-sidebar[data-state="collapsed"] .cn-sidebar-group-label {
height: 0;
padding: 0 12px;
opacity: 0;
}
.cn-sidebar-item {
padding: 8px 12px;
color: var(--text-secondary);
background: transparent;
cursor: pointer;
transition: background var(--duration-fast) var(--ease-state),
color var(--duration-fast) var(--ease-state);
}
.cn-sidebar-item:hover {
background: var(--accent-subtle);
color: var(--text-primary);
}
.cn-sidebar-item[data-active],
.cn-sidebar-item[data-active]:hover {
background: var(--surface-elevated);
color: var(--text-primary);
}
.cn-sidebar-item[data-active] > .cn-sidebar-icon {
color: var(--primary);
}
.cn-sidebar-label {
transition: opacity var(--duration-fast) var(--ease-state);
}
.cn-sidebar[data-state="collapsed"] .cn-sidebar-label {
opacity: 0;
}
.cn-sidebar-inset {
flex-grow: 1;
flex-direction: column;
min-width: 0;
}
}
Usage and properties
Keep the main sections of a workspace in a collapsible rail. Apply CSS after the component layer to restyle the documented parts.
Use the installation guide to add blinc_cn and its component stylesheet. Create the fixture bindings once, and return the demo from your UI builder. Values marked Bound accept Signal/Computed sources where the API supports them; structural options configure the component at construction.
| Property | Type | Update model | This example |
|---|---|---|---|
collapsed | Signal<bool> | Bound | values.confirmed |
item / section / content | builders | Construction | Workspace navigation and content |
Keyboard and interaction
Tab reaches each item; Enter or Space activates it. :focus-visible marks keyboard focus.
Customizing it
Add application CSS after the default component layer. These selectors are the restyling surface for this component:
.cn-sidebar.cn-sidebar-toggle.cn-sidebar-chevron.cn-sidebar-group.cn-sidebar-group-label.cn-sidebar-layout.cn-sidebar-inset.cn-sidebar-item.cn-sidebar-icon.cn-sidebar-label
Theme and component variables: --primary, --text-primary, --text-secondary, --surface, --border, --radius-default, --duration-fast, --ease-state.
Built-in themes
The Live theme control installs Blinc's own theme bundles, including their colours, focus rings, corner shapes and motion tokens.
pub fn theme_bundle_for(name: &str) -> blinc_theme::ThemeBundle {
match name {
"restrained" => blinc_theme::RestrainedTheme::bundle(),
"expressive" => blinc_theme::ExpressiveTheme::bundle(),
_ => blinc_theme::HybridTheme::bundle(),
}
.with_css(blinc_cn::cn_styles::CN_STYLES)
}Download the default widget and component stylesheets. Sample assets and licenses are available in the asset guide.
Documentation
From the canonical component guide.
The collapse state is a Signal<bool>. Click handlers can also update a
page signal, which the content binds independently:
use blinc::core::reactive::computed;
let collapsed = ctx.use_signal(false);
let page = ctx.use_signal("Dashboard".to_string());
let shown = computed(move |graph| graph.get(page).unwrap_or_default());
sidebar(collapsed)
.section("Main")
.item_active("Dashboard", to_svg(icons::HOUSE, 16.0), move || {
page.set("Dashboard".to_string());
})
.item("Projects", to_svg(icons::FOLDER, 16.0), move || {
page.set("Projects".to_string());
})
.section("Settings")
.item("Settings", to_svg(icons::SETTINGS, 16.0), move || {
page.set("Settings".to_string());
})
.content(move |_| div().p(6.0).child(Text::bound(shown.clone().into_reactive())))
The built-in toggle and external writes to collapsed control the same rail.
Clicking an item marks it active. .show_toggle(false) hides the toggle.
The content builder runs once; its argument is the selection at construction,
so use bindings or show/for_each regions for subsequent page changes.
Styling
The rail is .cn-sidebar with data-state="expanded" or "collapsed".
Its width uses --cn-sidebar-width (200px) and
--cn-sidebar-collapsed-width (41px), eased over the normal theme duration.
The adjacent page is .cn-sidebar-inset. Items use .cn-sidebar-item and
data-active; their parts are .cn-sidebar-icon and .cn-sidebar-label.
Section titles are .cn-sidebar-group-label.
.wide-nav.cn-sidebar { width: 280px; }
.wide-nav.cn-sidebar[data-state="collapsed"] { width: 64px; }
.wide-nav.cn-sidebar { transition: width 400ms var(--ease-state); }
let collapsed = ctx.use_signal(false);
sidebar(collapsed).class("wide-nav")
Platforms and version
The component API is available in Blinc 0.6.0. The Live example runs in a dedicated worker and requires a compatible GPU surface. Preview and Code remain available when Live cannot start. The canvas has a descriptive label; a full screen-reader DOM mirror is not provided. The gallery integration is based on revision e2e55c24e078.
Recordings and live demos use the default Hybrid theme with bundled Noto Sans. The recording uses a 560 × 320 logical viewport at twice that resolution. Live follows its container and actual display density. Source base revision · Exact build source · Artifact versions and checksums · Font license.