Overlays

Dialog

A focused confirmation step, with dismissal and trigger focus restoration.

Open ↗
Dialog interaction recorded with the Blinc headless renderer
Hybrid · dark · native recording · 2×

Usage and properties

Build and show the dialog from the trigger handler. The returned OverlayHandle supports explicit close. Dialog parts live in the overlay root, so selectors beneath .catalog-demo do not reach them.

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.

PropertyTypeUpdate modelThis example
title / descriptionStringBoundPublish this draft?
confirm_text / cancel_textStringBoundPublish / Keep editing
on_confirmFn()CallbackSet the published Signal
on_cancel / on_closeFn()CallbackOptional dismissal feedback
show()OverlayHandleLifecycleCalled from the trigger

Keyboard and interaction

Tab stays within the active dialog. Escape and backdrop click dismiss it. Closing restores focus to the trigger. Confirmation and cancellation callbacks take no arguments.

Customizing it

Add application CSS after the default component layer. These selectors are the restyling surface for this component:

  • .cn-dialog
  • .cn-dialog-title
  • .cn-dialog-description
  • .cn-dialog-motion

Theme and component variables: --surface-elevated, --text-primary, --text-secondary, --border, --radius-lg, --focus-ring.

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.

Dialogs ask for a decision. Sheets and drawers place content beside the main view. Install cn_bundle() as described in Getting Started to load their styles and motion.

The examples use these imports. ctx is the WindowedContext supplied to the application builder:

use blinc::cn::prelude::*;
use blinc::layout::prelude::{div, text};

Open a Dialog

Configure a builder and call .show() from an event handler:

button("Open dialog").on_click(|_| {
    dialog()
        .title("Continue?")
        .description("Review the details before continuing.")
        .content(|| div().child(text("Your current selection will be used.")))
        .confirm_text("Continue")
        .on_confirm(|| {
            toast_success("Confirmed").show();
        })
        .show();
})

.show() returns an OverlayHandle. Keep it when the application needs to call handle.close(). The default footer has Cancel and Confirm buttons, which close the dialog. Escape and a backdrop click also dismiss it.

on_confirm and on_cancel report their respective buttons. on_close reports dismissal by any route, including Escape and the backdrop. These callbacks take no arguments.

Confirmation Dialog

Use confirm_destructive(true) for a destructive action:

let deleted = ctx.use_signal(false);

button("Delete draft")
    .variant(ButtonVariant::Destructive)
    .on_click(move |_| {
        dialog()
            .title("Delete draft?")
            .description("This removes the draft from this session.")
            .confirm_text("Delete")
            .cancel_text("Keep draft")
            .confirm_destructive(true)
            .on_confirm(move || deleted.set(true))
            .show();
    })

alert_dialog() is an acknowledgement dialog: it hides Cancel. It is not a separate set of trigger, content or action components.

button("Show notice").on_click(|_| {
    alert_dialog()
        .title("Import complete")
        .description("Your files are ready to use.")
        .confirm_text("OK")
        .size(DialogSize::Small)
        .show();
})

Form Dialog

Create editor data once and clone its handle into the deferred body. Input changes can update a Signal that the confirmation callback reads:

use blinc::layout::widgets::text_input::text_input_data;

let name_data = text_input_data();
let name = ctx.use_signal(String::new());

button("Edit profile").on_click(move |_| {
    let name_data = name_data.clone();
    dialog()
        .title("Edit profile")
        .description("Update the name shown on your profile.")
        .content(move || {
            div().child(
                input(&name_data)
                    .label("Name")
                    .placeholder("Your name")
                    .on_change(move |value| name.set(value.to_string())),
            )
        })
        .confirm_text("Save")
        .on_confirm(move || {
            toast_success("Profile saved")
                .description(name.get())
                .show();
        })
        .show();
})

The default footer suits this form. .footer(|| div()...) replaces the entire footer, including its automatic Cancel and Confirm buttons; custom actions must arrange dismissal themselves.

Text That Changes

Titles, descriptions, confirm_text and cancel_text accept strings, Signals or Computeds. Bind them before showing the dialog:

let remaining = ctx.use_signal("3 files".to_string());

button("Review files").on_click(move |_| {
    dialog()
        .title("Selected files")
        .description(remaining)
        .content(move || {
            div().child(button("Remove one from selection").on_click(move |_| {
                remaining.set("2 files".to_string());
            }))
        })
        .confirm_text("Done")
        .show();
})

The description changes in place while the dialog remains open.

Sheet

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.

Drawer

A drawer is a left or right navigation panel, with the left as the default:

button("Open navigation").on_click(|_| {
    drawer_left()
        .size(DrawerSize::Medium)
        .title("Workspace")
        .child(|| div().child(text("Projects and recent activity")))
        .footer(|| div().child(muted("Signed in as Jane")))
        .show();
})

DrawerSide has Left and Right; DrawerSize has Narrow, Medium and Wide. Use a bottom sheet for a panel entering from below. .header(|| ...) replaces the title header. Each .child(|| ...) adds a body section.

When opened with the keyboard, a dialog focuses its first enabled control. Tab and Shift+Tab stay inside the topmost dialog or nested popup. Closing restores focus to the opening control if it still exists and is enabled. Pointer focus uses the same target without a keyboard outline; keyboard navigation uses the shared :focus-visible rules.

Styling and Motion

The panel classes are .cn-dialog, .cn-sheet and .cn-drawer. Dialog text uses .cn-dialog-title and .cn-dialog-description; sheets use .cn-sheet-title and .cn-sheet-description; drawers use .cn-drawer-title. Add a panel class with .class("...") and override it in application CSS.

The overlay wrapper carries data-motion="enter" or "exit" and remains mounted while its exit animation plays. Child transforms stay inside that motion, so rotated icons slide with the panel. The backdrop is .overlay-backdrop.

OverlayMotion wrapperDefault motion
Dialog.cn-dialog-motioncn-rise-in, cn-rise-out
Sheet.cn-sheet-motion-{left,right,top,bottom}cn-slide-from-{side}, cn-slide-to-{side}
Drawer.cn-drawer-motion-{left,right}cn-slide-from-{side}, cn-slide-to-{side}

Timing comes from theme tokens. Change it through rules:

.cn-dialog-motion[data-motion="enter"] {
    animation: cn-rise-in 400ms var(--ease-sheet);
}
.cn-sheet-motion-right[data-motion="exit"] {
    animation: cn-slide-to-right var(--duration-fast) var(--ease-in) forwards;
}

Builder Reference

BuilderContentActionsPresentation
dialog()title, description, contentconfirm_text, cancel_text, on_confirm, on_cancel, on_close, hide_cancel, footersize, confirm_destructive, class, id
alert_dialog()title, descriptionconfirm_text, on_confirmsize
sheet()title, description, content, footeron_close, show_closeside, size
drawer()title, header, child, child_builder, footeron_close, show_closeside, size, class, id

All four open with .show(). Their builders have no .open(signal) or .on_open_change(...) API. Dialog sizes are Small (400px maximum), Medium (500px), Large (600px) and Full (800px).

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.