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 / leftoffset, 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
import { AppFixedOverlay } from '@pharma4u/patternlab/vue'Basic usage
<AppFixedOverlay :bottom-offset="0" :left-offset="0" :right-offset="0">
<div class="c-panel">My action bar</div>
</AppFixedOverlay>2
3
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
<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>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
<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>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Props
| Prop | Type | Default | Description |
|---|---|---|---|
topOffset | number | null | null | Distance in px from the top of the viewport. When combined with scroll + target, acts as a minimum — the overlay never goes above this value. |
bottomOffset | number | null | null | Distance in px from the bottom of the viewport. |
rightOffset | number | null | null | Distance in px from the right edge of the viewport. |
leftOffset | number | null | null | Distance in px from the left edge of the viewport. |
target | string | '' | CSS selector of the element to track. Only used when scroll is true. |
scroll | boolean | false | Enables 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
| Slot | Description |
|---|---|
default | The 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
<AppFixedOverlay :bottom-offset="0" :left-offset="0" :right-offset="0">
<div class="c-panel">...</div>
</AppFixedOverlay>2
3
Bottom-right FAB
<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>2
3
4
5
Sticky sidebar below a fixed header
<!-- 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>2
3
4
Controlled visibility with v-if
The component has no built-in show/hide state. Control visibility in the parent:
<AppFixedOverlay v-if="isVisible" :bottom-offset="0" :left-offset="0" :right-offset="0">
<div class="c-panel">...</div>
</AppFixedOverlay>2
3
Accessibility
AppFixedOverlayrenders a plain<div>— applyroleandaria-*attributes to the content inside the slot.
Notes
- The component uses
h-pos-fixed, which maps toposition: fixed. - Scroll listeners are registered with
{ passive: true }onscrollto avoid blocking the main thread. - Listeners are cleaned up in
onBeforeUnmount— safe to use in dynamic component trees.