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.
Related concepts#
Read Interaction, focus, and selection for focus and event behavior. Use ScrollBox for content scrolling that is independent of the tab strip.