P11Modal Component
The
P11Modal component provides a standard, accessible modal dialog window. It is designed to overlay the application content, making everything inactive except for itself (and any P11FloatingPanel instances). Built on Bootstrap classes, it ensures responsiveness and accessibility out-of-the-box.
Note: Proper use of
AriaLabel or AriaLabelledby is crucial for screen reader users. The RestoreFocusId parameter allows controlling where focus returns after the modal closes, enhancing user experience for keyboard navigation.Modal Examples
This section demonstrates the usage of the <code>P11Modal</code> component with various configurations and highlights its key features.
Event Log
The following log tracks events from the modals, such as opening and closing.
No events logged yet.
Implementation Details
<div class="component-description mt-4">
<h2 class="mb-3">@AppState!.T("Modal Examples")</h2>
<p>
@AppState!.T("This section demonstrates the usage of the <code>P11Modal</code> component with various configurations and highlights its key features.")
</p>
<div class="d-flex flex-wrap gap-3 mb-5">
<P11Button Text="Open Basic Modal" OnClick="(() => OpenModal(1))" />
<P11Button Text="Open Large Modal" OnClick="(() => OpenModal(2))" />
<P11Button Text="Open Centered Modal" OnClick="(() => OpenModal(3))" />
<P11Button Text="Open Scrollable Modal" OnClick="(() => OpenModal(4))" />
<P11Button Id="restoreFocusButton" Text="Open Modal (Restore Focus)" OnClick="(() => OpenModal(5))" />
<P11Button Text="Open AriaLabel Only Modal" OnClick="(() => OpenModal(6))" />
<P11Button Text="Open Nested Modals" OnClick="(() => OpenModal(7))" />
<P11Button Text="Open fullscreen Modal" OnClick="(() => OpenModal(10))" />
<P11Button Text="Pass bootstrap content class" OnClick="(() => OpenModal(11))" />
<P11Button Text="Pass bootstrap header, body, footer classes" OnClick="(() => OpenModal(12))" />
</div>
</div>
@* ----- MODAL IMPLEMENTATIONS ----- *@
@* Example 1: Basic Modal *@
<P11Modal @bind-Visible="showModal1" OnClose="OnModalClosed"
AriaLabelledby="modalTitle1"
ShowDevelopmentErrors="true">
<HeaderContent>
<h5 id="modalTitle1" class="modal-title">@AppState!.T("Basic Modal Title")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This is a basic modal. You should not be able to interact with the background.")</p>
<p>@AppState!.T("Try pressing ESC or clicking the backdrop to close it.")</p>
<p>@AppState!.T("The focus should be trapped within this modal. Try tabbing around.")</p>
<input type="text" class="form-control mb-2" placeholder="Input 1" />
<input type="text" class="form-control" placeholder="Input 2" />
</BodyContent>
<FooterContent>
<P11Button Text="Close" OnClick="(() => CloseModal(1))" />
<P11Button Text="Save Changes" OnClick="@(() => { Console.WriteLine("Save Changes clicked!"); CloseModal(1); })" />
</FooterContent>
</P11Modal>
@* Example 2: Large Modal *@
<P11Modal @bind-Visible="showModal2" OnClose="OnModalClosed"
Size="Size.Large"
AriaLabelledby="modalTitle2">
<HeaderContent>
<h5 id="modalTitle2" class="modal-title">@AppState!.T("Large Modal Title")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This is a large modal. It takes up more screen space.")</p>
<input type="email" class="form-control" placeholder="Email" />
</BodyContent>
<FooterContent>
<P11Button Text="Close Large" OnClick="(() => CloseModal(2))" />
</FooterContent>
</P11Modal>
@* Example 3: Centered Modal *@
<P11Modal @bind-Visible="showModal3" OnClose="OnModalClosed"
Position="ModalPosition.Centered"
AriaLabelledby="modalTitle3">
<HeaderContent>
<h5 id="modalTitle3" class="modal-title">@AppState!.T("Centered Modal Title")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This modal is centered vertically and horizontally in the viewport.")</p>
<p>@AppState!.T("Ideal for alerts or short confirmations.")</p>
</BodyContent>
<FooterContent>
<P11Button Text="Close Centered" OnClick="(() => CloseModal(3))" />
</FooterContent>
</P11Modal>
@* Example 4: Scrollable Modal *@
<P11Modal @bind-Visible="showModal4" OnClose="OnModalClosed"
Position="ModalPosition.Scrollable"
AriaLabelledby="modalTitle4">
<HeaderContent>
<h5 id="modalTitle4" class="modal-title">@AppState!.T("Scrollable Modal Title")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This modal has a scrollable body. Use the scrollbar on the modal, not the background.")</p>
@for (int i = 0; i < 50; i++)
{
<p>@AppState!.T($"Line {i + 1} of very long content. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.")</p>
}
</BodyContent>
<FooterContent>
<P11Button Text="Close Scrollable" OnClick="(() => CloseModal(4))" />
</FooterContent>
</P11Modal>
@* Example 5: Focus Restoration *@
<P11Modal @bind-Visible="showModal5" OnClose="OnModalClosed"
AriaLabelledby="modalTitle5"
RestoreFocusId="restoreFocusButton">
<HeaderContent>
<h5 id="modalTitle5" class="modal-title">@AppState!.T("Focus Restoration Modal")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("When this modal closes, the focus should return to the \"Open Modal (Restore Focus)\" button.")</p>
<input type="text" class="form-control" placeholder="Type something..." />
</BodyContent>
<FooterContent>
<P11Button Text="Close & Restore" OnClick="(() => CloseModal(5))" />
</FooterContent>
</P11Modal>
@* Example 6: AriaLabel (without AriaLabelledby) *@
<P11Modal @bind-Visible="showModal6" OnClose="OnModalClosed"
AriaLabel="Information Dialog"
ShowDevelopmentErrors="true">
<HeaderContent>
<p>@AppState!.T("Informational Message")</p>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This modal uses <code>AriaLabel</code> because no <code>AriaLabelledby</code> was provided. Check console for warnings if ShowDevelopmentErrors is true.")</p>
<p>@AppState!.T("This is useful for simple alerts without a formal heading.")</p>
</BodyContent>
<FooterContent>
<P11Button Text="Got It!" OnClick="(() => CloseModal(6))" />
</FooterContent>
</P11Modal>
@* Example 7, 8, 9: Nested Modals *@
<P11Modal @bind-Visible="showModal7" OnClose="OnModalClosed"
AriaLabelledby="modalTitle7">
<HeaderContent>
<h5 id="modalTitle7" class="modal-title" tabindex="-1">@AppState!.T("Parent Modal")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This is the parent modal.")</p>
<P11Button Text="Open Child Modal" OnClick="(() => OpenModal(8))" />
<input type="text" class="form-control mt-2" placeholder="Parent input" />
</BodyContent>
<FooterContent>
<P11Button Text="Close Parent" OnClick="(() => CloseModal(7))" />
</FooterContent>
</P11Modal>
<P11Modal @bind-Visible="showModal8" OnClose="OnModalClosed"
AriaLabelledby="modalTitle8"
RestoreFocusId="modalTitle7"
PreventScroll="false">
<HeaderContent>
<h5 id="modalTitle8" class="modal-title" tabindex="-1">@AppState!.T("Child Modal")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This is a child modal. It should be on top of the parent.")</p>
<p>@AppState!.T("Focus should be trapped here.")</p>
<input type="text" class="form-control" placeholder="Child input" />
<P11Button Text="Open Grandchild Modal" OnClick="(() => OpenModal(9))" />
</BodyContent>
<FooterContent>
<P11Button Text="Close Child" OnClick="(() => CloseModal(8))" />
</FooterContent>
</P11Modal>
<P11Modal @bind-Visible="showModal9" OnClose="OnModalClosed"
AriaLabelledby="modalTitle9"
RestoreFocusId="modalTitle8"
PreventScroll="false">
<HeaderContent>
<h5 id="modalTitle9" class="modal-title" tabindex="-1">@AppState!.T("Grandchild Modal")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This is the grandchild modal. Deep nesting!")</p>
<input type="text" class="form-control" placeholder="Grandchild input" />
</BodyContent>
<FooterContent>
<P11Button Text="Close Grandchild" OnClick="(() => CloseModal(9))" />
</FooterContent>
</P11Modal>
@* Example 10: Fullscreen Modal *@
<P11Modal @bind-Visible="showModal10" OnClose="OnModalClosed"
AriaLabelledby="modalTitle1"
IsFullScreen="true"
ShowDevelopmentErrors="true">
<HeaderContent>
<h5 id="fullscreenModalTitle" class="modal-title">@AppState!.T("New User Profile")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This is a fullscreen modal that simulates a multi-step form or a new page.")</p>
<p>@AppState!.T("It covers the entire screen, giving the user a focused experience.")</p>
<div class="row">
<div class="col-md-6 mb-3">
<label for="firstName" class="form-label">@AppState!.T("First Name")</label>
<input type="text" class="form-control" id="firstName" placeholder="@AppState!.T("Enter first name")" />
</div>
<div class="col-md-6 mb-3">
<label for="lastName" class="form-label">@AppState!.T("Last Name")</label>
<input type="text" class="form-control" id="lastName" placeholder="@AppState!.T("Enter last name")" />
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">@AppState!.T("Email Address")</label>
<input type="email" class="form-control" id="email" placeholder="@AppState!.T("name@example.com")" />
</div>
<div class="mb-3">
<label for="address" class="form-label">@AppState!.T("Address")</label>
<textarea class="form-control" id="address" rows="3" placeholder="@AppState!.T("Enter full address")"></textarea>
</div>
</BodyContent>
<FooterContent>
<P11Button Text="Close" OnClick="(() => CloseModal(10))" />
<P11Button Text="Save Changes" OnClick="@(() => { Console.WriteLine("Save Changes clicked!"); CloseModal(10); })" />
</FooterContent>
</P11Modal>
@* Example 11: Content dark mode *@
<P11Modal @bind-Visible="showModal11" OnClose="OnModalClosed"
AriaLabelledby="modalTitle1"
ShowDevelopmentErrors="true"
CssClassContent="text-bg-dark">
<HeaderContent>
<h5 id="modalTitle1" class="modal-title">@AppState!.T("Basic Modal Title")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This is a basic modal. You should not be able to interact with the background.")</p>
<p>@AppState!.T("Try pressing ESC or clicking the backdrop to close it.")</p>
<p>@AppState!.T("The focus should be trapped within this modal. Try tabbing around.")</p>
<input type="text" class="form-control mb-2" placeholder="Input 1" />
<input type="text" class="form-control" placeholder="Input 2" />
</BodyContent>
<FooterContent>
<P11Button Text="Close" OnClick="(() => CloseModal(11))" />
<P11Button Text="Save Changes" OnClick="@(() => { Console.WriteLine("Save Changes clicked!"); CloseModal(11); })" />
</FooterContent>
</P11Modal>
@* Example 12: Header, Body, Footer *@
<P11Modal @bind-Visible="showModal12" OnClose="OnModalClosed"
AriaLabelledby="modalTitle1"
ShowDevelopmentErrors="true"
CssClassHeader="text-bg-danger"
CssClassBody="text-bg-success"
CssClassFooter="text-bg-primary">
<HeaderContent>
<h5 id="modalTitle1" class="modal-title">@AppState!.T("Basic Modal Title")</h5>
</HeaderContent>
<BodyContent>
<p>@AppState!.T("This is a basic modal. You should not be able to interact with the background.")</p>
<p>@AppState!.T("Try pressing ESC or clicking the backdrop to close it.")</p>
<p>@AppState!.T("The focus should be trapped within this modal. Try tabbing around.")</p>
<input type="text" class="form-control mb-2" placeholder="Input 1" />
<input type="text" class="form-control" placeholder="Input 2" />
</BodyContent>
<FooterContent>
<P11Button Text="Close" OnClick="(() => CloseModal(12))" />
<P11Button Text="Save Changes" OnClick="@(() => { Console.WriteLine("Save Changes clicked!"); CloseModal(12); })" />
</FooterContent>
</P11Modal>
<div class="component-description mt-4">
<h2 class="mb-3">@AppState!.T("Event Log")</h2>
<p>@AppState!.T("The following log tracks events from the modals, such as opening and closing.")</p>
<div class="card p-3">
@if (eventLogModal.Any())
{
<ul class="list-unstyled mb-0">
@foreach (var logEntry in eventLogModal)
{
<li>@logEntry</li>
}
</ul>
}
else
{
<p class="text-muted">@AppState!.T("No events logged yet.")</p>
}
</div>
</div>@code {
private bool showModal1;
private bool showModal2;
private bool showModal3;
private bool showModal4;
private bool showModal5;
private bool showModal6;
private bool showModal7; // Parent modal for nesting
private bool showModal8; // Child modal for nesting
private bool showModal9; // Grandchild modal for nesting
private bool showModal10; // Grandchild modal for fullscreen
private bool showModal11; // CSS class example
private bool showModal12; // CSS class example
private List<string> eventLogModal = new();
private void OpenModal(int modalNumber)
{
switch (modalNumber)
{
case 1: showModal1 = true; break;
case 2: showModal2 = true; break;
case 3: showModal3 = true; break;
case 4: showModal4 = true; break;
case 5: showModal5 = true; break;
case 6: showModal6 = true; break;
case 7: showModal7 = true; break;
case 8: showModal8 = true; break;
case 9: showModal9 = true; break;
case 10: showModal10 = true; break;
case 11: showModal11 = true; break;
case 12: showModal12 = true; break;
}
LogEventModal($"Modal {modalNumber} opened.");
}
private void CloseModal(int modalNumber)
{
switch (modalNumber)
{
case 1: showModal1 = false; break;
case 2: showModal2 = false; break;
case 3: showModal3 = false; break;
case 4: showModal4 = false; break;
case 5: showModal5 = false; break;
case 6: showModal6 = false; break;
case 7: showModal7 = false; break;
case 8: showModal8 = false; break;
case 9: showModal9 = false; break;
case 10: showModal10 = false; break;
case 11: showModal11 = false; break;
case 12: showModal12 = false; break;
}
LogEventModal($"Modal {modalNumber} closed by internal button.");
}
private void OnModalClosed()
{
LogEventModal("A modal was closed (via ESC, backdrop, or X button).");
}
private void LogEventModal(string message)
{
eventLogModal.Insert(0, $"{DateTime.Now:HH:mm:ss} - {message}");
if (eventLogModal.Count > 10)
{
eventLogModal.RemoveAt(eventLogModal.Count - 1);
}
StateHasChanged();
}
} Component API
| Parameter | Type | Default | Description |
|---|---|---|---|
Visible |
bool |
false |
Controls the visibility of the modal. Bind with @bind-Visible. |
HeaderContent |
RenderFragment |
- | Content to be rendered in the modal's header. |
BodyContent |
RenderFragment |
- | Content to be rendered in the modal's body. |
FooterContent |
RenderFragment |
- | Content to be rendered in the modal's footer. |
Size |
Size |
Size.Medium |
Specifies the size of the modal. |
Position |
ModalPosition |
ModalPosition.Default |
Specifies the position of the modal (e.g., Centered, Scrollable). |
IsFullScreen |
bool |
false |
Gets or sets a boolean indicating whether the modal should take up the full screen. If true, this overrides the default width/height set by the Position parameter. |
ShowCloseButton |
bool |
true |
If true, a close button (X icon) is shown in the product header. |
RestoreFocusId |
string |
null |
Specifies the ID of the element that should receive focus after the modal closes. This is crucial for accessibility, especially when using nested modals or opening from specific buttons. |
PreventScroll |
bool |
true |
Prevents scrolling on the body when the modal is open. Set to false if you manage scroll prevention globally or want background scrolling. |
AriaLabel |
string |
null |
Provides an accessible name for the modal dialog. Use when no visible title is available. Either AriaLabelledby or AriaLabel should be provided for accessibility. |
AriaLabelledby |
string |
null |
Refers to the ID of a visible element (e.g., an h1) that provides an accessible name for the modal dialog. Either AriaLabelledby or AriaLabel should be provided for accessibility. |
ShowDevelopmentErrors |
bool |
false |
If true, logs warnings to the console for common development issues (e.g., missing accessibility attributes). |
CssClassContent |
string? |
null |
Gets or sets an optional CSS class string that will be applied to the modal content of the component. |
CssClassHeader |
string? |
null |
Gets or sets an optional CSS class string that will be applied to the modal header of the component. |
CssClassBody |
string? |
null |
Gets or sets an optional CSS class string that will be applied to the modal body of the component. |
CssClassFooter |
string? |
null |
Gets or sets an optional CSS class string that will be applied to the modal footer of the component. |
| Events | |||
VisibleChanged |
EventCallback<bool> |
- | Event callback for when the visibility changes. |
OnClose |
EventCallback |
- | Event callback invoked when the modal is closed (via Escape key, backdrop click, or close button). |