P11ModalNative Component
The
P11ModalNative component is an adaptive, high-end dialog system. Unlike standard modals, it automatically adjusts its visual structure—including title alignment, close buttons, and action positions—to match the look and feel of Windows, macOS, iOS, or Android. This ensures a seamless, 'native-like' experience for users across different platforms within a Blazor environment.Mobile Back-Button & Swipe Support
To prevent users from accidentally leaving the page when they use the Android hardware back-button or the iOS back-swipe while a modal is open, use the following combination:
UseHistoryBack="true": Adds a virtual entry to the browser history. This 'traps' the first back-navigation so it only affects the modal, not the entire application.CloseTrigger: Connect this to your global Navigation Service. When Capacitor detects a native back-event, it triggers this action, allowing the modal to close gracefully and clean up the history entry automatically.
Adaptive UI
Automatically repositions header elements (like the macOS 'Traffic Lights' or iOS 'Done' buttons) based on the target device setting.Desktop & Mobile Pro
Supports desktop features like 'Maximize' and 'Sticky Headers' alongside mobile-first interactions and full-screen modes.
Pro Tip: Use the
NativeDevice parameter to switch layouts. For accessibility, the component maintains strict ARIA standards, ensuring that even with custom native layouts, screen readers correctly identify titles and controls.Modal native Examples
This section demonstrates the usage of the P11ModalNative component with various configurations and highlights its key features.
Windows / Web
macOS
Mobile Varianten
Implementation Details
<div class="container mt-5">
<div class="p-4 border rounded bg-white shadow-sm">
<div class="row g-4">
@* --- WINDOWS / WEB --- *@
<div class="col-md-4">
<h6 class="fw-bold text-uppercase text-muted small mb-3 border-bottom pb-1">Windows / Web</h6>
<div class="d-grid gap-2">
<button class="btn btn-sm btn-outline-secondary text-start" @onclick='(() => Open(NativeDevice.WEB, "Info Dialog", false, false))'>Standard (Kein Scroll/Max)</button>
<button class="btn btn-sm btn-outline-secondary text-start" @onclick='(() => Open(NativeDevice.WEB, "Dokument", false, true))'>Standard + Scroll</button>
<button class="btn btn-sm btn-outline-secondary text-start" @onclick='(() => Open(NativeDevice.WINDOWS, "Editor", true, true))'>Maximize + Scroll</button>
</div>
</div>
@* --- MAC --- *@
<div class="col-md-4">
<h6 class="fw-bold text-uppercase text-muted small mb-3 border-bottom pb-1">macOS</h6>
<div class="d-grid gap-2">
<button class="btn btn-sm btn-outline-secondary text-start" @onclick='(() => Open(NativeDevice.MAC, "Finder Info", false, false))'>macOS Info (Nur Rot)</button>
<button class="btn btn-sm btn-outline-secondary text-start" @onclick='(() => Open(NativeDevice.MAC, "System Log", true, true))'>macOS Full (Rot/Grün + Scroll)</button>
</div>
</div>
@* --- MOBILE VARIANTEN --- *@
<div class="col-md-4">
<h6 class="fw-bold text-uppercase text-muted small mb-3 border-bottom pb-1">Mobile Varianten</h6>
<div class="d-grid gap-2">
@* iOS Triple *@
<button class="btn btn-sm btn-primary text-start opacity-75" @onclick='(() => OpenMobile(NativeDevice.IPHONE, "Settings", false, false))'>iOS: Nur Links (Clean)</button>
<button class="btn btn-sm btn-primary text-start" @onclick='(() => OpenMobile(NativeDevice.IPHONE, "New Contact", true, false))'>iOS: Links + Rechts (Clean)</button>
<button class="btn btn-sm btn-primary text-start fw-bold" @onclick='(() => OpenMobile(NativeDevice.IPHONE, "Mail Edit", true, true))'>iOS: Buttons + Linie</button>
@* Android Triple *@
<div class="mt-2"></div>
<button class="btn btn-sm btn-success text-start opacity-75" @onclick='(() => OpenMobile(NativeDevice.ANDROID, "Profile", false, false))'>Android: Nur Links (Clean)</button>
<button class="btn btn-sm btn-success text-start" @onclick='(() => OpenMobile(NativeDevice.ANDROID, "Settings", true, false))'>Android: Links + Rechts (Clean)</button>
<button class="btn btn-sm btn-success text-start fw-bold" @onclick='(() => OpenMobile(NativeDevice.ANDROID, "System Info", true, true, "bg-light"))'>Android: Buttons + Linie + Gray</button>
</div>
</div>
</div>
</div>
</div>
@* Die Komponente *@
<P11ModalNative @bind-Visible="_isVisible"
NativeDevice="_currentDevice"
AllowMaximize="_allowMaximize"
Position="@(_shouldScroll ? ModalPosition.Scrollable : ModalPosition.Default)"
Title="@_modalTitle"
ShowHeaderSeparator="_showSeparator"
CssClassHeader="@_headerClass"
CloseButtonText="@_btnText"
CloseButtonIconCss="@_btnIcon">
<HeaderAction>
@if (_showSaveButton)
{
@if (_currentDevice == NativeDevice.IPHONE)
{
<button class="btn btn-link p-0 text-decoration-none fw-bold" style="color: #007aff; font-size: 1rem;" @onclick="Save">Fertig</button>
}
else if (_currentDevice == NativeDevice.ANDROID)
{
<button class="btn btn-link text-dark p-1" @onclick="Save"><i class="bi bi-check2 fs-4"></i></button>
}
}
</HeaderAction>
<BodyContent>
<div class="p-3">
<p class="text-muted small">Modus: <strong>@_currentDevice</strong></p>
@if (_shouldScroll)
{
<div style="height: 1000px; background: linear-gradient(180deg, #f8f9fa 0%, #dee2e6 100%); padding: 20px;" class="rounded border shadow-sm">
<p class="fw-bold">Scrollbarer Inhalt aktiv.</p>
</div>
}
else
{
<div class="alert alert-secondary border-0 rounded-3">Kompakter Inhalt ohne Scrollen.</div>
}
</div>
</BodyContent>
<FooterContent>
<div class="d-flex justify-content-end gap-2 w-100">
@if (_currentDevice == NativeDevice.MAC)
{
@* macOS Buttons mit BS 5.3 Utilities (rounded-2 = 6px radius) *@
<button class="btn btn-sm btn-outline-secondary rounded-2 px-3 border-secondary-subtle text-dark" style="font-size: 0.85rem; min-width: 80px;" @onclick="Close">Abbrechen</button>
<button class="btn btn-sm btn-primary rounded-2 px-3 shadow-sm" style="font-size: 0.85rem; min-width: 80px;" @onclick="Save">Speichern</button>
}
else if (_currentDevice == NativeDevice.WEB || _currentDevice == NativeDevice.WINDOWS)
{
<button class="btn btn-secondary" @onclick="Close">Abbrechen</button>
<button class="btn btn-primary px-4" @onclick="Save">OK</button>
}
</div>
</FooterContent>
</P11ModalNative>@code {
private bool _isVisible;
private NativeDevice _currentDevice;
private bool _allowMaximize;
private bool _shouldScroll;
private bool _showSeparator;
private bool _showSaveButton;
private string _modalTitle = "";
private string? _btnText;
private string? _btnIcon;
private string? _headerClass;
private void Open(NativeDevice dev, string title, bool max, bool scroll)
{
Reset();
_currentDevice = dev;
_modalTitle = title;
_allowMaximize = max;
_shouldScroll = scroll;
_isVisible = true;
}
private void OpenMobile(NativeDevice dev, string title, bool showSave, bool separator, string? bgColorClass = null)
{
Reset();
_currentDevice = dev;
_modalTitle = title;
_showSaveButton = showSave;
_showSeparator = separator;
_shouldScroll = true; // Mobile fast immer mit Scroll
_headerClass = bgColorClass;
_isVisible = true;
if (dev == NativeDevice.IPHONE)
{
_btnText = "Zurück";
_btnIcon = "bi bi-chevron-left";
}
}
private void Reset()
{
_allowMaximize = false;
_shouldScroll = false;
_showSeparator = true;
_showSaveButton = false;
_btnText = null;
_btnIcon = null;
_headerClass = null;
}
private void Save() => _isVisible = false;
private void Close() => _isVisible = false;
}Component API
| Parameter | Type | Default | Description |
|---|---|---|---|
Visible |
bool |
false |
Controls the visibility of the modal. Bind with @bind-Visible. |
NativeDevice |
NativeDevice |
NativeDevice.WEB |
Sets the visual style and header behavior (WEB, WINDOWS, MAC, IPHONE, ANDROID). |
AllowMaximize |
bool |
false |
Enables the maximize/zoom button for Desktop modes (Windows/macOS). |
| Content & Layout | |||
Title |
string? |
null |
The title displayed in the header. Position adapts to the NativeDevice. |
HeaderContent |
RenderFragment |
- | Additional custom content for the header area. |
HeaderAction |
RenderFragment |
- | Slot for primary actions (e.g., 'Save' or 'Done' buttons) positioned according to OS standards. |
BodyContent |
RenderFragment |
- | The main content of the modal. |
FooterContent |
RenderFragment |
- | Content for the modal's footer (usually Action-Buttons). |
| Visuals & UI | |||
ShowHeaderSeparator |
bool |
true |
If true, a border-bottom is shown under the header. Often disabled for clean iOS looks. |
Size |
Size |
Size.Medium |
The Bootstrap size of the modal dialog. |
Position |
ModalPosition |
Default |
Positioning logic. Use 'Scrollable' to keep the header/footer sticky while content scrolls. |
IsFullScreen |
bool |
false |
Forces the modal to take up the entire viewport. |
CloseButtonText |
string? |
null |
Custom text for the close/back button (e.g., 'Cancel' or 'Back'). |
CloseButtonIconCss |
string? |
null |
Icon class (e.g., 'bi bi-chevron-left') for the native close button. |
| Behavior & A11y | |||
ShowCloseButton |
bool |
true |
Determines if any close control (X, Dots, or Back-Button) is rendered. |
RestoreFocusId |
string? |
null |
ID of the element to refocus after closing. |
PreventScroll |
bool |
true |
Locks the background page scrolling while modal is active. |
UseHistoryBack |
bool |
false |
Creates a virtual browser history entry when opened. Enables closing via hardware back-button/swipe without leaving the page. |
CloseTrigger |
Action? |
null |
External trigger (Action) to close the modal, e.g., from a global Capacitor back-button listener. |
AriaLabel |
string? |
null |
Accessibility label for screen readers. |
| Custom Styling | |||
CssClassContent |
string? |
null |
CSS class for the .modal-content container. |
CssClassHeader |
string? |
null |
CSS class for the .modal-header (useful for background colors). |
| Events | |||
VisibleChanged |
EventCallback<bool> |
- | Fires when visibility changes (supports @bind-Visible). |
OnClose |
EventCallback |
- | Invoked when the modal is closed by any means. |