Skip to content

Surfaces

Modal

Bit.BlazorUIDialogPopup

Modal is a temporary surface that covers the page and takes it over until it is dealt with. It hosts whatever is put in it - a form, a license agreement, a confirmation - over an overlay that takes the clicks meant for the page behind it, and it behaves the way a dialog is expected to: the focus moves into it when it opens, Tab keeps cycling inside it while it is open, the page behind it stops scrolling, Escape and a click on the overlay dismiss it, and the focus goes back to whatever opened it once it closes. Each of those can be turned off on its own. It brings a header, body and footer chrome for the surfaces that want one, it can be placed anywhere on the screen or over the element it was declared inside of, it can be dragged around and left modeless, it is as big as its content unless it is given a width, a height or a cap on either, it can be driven by binding IsOpen or through its Open, Close and Toggle methods, and it can be kept in the page while closed so the state of what is inside it survives.

Notes

BitProModal, which used to carry the advanced half of this component in the Bit.BlazorUI.Extras package, has been merged into BitModal: the header, body and footer chrome, the positioning, the dragging, the modeless mode and the scroll handling are all parameters of BitModal now, and the Extras package no longer ships a modal of its own. Replace BitProModal with BitModal, BitProModalService with BitModalService, and the matching Container, Reference, Parameters and ClassStyles types with theirs.

Four things changed shape rather than name. ShowOverlay is gone: Modeless is what a Modal that leaves the page behind it usable says now, and it stands down the modality the Modal reports and the hold it takes on the page along with the overlay. A BitProModal always put its content in a padded, scrolling body; BitModal renders the content on its own unless a part of the chrome - a header, a footer, a close button - asks for one, so a migrated Modal that relied on that padding wants padding of its own. A Modal shown through the service now closes when the app navigates to another path, which CloseOnNavigation turns off. And Blocking blocks the pointer only: Escape still dismisses a blocking Modal unless NoDismissOnEscape says otherwise, so that a keyboard user is never left without a way out.

To show a Modal from anywhere in the app rather than from the markup of a page, use the BitModalService, which renders through a single BitModalContainer mounted in the layout.

Usage

Every example is live. Open its code to see exactly what produced the component running underneath.

Basic

IsOpen is what shows the Modal, and it is two-way bound so that the Modal can close itself: clicking the overlay or pressing Escape sets it back to false. Nothing of the Modal is in the page while it is closed, so the content is built when it opens and taken away again when it closes - unless it is told to stay.

While it is open the focus is inside it, Tab cycles within it, and closing it hands the focus back to the button that opened it - so a keyboard user never lands behind the overlay.

NoBorder takes away the accent line the Modal wears along its top edge.

Customizing content

A Modal given nothing but its content is only a surface: everything inside it - a header with a close button, a body, a footer - is the consumer's own markup, laid out however that markup wants. The section below is the other way round, where the Modal brings those three parts itself. Content taller than the screen scrolls inside the Modal rather than running off the edge of it, so all of it stays reachable however long it is.

Header & Footer

The Modal also brings a chrome of its own, for the surfaces that would otherwise build the same three parts by hand: HeaderText (or a Header template) puts a title bar at the top, FooterText (or a Footer template) an action bar at the bottom, and the content in between becomes the body. ShowCloseButton adds the close button to the header, and closes the Modal without a handler of its own.

The chrome is opt-in: a Modal given none of these parameters renders its content bare, exactly as the section above does. Once any of them is set, the surface becomes a column - the header and the footer stay put while the body scrolls between them - and the content goes into the body, whether it is passed as the child content or as the Body template.

Blocking

Blocking takes the light dismiss away: a click on the overlay no longer closes the Modal, so it has to be dismissed through an explicit action of its own - a close button, a Cancel, a confirmation. Use it where dismissing by accident would lose something, and give the Modal a visible way out whenever you do.

Only the pointer is blocked. Escape still dismisses a blocking Modal, since a keyboard user has no other way out unless one is offered; NoDismissOnEscape (next section) is what takes that away as well. A click the Modal turns down is answered with a short pulse rather than with nothing at all, so that a surface waiting to be dealt with never reads as a page that stopped responding. OnOverlayClick is where that click can be reacted to.

Keyboard dismiss

Escape dismisses the Modal from anywhere inside it, which is what the dialog pattern asks of every modal surface. NoDismissOnEscape turns that off for the Modals that must not be left by accident - an unsaved form, a step of a flow that has to be answered. Combined with Blocking it makes a Modal that can only be dismissed by its own actions.

