Skip to content

cwandev/vue-stick-to-bottom

Repository files navigation

vue-stick-to-bottom

Designed with AI chat bots in mind, unofficial Vue port of use-stick-to-bottom.

npm version npm downloads

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-anchor browser-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, ResizeObserver API 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.
    • scrollToBottom returns a Promise<boolean> which resolves to true once the scroll succeeds, or false if 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 add

Usage

<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 dev

This 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.

License

MIT

About

A lightweight Vue component and composable designed for AI chat applications to smoothly stick to the bottom of messages.

Topics

Resources

License

Stars

Watchers

Forks

Contributors