Sticky Stack
Stack several sticky headers/toolbars so each sticks below the previous ones.
| Package | @vizejs/ui/sticky-stack |
| Maturity | stable |
| Own the source | vize lib pull sticky-stack |
| Requires | context |
| Aliases | sticky header, stacked sticky, sticky toolbar, sticky offsets |
| Covers | position: sticky, react-sticky, Material top app bar stacking |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { StickyStack, StickyStackItem } from "@vizejs/ui/sticky-stack";
Or copy the source into your project with vize lib pull sticky-stack (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- App bar and section tabs that stay pinned one below the other while the page scrolls. -->
<script setup lang="ts">
import { StickyStack, StickyStackItem } from "@vizejs/ui/sticky-stack";
const sections = ["Overview", "Pricing", "Reviews", "Support"];
</script>
<template>
<StickyStack :offset="0">
<StickyStackItem as="header" :estimated-height="56">
<strong>Acme Store</strong>
</StickyStackItem>
<StickyStackItem as="nav" :estimated-height="40" aria-label="Sections">
<a v-for="section in sections" :key="section" :href="`#${section.toLowerCase()}`">
{{ section }}
</a>
</StickyStackItem>
<section v-for="section in sections" :key="section" :aria-label="section">
<h2>{{ section }}</h2>
<p>Details about {{ section.toLowerCase() }} for this product.</p>
</section>
</StickyStack>
</template>
API
StickyStack
Source: src/families/layout/sticky-stack/sticky-stack.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as |
PrimitiveAs |
"div" |
Native element, custom element, or component to render. |
offset |
number |
0 |
Distance from the top of the scroll port where the first item sticks (e.g. the height of a fixed app bar). |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
StickyStackSlotState |
Page content containing StickyStackItems. Receives the total stack height, which includes the items once they have registered (after the first render). |
Exposed
| Member | Type | Description |
|---|---|---|
total |
— | |
refresh |
— |
StickyStackItem
Source: src/families/layout/sticky-stack/sticky-stack-item.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as |
PrimitiveAs |
"div" |
Native element, custom element, or component to render. |
estimatedHeight |
number |
0 |
Height used before the item is measured (server rendering and hydration), so items below get their offsets on first paint. |
disabled |
boolean |
false |
Scroll normally instead of sticking; later items no longer stack below it. |
Events
| Event | Payload | Description |
|---|---|---|
stuckChange |
[stuck: boolean] |
Fired when the item becomes stuck (true) or scrolls free again (false). |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
StickyStackItemSlotState |
Item content. Receives the resolved offset and whether the item is stuck. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
top |
— | |
stuck |
— | |
measure |
— |
Behavior
Normative state x input -> outcome table for sticky-stack.vue and
sticky-stack-item.vue (@vizejs/ui/sticky-stack). Rows are proven by
sticky-stack.test.ts and sticky-stack-ssr.test.ts; compile-only assertions
live in sticky-stack.types.test-d.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| SS1 | heights, base | compute offsets | each item's top is the base plus earlier enabled heights; invalid values count as 0 |
stacks offsets and skips disabled heights |
| SS2 | registrations | sort | items follow document order; detached items follow in registration order | orders entries by document position and keeps detached ones in registration order |
| SS3 | mounted stack | measure, scroll, remove | measured heights replace estimates, data-stuck/stuckChange follow scroll, removals restack |
items stick below the base offset plus measured heights of earlier items |
| SS4 | disabled item | render | the item is not sticky and adds no offset to later items | disabled items scroll normally and add no offset |
| SS5 | no provider | mount an item | stable VIZE_UI_CONTEXT_MISSING: StickyStack diagnostic |
items outside a stack throw the context diagnostic |
| SS6 | SSR | isolated requests | byte-identical markup with offsets from estimatedHeight |
renders byte-identical estimated offsets across SSR requests |
| SS7 | hydration | mount over server markup | no mismatch warnings | hydrates estimated offsets without mismatch warnings |
| SS8 | DOM/SSR/Vapor | compile | both SFCs compile in every renderer lane | scripts/check-renderers.ts |
The stack exposes --vize-ui-sticky-stack-height for scroll-margin-top and
similar consumer CSS once its items have registered.