The key is handled where the keyboard is, so it works because the focus is inside the Modal; a Modal that opted out of the focus handling with NoAutoFocus only sees Escape once something inside it has been focused. OnEscapeKeyDown is invoked for every Escape, including the ones a Modal refuses to be dismissed by - which, like a turned-down overlay click, is answered with a short pulse.

Focus management

A Modal opens with the focus inside it and closes by handing it back to whatever opened it, and while it is open Tab and Shift+Tab keep cycling within it instead of walking off into the page behind the overlay. Three parameters opt out of the three halves of that: NoAutoFocus leaves the focus where it was, NoFocusTrap lets Tab leave the Modal, and NoRestoreFocus leaves the focus wherever it ended up when the Modal closed.

The focus lands on the first focusable element of the content, which is not always the one worth starting at: marking an element with data-autofocus names the one that is. The second Modal here starts on its text field rather than on the close button ahead of it.

Scroll lock

A Modal takes the page over, so the page is held still for as long as one is open: the wheel and the touch drag act on the Modal instead of scrolling away what the user is coming back to. The room the scrollbar took is added back as padding while the page is held, so taking it away shifts nothing sideways, and the holds are counted - two Modals open at once both hold the page and it is only handed back once the last of them closes.

NoScrollLock leaves the page scrolling, for the Modals that sit over a page the user is meant to keep reading. A Modeless Modal never holds the page in the first place, since it is meant to leave what is behind it usable. What gets held is the scroller the page actually has: inside a BitAppShell - as this documentation is - that is the shell's own scrolling region, which the shell cascades and the Modal picks up without being told, since the body of such an app never scrolls and holding it would hold nothing. Any other layout that scrolls a region of its own names that region with ScrollerSelector or ScrollerElement.

AutoToggleScroll is the other way to hold a scroller still: instead of the counted hold above, the Modal takes the overflow off the scroller while it is open and hands it back when it closes, and the room that gave back is what an absolutely positioned Modal is pushed down by. A Modal that does this holds its own scroller, so the hold on the page is stood down for it - the two would else both be holding the same page. It aims at the same scroller the hold above does: what ScrollerElement or ScrollerSelector names, then the shell's, then the page.

Sizing

A Modal is as big as what is inside it, capped to the screen so that all of it stays reachable. Width, Height, MaxWidth and MaxHeight take any CSS length and give it a size of its own instead. MaxWidth is the one most Modals want: it gives the text a measure to be read at rather than letting it run the width of a wide screen. Width makes the Modal stand still while its content changes - the panes of a wizard, a body that arrives after the Modal opened - and MaxHeight starts the Modal's own scrolling before it reaches the edge of the screen.

FullWidth and FullHeight stretch the Modal to the width and the height of the area it is placed in - the viewport, for a Modal rendered at the page level - and FullSize is the two of them together, for the Modal that takes the whole screen, which is what a small screen usually wants from a surface with a form on it. The four lengths are written as inline styles, so each of them takes precedence over the matching Full parameter and over anything a stylesheet gives the Modal.


Overlay

The overlay is the layer that takes the click that light dismisses the Modal. It is see-through by default, so the page behind it stays readable; ModeFull gives it an opaque background that dims that page as well, for the Modals that are meant to be the only thing being read.

The overlay is also the only thing in the layer that catches a pointer at all, so whether it is there is the same question as whether the Modal is modal. A Modal that leaves the page usable is not modal any more, and Modeless is the one parameter that says so: it drops the overlay and tells assistive technologies the Modal is not modal, which also stands down the two holds a modal surface takes on the page - the focus trap and the scroll lock - since keeping the keyboard, or the wheel, inside a surface the pointer is free to leave would only ever be half a barrier. Without an overlay there is no click to dismiss the Modal by, so Escape and an explicit action are what is left. Set AriaModal to false on its own to report a Modal as modeless while keeping its overlay.

Position

A Modal sits in the middle of the area it covers. Position moves it to any of the nine places along the two edges - TopLeft, BottomCenter and the rest - which is what a surface that has to leave part of the page in view reaches for. The Start and End members are the direction-aware counterparts of Left and Right, so they follow the Modal in RTL rather than staying on the same physical side.

AbsolutePosition takes the Modal off the screen and puts it over the element it was declared inside of instead, for the surfaces that belong to a region of the page rather than to the page. That element has to establish a containing block of its own (position: relative) for the Modal to land on it.




