opentui

Build terminal UIs with OpenTUI. Covers Core, frameworks, components, application APIs, testing, extensions, integrations, deployment, and public API lookup.

Install
npx skills add 'https://github.com/anomalyco/opentui/tree/main/packages/web/src/content'
Download bundle ↓
main · ac753b4Scanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

docs/components/tab-select.mdx

docs/components/tab-select.mdxBrowse 89 files
View on GitHub
← Back to SKILL.md

title: TabSelect description: Choose one option from a horizontal tab list

TabSelect

TabSelect shows peer choices in a horizontal tab strip and scrolls the strip as the selection moves. Use Select for a vertical option list.

Focus the component to receive keyboard input.

Availability

FieldAvailability
Package@opentui/core
Core renderableTabSelectRenderable
React<tab-select> (automatic)
Solid<tab_select> (automatic)
StatusBuilt in

Basic usage

Renderable API

import { TabSelectRenderable, TabSelectRenderableEvents, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const tabs = new TabSelectRenderable(renderer, {
  id: "tabs",
  width: 36,
  options: [
    { name: "Home", description: "View project overview" },
    { name: "Files", description: "Browse project files" },
    { name: "Settings", description: "Configure the project" },
  ],
  tabWidth: 12,
})

tabs.on(TabSelectRenderableEvents.ITEM_SELECTED, (index, option) => {
  console.log("Tab selected:", option.name)
})

tabs.focus()
renderer.root.add(tabs)

After pressing Right, Files is selected:

 Home        Files       Settings
            ▬▬▬▬▬▬▬▬▬▬▬▬
 Browse project files

Keyboard navigation

When focused, the tab select responds to these keys:

KeyAction
Left / [Move to previous tab
Right / ]Move to next tab
EnterSelect current tab

Events

Item selected

Emitted when the user presses Enter on a tab:

import { TabSelectRenderableEvents, type TabSelectOption } from "@opentui/core"

tabs.on(TabSelectRenderableEvents.ITEM_SELECTED, (index: number, option: TabSelectOption) => {
  console.log(`Selected tab ${index}: ${option.name}`)
  // Switch to the corresponding panel
})

Selection update

Emitted after successful movement or a valid setSelectedIndex() call:

import { TabSelectRenderableEvents, type TabSelectOption } from "@opentui/core"

tabs.on(TabSelectRenderableEvents.SELECTION_CHANGED, (index: number, option: TabSelectOption) => {
  console.log(`Hovering: ${option.name}`)
})

Every valid setSelectedIndex() call emits the event, even when it assigns the current index. A blocked directional movement does not emit it.

Properties

PropertyTypeDefaultDescription
widthnumber-Total component width
optionsTabSelectOption[][]Available tabs
tabWidthnumber20Width of each tab
backgroundColorstring | RGBAtransparentBackground color
textColorstring | RGBA#FFFFFFNormal tab text
focusedBackgroundColorstring | RGBAinitial base colorBackground color when focused
focusedTextColorstring | RGBAinitial base colorText color when focused
selectedBackgroundColorstring | RGBA#334455Selected tab background
selectedTextColorstring | RGBA#FFFF00Selected tab text
selectedDescriptionColorstring | RGBA#CCCCCCDescription text color
showScrollArrowsbooleantrueShow scroll indicators
showDescriptionbooleantrueShow tab descriptions
showUnderlinebooleantrueShow underline on selected tab
wrapSelectionbooleanfalseWrap around when navigating
keyBindingsTabSelectKeyBinding[]-Custom key bindings
keyAliasMapRecord<string, string>-Key alias mappings

If you omit a focused color, the constructor copies the corresponding base color. If you omit both values, the focused background uses #1a1a1a, and the focused text uses #FFFFFF.

Example: tabbed interface

import {
  BoxRenderable,
  TabSelectRenderable,
  TabSelectRenderableEvents,
  TextRenderable,
  createCliRenderer,
} from "@opentui/core"

const renderer = await createCliRenderer()

function createPanel(content: string) {
  const panel = new BoxRenderable(renderer, { padding: 1 })
  panel.add(new TextRenderable(renderer, { content }))
  return panel
}

const panels = {
  home: createPanel("Home content here"),
  files: createPanel("File browser here"),
  settings: createPanel("Settings form here"),
}

const container = new BoxRenderable(renderer, {
  width: 60,
  height: 20,
  borderStyle: "rounded",
})

const tabs = new TabSelectRenderable(renderer, {
  width: 60,
  tabWidth: 20,
  options: [
    { name: "Home", description: "Dashboard" },
    { name: "Files", description: "Browse files" },
    { name: "Settings", description: "Preferences" },
  ],
})

let currentPanel = panels.home
const contentArea = new BoxRenderable(renderer, {
  flexGrow: 1,
  padding: 1,
})
contentArea.add(currentPanel)

tabs.on(TabSelectRenderableEvents.ITEM_SELECTED, (_index, option) => {
  contentArea.remove(currentPanel)
  switch (option.name) {
    case "Home":
      currentPanel = panels.home
      break
    case "Files":
      currentPanel = panels.files
      break
    case "Settings":
      currentPanel = panels.settings
      break
  }
  contentArea.add(currentPanel)
})

container.add(tabs)
container.add(contentArea)

renderer.root.add(container)
tabs.focus()

Programmatic control

// Get current tab index
const currentIndex = tabs.getSelectedIndex()

// Set tab programmatically
tabs.setSelectedIndex(1)

// Update tabs dynamically
tabs.setOptions([
  { name: "New Tab 1", description: "Updated" },
  { name: "New Tab 2", description: "Also updated" },
])

Scroll behavior

When there are more tabs than fit in the width, the component automatically handles horizontal scrolling as you navigate with the keyboard.

Read Interaction, focus, and selection for focus and event behavior. Use ScrollBox for content scrolling that is independent of the tab strip.