Modal
Display a dialog over the page content.
use herogpui::components::modal::{Modal, ModalCloseTrigger, ModalSize};Modal on docs.rsSource at v0.14.0
Usage
Showing Sizes, live: HeroGPUI compiled to WebAssembly. The canvas has no accessibility tree; each example's description and code below carry the same information.
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Modal, ModalCloseTrigger, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w_full()
.gap(px(12.))
.children([
("md-size-xs", "Xs", ModalSize::Xs),
("md-size-sm", "Sm", ModalSize::Sm),
("md-size-md", "Md", ModalSize::Md),
("md-size-lg", "Lg", ModalSize::Lg),
("md-size-cover", "Cover", ModalSize::Cover),
("md-size-full", "Full", ModalSize::Full),
]
.into_iter()
.map(|(key, label, size)| {
let open = self.demo_overlay(key);
overlay_demo(
open,
key,
label,
Modal::new()
.id(key)
.is_open(open)
.size(size)
.title(format!("Size: {label}"))
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child("Every size shares one panel style."))
.on_open_change(cx.listener(
move |this, v: &bool, _, cx| {
this.set_demo_flag(key, *v);
cx.notify();
},
))
.into_any_element(),
cx,
)
}))Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Modal, ModalCloseTrigger, ModalPlacement, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w_full()
.gap(px(12.))
.children([
("md-place-auto", "Auto", ModalPlacement::Auto),
("md-place-center", "Center", ModalPlacement::Center),
("md-place-top", "Top", ModalPlacement::Top),
("md-place-bottom", "Bottom", ModalPlacement::Bottom),
]
.into_iter()
.map(|(key, label, placement)| {
let open = self.demo_overlay(key);
overlay_demo(
open,
key,
label,
Modal::new()
.id(key)
.is_open(open)
.placement(placement)
.title(format!("Placement: {label}"))
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child("The panel keeps its own size."))
.on_open_change(cx.listener(
move |this, v: &bool, _, cx| {
this.set_demo_flag(key, *v);
cx.notify();
},
))
.into_any_element(),
cx,
)
}))Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Modal, ModalCloseTrigger, ModalScroll, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w_full()
.gap(px(12.))
.children([
("md-scroll-inside", "Inside", ModalScroll::Inside),
("md-scroll-outside", "Outside", ModalScroll::Outside),
]
.into_iter()
.map(|(key, label, scroll)| {
let open = self.demo_overlay(key);
overlay_demo(
open,
key,
label,
Modal::new()
.id(key)
.is_open(open)
.scroll(scroll)
.title(format!("Scroll: {label}"))
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().flex().flex_col().gap(px(8.)).children(
(1..=12).map(|n| {
gpui::div().child(format!("Paragraph {n} of twelve."))
}),
))
.on_open_change(cx.listener(
move |this, v: &bool, _, cx| {
this.set_demo_flag(key, *v);
cx.notify();
},
))
.into_any_element(),
cx,
)
}))Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, Button, Modal, ModalCloseTrigger, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, App, Context, px};
use herogpui::gpui;
let md_controlled = self.demo_overlay("md-controlled");
gpui::div()
.flex()
.flex_col()
.w_full()
.gap(px(12.))
.children(vec![
para(
&format!(
"The flag lives with the caller: {}",
if md_controlled { "open" } else { "closed" }
),
cx,
),
overlay_demo(
self.demo_overlay("md-controlled"),
"md-controlled",
"Open (controlled)",
Modal::new()
.id("modal-controlled")
.is_open(md_controlled)
.title("Controlled")
.radius(px(12.))
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child("Closing reports through onOpenChange."))
.on_open_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("md-controlled", *v);
cx.notify();
}))
.into_any_element(),
cx,
),
])Also calls para, overlay_min_h, el_id, overlay_demo (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, InputType, Modal, ModalCloseTrigger, ModalSize, TextField, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
let md_form = self.demo_overlay("md-form");
overlay_demo(
self.demo_overlay("md-form"),
"md-form",
"Open form modal",
Modal::new()
.id("modal-form")
.is_open(md_form)
.title("Invite a teammate")
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(
gpui::div()
.flex()
.flex_col()
.gap(px(12.))
.child(
TextField::new(self.demo_text("md-form-name", "", cx))
.label("Name"),
)
.child(
TextField::new(self.demo_text("md-form-email", "", cx))
.label("Email")
.input_type(InputType::Email),
),
)
.footer_child(
Button::new("modal-form-send")
.label("Send invite")
.on_press(cx.listener(|this, _, _, cx| {
this.set_demo_flag("md-form", false);
cx.notify();
})),
)
.on_open_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("md-form", *v);
cx.notify();
}))
.into_any_element(),
cx,
)Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Avatar, Modal, ModalCloseTrigger, ModalSize};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let md_custom = self.demo_overlay("md-custom");
overlay_min_h(
gpui::div()
.relative()
.flex()
.flex_col()
.items_center()
.w_full(),
md_custom,
120.,
)
.child(
gpui::div()
.id("modal-custom-trigger")
.cursor_pointer()
.child(Avatar::new("modal-custom-avatar").name("Jane Doe"))
.on_click(cx.listener(|this, _, _, cx| {
this.set_demo_flag("md-custom", true);
cx.notify();
})),
)
.child(
Modal::new()
.id("modal-custom")
.is_open(md_custom)
.title("Jane Doe")
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child("Any element can open a modal."))
.on_open_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("md-custom", *v);
cx.notify();
})),
)Also calls overlay_min_h (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Backdrop, Button, Modal, ModalCloseTrigger, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w_full()
.gap(px(12.))
.children(herogpui::core::Backdrop::ALL
.iter()
.map(|backdrop| {
let key: &'static str = match backdrop {
herogpui::core::Backdrop::Opaque => "md-bd-opaque",
herogpui::core::Backdrop::Blur => "md-bd-blur",
herogpui::core::Backdrop::Transparent => "md-bd-transparent",
};
let open = self.demo_overlay(key);
overlay_demo(
open,
key,
backdrop.label(),
Modal::new()
.id(key)
.is_open(open)
.backdrop(*backdrop)
.title(format!("Backdrop: {}", backdrop.label()))
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child("The scrim behind the panel."))
.on_open_change(cx.listener(
move |this, v: &bool, _, cx| {
this.set_demo_flag(key, *v);
cx.notify();
},
))
.into_any_element(),
cx,
)
}))Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
`Backdrop::Blur` is the strongest variant the token set has; anything past it is the caller's own scrim. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Backdrop, Button, Modal, ModalCloseTrigger, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
let md_bd_custom = self.demo_overlay("md-bd-custom");
overlay_demo(
self.demo_overlay("md-bd-custom"),
"md-bd-custom",
"Open with a blurred backdrop",
Modal::new()
.id("modal-bd-custom")
.is_open(md_bd_custom)
.backdrop(Backdrop::Blur)
.title("Blurred")
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child("The page behind is blurred."))
.on_open_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("md-bd-custom", *v);
cx.notify();
}))
.into_any_element(),
cx,
)Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
`isDismissible` decides whether the backdrop closes it; `isKeyboardDismissDisabled` decides whether Escape does. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Modal, ModalCloseTrigger, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
let md_no_dismiss = self.demo_overlay("md-no-dismiss");
overlay_demo(
self.demo_overlay("md-no-dismiss"),
"md-no-dismiss",
"Open a non-dismissable modal",
Modal::new()
.id("modal-no-dismiss")
.is_open(md_no_dismiss)
.title("Confirm first")
.is_dismissible(false)
.is_keyboard_dismiss_disabled(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child(
"The backdrop and Escape are both inert; the composed X and the button still close.",
))
.footer_child(
Button::new("modal-no-dismiss-ok").label("Got it").on_press(
cx.listener(|this, _, _, cx| {
this.set_demo_flag("md-no-dismiss", false);
cx.notify();
}),
),
)
.on_open_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("md-no-dismiss", *v);
cx.notify();
}))
.into_any_element(),
cx,
)Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
The close affordance is spelled by composition: the `Close Methods` example closes through footer buttons and composes no close trigger, so the corner slot stays bare. Every other example composes the built-in close trigger for the corner X. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Modal, ModalCloseTrigger, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
let md_close = self.demo_overlay("md-close");
overlay_demo(
self.demo_overlay("md-close"),
"md-close",
"Open (no close trigger)",
Modal::new()
.id("modal-close")
.is_open(md_close)
.title("Close me")
.is_dismissible(true)
.child(
gpui::div().child(
"The corner slot is bare; the footer button closes."
)
)
.footer_child(
Button::new("modal-close-ok").label("Close").on_press(
cx.listener(|this, _, _, cx| {
this.set_demo_flag("md-close", false);
cx.notify();
}),
),
)
.on_open_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("md-close", *v);
cx.notify();
}))
.into_any_element(),
cx,
)Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
The motion is the one theme declares: the panel shrinks in from 105% over 250ms on `ease-out-quad` and leaves at 95% over 100ms. `Motion on` in the navbar switches it off, which is the `prefers-reduced-motion` path. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Modal, ModalCloseTrigger, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, Context, px};
use herogpui::gpui;
let md_anim = self.demo_overlay("md-anim");
overlay_demo(
self.demo_overlay("md-anim"),
"md-anim",
"Open and watch the panel",
Modal::new()
.id("modal-anim")
.is_open(md_anim)
.title("Animated")
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child("Close and reopen to see it again."))
.on_open_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("md-anim", *v);
cx.notify();
}))
.into_any_element(),
cx,
)Also calls overlay_min_h, el_id, overlay_demo (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Modal, ModalCloseTrigger, ModalSize, Variant, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let is_open = self.modal_open;
gpui::div()
.flex()
.flex_col()
.w_full()
.gap(px(12.))
.children(vec![
overlay_min_h(
gpui::div()
.relative()
.flex()
.flex_col()
.items_center()
.w_full(),
is_open,
280.,
)
.child(
Button::new("modal-open")
.label("Open modal")
.on_press(cx.listener(|this, _, _, cx| {
this.modal_open = true;
cx.notify();
})),
)
.child(
Modal::new()
.id("modal")
.is_open(is_open)
// `Modal.Icon` sits above the heading.
.icon(icons::MAIL)
.title("Create account")
.is_dismissible(true)
.child(ModalCloseTrigger::new())
.child(gpui::div().child("Sign up to get started with HeroGPUI."))
.footer_child(
Button::new("modal-cancel")
.label("Cancel")
.variant(Variant::Tertiary)
.on_press(cx.listener(|this, _, _, cx| {
this.modal_open = false;
cx.notify();
})),
)
.footer_child(Button::new("modal-ok").label("Sign up").on_press(
cx.listener(|this, _, _, cx| {
this.modal_open = false;
cx.notify();
}),
))
.on_close(cx.listener(|this, _, _, cx| {
this.modal_open = false;
cx.notify();
})),
)
.into_any_element(),
])Also calls overlay_min_h (included when you copy).
HeroGPUI extension, not a example: `CommandPalette` is a modal command search on the same overlay stack as `Modal`. Press Cmd-K (Ctrl-K off macOS) anywhere in the gallery, or the button below: type to filter every page and two appearance commands (each query word must match a label, keyword or group), Up and Down move the highlight, Enter runs it and Escape closes. The shell binds the shortcut in a root `on_key_down` with `is_command_palette_shortcut`. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, Button, Kbd, Modal, ModalCloseTrigger, ModalSize, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
Button::new("modal-command-palette-open")
.label("Open command palette")
.variant(Variant::Secondary)
.child(
gpui::div()
.flex()
.gap(px(2.))
.child(Kbd::new().child(if cfg!(target_os = "macos") { "⌘" } else { "Ctrl" }))
.child(Kbd::new().child("K")),
)
.on_press(cx.listener(|this, _, _, cx| {
this.command_palette_open = true;
cx.notify();
}))
.into_any_element(),
para(&format!("Last command: {}", self.command_palette_last), cx),
])Also calls para (included when you copy).
Helpers used by these examples
Plain functions over the public API, defined once here and copied with each example that calls them.
fn overlay_min_h(mut frame: gpui::Div, open: bool, height: f32) -> gpui::Div {
if open {
frame = frame.min_h(px(height));
}
frame
}fn el_id(s: String) -> gpui::ElementId {
gpui::ElementId::Name(s.into())
}/// One overlay demo: the trigger, and the panel it opens.
///
/// An overlay needs a positioned ancestor and enough height to show the panel,
/// and each demo owns its own open flag -- v3's pages show one variant per
/// example, so a shared flag would open all of them at once.
fn overlay_demo(
open: bool,
key: &'static str,
label: &str,
panel: AnyElement,
cx: &mut Context<'_, Gallery>,
) -> AnyElement {
// The panel is `absolute inset-0` inside this frame, and v3's body is
// `min-h-0 flex-1`, so a short frame squeezes the body to nothing.
// Reserve that height only while the panel is open; a closed trigger
// sitting in a 320px hole was the empty-card gap on overlay pages.
overlay_min_h(
gpui::div()
.relative()
.flex()
.flex_col()
.items_center()
.w_full(),
open,
320.,
)
.child(
Button::new(format!("{key}-open"))
.label(label.to_owned())
.variant(Variant::Secondary)
.on_press(cx.listener(move |this, _, _, cx| {
this.set_demo_flag(key, true);
cx.notify();
})),
)
.child(panel)
.into_any_element()
}fn para(text: &str, cx: &App) -> gpui::AnyElement {
let colors = cx.colors();
gpui::div()
.text_size(px(14.5))
.line_height(px(24.))
.text_color(colors.foreground)
.max_w(px(720.))
.child(text.to_owned())
.into_any_element()
}Anatomy
Rust types
Modal is assembled from these builders. The API reference lists each one.
Customization
Shared appearance belongs on ThemeBuilder::components and .recipe("name"). Typed builders change how Modal looks at one call site. Every builder also carries sx for placement, width and flex: it takes GPUI's own styling methods and refines them over the component's root after the theme, so an instance override wins. Behaviour builders live in the API reference below.
Styling
| Method | Values | Description |
|---|---|---|
backdrop(Backdrop)Partial | Backdrop::Opaque | Backdrop::Blur | Backdrop::Transparent | Backdrop overlay style; all three variants render, but gpui has no backdrop-filter, so Blur draws a lighter scrim rather than a blur. |
placement(ModalPlacement) | ModalPlacement::Auto | ModalPlacement::Center | ModalPlacement::Top | ModalPlacement::Bottom | Modal position on screen; Auto reads centered from v3's sm step up, which is a desktop app's only reading, while the mobile bottom-sheet branch is unreachable. |
size(ModalSize)Partial | ModalSize::Xs | ModalSize::Sm | ModalSize::Md | ModalSize::Lg | ModalSize::Cover | ModalSize::Full | Modal size variant; the four max-width steps match, Cover and Full fill the width and Full drops the shadow, but also stretches both to the viewport height and keeps Full square. |
// The one slot for caller-owned low-level styling: GPUI's
// styling methods, refined over the root element last.
.sx(|el| {
el.bg(gpui::rgba(0xffa500ff)) // background
.text_color(gpui::rgba(0x000000ff)) // text
.w(gpui::px(13.)) // size
.h(gpui::px(12.))
})API reference
Against HeroUI v3.2.6: 17 entries ported, 14 partial, 15 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.
Builders
| Builder | Values | Default | Description |
|---|---|---|---|
ParentElement::Partial | AnyElement | — | Trigger and container elements; the port's ParentElement::extend feeds the body instead, with the backdrop built in and the trigger left to the caller. |
is_open + on_open_changePartial | UseOverlayStateReturn | — | Controlled overlay state; takes the two builders the hook would feed rather than the hook return. |
backdrop(Partial | Backdrop:: | Backdrop:: | Backdrop overlay style; all three variants render, but gpui has no backdrop-filter, so Blur draws a lighter scrim rather than a blur. |
is_dismissible(Partial | true | | true | Close on backdrop click; gates outside-press dismissal on it. The composed close trigger is not the backdrop: like it renders and closes regardless of this flag. |
is_keyboard_dismiss_disabled( | true | | false | Disable ESC key to close. |
is_open( | true | | — | Controlled open state. |
on_open_change( | Fn( | — | Open state change handler; fires with false on every dismissal path. |
placement( | ModalPlacement:: | ModalPlacement:: | Modal position on screen; Auto reads centered from v3's sm step up, which is a desktop app's only reading, while the mobile bottom-sheet branch is unreachable. |
scroll(Partial | ModalScroll:: | ModalScroll:: | Scroll behavior; both modes scroll, but v3's Inside caps the dialog at the container's content box while caps at the viewport, which only differs when the content overflows. |
size(Partial | ModalSize:: | ModalSize:: | Modal size variant; the four max-width steps match, Cover and Full fill the width and Full drops the shadow, but also stretches both to the viewport height and keeps Full square. |
title + footer_child + extend bodyPartial | AnyElement | | — | Content or render function; feeds the body through ParentElement::extend, the title through title and the footer through footer_child, and the close argument is not a render value — the close affordance is the composed ModalCloseTrigger part. |
title(Partial | AnyElement | — | Header content; renders its header only when a title or icon is set, and both share the monolithic prop. |
ParentElement::Partial | AnyElement | — | Body content; ParentElement::extend feeds the body, but v3's per-part prop surface is not exposed. |
footer_child( | AnyElement | — | Footer content; repeated footer_child calls stack actions in the end-aligned row. |
new( | AnyElement | — | The modal composes this part as a child: without children it draws the built-in CloseButton wired to the modal's dismissal paths, and custom children replace the CloseButton's glyph while the press stays automatically wired to close — hands the composed part v3's slot="close" chaining. |
Not ported14 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
Modal.Trigger.children | Custom trigger content; renders no trigger slot, so callers drive is_open from their own control. |
Modal.Trigger.className | Styles for the trigger. |
Modal.Backdrop.className | Backdrop styles. |
Modal.Backdrop.UNSTABLE_portalContainer | Custom portal container; renders the overlay inline in the element tree, and gpui has no portal. |
Modal.Container.className | Container styles. |
Modal.Dialog.className | Styles for the dialog. |
Modal.Dialog.role | ARIA role override; gpui exposes no accessibility tree. |
Modal.Dialog.aria-label | Accessibility label; gpui exposes no accessibility tree. |
Modal.Dialog.aria-labelledby | ID of the label element; gpui exposes no accessibility tree. |
Modal.Dialog.aria-describedby | ID of the description element; gpui exposes no accessibility tree. |
Modal.Header.className | Styles for the header. |
Modal.Body.className | Styles for the body. |
Modal.Footer.className | Styles for the footer. |
Modal.CloseTrigger.className | Close trigger styles. |
Parts
| Part | Description |
|---|---|
Modal | Overlay root that owns open state and the focus trap; configured callbacks receive every enabled dismissal path. |
Modal | Scrim behind the panel, dimmed but press-less; outside presses dismiss through the panel's own bounds. |
Modal | Window-pinning wrapper that applies the placement alignment, the scroll mode and the 40px inset. |
Modal | The panel itself: overlay surface, 24px inset, viewport cap for Inside scroll. |
ModalPartial | Title column with the icon; the monolithic port renders it only when a title or icon is set, without a composable children slot. |
ModalPartial | Glyph box above the heading; takes an asset path and a role colour rather than composed children. |
ModalPartial | Title text; accepts a string via the title builder instead of composed children. |
Modal | Content area fed by ParentElement::extend children, scrollable in Inside mode. |
Modal | End-aligned action row fed by repeated footer_child calls. |
ModalCloseTrigger | Composed part pinned 16px from the top end: the default content is the built-in CloseButton wired to the modal's dismissal paths regardless of is_dismissible, and custom children replace the glyph while staying wired to close. With no dismissal callback to wire the part draws nothing. |
Not ported1 HeroUI entry not ported
| HeroUI v3 | Why it is not ported |
|---|---|
Modal.Trigger | Element that opens the modal; renders no trigger slot, so callers drive is_open from their own control. |
States
| State | Builder | Description |
|---|---|---|
FocusPartial | CloseButton tab_stop_handle ring | Applied to the trigger, the dialog and the close button; the close button rings from its tab stop and the dialog claims the panel's focus handle, while the trigger is not a modal part here. |
Hover | CloseButton hover surface | Applied to the close button on hover. |
ActivePartial | CloseButton .active centered root-bounds shrink | Applied to the trigger and close button when pressed; the close button uses a centered root-bounds shrink while fixed child content remains unscaled, and the absent trigger has no surface. |
Entering | OverlayPhase::Open branch | Applied during the modal opening animation; the panel zooms in from 105% at 250ms. |
Exiting | overlay_phase::Exiting | Applied during the modal closing animation; the panel and scrim stay mounted for the 100ms exit. |
Placement | ModalPlacement anchored match | Auto, top, center and bottom anchor the dialog and choose the container alignment. |