use-stick-to-bottom

repository·main·Indexed 20 days ago

https://github.com/stackblitz-labs/use-stick-to-bottom

A lightweight, zero-dependency React library designed for AI chatbot interfaces. It provides a hook and component to automatically stick to the bottom of a container and smoothly animate content to maintain visual position when new content is added. Includes the <StickToBottom> component for declarative implementation and the useStickToBottom hook for custom programmatic control.

Tokens
3.7K
Snippets
11
Records
14
Agent score
71%

What's inside use-stick-to-bottom

  1. Use the `<StickToBottom>` component

    main

    The <StickToBottom> component acts as a provider that manages scroll stickiness logic. It can be used in two ways:

    1. Render Prop Pattern: Pass a function as children to access the StickToBottomContext directly.
    2. Component Pattern: Use the <StickToBottom.Content> sub-component inside <StickToBottom> to automatically wire up the necessary scroll and content refs.

    Configuration Options

    PropTypeDescription
    massnumberPhysics mass for the spring animation
    dampingnumberPhysics damping for the spring animation
    stiffnessnumberPhysics stiffness for the spring animation
    resizebooleanWhether to resize on window resize
    initialbooleanWhether to start in a stick-to-bottom state
    targetScrollTopGetTargetScrollTopA function to customize how the target scroll position is calculated
    instanceStickToBottomInstanceAn existing instance to use instead of creating a new one
    contextRefReact.Ref<StickToBottomContext>A ref to access the context object imperatively
    children((context: StickToBottomContext) => ReactNode) | ReactNodeThe content to render, optionally as a function receiving the context
    import { StickToBottom } from 'use-stick-to-bottom';
    
    function MyComponent() {
      return (
        <StickToBottom resize damping={20}>
          <StickToBottom.Content>
            {({ scrollToBottom, isAtBottom }) => (
              <div>
                <button onClick={scrollToBottom}>Scroll to bottom</button>
                <p>Is at bottom: {isAtBottom ? 'Yes' : 'No'}</p>
                <div style={{ height: '1000px' }}>Long Content</div>
              </div>
            )}
          </StickToBottom.Content>
        </StickToBottom>
      );
    }
  2. Use the <StickToBottom> component for automatic scrolling

    main

    The <StickToBottom> component is the easiest way to implement stick-to-bottom behavior. It manages refs for you and provides a context that child components can consume to check if the user is at the bottom or to trigger a scroll.

    Key props:

    • resize: Controls how the component handles content resizing (e.g., "smooth").
    • initial: Controls the initial scroll behavior (e.g., "smooth").

    Sub-components:

    • <StickToBottom.Content>: Wraps the content that should be tracked for resizing and scrolling.
    import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
    
    function Chat() {
      return (
        <StickToBottom className="h-[50vh] relative" resize="smooth" initial="smooth">
          <StickToBottom.Content className="flex flex-col gap-4">
            {messages.map((message) => (
              <Message key={message.id} message={message} />
            ))}
          </StickToBottom.Content>
    
          <ScrollToBottom />
    
          {/* This component uses `useStickToBottomContext` to scroll to bottom when the user enters a message */}
          <ChatBox />
        </StickToBottom>
      );
    }
    
    function ScrollToBottom() {
      const { isAtBottom, scrollToBottom } = useStickToBottomContext();
    
      return (
        !isAtBottom && (
          <button
            className="absolute i-ph-arrow-circle-down-fill text-4xl rounded-lg left-[50%] translate-x-[-50%] bottom-0"
            onClick={() => scrollToBottom()}
          />
        )
      );
    }
  3. Use the useStickToBottom hook for custom implementations

    main

    If you need to integrate stick-to-bottom behavior into an existing component structure without using the provided component wrapper, use the useStickToBottom hook. You must manually attach the returned refs to your container and content elements.

    Returns:

    • scrollRef: Attach this to the scrollable container (the element with overflow: auto or scroll).
    • contentRef: Attach this to the inner content element that holds the items being added.
    import { useStickToBottom } from 'use-stick-to-bottom';
    
    function Component() {
      const { scrollRef, contentRef } = useStickToBottom();
    
      return (
        <div style={{ overflow: 'auto' }} ref={scrollRef}>
          <div ref={contentRef}>
            {messages.map((message) => (
              <Message key={message.id} message={message} />
            ))}
          </div>
        </div>
      );
    }
  4. Access stick-to-bottom state via useStickToBottomContext

    main

    When using the <StickToBottom> component, child components can use the useStickToBottomContext hook to interact with the scroll state.

    Available properties:

    • isAtBottom: A boolean indicating if the user is currently at the bottom of the container.
    • scrollToBottom: A function that triggers a smooth scroll to the bottom. It returns a Promise<boolean> which resolves to true if the scroll was successful, or false if the scroll was cancelled (e.g., by the user scrolling up).
  5. Configure `useStickToBottom` options

    main

    When initializing useStickToBottom, you can pass a StickToBottomOptions object to customize behavior.

    Options

    • resize: An Animation type defining how the container scrolls when the content size changes.
    • initial: An Animation or boolean defining the initial scroll behavior when the component mounts.
    • targetScrollTop: A function (targetScrollTop: number, context: ScrollElements) => number that allows you to override the calculated target scroll position.

    Spring Animation Configuration

    If you use a spring animation instead of 'instant', you can tune the following:

    • damping: (0 to 1) How much to damp the animation. 0 is no damping, 1 is full damping. Default: 0.7.
    • stiffness: How fast/slow the animation gets up to speed. Default: 0.05.
    • mass: The inertial mass. Higher numbers make the animation slower. Default: 1.25.
    const { scrollRef, contentRef } = useStickToBottom({
      resize: {
        damping: 0.8,
        stiffness: 0.1,
        mass: 1.0
      },
      initial: 'instant'
    });
  6. Use the `useStickToBottom` hook

    main

    The useStickToBottom hook provides programmatic control over a scrollable container to keep it stuck to the bottom (e.g., for chat windows). It returns refs to attach to the scroll container and the content element, along with methods to trigger scrolling and state information.

    To use it, you must provide two refs:

    1. scrollRef: Attach this to the element that has the overflow/scroll property.
    2. contentRef: Attach this to the element containing the actual content that changes size.

    Basic Usage

    import { useStickToBottom } from 'use-stick-to-bottom';
    
    function ChatWindow() {
      const {
        scrollRef, 
        contentRef, 
        scrollToBottom, 
        isAtBottom 
      } = useStickToBottom();
    
      const handleNewMessage = async () => {
        // ... add message to state
        await scrollToBottom();
      };
    
      return (
        <div ref={scrollRef} style={{ overflowY: 'auto', height: '400px' }}>
          <div ref={contentRef}>
            {/* Messages go here */}
          </div>
        </div>
      );
    }
  7. Understand `useStickToBottom` state and properties

    main

    The hook returns several properties to help you react to the scroll state:

    • isAtBottom: boolean. True if the user is at the bottom or near the bottom (within 70px).
    • isNearBottom: boolean. True if the user is within the STICK_TO_BOTTOM_OFFSET_PX (70px) threshold.
    • escapedFromLock: boolean. True if the user has manually scrolled up, breaking the 'stick to bottom' behavior.
    • scrollRef: A ref to be attached to the scrollable element.
    • contentRef: A ref to be attached to the content element.
    • state: The raw StickToBottomState object containing low-level metrics like scrollTop, velocity, and scrollDifference.
  8. Access stickiness state with `useStickToBottomContext`

    main

    If you are building custom components that need to live inside a <StickToBottom> tree, use the useStickToBottomContext hook to access the current scroll state and control methods.

    Note: This hook must be called within a component that is a descendant of a <StickToBottom> component.

    import { useStickToBottomContext } from 'use-stick-to-bottom';
    
    function MySubComponent() {
      const { isAtBottom, scrollToBottom } = useStickToBottomContext();
      
      return (
        <button onClick={scrollToBottom} disabled={isAtBottom}>
          Jump to Bottom
        </button>
      );
    }
  9. Use the `<StickToBottom.Content>` component

    main

    The <StickToBottom.Content> component is a specialized wrapper designed to be used inside a <StickToBottom> provider. It automatically handles the assignment of scrollRef (to the outer scrollable container) and contentRef (to the inner content container) required for the stick-to-bottom logic to function.

    It applies height: 100%, width: 100%, and scrollbar-gutter: stable both-edges to the scroll container to prevent layout shifts when scrollbars appear/disappear.

    <StickToBottom>
      <StickToBottom.Content scrollClassName="my-scroll-container">
        {/* Your content here */}
      </StickToBottom.Content>
    </StickToBottom>
  10. Import use-stick-to-bottom exports

    main

    The library provides two primary ways to implement 'stick to bottom' behavior: the useStickToBottom hook for manual control and the <StickToBottom> component for a declarative approach. Both are exported from the main entrypoint.

    import { useStickToBottom, StickToBottom } from 'use-stick-to-bottom';