true-perfect-code
Version: 1.2.66

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.
Automated Native Back-Stack & Gesture Management

This component automatically manages its Z-Index and registers itself in the global modal stack.

  • Native Gesture Support: Swipe-Back (iOS) and hardware Back-Button (Android) are handled via the global stack and event bridge.
  • Global Control via Service: Use the IEventStateService to access the current modal state and trigger close actions (e.g. TryCloseTopModalAsync) from your MainLayout or App logic.
  • Cleanup on Navigation: Call ClearAllNativeModalsAsync when navigating away to reset all states.

Note: For integration details and examples see: EventStateService documentation

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


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;
}



1. Best Practice Setup Guide

To enable native features like iOS Edge-Swipe or Android Hardware Back-Button management, follow these steps:

Step A: Service Registration (Program.cs)

Register the IEventStateService as a Singleton in Blazor WASM to maintain a consistent modal stack across the application lifecycle.

// Registration in Blazor WASM / Capacitor
    builder.Services.AddSingleton<IEventStateService, EventStateService>();
Step B: MainLayout Integration

The MainLayout acts as the central coordinator. You must subscribe to the navigation events and initialize the bridge.

protected override void OnInitialized()
    {
        // Subscribe to stack changes to trigger StateHasChanged
        EventState.OnStackChanged += HandleStackChanged;

        // Critical: Listen for iOS swipe events when no modals are open
        EventState.OnGlobalBackNavigationRequested += HandleIOSGlobalBack;
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // 1. Determine platform and map to NativeDevice enum
            var device = GetCurrentDevice(); 

            // 2. Optional: Configure Debug Mode and Swipe behavior
    #if DEBUG
            await EventState.SetDebugModeAsync(true);
    #endif
            EventState.SwipeConfig.SwipeThreshold = 40;

            // 3. Initialize the JS-Bridge and internal DotNetObjectReference
            await EventState.InitializeAsync(device);

            // 4. Capacitor specific: Register the MainLayout for Android Hardware Back
            await Platform.RegisterNativeNavigationAsync(_dotNetRef);
        }
    }
Step C: Android Hardware Back-Button (Platform Bridge) Action Required
Crucial Note: P11 handles iOS Edge-Swipe automatically. However, Android Hardware Buttons are part of the OS layer. P11 cannot "magic" them away; you must bridge them from your host project.
1. For Capacitor Projects (Recommended & Tested)

Ensure Capacitor is initialized (e.g., in index.html). Your bridge script (cap.js) must forward the hardware event to Blazor:

// Inside your cap.js bridge
            registerNavigationHelper: function (helper) {
                this._dotNetHelper = helper;

                if (window.pE_Capacitor?.App) {
                    window.pE_Capacitor.App.addListener('backButton', async (data) => {
                        if (this._dotNetHelper) {
                            // Bridge to MainLayout.razor
                            await this._dotNetHelper.invokeMethodAsync('HandleNativeBack', data.canGoBack);
                        }
                    });
                }
            }
2. For .NET MAUI Projects (Untested / Community Info)
Disclaimer: This approach is theoretically sound for MAUI developers but has not been fully tested in our current P11 production cycle. Use at your own risk.

In MAUI, you can bypass JavaScript and call the Service directly from C#:

// Inside AppShell.xaml.cs (MAUI Android logic)
            protected override async void OnBackButtonPressed()
            {
                // Ask P11 if it can handle the back-action (close a modal)
                var wasModalClosed = await EventStateService.TryCloseTopModalAsync();
    
                if (!wasModalClosed) {
                    base.OnBackButtonPressed(); // Normal navigation if no modal was open
                }
            }

2. Implementation Rules & Best Practices

To ensure maximum stability and prevent "ghost" overlays or incorrect stacking behavior, please strictly follow these two essential rules:

Persistent Modal IDs (Guids)

The P11ModalNative uses a global stack to manage back-navigation and gestures. To keep this stack accurate, every modal must have a unique, persistent ID.

Rule: Define the Guid as a private field in your @code block, not directly in the component parameter.

Why? If you use Id="@Guid.NewGuid()", the ID changes every time Blazor triggers SetParametersAsync. This confuses the internal stack management and may prevent the modal from closing correctly.

// CORRECT: The ID remains constant during the page lifecycle
            private Guid _myModalId = Guid.NewGuid(); 

            // Usage in Razor
            <P11ModalNative Id="_myModalId" @bind-Visible="_showModal" ... />
Avoiding Nested Modals (iOS Stacking Context)

Testing revealed a critical behavior in how iOS engines handle CSS Stacking Contexts compared to Android or Windows.

Rule: Always place your P11ModalNative components as siblings on the same hierarchy level. Never nest one modal inside the code block of another.

