Skip to content
PorabuildHeroGPUI component library
v0.14.0 · Apache-2.0

Overlays

Modal

Display a dialog over the page content.

Rust
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.

Sizes

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Placement

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Scroll Behavior

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Controlled State

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

With Form

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Custom Trigger

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Backdrop Variants

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Custom Backdrop

`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>.

Rust
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).

Dismiss Behavior

`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>.

Rust
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).

Close Methods

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>.

Rust
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).

Custom Animations

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>.

Rust
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).

Usage

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Command Palette

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>.

Rust
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.

overlay_min_h
fn overlay_min_h(mut frame: gpui::Div, open: bool, height: f32) -> gpui::Div {
    if open {
        frame = frame.min_h(px(height));
    }
    frame
}
el_id
fn el_id(s: String) -> gpui::ElementId {
    gpui::ElementId::Name(s.into())
}
overlay_demo
/// 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()
}
para
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

ModalModalCloseTrigger

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

Modal styling
MethodValuesDescription
backdrop(Backdrop)PartialBackdrop::Opaque | Backdrop::Blur | Backdrop::TransparentBackdrop 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::BottomModal 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)PartialModalSize::Xs | ModalSize::Sm | ModalSize::Md | ModalSize::Lg | ModalSize::Cover | ModalSize::FullModal 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.
Rust
// 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

Modal builders
BuilderValuesDefaultDescription
ParentElement::extend body onlyPartialAnyElement—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_changePartialUseOverlayStateReturn—Controlled overlay state; takes the two builders the hook would feed rather than the hook return.
backdrop(Backdrop)PartialBackdrop::Opaque | Backdrop::Blur | Backdrop::TransparentBackdrop::OpaqueBackdrop overlay style; all three variants render, but gpui has no backdrop-filter, so Blur draws a lighter scrim rather than a blur.
is_dismissible(bool)Partialtrue | falsetrueClose 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(bool)true | falsefalseDisable ESC key to close.
is_open(bool)true | false—Controlled open state.
on_open_change(callback)Fn(isOpen: bool) -> ()—Open state change handler; fires with false on every dismissal path.
placement(ModalPlacement)ModalPlacement::Auto | ModalPlacement::Center | ModalPlacement::Top | ModalPlacement::BottomModalPlacement::AutoModal 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(ModalScroll)PartialModalScroll::Inside | ModalScroll::OutsideModalScroll::InsideScroll 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(ModalSize)PartialModalSize::Xs | ModalSize::Sm | ModalSize::Md | ModalSize::Lg | ModalSize::Cover | ModalSize::FullModalSize::MdModal 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 bodyPartialAnyElement | Fn({close}) -> 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(value)PartialAnyElement—Header content; renders its header only when a title or icon is set, and both share the monolithic prop.
ParentElement::extendPartialAnyElement—Body content; ParentElement::extend feeds the body, but v3's per-part prop surface is not exposed.
footer_child(el)AnyElement—Footer content; repeated footer_child calls stack actions in the end-aligned row.
new() + ParentElement childrenAnyElement—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
Modal builders not ported
HeroUI v3Why it is not ported
Modal.Trigger.childrenCustom trigger content; renders no trigger slot, so callers drive is_open from their own control.
Modal.Trigger.classNameStyles for the trigger.
Modal.Backdrop.classNameBackdrop styles.
Modal.Backdrop.UNSTABLE_portalContainerCustom portal container; renders the overlay inline in the element tree, and gpui has no portal.
Modal.Container.classNameContainer styles.
Modal.Dialog.classNameStyles for the dialog.
Modal.Dialog.roleARIA role override; gpui exposes no accessibility tree.
Modal.Dialog.aria-labelAccessibility label; gpui exposes no accessibility tree.
Modal.Dialog.aria-labelledbyID of the label element; gpui exposes no accessibility tree.
Modal.Dialog.aria-describedbyID of the description element; gpui exposes no accessibility tree.
Modal.Header.classNameStyles for the header.
Modal.Body.classNameStyles for the body.
Modal.Footer.classNameStyles for the footer.
Modal.CloseTrigger.classNameClose trigger styles.

Parts

Modal parts
PartDescription
ModalOverlay root that owns open state and the focus trap; configured callbacks receive every enabled dismissal path.
ModalScrim behind the panel, dimmed but press-less; outside presses dismiss through the panel's own bounds.
ModalWindow-pinning wrapper that applies the placement alignment, the scroll mode and the 40px inset.
ModalThe panel itself: overlay surface, 24px inset, viewport cap for Inside scroll.
ModalPartialTitle column with the icon; the monolithic port renders it only when a title or icon is set, without a composable children slot.
ModalPartialGlyph box above the heading; takes an asset path and a role colour rather than composed children.
ModalPartialTitle text; accepts a string via the title builder instead of composed children.
ModalContent area fed by ParentElement::extend children, scrollable in Inside mode.
ModalEnd-aligned action row fed by repeated footer_child calls.
ModalCloseTriggerComposed 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
Modal parts not ported
HeroUI v3Why it is not ported
Modal.TriggerElement that opens the modal; renders no trigger slot, so callers drive is_open from their own control.

States

Modal states
StateBuilderDescription
FocusPartialCloseButton tab_stop_handle ringApplied 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.
HoverCloseButton hover surfaceApplied to the close button on hover.
ActivePartialCloseButton .active centered root-bounds shrinkApplied 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.
EnteringOverlayPhase::Open branchApplied during the modal opening animation; the panel zooms in from 105% at 250ms.
Exitingoverlay_phase::ExitingApplied during the modal closing animation; the panel and scrim stay mounted for the 100ms exit.
PlacementModalPlacement anchored matchAuto, top, center and bottom anchor the dialog and choose the container alignment.