0px mobile

Mobile Overlay Header: Implementation Guide

A header that collapses into an expandable overlay on mobile and renders as a full static bar on desktop (1200px+). Demonstrated with an order tracking context, powered by Bootstrap 4's collapse component. Resize the browser window past 1200px to see both modes.

1

What This Pattern Does

On desktop (1200px and wider) the header bar is a normal, always-visible block element. It sits in the document flow and pushes page content down below it.

On mobile (under 1200px) the header content is hidden by default. A compact toggle button appears in the top-right corner of the bar. Clicking it expands the header content as a drop-down panel that overlays the page rather than reflowing it, then dismisses on a second click or an outside click.

The two properties that create the overlay effect are position: absolute on the panel (mobile) and position: static on the panel (desktop). The toggle button's animated width (33% when closed, 100% when open) is a secondary visual cue driven by a transition: width rule. Expand/collapse itself is handled by Bootstrap's collapse component rather than hand-rolled JavaScript.

2

Live Demo

Try it: Narrow the browser below 1200px to activate mobile mode. The toggle button appears at the top-right. Click it to expand or collapse the order details. Click anywhere outside the panel to dismiss it. The badge in the top-right corner of the page shows your current viewport width.
Order Number #ORD-2024-00847
Item Ergonomic Office Chair Model XC-9000, Charcoal
Status In Transit
Est. Delivery Jan 15, 2025
Carrier / Tracking FastShip Logistics 1Z999AA1234567890

Page content lives here.

On mobile the expanded panel floats over this text. It does not push it down. On desktop the header above is always visible and part of the normal page flow.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

3

HTML Structure

Three structural layers make up the pattern. Each has a single responsibility: the outer wrapper anchors the overlay, the toggle button triggers it, and the panel holds the content.

Bootstrap 4 version (used by the live demo above)
<!--
    LAYER 1: OUTER WRAPPER
    position: relative on this element anchors the absolutely-positioned
    panel so it drops directly below this bar (not the page body).
-->
<div class="order-header-outer">

    <!--
        LAYER 2: TOGGLE BUTTON
        data-toggle="collapse" + data-target wire this button to the
        panel automatically. Bootstrap's JS handles the open/close state.
        d-xl-none hides the button entirely at the xl breakpoint (1200px+).
    -->
    <button class="order-header-toggle collapsed d-xl-none"
            type="button"
            data-toggle="collapse"
            data-target="#orderHeaderPanel"
            aria-expanded="false"
            aria-controls="orderHeaderPanel">
        Order Details
    </button>

    <!--
        LAYER 3: PANEL
        Bootstrap's .collapse class starts the panel hidden; .show is
        added/removed by the JS plugin when it opens or closes.
        d-xl-block forces the panel visible at the xl breakpoint,
        overriding the collapsed state on desktop.
    -->
    <div class="collapse d-xl-block order-header-panel" id="orderHeaderPanel">
        <div class="order-header-container">

            <!-- One .order-header-frame per data field -->
            <div class="order-header-frame">
                <span class="order-header-label">Order Number</span>
                <span class="order-header-value">#ORD-2024-00847</span>
            </div>

            <!-- Repeat for each additional field -->

        </div>
    </div>