The Issue: If Modal B is nested inside Modal A's code, iOS sometimes collapses the entire stack when the topmost modal is swiped closed, causing all parent modals to disappear instantly.

CORRECT (Siblings)
      <P11ModalNative Id="ID1">
               <!-- Content only -->
            </P11ModalNative>

            <P11ModalNative Id="ID2">
               <!-- Content only -->
            </P11ModalNative>
WRONG (Nested)
      <P11ModalNative Id="ID1">
               <P11ModalNative Id="ID2"> 
                  <!-- This fails on iOS! -->
               </P11ModalNative>
            </P11ModalNative>

3. Enabling Native Mobile Look & Feel

By default, the P11 Library remains platform-agnostic to support Blazor Server and WPF. To activate the high-end native behaviors (like Viewport fixation, iOS Safe Areas, and Swipe-container optimization), you must explicitly enable the mobile mode in your host project.

Activation via index.html

In your Blazor WASM (Capacitor) or MAUI project, locate the index.html and add the class p11-mobile to the root <html> tag:

<!-- index.html: Mandatory for Capacitor & MAUI -->
            <html lang="en" class="p11-mobile">
                ...
            </html>
What happens under the hood?

Once the p11-mobile class is present, the p11.css activates several critical native rules:

1. Viewport Fixation

The body is set to position: fixed. This prevents the entire browser layout from shifting or expanding during translateX animations (Swipe-to-close).

2. Safe Area Management

Automatically respects env(safe-area-inset-top) for modern iPhones, ensuring content doesn't disappear under the "Notch" or the home indicator.

3. Gesture Interaction

Optimizes touch-action for buttons and disables text-selection on UI elements to mimic native app behavior, while keeping inputs selectable.

4. Layout Stability

Couples the footer and #app container correctly, so that during a swipe-back gesture, the UI moves as one stable unit without "white edges".

Recommendation: Do not use the p11-mobile class for Blazor Server or Blazor WPF unless you specifically want the fixed, app-like viewport behavior on those platforms as well.

4. Advanced Tuning via ThemeService (Global Overrides)

To avoid rebuilding NuGet packages for minor CSS layout adjustments, P11 uses a Global Variable approach. All layout-critical values are stored in the :root element and can be overridden at runtime via C#.

Runtime Customization

Use the ThemeService to inject new values into the :root selector. This is typically done in your MainLayout.razor during initialization.

// Adjusting the native behavior live from C# code
ThemeService.SetScopedVariables(":root", new Dictionary<string, string> {
    { "--p11-footer-fix", "0px" },                // Manually move footer to the very bottom
    { "--p11-main-padding-bottom", "20px" },      // Add more scroll-breathing room
    { "--p11-swipe-transition", "0.2s ease-out" }, // Make animations snappier
    { "--p11-overlay-bg", "rgba(0,0,0,0.8)" }     // Change backdrop darkness
});
Available Configuration Variables
Variable Default (Fallback) Description
--p11-footer-fix 0px (or 28px via JS) Vertical offset for the footer (overrides iOS auto-detection).
--p11-main-padding-bottom 15px Extra space at the end of the scrollable <main> area.
--p11-safe-area-top env(...) Top margin for status bars and notches.
--p11-swipe-transition 0.3s ... Duration and feel of the Modal slide-in/out effect.
--p11-overlay-bg #000000 The background color of the swipe-to-close overlay.
Pro-Tip: By targeting :root, you ensure that your settings propagate through the entire application instantly, bypassing any hardcoded library defaults.


Implementation Details