Draggable

Draggable lets the user move the Modal around, for the surfaces that have to be pushed aside to see what they are covering - a picker held open next to the field it fills in, a tool window. The whole Modal is the handle by default, which takes the text selection and the scrolling inside it with the drag; DragElementSelector names a part of the content to drag it by instead, and leaves the rest of it usable.

Accessibility

A dialog needs a name. TitleAriaId points at the element that holds the visible title and SubtitleAriaId at the one that describes it, which is what a screen reader reads out when the Modal opens. AriaLabel is the fallback for a Modal that has no visible title to point at.

IsAlert renders the Modal as an alertdialog rather than a dialog, which is the role for a surface that interrupts to report something the user has to acknowledge - an error, a destructive confirmation. Keep it for the ones that really do interrupt: an alertdialog is announced more forcefully than a dialog.

Keeping the content

A Modal is built when it opens and taken away when it closes, which is what keeps a page that declares many of them cheap - and what makes each of them start over: a half-filled form inside one is gone by the time it is opened again. KeepMounted hides the Modal instead of taking it away, so its content, and whatever state that content holds, is still there the next time it opens.

Nothing is rendered before the first time the Modal opens, so a Modal that is never opened still costs nothing, and a kept Modal is inert and hidden from assistive technologies while it is closed. Type into both fields below, close each Modal and open it again: only the kept one still has what was typed.

Events

OnOpen fires once the Modal is in the page and its focus handling has run, which makes it the place to load what the Modal shows. OnDismiss fires whenever it closes, however it was closed.

OnOverlayClick and OnEscapeKeyDown are the two ways a user asks a Modal to go away, and both fire for every attempt - including the ones a Blocking or NoDismissOnEscape Modal turns down. That is what makes them the place to react to a dismissal that was refused: warn about unsaved work, or answer on the Modal's own terms.



Opened? [False]

Dismissed? [False]

Overlay clicked? [False]

Escape pressed? [False]

Programmatic control

A Modal captured with @ref can be driven through its Open, Close and Toggle methods, which is the way to reach one from code that has no field to bind IsOpen to. The methods and the binding are the same state, so a Modal opened through Open still closes on Escape or on a click on the overlay.

DefaultIsOpen is the state a Modal starts in while it is left to manage its own openness - that is, while IsOpen is not set at all. It is read once, so closing such a Modal is not undone by the next render.

Nested modals

A Modal declared inside the content of another one opens on top of it, which is what a confirmation asked for from inside a form looks like. Each of them keeps its own half of the dialog contract: Escape dismisses the innermost one and leaves the one underneath open, the overlays stack so a click only ever reaches the topmost, and the focus goes back to the button inside the first Modal that opened the second.

The holds on the page are counted rather than toggled, so both Modals hold it and it is only handed back once the last of them closes. Keep the nesting to the one step it takes to answer a question: a stack the user has to climb back down is a flow that wanted to be a page.

External Icons

CloseIcon takes an icon from an external library - FontAwesome, Material Icons, Bootstrap Icons - for the close button of the Modal, and takes precedence over CloseIconName, which names one of the built-in Fluent UI icons.

Style & Class

Style and Class reach the root element of the Modal - the layer that covers the page - while Styles and Classes reach each of its parts by name: Root, Overlay and Content for the surface itself, and HeaderContainer, Header, CloseButton, CloseIcon, Body and Footer for the parts of the chrome. Reach for the second pair whenever one of those, rather than the Modal as a whole, is what needs restyling.

Component's Style & Class:







Styles & Classes:







The parts of the chrome:

RTL

Set Dir to BitDir.Rtl to lay the Modal and everything inside it out right to left.

API

Every parameter, public member, sub-class and enum this component exposes.

BitModal parameters

