Sticky / Affix
Pin elements inside the nearest scrollable container as the user scrolls. If no scrollable parent exists, the element falls back to the viewport.
Custom Offset
Use data-vd-affix-offset to control when the element becomes stuck. The value is the distance in pixels from the top of the active scroll context.
<div
class="vd-affix vd-affix-bordered"
data-vd-affix
data-vd-affix-offset="60">
<p>I stick 60px below the top of my scroll container</p>
</div>Style Variants
Combine modifier classes to fine-tune the look when the element becomes stuck.
.vd-affix-bordered — adds a bottom border when stuck .vd-affix-no-shadow — suppresses the default drop shadow API Reference
Wiring
import { ref } from 'vue';
import { useAffix } from "@vanduo-oss/vd3";
const root = ref<HTMLElement | null>(null);
useAffix(root); // wires .vd-affix / [data-vd-affix] inside root; cleanup on unmount
// react to pin / unpin
root.value?.addEventListener('affix:stuck', () => { /* … */ });CSS Classes
| Class | Description |
|---|---|
.vd-affix | Base class for affix-enabled elements. Pairs with data-vd-affix. |
.vd-sticky | Alias for .vd-affix. It participates in the same JS-driven affix behavior. |
.is-stuck | Applied automatically while the element is pinned inside its active scroll context. |
.vd-affix-no-shadow | Modifier that removes the default box-shadow applied in the stuck state. |
.vd-affix-bordered | Modifier that adds a subtle bottom border instead of a shadow when stuck. |
Data Attributes
| Attribute | Description |
|---|---|
data-vd-affix | Enables the affix behavior on the element. Detected by useAffix(root). |
data-vd-affix-offset | Distance in pixels from the top of the nearest scrollable container, or the viewport when no scrollable parent exists. Defaults to 0. |
Composable API
| Symbol | Description |
|---|---|
useAffix(root) | Composable — wires every .vd-affix / [data-vd-affix] inside the root ref; toggles .is-stuck via a sentinel + IntersectionObserver. Call once in setup(). |
(automatic cleanup) | The observer and generated sentinel are removed on component unmount. |
Events
| Event | Description |
|---|---|
affix:stuck | Fired on the element when it becomes pinned. event.detail includes the offset and active root. |
affix:unstuck | Fired on the element when it returns to its natural position. |
Accessibility
- Sticky elements do not trap focus — keyboard navigation continues naturally past them.
- The auto-generated placeholder prevents content from jumping, avoiding disorientation for screen magnifier users.
- Use
aria-labelon sticky navigation to distinguish it from the main page nav (e.g.aria-label="Sticky toolbar"). - Stuck state changes are visual-only; ensure the element's purpose is clear without relying on position.