<div class="component-description mt-4">
    <P11Button Text="Open Modal 1" OnClick="() => isOpenModalEx2_1 = true" />
    <br />

    @if (isOpenModalEx2_1)
    {
        <P11ModalNative @bind-Visible="@isOpenModalEx2_1" OnClose="async () => { }"
                        Id="@guidModalEx2_1"
                        NativeDevice="NativeDevice.IPHONE"
                        ShowHeaderSeparator="true"
                        CloseButtonText="Back"
                        CloseButtonIconCss="bi bi-chevron-left"
                        AriaLabelledby="modalTitle1Help"
                        CssClassContent="p11-form-bg-01"
                        Size="Size.Large">
            <HeaderContent>
                <h5 id="specific-position-panel-settings" class="modal-title">Modal 1</h5>
            </HeaderContent>
            <BodyContent>

                <h3>Modal 1</h3>
                <P11Button Text="Open Modal 2" OnClick="() => isOpenModalEx2_2 = true" />

                <div class="alert alert-info mt-3">
                    <strong>P11 Stack Info:</strong>
                    <ul class="list-unstyled mb-0">
                        <li>Open Modals: <strong>@EventState.ModalCount</strong></li>
                        <li>Top Modal ID: <small>@EventState.TopModalId</small></li>
                        <li>Stack Active: <span class="badge @(EventState.IsAnyModalOpen ? "bg-success" : "bg-danger")">
                            @(EventState.IsAnyModalOpen ? "YES" : "NO")
                        </span></li>
                    </ul>
                </div>

                <div class="d-flex flex-column gap-2 mt-3">
                    <P11Button Text="Simulate external closing" 
                                OnClick="async() => {
                                    if(EventState.IsAnyModalOpen)
                                        await EventState.TryCloseTopModalAsync();
                                }" />

                    <P11Button Text="Toggle Debug Logs" 
                                OnClick="() => EventState.DebugMode = !EventState.DebugMode" />
                </div>

                <div class="mt-3">
                    <h6>Full Stack Trace:</h6>
                    <div style="font-size: 10px; font-family: monospace; background: #eee; padding: 5px;">
                        @foreach (var id in EventState.ModalStack)
                        {
                            <div>ID: @id @(id == EventState.TopModalId ? "<-- TOP" : "")</div>
                        }
                    </div>
                </div>

            </BodyContent>
        </P11ModalNative>
    }

    @if (isOpenModalEx2_2)
    {
        <P11ModalNative @bind-Visible="@isOpenModalEx2_2" OnClose="async () => { }"
                        Id="@guidModalEx2_2"
                        NativeDevice="NativeDevice.IPHONE"
                        ShowHeaderSeparator="true"
                        CloseButtonText="Back"
                        CloseButtonIconCss="bi bi-chevron-left"
                        AriaLabelledby="modalTitle1Help"
                        CssClassContent="p11-form-bg-01"
                        Size="Size.Large">
            <HeaderContent>
                <h5 id="specific-position-panel-settings" class="modal-title">Modal 2</h5>
            </HeaderContent>
            <BodyContent>

                <h3>Modal 2</h3>
                <P11Button Text="Open Modal 3" OnClick="() => isOpenModalEx2_3 = true" />

                <div class="alert alert-info mt-3">
                    <strong>P11 Stack Info:</strong>
                    <ul class="list-unstyled mb-0">
                        <li>Open Modals: <strong>@EventState.ModalCount</strong></li>
                        <li>Top Modal ID: <small>@EventState.TopModalId</small></li>
                        <li>Stack Active: <span class="badge @(EventState.IsAnyModalOpen ? "bg-success" : "bg-danger")">
                            @(EventState.IsAnyModalOpen ? "YES" : "NO")
                        </span></li>
                    </ul>
                </div>

                <div class="d-flex flex-column gap-2 mt-3">
                    <P11Button Text="Simulate external closing" 
                                OnClick="async() => {
                                    if(EventState.IsAnyModalOpen)
                                        await EventState.TryCloseTopModalAsync();
                                }" />

                    <P11Button Text="Toggle Debug Logs" 
                                OnClick="() => EventState.DebugMode = !EventState.DebugMode" />
                </div>

                <div class="mt-3">
                    <h6>Full Stack Trace:</h6>
                    <div style="font-size: 10px; font-family: monospace; background: #eee; padding: 5px;">
                        @foreach (var id in EventState.ModalStack)
                        {
                            <div>ID: @id @(id == EventState.TopModalId ? "<-- TOP" : "")</div>
                        }
                    </div>
                </div>                      

            </BodyContent>
        </P11ModalNative>
    }

    @if (isOpenModalEx2_3)
    {
        <P11ModalNative @bind-Visible="@isOpenModalEx2_3" OnClose="async () => { }"
                        Id="@guidModalEx2_3"
                        NativeDevice="NativeDevice.IPHONE"
                        ShowHeaderSeparator="true"
                        CloseButtonText="Back"
                        CloseButtonIconCss="bi bi-chevron-left"
                        AriaLabelledby="modalTitle1Help"
                        CssClassContent="p11-form-bg-01"
                        Size="Size.Large">
            <HeaderContent>
                <h5 id="specific-position-panel-settings" class="modal-title">Modal 3</h5>
            </HeaderContent>
            <BodyContent>

                <h3>Modal 3</h3>

                <div class="alert alert-info mt-3">
                    <strong>P11 Stack Info:</strong>
                    <ul class="list-unstyled mb-0">
                        <li>Open Modals: <strong>@EventState.ModalCount</strong></li>
                        <li>Top Modal ID: <small>@EventState.TopModalId</small></li>
                        <li>Stack Active: <span class="badge @(EventState.IsAnyModalOpen ? "bg-success" : "bg-danger")">
                            @(EventState.IsAnyModalOpen ? "YES" : "NO")
                        </span></li>
                    </ul>
                </div>

                <div class="d-flex flex-column gap-2 mt-3">
                    <P11Button Text="Simulate external closing" 
                                OnClick="async() => {
                                    if(EventState.IsAnyModalOpen)
                                        await EventState.TryCloseTopModalAsync();
                                }" />

                    <P11Button Text="Toggle Debug Logs" 
                                OnClick="() => EventState.DebugMode = !EventState.DebugMode" />
                </div>

                <div class="mt-3">
                    <h6>Full Stack Trace:</h6>
                    <div style="font-size: 10px; font-family: monospace; background: #eee; padding: 5px;">
                        @foreach (var id in EventState.ModalStack)
                        {
                            <div>ID: @id @(id == EventState.TopModalId ? "<-- TOP" : "")</div>
                        }
                    </div>
                </div>

            </BodyContent>
        </P11ModalNative>
    }

