New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

ink-scroll-view

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ink-scroll-view

A ScrollView component for Ink CLI applications

Source
npmnpm
Version
0.3.4
Version published
Weekly downloads
492K
39.18%
Maintainers
1
Weekly downloads
 
Created
Source

ink-scroll-view

A robust, performance-optimized ScrollView component for Ink CLI applications.

License Version Downloads

Visit the Project Website.

📘 Documentation

Read the Documentation.

✨ Features

  • 📦 Flexible Container: Handles content larger than the visible terminal viewport.
  • ⚡ Performance First:
    • Optimistic Updates: Immediate state updates for smoother interaction.
    • Efficient Re-rendering: Renders all children but strictly manages visibility via overflow and offsets, ensuring correct layout without layout thrashing.
  • 📏 Auto-Measurement: Automatically measures child heights using a virtually rendered DOM.
  • 🔁 Dynamic Content: Supports adding, removing, and expanding/collapsing items on the fly.
  • ⚓️ Layout Stability: Includes logic to maintain scroll position context when content changes.

🎬 Demos

Try the interactive Showcase.

Scrolling

Scrolling Demo

Dynamic Items

Dynamic Items Demo

Expand/Collapse

Expand Demo

Resize

Resize Demo

Dynamic Width

Width Demo

📦 Installation

npm install ink-scroll-view
# Peer dependencies
npm install ink react

🚀 Usage

ScrollView is a layout primitive. It does not capture user input automatically. You must control it programmatically using React refs and Ink's useInput.

import React, { useRef, useEffect } from "react";
import { render, Text, Box, useInput, useStdout } from "ink";
import { ScrollView, ScrollViewRef } from "ink-scroll-view";

const App = () => {
  const scrollRef = useRef<ScrollViewRef>(null);
  const { stdout } = useStdout();

  // 1. Handle Terminal Resizing due to manual window change
  useEffect(() => {
    const handleResize = () => scrollRef.current?.remeasure();
    stdout?.on("resize", handleResize);
    return () => {
      stdout?.off("resize", handleResize);
    };
  }, [stdout]);

  // 2. Handle Keyboard Input
  useInput((input, key) => {
    if (key.upArrow) {
      scrollRef.current?.scrollBy(-1); // Scroll up 1 line
    }
    if (key.downArrow) {
      scrollRef.current?.scrollBy(1); // Scroll down 1 line
    }
    if (key.pageUp) {
      // Scroll up by viewport height
      const height = scrollRef.current?.getViewportHeight() || 1;
      scrollRef.current?.scrollBy(-height);
    }
    if (key.pageDown) {
      const height = scrollRef.current?.getViewportHeight() || 1;
      scrollRef.current?.scrollBy(height);
    }
  });

  return (
    <Box
      height={10}
      width="100%"
      borderStyle="single"
      borderColor="green"
      flexDirection="column"
    >
      <ScrollView ref={scrollRef}>
        {Array.from({ length: 50 }).map((_, i) => (
          <Text key={i}>Item {i + 1} - content with variable length...</Text>
        ))}
      </ScrollView>
    </Box>
  );
};

render(<App />);

📐 How it Works

The component renders all children into a container but shifts the content vertically using marginTop. The parent box with overflow="hidden" acts as the "viewport".

┌─────────────────────────┐
│  (hidden content)       │ ← Content above viewport
│  ...                    │
├─────────────────────────┤ ← scrollOffset (distance from top)
│  ┌───────────────────┐  │
│  │ Visible Viewport  │  │ ← What user sees
│  │                   │  │
│  └───────────────────┘  │
├─────────────────────────┤
│  (hidden content)       │ ← Content below viewport
│  ...                    │
└─────────────────────────┘

📚 API Reference

For detailed API documentation, see API Reference.

Props (ScrollViewProps)

Inherits standard BoxProps from Ink.

PropTypeDescription
childrenReactNodeOptional. List of child elements. Must use unique keys (strings/numbers).
onScroll(offset: number) => voidCalled when scroll position changes.
onViewportSizeChange(layout: { width, height }) => voidCalled when the viewport dimensions change.
onContentHeightChange(height: number) => voidCalled when the total content height changes.
onItemHeightChange(index, height, previousHeight) => voidCalled when an individual item's height changes.
...BoxPropsAny other prop accepted by Ink's Box.

Ref Methods (ScrollViewRef)

Access these via ref.current.

MethodSignatureDescription
scrollTo(offset: number) => voidScrolls to an absolute Y offset from the top.
scrollBy(delta: number) => voidScrolls by a relative amount (negative = up, positive = down).
scrollToTop() => voidHelper to scroll to offset 0.
scrollToBottom() => voidHelper to scroll to the maximum possible offset (contentHeight - viewportHeight).
getScrollOffset() => numberReturns the current scroll offset.
getContentHeight() => numberReturns the total height of all content items.
getViewportHeight() => numberReturns the current height of the visible area.
getBottomOffset() => numberReturns the scroll offset when scrolled to the bottom (contentHeight - viewportHeight).
getItemHeight(index: number) => numberReturns the measured height of a specific item by its index.
getItemPosition(index: number) => { top, height }Returns the position (top offset) and height of a specific item.
remeasure() => voidRe-checks viewport dimensions. Must call this on terminal resize.
remeasureItem(index: number) => voidForces a specific child to re-measure. Useful for dynamic content (expand/collapse) that doesn't trigger a full re-render.

💡 Tips

  • Unique Keys: Always provide stable, unique key props (strings or numbers) to your children. This allows ScrollView to accurately track height changes even when items are re-ordered or removed.
  • Terminal Resizing: Ink components don't automatically know when the terminal window resizes. You need to listen to process.stdout's resize event and call remeasure() on the ref.
  • Dynamic Content: If you have an item that expands (e.g., "See more"), calling remeasureItem(index) is more efficient than forcing a full update.

This package is part of a family of Ink scroll components:

PackageDescription
ink-scroll-viewCore scroll container component (this package)
ink-scroll-listA scrollable list component built on top of ink-scroll-view with focus management and item selection
ink-scroll-barA standalone scrollbar component that can be used with any scroll container

License

MIT

Keywords

react

FAQs

Package last updated on 31 Dec 2025

Related posts