Skip to content

Modal hides content below the fold with no indication of overflow #2427

@schoberg

Description

@schoberg

Flux version

2.10.2

Livewire version

3.7.6

Tailwind version

4.1.18

Browser and Operating System

Chrome on macOS

What is the problem?

When a modal's content is taller than the viewport, it can look visually complete. It's a bad UX pattern and users don't know to scroll depending on how the content hits at the cutoff. (easiest to visualize in the video below)

The native <dialog> element constrains content to the viewport with internal scrolling. But the modal's padding, rounded corners, and shadow all render within that box, so the bottom looks like the natural end.

A related issue: when content grows dynamically (accordion, async load), the centering causes the modal to jump/reposition.

The common UI patter to solve this is the <dialog> becomes a transparent full-viewport scroll container, and the visible modal is an inner panel. The whole modal scrolls as a unit, making overflow obvious and eliminating the jumping issue.

Code snippets to replicate the problem

Presents with any long content modal or any dynamic content modal (top varient fix)

<flux:modal name="terms" class="md:w-xl">
    <div class="space-y-4">
        <flux:heading size="lg">Terms of Service</flux:heading>
        <flux:text>Please review and accept the following terms.</flux:text>

        <flux:subheading size="lg">1. Introduction</flux:subheading>
        <flux:text>Welcome to our platform. These Terms of Service govern your use of our website and services. By accessing or using our services, you agree to be bound by these terms.</flux:text>

        <flux:subheading size="lg">2. Account Registration</flux:subheading>
        <flux:text>To access certain features, you must register for an account. You agree to provide accurate, current, and complete information during registration.</flux:text>

        <!-- Repeat sections 3-10 so content extends well below the viewport -->
        <!-- The modal will look complete after section 5 or so. Nothing indicates sections 6-10 exist. -->
    </div>
</flux:modal>

Screenshots/ screen recordings of the problem

Here's a 2 min video explaining both issues and showing the fixes:
https://img.dropinblog.com/FDGMtS65

Screen recording has 4 short parts:

Problem: tall modal looks complete, content hidden below
Fix: same modal scrolls as a unit, overflowing obvious
Problem: expanding content causes modal to jump causing disorientation
Fix: modal stays pinned, grows downward when using top varient

How do you expect it to work?

The modal should scroll as a visible unit so it's immediately obvious when content extends below the fold.

The fix is to make the <dialog> a transparent full-viewport scroll container and move the visual styling to an inner panel div.

Here is a working implementation for non-flyout modals (flyouts are unaffected)

@php
// Extract 'top' variant to boolean (same pattern as 'flyout')
if ($variant === 'top') {
    $top = true;
    $variant = null;
}

// ... existing flyout/closable/dismissible logic unchanged ...

if ($flyout) {
    // ... unchanged ...
} else {
    // Dialog is now a transparent scroll container, so no classes needed on it
    $classes = Flux::classes();

    // Visual styling moves to the inner panel div
    $panelClasses = Flux::classes()
        ->add('relative flux-modal-panel')
        ->add(($top ?? false) ? 'self-start' : '')
        ->add(match ($variant) {
            default => 'p-6 [:where(&)]:max-w-xl [:where(&)]:min-w-xs shadow-lg rounded-xl',
            'bare' => '',
        })
        ->add(match ($variant) {
            default => 'bg-white dark:bg-zinc-800 ring ring-black/5 dark:ring-zinc-700 shadow-lg rounded-xl',
            'bare' => 'bg-transparent',
        });
}
@endphp

{{-- ... existing <ui-modal> and <dialog> opening unchanged ... --}}

<dialog
    wire:ignore.self
    @if ($flyout)
        {{ $styleAttributes->class($classes) }}
    @else
        class="flux-dialog-scroll"
    @endif
    @if ($name) data-modal="{{ $name }}" @endif
    @if ($flyout) data-flux-flyout @endif
    x-data
    {{-- ... existing x-on:modal-show/close handlers unchanged ... --}}
>
    @if ($flyout)
        {{-- Flyout: unchanged --}}
        {{ $slot }}

        <?php if ($closable): ?>
            {{-- ... existing close button unchanged ... --}}
        <?php endif; ?>
    @else
        {{-- Default/Bare: scroll container with centering wrapper + panel --}}
        <div
            class="flex min-h-full items-center justify-center p-4 sm:p-6"
            @if ($dismissible !== false)
                x-data="{ _mds: false }"
                x-on:mousedown.self="_mds = true"
                x-on:click.self="if (_mds) $el.closest('dialog').close(); _mds = false"
            @endif
        >
            <div {{ $styleAttributes->class($panelClasses) }}>
                {{ $slot }}

                <?php if ($closable): ?>
                    {{-- ... existing close button unchanged ... --}}
                <?php endif; ?>
            </div>
        </div>
    @endif
</dialog>

Then the needed css should be something like..

/* Dialog as transparent scroll container (non-flyout) */
[data-flux-modal] > dialog.flux-dialog-scroll {
    position: fixed;
    inset: 0;
    margin: 0;
    padding: 0;
    max-height: none;
    max-width: none;
    width: 100%;
    height: 100%;
    overflow-y: auto;
    background: transparent;
    border: none;
    box-shadow: none;
    outline: none;
    transform: none !important;
}

/* Panel scale + fade (replaces dialog-level animation) */
[data-flux-modal] > dialog.flux-dialog-scroll .flux-modal-panel {
    opacity: 0;
    transform: scale(0.95);
    transition: all 0.075s allow-discrete;
}

[data-flux-modal] > dialog.flux-dialog-scroll[open] .flux-modal-panel {
    opacity: 1;
    transform: scale(1);
    transition: all 0.15s allow-discrete;
}

@starting-style {
    [data-flux-modal] > dialog.flux-dialog-scroll[open] .flux-modal-panel {
        opacity: 0;
        transform: scale(0.95);
    }
}

Please confirm (incomplete submissions will not be addressed)

  • I have provided easy and step-by-step instructions to reproduce the bug.
  • I have provided code samples as text and NOT images.
  • I understand my bug report will be closed if I haven't met the criteria above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions