Skip to content

AppFixedOverlay (Vue Component) ​

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.

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.