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#

Field Availability
Package @opentui/core
Core renderable TabSelectRenderable
React <tab-select> (automatic)
Solid <tab_select> (automatic)
Status Built 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:

Keyboard navigation#

When focused, the tab select responds to these keys:

Key Action
Left / [ Move to previous tab
Right / ] Move to next tab
Enter Select 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#

Property Type Default Description
width number - Total component width
options TabSelectOption[] [] Available tabs
tabWidth number 20 Width of each tab
backgroundColor string | RGBA transparent Background color
textColor string | RGBA #FFFFFF Normal tab text
focusedBackgroundColor string | RGBA initial base color Background color when focused
focusedTextColor string | RGBA initial base color Text color when focused
selectedBackgroundColor string | RGBA #334455 Selected tab background
selectedTextColor string | RGBA #FFFF00 Selected tab text
selectedDescriptionColor string | RGBA #CCCCCC Description text color
showScrollArrows boolean true Show scroll indicators
showDescription boolean true Show tab descriptions
showUnderline boolean true Show underline on selected tab
wrapSelection boolean false Wrap around when navigating
keyBindings TabSelectKeyBinding[] - Custom key bindings
keyAliasMap Record<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.