</div>
Vanilla JS version (no framework required, if you'd rather not pull in Bootstrap)
<!-- Same three layers, but the panel uses .show instead of Bootstrap's
     .collapse / .show and the open/close logic is written by hand
     (see Section 6 for the matching JavaScript). -->
<button class="order-header-toggle collapsed"
        id="orderToggleBtn"
        type="button"
        aria-expanded="false"
        aria-controls="orderHeaderPanel">
    Order Details
</button>

<div class="order-header-panel" id="orderHeaderPanel">
    ...
</div>
4

CSS: Base Styles (Mobile-First)

These rules apply at all viewport widths. The desktop media query in Section 5 selectively overrides a handful of them. Because Bootstrap's collapse component now owns the show/hide mechanics, the panel itself needs far fewer custom rules than a hand-rolled version would; just enough to make it overlay the page instead of sitting in the normal flow.

.order-header-outer
.order-header-outer {

    position: relative;
    /*
     * Creates a positioning context so that position:absolute on the panel
     * is calculated from THIS element's edges, not from the nearest
     * positioned ancestor elsewhere in the DOM.
     * Without this, top:100% on the panel would not mean "below the toggle bar."
     */

    display: flex;
    justify-content: flex-end;
    /*
     * Pushes the toggle button to the right edge.
     * When the button is in its collapsed (33%-wide) state it appears as a
     * compact tab in the corner rather than a left-aligned strip.
     */
}
.order-header-panel
.order-header-panel {

    position: absolute;
    /*
     * Lifts the panel out of the normal document flow entirely.
     * It will not push down sibling elements; it floats on top of them.
     * This is the property that gives the panel its "overlay" behavior.
     * Bootstrap's .collapse class still drives the height animation and
     * the show/hidden state. This rule only changes how the panel is
     * positioned once it's visible.
     */

    top: 100%;
    /*
     * 100% equals the full height of the containing block (.order-header-outer).
     * This places the top edge of the panel immediately below the toggle button.
     */

    left: 0;
    right: 0;
    /*
     * Stretches the panel to the full width of .order-header-outer.
     */

    z-index: 200;
    /*
     * Stacks the panel above other page content when it is open.
     * Raise this number if modals, sticky navbars, or other overlays
     * appear in front of the panel unexpectedly.
     */
}
.order-header-toggle
.order-header-toggle {

    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 15px;

    width: 100%;
    /*
     * Full width when the panel is expanded (no .collapsed class).
     * The width transitions smoothly; see transition below.
     */

    border: none;
    padding: 10px 20px;
    cursor: pointer;
    white-space: nowrap;
    min-width: max-content;
    /*
     * Ensures the button never becomes narrower than its own text content
     * during the width animation.
     */

    transition: width 0.35s ease;
    /*
     * Smoothly animates the width change between 100% (open) and
     * 33% (collapsed). This runs independently of Bootstrap's own
     * collapse animation on the panel. The two transitions are timed
     * to match (0.35s) so they finish together.
     */
}

.order-header-toggle.collapsed {

    width: 33%;
    /*
     * Compact tab size when the panel is hidden. Because .order-header-outer
     * uses justify-content:flex-end, this 33%-wide button sits anchored to
     * the right side of the bar. The small script in Section 6 toggles this
     * class in sync with Bootstrap's show.bs.collapse / hide.bs.collapse events.
     */
}

.order-header-toggle::after {
    content: "\2212"; /* − minus sign, shown when OPEN */
    font-size: 22px;
    line-height: 1;
}

.order-header-toggle.collapsed::after {
    content: "\002B"; /* + plus sign, shown when CLOSED */
}
.order-header-container and .order-header-frame
.order-header-container {

    display: flex;
    flex-direction: column;
    /*
     * On mobile: stacks each data frame (label + value pair) vertically.
     * The desktop media query overrides this to flex-direction:row.
     */

    align-items: flex-start;
    row-gap: 22px;
    padding: 18px 24px 24px;

    overflow-wrap: anywhere;
    word-break: break-word;
    /*
     * Allows long strings (order numbers, tracking IDs) to wrap at any
     * character instead of overflowing their container.
     */
}

.order-header-frame {

    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;

    min-width: 0;
    /*
     * A common flexbox fix: flex items default to min-width:auto which
     * can cause them to overflow their container when content is wide.
     */

    max-width: 100%;
}

.order-header-frame:empty {
    display: none;
    /*
     * Automatically hides any frame element with no child content.
     * Useful when fields are conditionally rendered server-side.
     */
}
5

CSS: Desktop Breakpoint (1200px+)

At 1200px and above the overlay behavior is entirely disabled. Bootstrap's d-xl-none and d-xl-block utilities already take care of hiding the toggle and forcing the panel visible, so this media query only needs to handle layout: the column-to-row switch for the data frames.

@media (min-width: 1200px) {

    .order-header-outer {
        display: block;
        /*
         * Reverts from flex to block. The toggle button is hidden by
         * d-xl-none so there is nothing left to right-align.
         */
    }

    .order-header-container {
        flex-direction: row;
        /*
         * Switches from the mobile vertical stack to a horizontal row
         * so data frames sit side by side across the full bar width.
         */

        justify-content: space-between;
        align-items: center;
        flex-wrap: nowrap;
        padding: 10px 36px;
        gap: 36px;
        row-gap: 0;
        min-height: 80px;
    }

    .order-header-frame {
        flex: 0 1 auto;
        /*
         * flex-grow:0   = frame will NOT stretch to fill extra space.
         * flex-shrink:1 = frame CAN shrink if the bar is too narrow.
         * flex-basis:auto = frame starts at its natural content width.
         */

        min-width: 0;
    }

}
Note what's missing compared to a hand-rolled version: there's no display:none rule for the toggle and no position:static; max-height:none override for the panel; d-xl-none and d-xl-block on the markup itself already handle both.
6

JavaScript: Syncing the Toggle & Outside-Click

Bootstrap's collapse plugin handles the actual show/hide animation and the .show class on the panel automatically, so that part needs no custom code. Two small things are still hand-written: keeping the .collapsed modifier (and its +/− icon and width animation) on the button in sync with the panel's state, and dismissing the panel when the user clicks outside it on mobile.

Bootstrap 4 equivalent, used by the live demo above
(function () {

    var xlBreakpoint = 1200;
    var panel = document.getElementById('orderHeaderPanel');
    var btn   = document.getElementById('orderToggleBtn');

    function isMobile() {
        return window.innerWidth < xlBreakpoint;
    }

    /* Keep the button's .collapsed class + aria-expanded in sync with
       Bootstrap's own collapse events, instead of toggling it manually
       on click. */
    $(panel).on('show.bs.collapse', function () {
        btn.classList.remove('collapsed');
        btn.setAttribute('aria-expanded', 'true');
    });

    $(panel).on('hide.bs.collapse', function () {
        btn.classList.add('collapsed');
        btn.setAttribute('aria-expanded', 'false');
    });

    /* ── OUTSIDE-CLICK TO CLOSE (mobile only) ──────────────────────── */
    document.addEventListener('click', function (e) {

        if (!isMobile() || !panel.classList.contains('show')) {
            return;
        }

        var outer = panel.closest('.order-header-outer');

        if (outer && !outer.contains(e.target)) {
            $(panel).collapse('hide'); /* Bootstrap jQuery API */
        }
    });

})();
Vanilla JS alternative (if you skip Bootstrap entirely)
/*
 * Without Bootstrap's collapse plugin, the panel uses max-height +
 * overflow:hidden instead, and the click handler drives the .show class
 * itself rather than relying on show.bs.collapse / hide.bs.collapse.
 */
btn.addEventListener('click', function () {
    var isCollapsed = btn.classList.contains('collapsed');

    if (isCollapsed) {
        panel.style.maxHeight = panel.scrollHeight + 'px';
        panel.classList.add('show');
        btn.classList.remove('collapsed');
    } else {
        panel.style.maxHeight = '0px';
        panel.classList.remove('show');
        btn.classList.add('collapsed');
    }
});
7

How It All Fits Together

Mobile, closed: The outer wrapper is a flex row. The toggle button (33% wide, right-aligned, visible because the viewport is below the xl breakpoint) is shown. The panel carries Bootstrap's collapse class with no show, so it's hidden and takes up no space.

Mobile, open: Clicking the button fires Bootstrap's collapse plugin, which adds .show to the panel and animates its height. Our show.bs.collapse listener removes .collapsed from the button (animating it to 100% width) and flips the icon to a minus sign. Because the panel is position:absolute, it overlays the page content below rather than pushing it down. Clicking outside fires the outside-click handler, which calls $(panel).collapse('hide') to reverse everything.

Desktop: d-xl-none hides the toggle entirely and d-xl-block forces the panel to display:block regardless of its collapse state. The media query switches the container to a horizontal row. The outside-click handler exits immediately because isMobile() returns false.

Class / Modifier Responsibility
.order-header-outer Positioning anchor (position:relative) and flex host for right-aligning the collapsed toggle button.
.order-header-panel The expandable content area. Absolute overlay on mobile (position:absolute; top:100%); forced visible and in-flow on desktop via d-xl-block.
collapse / show Bootstrap's own classes, added and animated by the collapse plugin to reveal or hide the panel.
.order-header-toggle Mobile-only button (d-xl-none). Animates between 33% and 100% width via transition:width.
.order-header-toggle.collapsed Modifier kept in sync with the panel's Bootstrap collapse events. Shrinks the button to 33% and swaps the ::after pseudo-element from − to +.
.order-header-container Inner flex container. Vertical column on mobile; horizontal row on desktop via the 1200px media query.
.order-header-frame Wraps a label + value pair. :empty auto-hides frames with no server-rendered content. min-width:0 prevents flex overflow.