A ScrollView component for Ink CLI applications
npm install ink-scroll-viewA robust, performance-optimized ScrollView component for Ink CLI applications.
Visit the Project Website.
Read the Documentation.
- š¦ 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.
Try the interactive Showcase.
``bash`
npm install ink-scroll-viewPeer dependencies
npm install ink react
ScrollView is a layout primitive. It does not capture user input automatically. You must control it programmatically using React refs and Ink's useInput.
`tsx
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
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 (
width="100%"
borderStyle="single"
borderColor="green"
flexDirection="column"
>
{Array.from({ length: 50 }).map((_, i) => (
))}
);
};
render(
`
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
ā ... ā
āāāāāāāāāāāāāāāāāāāāāāāāāāā
For detailed API documentation, see API Reference.
Inherits standard BoxProps from Ink.
| Prop | Type | Description |
| :---------------------- | :---------------------------------------- | :----------------------------------------------------------------------------------------- |
| children | ReactNode | Optional. List of child elements. Must use unique keys (strings/numbers). |onScroll
| | (offset: number) => void | Called when scroll position changes. |onViewportSizeChange
| | (layout: { width, height }) => void | Called when the viewport dimensions change. |onContentHeightChange
| | (height: number) => void | Called when the total content height changes. |onItemHeightChange
| | (index, height, previousHeight) => void | Called when an individual item's height changes. |debug
| | boolean | Optional. If true, overflows content instead of hiding it (useful for debugging layout). |BoxProps
| ... | | Any other prop accepted by Ink's Box. |
Access these via ref.current.
| Method | Signature | Description |
| :------------------ | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------- |
| scrollTo | (offset: number) => void | Scrolls to an absolute Y offset from the top. |scrollBy
| | (delta: number) => void | Scrolls by a relative amount (negative = up, positive = down). |scrollToTop
| | () => void | Helper to scroll to offset 0. |scrollToBottom
| | () => void | Helper to scroll to the maximum possible offset (contentHeight - viewportHeight). |getScrollOffset
| | () => number | Returns the current scroll offset. |getContentHeight
| | () => number | Returns the total height of all content items. |getViewportHeight
| | () => number | Returns the current height of the visible area. |getBottomOffset
| | () => number | Returns the scroll offset when scrolled to the bottom (contentHeight - viewportHeight). |getItemHeight
| | (index: number) => number | Returns 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
| | () => void | Re-checks viewport dimensions. Must call this on terminal resize. |remeasureItem
| | (index: number) => void | Forces a specific child to re-measure. Useful for dynamic content (expand/collapse) that doesn't trigger a full re-render. |
For advanced use cases where you need full control over the scroll state (e.g., synchronizing multiple views, animating transitions), you can use ControlledScrollView.
It accepts a scrollOffset prop instead of managing it internally.
`tsx
import { ControlledScrollView } from "ink-scroll-view";
// ...
const [offset, setOffset] = useState(0);
return (
// ... other props
>
{children}
);
`
1. 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.process.stdout
2. Terminal Resizing: Ink components don't automatically know when the terminal window resizes. You need to listen to 's resize event and call remeasure() on the ref.remeasureItem(index)` is more efficient than forcing a full update.
3. Dynamic Content: If you have an item that expands (e.g., "See more"), calling
This package is part of a family of Ink scroll components:
| Package | Description |
| :----------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- |
| ink-scroll-view | Core scroll container component (this package) |
| ink-scroll-list | A scrollable list component built on top of ink-scroll-view with focus management and item selection |
| ink-scroll-bar | A standalone scrollbar component that can be used with any scroll container |
MIT