Skip to content

AppFixedOverlay

AppFixedOverlay is a headless Vue layout primitive that renders any slotted content at a fixed position on the viewport.

It supports two modes:

  • Static — fixed at a given top / bottom / right / left offset, unaffected by scroll.
  • Scroll-aware — tracks a target element's distance from the top of the viewport so the overlay never visually overlaps it (useful for toolbars below sticky headers).

When to use

Use AppFixedOverlay whenever you need content pinned to the viewport: action bars, floating panels, notification trays, sticky CTAs. The component owns only the positioning — you are responsible for styling the content inside the slot.


Vue Component: AppFixedOverlay

Quick Start

Import

ts
import { AppFixedOverlay } from '@pharma4u/patternlab/vue'

Basic usage

vue
<AppFixedOverlay :bottom-offset="0" :left-offset="0" :right-offset="0">
  <div class="c-panel">My action bar</div>
</AppFixedOverlay>

Live Examples

Scroll-aware sticky notification

Tracks the #quick-start heading on this page. Scroll down after activating it — the chip follows the section boundary and never goes above topOffset=80 (below the site nav).

Show Code
vue
<script setup>
import { ref } from 'vue'
import { AppFixedOverlay, BasicButton } from '@pharma4u/patternlab/vue'

const showNotification = ref(false)
</script>

<template>
  <BasicButton size="small" @click="showNotification = !showNotification">
    {{ showNotification ? 'Hide' : 'Show sticky notification' }}
  </BasicButton>

  <AppFixedOverlay
    v-if="showNotification"
    scroll
    target="#quick-start"
    :top-offset="80"
    :right-offset="24"
  >
    <div role="status" aria-live="polite" class="my-notification-chip">
      <i class="far fa-bell" aria-hidden="true"></i>
      <span><strong>3</strong> neue Meldungen</span>
      <button class="c-btn -icon-btn -small" @click="showNotification = false">
        <i class="far fa-times" aria-hidden="true"></i>
      </button>
    </div>
  </AppFixedOverlay>
</template>

How scroll-tracking works

top = Math.max(topOffset, target.getBoundingClientRect().top)

While #quick-start is visible → the chip sits just below it.
Once it scrolls off-screen → the chip locks at topOffset (80 px from the top).


Chatbot widget (fixed bottom-right)

A fixed FAB that expands into a mini chat panel. The AppFixedOverlay owns only the position — the open/closed state lives in the slot content.

Show Code
vue
<script setup>
import { ref } from 'vue'
import { AppFixedOverlay } from '@pharma4u/patternlab/vue'

const chatOpen = ref(true)
</script>

<template>
  <AppFixedOverlay :bottom-offset="24" :right-offset="24">

    <!-- Collapsed: FAB bubble -->
    <button
      v-if="!chatOpen"
      class="c-btn -icon-btn -primary"
      style="width:52px;height:52px;border-radius:50%;"
      aria-label="Chat öffnen"
      @click="chatOpen = true"
    >
      <i class="far fa-comment-dots fa-lg" aria-hidden="true"></i>
    </button>

    <!-- Expanded: chat panel -->
    <div v-else role="dialog" aria-label="Assistent" class="my-chat-panel">
      <header class="chat-header">
        <span><i class="far fa-robot" aria-hidden="true"></i> Assistent</span>
        <button class="c-btn -icon-btn -small" aria-label="Minimieren" @click="chatOpen = false">
          <i class="far fa-minus" aria-hidden="true"></i>
        </button>
      </header>
      <div class="chat-body">
        <div class="chat-bubble">Hallo! Wie kann ich Ihnen helfen?</div>
      </div>
      <div class="chat-input">
        <input type="text" class="c-input" placeholder="Nachricht eingeben …" />
        <button class="c-btn -icon-btn -small -primary" aria-label="Senden">
          <i class="far fa-paper-plane" aria-hidden="true"></i>
        </button>
      </div>
    </div>

  </AppFixedOverlay>
</template>

Props

PropTypeDefaultDescription
topOffsetnumber | nullnullDistance in px from the top of the viewport. When combined with scroll + target, acts as a minimum — the overlay never goes above this value.
bottomOffsetnumber | nullnullDistance in px from the bottom of the viewport.
rightOffsetnumber | nullnullDistance in px from the right edge of the viewport.
leftOffsetnumber | nullnullDistance in px from the left edge of the viewport.
targetstring''CSS selector of the element to track. Only used when scroll is true.
scrollbooleanfalseEnables scroll and resize listeners. Updates the computed top position on every scroll frame.

Prop interaction

A prop set to null (the default) means that CSS property is not applied — the browser default is used. Setting leftOffset=0 and rightOffset=0 together stretches the overlay across the full viewport width.


Slots

SlotDescription
defaultThe content rendered inside the fixed container. The component adds no visual styling — sizing, background, and shadow are the caller's responsibility.

Common Patterns

Full-width bottom bar

vue
<AppFixedOverlay :bottom-offset="0" :left-offset="0" :right-offset="0">
  <div class="c-panel">...</div>
</AppFixedOverlay>

Bottom-right FAB

vue
<AppFixedOverlay :bottom-offset="24" :right-offset="24">
  <button class="c-btn -primary -icon-btn">
    <i class="fas fa-plus" aria-hidden="true"></i>
  </button>
</AppFixedOverlay>
vue
<!-- Header is 64px tall and position:fixed -->
<AppFixedOverlay scroll target="#app-header" :top-offset="64" :right-offset="0">
  <nav class="my-side-nav">...</nav>
</AppFixedOverlay>

Controlled visibility with v-if

The component has no built-in show/hide state. Control visibility in the parent:

vue
<AppFixedOverlay v-if="isVisible" :bottom-offset="0" :left-offset="0" :right-offset="0">
  <div class="c-panel">...</div>
</AppFixedOverlay>

Accessibility

  • AppFixedOverlay renders a plain <div> — apply role and aria-* attributes to the content inside the slot.

Notes

  • The component uses h-pos-fixed, which maps to position: fixed.
  • Scroll listeners are registered with { passive: true } on scroll to avoid blocking the main thread.
  • Listeners are cleaned up in onBeforeUnmount — safe to use in dynamic component trees.