vue-stick-to-bottom
v1.0.0
Published
A lightweight Vue utility intended mainly for AI chat applications, for smoothly sticking to the bottom of messages.
Downloads
15,861
Maintainers
Readme
vue-stick-to-bottom
Designed with AI chat bots in mind, unofficial Vue port of use-stick-to-bottom.
A lightweight Vue 3 component and composable that automatically sticks to the bottom of a scrollable container and smoothly animates content to keep its visual position on screen while new content is being added.
Features
- Does not require
overflow-anchorbrowser-level CSS support (which Safari does not support). - Can be connected to any existing component using a composable with refs, or simply use the provided component which handles refs for you plus provides context via
provide/inject. - Uses the modern, yet well-supported,
ResizeObserverAPI to detect when content resizes.- Supports content shrinking without losing stickiness — not just getting taller.
- Correctly handles Scroll Anchoring. When content above the viewport resizes, the content currently displayed in the viewport won't jump up or down.
- Allows the user to cancel the stickiness at any time by scrolling up.
- Clever logic distinguishes user scrolling from custom animation scroll events (without debouncing which could cause some events to be missed).
- Works well on mobile devices too.
- Uses a custom smooth scrolling algorithm with velocity-based spring animations (configurable parameters).
- Other libraries use easing functions with durations, which don't work well when streaming in new content with variable sizing — common for AI chatbot use cases.
scrollToBottomreturns aPromise<boolean>which resolves totrueonce the scroll succeeds, orfalseif the scroll was cancelled.
- Zero runtime dependencies.
- Tree-shakable ESM build with full TypeScript declarations.
Installation
pnpm add vue-stick-to-bottom
# or: npm install / yarn addUsage
<StickToBottom> Component
<script setup lang="ts">
import { StickToBottom, useStickToBottomContext } from 'vue-stick-to-bottom'
</script>
<template>
<StickToBottom class="h-[50vh] relative" resize="smooth" :initial="{ mass: 10 }">
<div class="flex flex-col gap-4">
<Message v-for="msg in messages" :key="msg.id" :message="msg" />
</div>
<template #overlay>
<ScrollToBottom />
</template>
<template #after>
<ChatBox />
</template>
</StickToBottom>
</template>The ScrollToBottom child component can use useStickToBottomContext to access state and actions:
<script setup lang="ts">
import { useStickToBottomContext } from 'vue-stick-to-bottom'
const { isAtBottom, scrollToBottom } = useStickToBottomContext()
</script>
<template>
<button
v-if="!isAtBottom"
class="absolute bottom-2 left-1/2 -translate-x-1/2"
@click="scrollToBottom()"
>
↓ Scroll to bottom
</button>
</template>useStickToBottom Composable
For full control over the DOM structure, use the composable directly:
<script setup lang="ts">
import { useStickToBottom } from 'vue-stick-to-bottom'
const { scrollRef, contentRef, isAtBottom, scrollToBottom } = useStickToBottom()
</script>
<template>
<div ref="scrollRef" style="overflow: auto; height: 50vh;">
<div ref="contentRef">
<Message v-for="msg in messages" :key="msg.id" :message="msg" />
</div>
</div>
<button v-if="!isAtBottom" @click="scrollToBottom()">↓</button>
</template>Slot Props
All slots (default, overlay, after) expose the following props for convenience:
<StickToBottom>
<template #default="{ isAtBottom, scrollToBottom }">
<!-- Use slot props directly without useStickToBottomContext -->
</template>
</StickToBottom>API
<StickToBottom> Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| resize | Animation | Spring | Animation to use when content resizes. |
| initial | Animation \| boolean | Spring | Animation for initial scroll. false to start at top. |
| targetScrollTop | GetTargetScrollTop | — | Custom function to compute the target scroll position. |
| damping | number | 0.7 | Spring damping (0–1). 0 = no damping, 1 = full. |
| stiffness | number | 0.05 | Spring stiffness — how fast the animation accelerates. |
| mass | number | 1.25 | Spring mass — higher = slower animation. |
| anchor | 'auto' \| 'none' | 'auto' | Maps to CSS overflow-anchor. Use 'none' to disable browser scroll anchoring. |
Animation type
type Animation = ScrollBehavior | SpringAnimation
// ScrollBehavior: 'auto' | 'instant' | 'smooth'
// SpringAnimation: { damping?: number; stiffness?: number; mass?: number }<StickToBottom> Slots
| Slot | Description |
|------|-------------|
| default | Content inside the scrollable area. |
| overlay | Floats above the scrollable content (e.g. scroll-to-bottom button). Positioned within the relative wrapper. |
| after | Outside the scroll area entirely (e.g. input box, external controls). |
All slots receive { isAtBottom, isNearBottom, escapedFromLock, scrollToBottom, stopScroll } as slot props.
useStickToBottom(options?)
Returns:
| Property | Type | Description |
|----------|------|-------------|
| scrollRef | Ref<HTMLElement \| null> | Template ref for the scroll container. |
| contentRef | Ref<HTMLElement \| null> | Template ref for the content wrapper. |
| isAtBottom | Ref<boolean> | Whether the container is at (or very near) the bottom. |
| isNearBottom | Ref<boolean> | Whether the container is within ~70px of the bottom. |
| escapedFromLock | Ref<boolean> | Whether the user has scrolled up to cancel stickiness. |
| scrollToBottom | ScrollToBottom | Programmatically scroll to the bottom. |
| stopScroll | StopScroll | Stop the current scroll animation and release the lock. |
| setOptions | (options) => void | Update engine options at runtime. |
scrollToBottom(options?)
type ScrollToBottomOptions =
| ScrollBehavior
| {
animation?: Animation
wait?: boolean | number // Wait for existing scrolls or delay in ms
ignoreEscapes?: boolean // Prevent user from cancelling this scroll
preserveScrollPosition?: boolean // Only scroll if already at bottom
duration?: number | Promise<void> // Extra duration to maintain the scroll
}Returns Promise<boolean> — resolves to true if scroll completed, false if cancelled.
useStickToBottomContext()
Call inside a child component of <StickToBottom> to access the context via inject. Throws if used outside the component tree.
Demo
Run the built-in demo:
pnpm devThis opens a playground showcasing smooth vs instant columns, a speed slider, a floating scroll-to-bottom button, and stop-scroll controls.
Credits
This is a Vue 3 port of use-stick-to-bottom by Sam Denty / StackBlitz.