Name Type Default value Description
AbsolutePosition bool false When true, the Modal is positioned absolute instead of fixed, so that it covers the element it was declared inside of rather than the screen. That element has to establish a containing block of its own (position: relative).
AriaModal bool true Whether the Modal should be announced as modal to assistive technologies. It is also what decides whether the Modal keeps the keyboard inside itself: a Modal that is not announced as modal leaves the page behind it reachable with the keyboard the way it is reachable with the pointer.
AutoToggleScroll bool false Enables the auto scrollbar toggle behavior of the Modal, which takes the overflow off the scroller while it is open and hands it back once it closes. A Modal that does this holds its own scroller, so the hold it would otherwise take on the page is stood down for it. The scroller is the one named by ScrollerElement or ScrollerSelector, then the one of the BitAppShell the Modal is inside of, and the page when it is inside none.
Blocking bool false When enabled, prevents the Modal from being light dismissed by clicking outside the Modal (on the overlay). Escape still dismisses it unless NoDismissOnEscape is set as well.
Body RenderFragment? null The content of the body section of the Modal, the alias of ChildContent, which it takes precedence over. This is what a Modal that also declares a Header or a Footer uses to keep the three of them side by side.
ChildContent RenderFragment? null The content of the Modal, it can be any custom tag or text.
Classes BitModalClassStyles? null Custom CSS classes for different parts of the BitModal component.
CloseButtonTitle string? null The title (and aria-label) of the close button for accessibility and localization. Defaults to "Close" when not set.
CloseIcon BitIconInfo? null The icon of the close button, provided as custom CSS classes of an external icon library. Takes precedence over CloseIconName when both are set.
CloseIconName string? null The name of the icon of the close button, from the built-in Fluent UI icons. Defaults to Cancel when not set.
DefaultIsOpen bool? null The initial opening state of the Modal in the uncontrolled mode, which is when the IsOpen parameter is not set.
DragElementSelector string? null The CSS selector of the drag element, which is the content of the Modal by default. Ignored by a Modal that is not Draggable.
Draggable bool false Whether the Modal can be dragged around.
Footer RenderFragment? null The template used to render the footer section of the Modal.
FooterText string? null The text of the footer section of the Modal.
FullHeight bool false Makes the Modal height 100% of its parent container.
FullSize bool false Makes the Modal width and height 100% of its parent container, which is FullWidth and FullHeight in one parameter.
FullWidth bool false Makes the Modal width 100% of its parent container.
Header RenderFragment? null The template used to render the header section of the Modal. Takes precedence over HeaderText when both are set.
HeaderText string? null The text of the header section of the Modal.
Height string? null The CSS height of the Modal (any CSS length). A Modal is as tall as its content when this is not set. It is written as an inline style on the content box, so it takes precedence over FullHeight, and it is capped by MaxHeight - or, when that is not set either, by the height of the screen.
IsAlert bool? null Determines the ARIA role of the Modal (alertdialog/dialog). A Blocking Modal that is not Modeless announces itself as an alertdialog when this is not set, since a surface that refuses to be dismissed by a click outside of it is one waiting to be answered.
IsOpen bool false Whether the Modal is displayed.
KeepMounted bool false Keeps the Modal in the page while it is closed instead of taking it out and building it again the next time it opens, so the content - and whatever state it holds - survives being closed. Nothing is rendered before the first time the Modal opens, and a kept Modal is inert and hidden from assistive technologies while it is closed.
MaxHeight string? null The CSS height the Modal is not to grow past, however long its content is (any CSS length). The height of the screen is the cap when this is not set, which is what keeps a Modal longer than the screen reachable: it scrolls inside itself rather than running off both ends of the page.
MaxWidth string? null The CSS width the Modal is not to grow past, however wide its content is (any CSS length). The width of the screen is the cap when this is not set, which leaves a Modal as wide as its content - and on a wide screen that can be a line of text too long to read comfortably.
ModeFull bool false Renders the overlay in full mode that gives it an opaque background. The overlay catches the clicks meant for the page behind it either way; this is what makes it dim that page as well.
Modeless bool false Whether the Modal should be modeless (e.g. not dismiss when focusing/clicking outside of the Modal). If true: Blocking is ignored, there is no overlay, and the Modal neither reports itself modal nor holds the keyboard or the page.
NoAutoFocus bool false Prevents the Modal from moving the focus into itself when it opens. By default the focus lands on the first focusable element of the content, or on the element inside it marked with the data-autofocus attribute, or on the content itself when it holds nothing focusable.
NoBorder bool false Removes the default top border of the Modal.
NoDismissOnEscape bool false Prevents the Modal from being dismissed by pressing the Escape key.
NoFocusTrap bool false Prevents the Modal from keeping the keyboard focus inside itself while it is open. The trap is only set up for a Modal that reports itself modal (see AriaModal) in the first place.
NoRestoreFocus bool false Prevents the Modal from handing the focus back to the element that had it before the Modal opened. The focus is only handed back when nothing else has taken it in the meantime.
NoScrollLock bool false Prevents the Modal from holding the page still while it is open. By default the page behind an open Modal is held, with the room the scrollbar took added back as padding so that nothing shifts sideways; the holds are counted, so the page is only handed back once the last open Modal closes. A Modeless Modal never holds the page in the first place, and a Modal that toggles the scroll itself (see AutoToggleScroll) holds its scroller instead. The gestures that land on a Modal that leaves the page scrolling are handed to the scroller behind it - the one ScrollerElement or ScrollerSelector names, or the application shell's - since the layer the Modal is drawn in is fixed to the viewport, where the wheel would else reach a document that does not scroll.
OnDismiss EventCallback<MouseEventArgs> A callback function for when the Modal is dismissed.
OnEscapeKeyDown EventCallback<KeyboardEventArgs> A callback function for when the Escape key is pressed inside the Modal. It is invoked for every Escape, including the ones a Modal with NoDismissOnEscape refuses to be dismissed by, which makes it the counterpart of OnOverlayClick for the keyboard.
OnOpen EventCallback A callback function for when the Modal is opened, invoked after it has rendered and its focus handling has run.
OnOverlayClick EventCallback<MouseEventArgs> A callback function for when somewhere on the overlay element of the Modal is clicked. It is invoked for every overlay click, including the ones a Blocking Modal refuses to be dismissed by.
Position BitPosition? null Position of the Modal on the screen. The Modal sits in the middle of the area it covers when this is not set.
ScrollerElement ElementReference? null The element reference of the scroller the Modal holds while it is open. Takes precedence over ScrollerSelector and over the scroller a BitAppShell cascades, and is read by both holds: the one the Modal takes by default and the overflow toggle of AutoToggleScroll.
ScrollerSelector string? null The CSS selector of the element whose scrolling the Modal holds while it is open. A Modal inside a BitAppShell holds the shell's scroller without being told to, since the shell cascades it; the page (body) is what is held when there is no shell and this is not set. Any other layout that scrolls a region of its own names that region here, since holding a page that never scrolls holds nothing.
ShowCloseButton bool false Shows the close button of the Modal, which closes it without a handler of its own.
Styles BitModalClassStyles? null Custom CSS styles for different parts of the BitModal component.
SubtitleAriaId string? null ARIA id for the subtitle of the Modal, if any.
TitleAriaId string? null ARIA id for the title of the Modal, if any.
Width string? null The CSS width of the Modal (any CSS length). A Modal is as wide as its content when this is not set. It is written as an inline style on the content box, so it takes precedence over FullWidth, and it is capped by MaxWidth - or, when that is not set either, by the width of the screen.

