Scroll Components

ScrollView, ScrollBar, ScrollBarBox, and ScrollList for building scrollable terminal UIs

Scroll Components

@visulima/tui provides two approaches to scrolling:

  1. CSS-level scrolling via Box props (overflow="scroll", scrollTop, sticky) — integrated into the layout engine with native scrollbar rendering and sticky header support. See the Box component docs.
  2. Component-based scrolling via ScrollView, ScrollList, etc. — higher-level React components with imperative APIs, selection management, and alignment modes.

Both can be used independently or together. CSS-level scroll is best for simple overflow clipping with scrollbars; component-based scroll is best for lists with selection, dynamic content measurement, and keyboard-driven navigation.

Component-Based Scroll

Ported from the ByteLand scroll ecosystem. Import from @visulima/tui/ink.

import { ControlledScrollView, ScrollBar, ScrollBarBox, ScrollList, ScrollView } from "@visulima/tui/ink";

ScrollView

A scrollable viewport that manages its own scroll state. Control it imperatively via a ref.

!Scrolling Demo

import { useRef } from "react";
import { Box, ScrollView, Text, useInput } from "@visulima/tui/ink";
import type { ScrollViewRef } from "@visulima/tui/ink";

const Demo = () => {
    const scrollRef = useRef<ScrollViewRef>(null);

    useInput((input, key) => {
        if (key.downArrow) scrollRef.current?.scrollBy(1);
        if (key.upArrow) scrollRef.current?.scrollBy(-1);
    });

    return (
        <Box height={10} borderStyle="single">
            <ScrollView ref={scrollRef}>
                {items.map((item) => (
                    <Text key={item.id}>{item.label}</Text>
                ))}
            </ScrollView>
        </Box>
    );
};

Dynamic Items

!Dynamic Items Demo

Expand / Collapse

!Expand Demo

Terminal Resize

!Resize Demo

Width Changes (Text Wrapping)

!Width Demo

Props

Extends BoxProps.

PropTypeDescription
onScrollfunctionCallback with current scroll offset
onViewportSizeChangefunctionFires when viewport dimensions change
onContentHeightChangefunctionFires when total content height changes
onItemHeightChangefunctionFires when an individual item resizes
debugbooleanDisables overflow hidden for debugging

Ref Methods

MethodDescription
scrollTo(offset)Scroll to absolute position
scrollBy(delta)Scroll by relative amount
scrollToTop()Scroll to top
scrollToBottom()Scroll to bottom
getScrollOffset()Current scroll position
getContentHeight()Total content height
getViewportHeight()Visible viewport height
getBottomOffset()Max scroll offset
getItemHeight(i)Height of item at index
getItemPosition(i)Position and height of item at index
remeasure()Re-measure viewport dimensions
remeasureItem(i)Re-measure a specific child

ControlledScrollView

Lower-level variant where the parent owns the scrollOffset state.

import { useState } from "react";
import { Box, ControlledScrollView, Text, useInput } from "@visulima/tui/ink";

const Demo = () => {
    const [offset, setOffset] = useState(0);

    useInput((input, key) => {
        if (key.downArrow) setOffset((o) => o + 1);
        if (key.upArrow) setOffset((o) => Math.max(0, o - 1));
    });

    return (
        <Box height={10} borderStyle="single">
            <ControlledScrollView scrollOffset={offset}>
                {items.map((item) => (
                    <Text key={item.id}>{item.label}</Text>
                ))}
            </ControlledScrollView>
        </Box>
    );
};

ScrollBar

A visual scroll position indicator. Works in two placement modes:

  • Border mode (placement="left" or "right"): Integrates with a container's border.
  • Inset mode (placement="inset"): Placed inside the content area.

Border Mode

!Border Mode Demo

Inset Mode

!Inset Mode Demo

Auto Hide

!Auto Hide Demo

import { Box, ScrollBar, Text } from "@visulima/tui/ink";

<Box flexDirection="row">
    <Box borderStyle="single" borderRight={false} height={10}>
        <Text>Content here</Text>
    </Box>
    <ScrollBar placement="right" style="single" contentHeight={100} viewportHeight={10} scrollOffset={scrollOffset} />
</Box>;

Props

PropTypeDefaultDescription
contentHeightnumberrequiredTotal content height
viewportHeightnumberrequiredVisible viewport height
scrollOffsetnumberrequiredCurrent scroll position
placementScrollBarPlacement'right''left', 'right', or 'inset'
styleScrollBarStylevariesVisual style
thumbCharstring---Custom thumb character (inset only)
trackCharstring---Custom track character (inset only)
autoHidebooleanfalseHide when content fits (inset only)
colorstring---Scroll bar color
dimColorbooleanfalseDimmed styling

ScrollBarBox

A Box with a built-in scroll bar on one border.

import { ScrollBarBox, Text } from "@visulima/tui/ink";

<ScrollBarBox height={12} borderStyle="single" contentHeight={50} viewportHeight={10} scrollOffset={scrollOffset}>
    {visibleItems.map((item) => (
        <Text key={item.id}>{item.label}</Text>
    ))}
</ScrollBarBox>;

Props

Extends BoxProps.

PropTypeDefaultDescription
contentHeightnumberrequiredTotal content height
viewportHeightnumberrequiredVisible viewport height
scrollOffsetnumberrequiredCurrent scroll position
scrollBarPosition'left' | 'right''right'Which side to show the bar
scrollBarAutoHidebooleanfalseHide thumb when content fits
thumbCharstring---Custom thumb character

ScrollList

A ScrollView with externally controlled selection and automatic scroll-into-view.

Selection & Navigation

!Selection Demo

Scroll Alignment Modes

!Alignment Demo

Expand / Collapse

!Expand Demo

Dynamic Items

!Dynamic Demo

import { useRef, useState } from "react";
import { Box, ScrollList, Text, useInput } from "@visulima/tui/ink";

const Demo = () => {
    const [selected, setSelected] = useState(0);
    const items = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];

    useInput((input, key) => {
        if (key.downArrow) setSelected((i) => Math.min(i + 1, items.length - 1));
        if (key.upArrow) setSelected((i) => Math.max(i - 1, 0));
    });

    return (
        <ScrollList height={5} selectedIndex={selected}>
            {items.map((item, i) => (
                <Box key={i}>
                    <Text color={i === selected ? "blue" : "white"}>
                        {i === selected ? "> " : "  "}
                        {item}
                    </Text>
                </Box>
            ))}
        </ScrollList>
    );
};

Props

Extends ScrollViewProps.

PropTypeDefaultDescription
selectedIndexnumber---Currently selected item (controlled)
scrollAlignmentScrollAlignment'auto'How to position the selected item in view

Alignment modes:

  • 'auto' --- Minimal scrolling to bring item into view
  • 'top' --- Align item to top of viewport
  • 'bottom' --- Align item to bottom of viewport
  • 'center' --- Center item in viewport
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues