Sheet
A bottom sharing panel with project members, guest permissions and a clear completion action.

use blinc_cn as cn;
use blinc_core::reactive::Signal;
use blinc_layout::prelude::*;
pub fn sheet_demo(confirmed: Signal<bool>) -> Div {
let access = confirmed;
div().class("demo-stack").class("demo-stack--center").child(
cn::button("Share project")
.variant(cn::ButtonVariant::Outline)
.on_click(move |_| {
cn::sheet_bottom()
.size(cn::SheetSize::Large)
.title("Share project")
.description("Manage access to Component library.")
.content(move || {
let mut members = div().class("demo-share-members");
for (initials, name, detail) in
[("AL", "Alex Lee", "Project owner"), ("MJ", "Maya Jones", "Can edit")]
{
members = members.child(
div()
.class("demo-share-member")
.child(cn::avatar().fallback(initials))
.child(
div()
.class("demo-member-copy")
.child(text(name).class("demo-label"))
.child(text(detail).class("demo-caption")),
),
);
}
div()
.class("demo-share-content")
.child(members)
.child(cn::separator())
.child(
div()
.class("demo-share-permission")
.child(cn::icon(blinc_icons::icons::GLOBE).size(cn::IconSize::Small))
.child(
div()
.class("demo-member-copy")
.class("demo-share-permission-copy")
.child(text("Anyone with the link").class("demo-label"))
.child(text("Allow guests to view this project").class("demo-caption")),
)
.child(cn::switch(access).label("Guests")),
)
})
.footer(|| {
div().w_full().flex_row().justify_end().child(
cn::button("Done")
.on_click(|_| {
let handle = blinc_layout::overlay_state::overlay_stack()
.lock()
.ok()
.and_then(|s| s.iter_top_down().next().map(|e| e.handle));
if let Some(handle) = handle {
handle.close();
}
})
.build_component()
.class("demo-share-done"),
)
})
.show();
})
.build_component()
.id("demo-action"),
)
}
@layer components {
.cn-sheet {
font-family: var(--font-sans);
}
.cn-sheet-close:focus-visible {
outline: 2px solid var(--focus-ring);
outline-offset: 2px;
}
.cn-sheet {
color: var(--text-primary);
box-shadow: var(--shadow-xl);
}
.cn-sheet-title {
color: var(--text-primary);
}
.cn-sheet-description {
color: var(--text-secondary);
}
.cn-sheet-separator {
background: var(--border);
}
.cn-sheet-close {
color: var(--text-secondary);
border-radius: var(--radius-md);
transition:
background-color var(--duration-fast) var(--ease-state),
color var(--duration-fast) var(--ease-state);
}
.cn-sheet-close:hover {
background: color-mix(in srgb, var(--text-primary) 8%, transparent);
color: var(--text-primary);
}
.cn-sheet {
background: var(--cn-sheet-bg, var(--surface-elevated));
border: 1px solid var(--cn-sheet-border, var(--border));
}
@keyframes cn-slide-from-right {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes cn-slide-to-right {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
@keyframes cn-slide-from-left {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
@keyframes cn-slide-to-left {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@keyframes cn-slide-from-bottom {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
@keyframes cn-slide-to-bottom {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
@keyframes cn-slide-from-top {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
@keyframes cn-slide-to-top {
from {
transform: translateY(0);
}
to {
transform: translateY(-100%);
}
}
.cn-sheet-motion-right[data-motion="enter"] {
animation: cn-slide-from-right var(--duration-slow) var(--ease-sheet);
}
.cn-sheet-motion-right[data-motion="exit"] {
animation: cn-slide-to-right var(--duration-normal) var(--ease-sheet) forwards;
}
.cn-sheet-motion-left[data-motion="enter"] {
animation: cn-slide-from-left var(--duration-slow) var(--ease-sheet);
}
.cn-sheet-motion-left[data-motion="exit"] {
animation: cn-slide-to-left var(--duration-normal) var(--ease-sheet) forwards;
}
.cn-sheet-motion-bottom[data-motion="enter"] {
animation: cn-slide-from-bottom var(--duration-slow) var(--ease-sheet);
}
.cn-sheet-motion-bottom[data-motion="exit"] {
animation: cn-slide-to-bottom var(--duration-normal) var(--ease-sheet) forwards;
}
.cn-sheet-motion-top[data-motion="enter"] {
animation: cn-slide-from-top var(--duration-slow) var(--ease-sheet);
}
.cn-sheet-motion-top[data-motion="exit"] {
animation: cn-slide-to-top var(--duration-normal) var(--ease-sheet) forwards;
}
}
Usage and properties
Open supporting content in a panel at the edge of the view. 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 |
|---|---|---|---|
side | sheet helper | Construction | sheet_right() |
title / description / content | strings and child builder | Construction | Workspace settings |
Keyboard and interaction
Tab stays inside the open sheet and Escape closes it. Focus returns to the trigger.
Customizing it
Add application CSS after the default component layer. These selectors are the restyling surface for this component:
.cn-sheet.cn-sheet-title.cn-sheet-description.cn-sheet-close.cn-sheet-separator.demo-share-content.demo-share-member.demo-share-permission.cn-sheet-header.cn-sheet-content.cn-sheet-footer.demo-share-done.demo-share-permission-copy
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.
A sheet can enter from any edge. The default is the right edge:
button("Open settings").on_click(|_| {
sheet()
.side(SheetSide::Right)
.size(SheetSize::Medium)
.title("Settings")
.description("Preferences for this workspace.")
.content(|| div().child(text("Settings content")))
.footer(|| div().child(muted("Changes are saved automatically.")))
.show();
})
sheet_left(), sheet_right(), sheet_top() and sheet_bottom() select the
edge directly. SheetSize is Small, Medium, Large or Full.
.show_close(false) hides the close button; .on_close(|| ...) observes
dismissal. The title and description accept reactive values.
Sheet and Drawer close controls are standard ghost icon buttons. Their existing
.cn-sheet-close and .cn-drawer-close classes remain available for styling.
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 × 480 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.