BitModal public members

Name Type Default value Description
Open Task Opens the Modal programmatically.
Close Task Closes the Modal programmatically.
Toggle Task Toggles the Modal between its open and closed states.

BitComponentBase parameters

Name Type Default value Description
AriaLabel string? null Gets or sets the accessible label for the component, used by assistive technologies.
Class string? null Gets or sets the CSS class name(s) to apply to the rendered element.
Dir BitDir? null Gets or sets the text directionality for the component's content.
ForceAnimation bool false Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested.
HtmlAttributes Dictionary<string, object> new Dictionary<string, object>() Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters.
Id string? null Gets or sets the unique identifier for the component's root element.
IsEnabled bool true Gets or sets a value indicating whether the component is enabled and can respond to user interaction.
Style string? null Gets or sets the CSS style string to apply to the rendered element.
TabIndex string? null Gets or sets the tab order index for the component when navigating with the keyboard.
Visibility BitVisibility BitVisibility.Visible Gets or sets the visibility state (visible, hidden, or collapsed) of the component.

BitComponentBase public members

Name Type Default value Description
UniqueId Guid Guid.NewGuid() Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed.
RootElement ElementReference Gets the reference to the root HTML element associated with this component.

BitPosition enum

Name Value Description
TopLeft 0
TopCenter 1
TopRight 2
TopStart 3
TopEnd 4
CenterLeft 5
Center 6
CenterRight 7
CenterStart 8
CenterEnd 9
BottomLeft 10
BottomCenter 11
BottomRight 12
BottomStart 13
BottomEnd 14

BitVisibility enum

Name Value Description
Visible 0 The content of the component is visible.
Hidden 1 The content of the component is hidden, but the space it takes on the page remains (visibility:hidden).
Collapsed 2 The component is hidden (display:none).

BitDir enum

Name Value Description
Ltr 0 Ltr (left to right) is to be used for languages that are written from the left to the right (like English).
Rtl 1 Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic).
Auto 2 Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element.

Feedback

Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.