Layout Components
Avatars, separators, scroll areas and expandable sections provide structure
around application content. Install cn_bundle() as described in
Getting Started. Examples use these imports and the application
builder's ctx:
#![allow(unused)] fn main() { use blinc::cn::prelude::*; use blinc::layout::prelude::{div, img, text}; }
Avatar
An image can fall back to initials:
#![allow(unused)] fn main() { avatar().src("user.jpg").alt("Jane Doe").fallback("JD") }
Sizes are ExtraSmall (24px), Small (32px), Medium (40px, the default),
Large (48px) and ExtraLarge (64px):
#![allow(unused)] fn main() { div().flex_row().gap(2.0) .child(avatar().fallback("JD").size(AvatarSize::Small)) .child(avatar().fallback("AL").size(AvatarSize::Large)) }
.online(), .offline(), .away() and .busy() add a status dot:
#![allow(unused)] fn main() { avatar().fallback("JD").online() }
The root is .cn-avatar; its fill and initials use --cn-avatar-bg and
--cn-avatar-fg. An empty avatar shows .cn-avatar-placeholder. The status
dot is .cn-avatar-status with data-status.
Avatar Group
#![allow(unused)] fn main() { avatar_group() .max(3) .child(avatar().fallback("JD")) .child(avatar().fallback("AL")) .child(avatar().fallback("MK")) .child(avatar().fallback("RS")) .child(avatar().fallback("TW")) }
This shows three avatars and a +2 chip. The group is .cn-avatar-group;
the overflow chip is .cn-avatar-group__more. Their rings and fills are CSS.
Separator
The default separator is horizontal. Set an explicit height on a vertical separator:
#![allow(unused)] fn main() { div().flex_row().items_center().gap(2.0) .child(text("Account")) .child(separator().vertical().h(24.0)) .child(text("Settings")) }
A labelled horizontal divider uses flexible containers around the lines:
#![allow(unused)] fn main() { div().flex_row().items_center().gap(2.0) .child(div().flex_grow().child(separator())) .child(muted("or")) .child(div().flex_grow().child(separator())) }
The separator carries data-orientation. Its .cn-separator rule reads
--cn-separator-color.
Aspect Ratio
Wrap content in a fixed ratio:
#![allow(unused)] fn main() { aspect_ratio(16.0 / 9.0) .w(320.0) .child(img("video-thumbnail.jpg").cover()) }
Convenience builders include aspect_ratio_16_9(), aspect_ratio_4_3(),
aspect_ratio_square(), aspect_ratio_9_16() and aspect_ratio_21_9().
CSS aspect-ratio changes the container's native layout constraint. Set one dimension and let the other follow it; setting both width and height explicitly fixes both dimensions.
.cn-aspect-ratio {
width: 240px;
aspect-ratio: 4 / 3;
}
The gallery compares all eight presets, including 3:2, 2:3 and 3:4. Values may be a positive number, a positive fraction such as 16 / 9, or auto to clear the constraint.
Scroll Area
#![allow(unused)] fn main() { scroll_area().h(240.0).child( div().flex_col().gap(2.0) .children((0..50).map(|i| text(format!("Item {i}")))), ) }
Use .horizontal() or .both_directions() to change the scroll direction:
#![allow(unused)] fn main() { scroll_area().horizontal().w(320.0).h(120.0).child( div().flex_row().gap(2.0) .children((0..10).map(|i| { div().w(150.0).flex_shrink_0() .child(card().child(text(format!("Card {i}")))) })), ) }
.scrollbar(ScrollbarVisibility::Always) keeps the bars visible;
Auto is the default. Scrollbar thumb and track colours currently use
.thumb_color(..) and .track_color(..) rather than CSS scrollbar-color.
Collapsible
Pass a Signal<bool> to both the trigger and content:
#![allow(unused)] fn main() { let open = ctx.use_signal(false); div().flex_col() .child(CollapsibleTrigger::new("Show details", open)) .child(collapsible(open).content(|| { div().child(text("Details remain mounted while this section folds.")) })) }
Writing open.set(true) from an event or application callback opens the same
section. collapsible_section combines the trigger and body:
#![allow(unused)] fn main() { let open = ctx.use_signal(false); collapsible_section("Details", open, || { div().child(text("Additional information")) }) }
There are no separate collapsible_trigger() or collapsible_content()
functions. The parts are .cn-collapsible-trigger, .cn-collapsible-content
and .cn-chevron; their data-state is open or closed. CSS sets the
closed height and chevron transform, with theme-timed expansion.
Accordion
Add each section with a key, label and body builder. One section opens at a time by default:
#![allow(unused)] fn main() { accordion() .default_open("start") .item("start", "How do I get started?", || { div().child(text("Install Blinc and create an application.")) }) .item("toolchain", "Which toolchain do I need?", || { div().child(text("Blinc 0.6 requires Rust 1.89 or newer.")) }) }
.multi_open() allows several sections to stay open:
#![allow(unused)] fn main() { accordion().multi_open() .item("account", "Account", || div().child(text("Account details"))) .item("billing", "Billing", || div().child(text("Billing details"))) }
For application-controlled sections, use .item_with_state(...):
#![allow(unused)] fn main() { let open = ctx.use_signal(true); accordion().item_with_state("help", "Help", open, || { div().child(text("The application can open or close this section.")) }) }
Each section's trigger and body use .cn-accordion-trigger and
.cn-accordion-content, with data-state="open" or "closed".
Content wrappers and spacing belong to the application. Accordion renders the
body returned by the closure directly; add your own padded container when needed:
#![allow(unused)] fn main() { accordion().item("help", "Help", || { div().class("help-copy").child(text("How can we help?")) }) }
.help-copy { padding: 12px; }
Keep padding on that caller-owned container so .cn-accordion-content can fold
to zero height.
Override those rules instead of selecting colours in the body builder.
Resizable
Panel sizes and limits are pixels. A flex_grow() panel takes the remaining
space, and the group inserts its handles between panels:
#![allow(unused)] fn main() { resizable_group().horizontal() .panel(resizable_panel() .default_size(200.0).min_size(100.0).max_size(400.0) .child(text("Left panel"))) .panel(resizable_panel().flex_grow().child(text("Right panel"))) .on_resize(|sizes| { let _ = sizes; }) }
A vertical group uses the same panel builder:
#![allow(unused)] fn main() { resizable_group().vertical() .panel(resizable_panel().default_size(150.0).child(text("Top panel"))) .panel(resizable_panel().flex_grow().child(text("Bottom panel"))) }
The .cn-resizable-handle contains .cn-resizable-line. Its hover and
data-state="dragging" rules control the line's appearance. There is no
standalone resizable_handle() builder.
Split Pane Example
Nested groups combine horizontal navigation with vertically split content:
#![allow(unused)] fn main() { resizable_group().horizontal() .panel(resizable_panel().default_size(200.0).min_size(120.0) .child(tree_view().node("src", "src", |n| { n.expanded().child("main", "main.rs", |n| n) }))) .panel(resizable_panel().flex_grow().child( resizable_group().vertical() .panel(resizable_panel().flex_grow().child(text("Editor area"))) .panel(resizable_panel().default_size(120.0) .child(text("Output area"))), )) }
Place the group in a parent with a definite width and height so its flexible panels have space to divide.