</div>
@code {
    // Example 2
    private bool isOpenModalEx2_1 = false;
    private bool isOpenModalEx2_2 = false;
    private bool isOpenModalEx2_3 = false;
    private bool isOpenModalEx2_4 = false;
    private Guid guidModalEx2_1 = Guid.NewGuid();
    private Guid guidModalEx2_2 = Guid.NewGuid();
    private Guid guidModalEx2_3 = Guid.NewGuid();
    private Guid guidModalEx2_4 = Guid.NewGuid();

    protected override void OnInitialized()
    {
        // 1. Stack-Changes für UI Updates
        EventState.OnStackChanged += HandleStackChanged;
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // Initialisierung des Services (wie im MainLayout)
            await EventState.InitializeAsync(NativeDevice.IPHONE);
        }
    }

    private void HandleStackChanged() => InvokeAsync(StateHasChanged);

    public override void Dispose()
    {
        EventState.OnStackChanged -= HandleStackChanged;
    }

    /* -------------------------------------------------------------------------
    REFERENCE: Required MainLayout Implementation
    The following logic must exist in your MainLayout.razor to bridge 
    native OS events to the P11 Modal Native system:
    -------------------------------------------------------------------------

    // 1. Native Bridge Entry Point (Android/Capacitor)
    [JSInvokable("HandleNativeBack")]
    public async Task HandleNativeBack(bool canGoBack)
    {
        // Check if any P11 Modal is open and close the topmost one
        bool wasModalClosed = await _eventState.TryCloseTopModalAsync();

        if (wasModalClosed) return; // Event consumed, do nothing more

        // Otherwise: Standard navigation logic (e.g., NavigateBack or Exit App)
        await ProcessGlobalBackLogic(canGoBack);
    }

    // 2. iOS Swipe Listener (Global back when no modals are open)
    private async Task HandleIOSGlobalBack()
    {
        // P11 calls this when a swipe occurs but the Modal Stack is empty
        await ProcessGlobalBackLogic(true);
    }

    // 3. Lifecycle Registration
    protected override void OnInitialized()
    {
        _eventState.OnStackChanged += HandleModalStackChanged;
        _eventState.OnGlobalBackNavigationRequested += HandleIOSGlobalBack;
    }
    */
}


Component API

Parameter Type Default Description
Visible bool false Controls the visibility of the modal. Bind with @bind-Visible.
Id Guid - Unique identifier for the modal stack. Mandatory for reliable back-navigation tracking.
NativeDevice NativeDevice WEB Sets the visual style and header behavior (WEB, WINDOWS, MAC, IPHONE, ANDROID).
IsNativeControlFullscreen bool true Forces fullscreen mode on mobile devices (iPhone/Android) regardless of other settings.
AllowMaximize bool false Enables the maximize/zoom button for Desktop modes (Windows/macOS).
CloseOnOutsideClick bool false If true, clicking the backdrop closes the modal.
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') 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
Size Size Medium The Bootstrap size of the modal dialog.
Position ModalPosition Default Positioning logic (Default or Scrollable).
IsFullScreen bool false Forces the modal to take up the entire viewport.
ShowHeaderSeparator bool true Shows a border-bottom under the header.
CloseButtonText string? null Custom text for the close button (e.g., 'Zurück' for iOS).
CloseButtonIconCss string? null Icon class (e.g., 'bi bi-chevron-left') for the close button.
Custom CSS Classes
CssClassContent string? null CSS class for the .modal-content container.
CssClassHeader string? null CSS class for the .modal-header.
CssClassBody string? null CSS class for the .modal-body.
CssClassFooter string? null CSS class for the .modal-footer.
Behavior & A11y
ShowCloseButton bool true Determines if the close control is rendered.
PreventScroll bool true Locks background page scrolling.
RestoreFocusId string? null Element ID to refocus after closing.
AriaLabel string? null Accessibility label.
AriaLabelledby string? null ID of the labeling element.
Events
VisibleChanged EventCallback<bool> - Supports @bind-Visible.
OnClose EventCallback - Invoked when the modal is closed.
An unhandled error has occurred. Reload 🗙