Skip to main content

Overview

Bunli TUI provides a comprehensive set of components for building beautiful terminal user interfaces. All components are built on OpenTUI and integrate seamlessly with React.

Layout Components

Stack

Flexbox-based layout container.
interface StackProps {
  children: ReactNode
  direction?: 'row' | 'column'
  gap?: number
  align?: 'flex-start' | 'center' | 'flex-end' | 'stretch'
  justify?: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly'
  padding?: number
  border?: boolean
  title?: string
  style?: Record<string, unknown>
}

Example

import { Stack } from '@bunli/tui'

function App() {
  return (
    <Stack direction="column" gap={2} padding={2} border>
      <text content="Header" />
      <Stack direction="row" gap={1}>
        <text content="Left" />
        <text content="Right" />
      </Stack>
    </Stack>
  )
}

Grid

Grid layout for organizing content.
interface GridProps {
  children: ReactNode
  columns?: number
  gap?: number
}

Example

import { Grid, Card } from '@bunli/tui'

function Dashboard() {
  return (
    <Grid columns={3} gap={2}>
      <Card title="Users">100</Card>
      <Card title="Posts">500</Card>
      <Card title="Comments">2.5k</Card>
    </Grid>
  )
}

Container

Max-width container with padding.
interface ContainerProps {
  children: ReactNode
  maxWidth?: number
  padding?: number
}

Display Components

Card

Container for grouping related content.
interface CardProps {
  title: string
  description?: string
  tone?: 'default' | 'accent' | 'success' | 'warning' | 'danger'
  size?: 'sm' | 'md' | 'lg'
  emphasis?: 'subtle' | 'outline' | 'solid'
  children?: ReactNode
}

Example

import { Card } from '@bunli/tui'

function Profile() {
  return (
    <Card
      title="User Profile"
      description="Manage your account"
      tone="accent"
      size="md"
    >
      <text content="Name: John Doe" />
      <text content="Email: [email protected]" />
    </Card>
  )
}

Alert

Display important messages.
interface AlertProps {
  tone?: 'info' | 'success' | 'warning' | 'danger'
  size?: 'sm' | 'md' | 'lg'
  emphasis?: 'subtle' | 'outline' | 'solid'
  title?: string
  message: string
  children?: ReactNode
}

Example

import { Alert } from '@bunli/tui'

function Notifications() {
  return (
    <>
      <Alert tone="success" message="Profile updated successfully!" />
      <Alert 
        tone="warning" 
        title="Warning"
        message="Your session will expire soon"
      />
      <Alert tone="danger" message="Failed to save changes" />
    </>
  )
}

Badge

Small status indicator.
interface BadgeProps {
  label: string
  tone?: 'default' | 'accent' | 'success' | 'warning' | 'danger'
  size?: 'sm' | 'md' | 'lg'
  emphasis?: 'subtle' | 'outline' | 'solid'
}

Example

import { Badge } from '@bunli/tui'

function Status() {
  return (
    <box style={{ flexDirection: 'row', gap: 1 }}>
      <Badge label="Active" tone="success" />
      <Badge label="Pending" tone="warning" />
      <Badge label="Failed" tone="danger" />
    </box>
  )
}

Divider

Visual separator.
interface DividerProps {
  width?: number
  char?: string
}

Example

import { Divider } from '@bunli/tui'

function Menu() {
  return (
    <box style={{ flexDirection: 'column' }}>
      <text content="Header" />
      <Divider />
      <text content="Content" />
      <Divider char="=" />
      <text content="Footer" />
    </box>
  )
}

EmptyState

Placeholder for empty content.
interface EmptyStateProps {
  title: string
  description?: string
  icon?: string
  action?: ReactNode
}

Example

import { EmptyState } from '@bunli/tui'

function UserList({ users }) {
  if (users.length === 0) {
    return (
      <EmptyState
        title="No users found"
        description="Add your first user to get started"
        icon="👤"
      />
    )
  }

  return <>{/* render users */}</>
}

KeyValueList

Display key-value pairs.
interface KeyValueListProps {
  items: KeyValueItem[]
  gap?: number
}

interface KeyValueItem {
  key: string
  value: string | ReactNode
  hint?: string
}

Example

import { KeyValueList } from '@bunli/tui'

function SystemInfo() {
  return (
    <KeyValueList
      items={[
        { key: 'OS', value: 'Linux' },
        { key: 'Memory', value: '16 GB' },
        { key: 'CPU', value: 'Intel i7', hint: '8 cores' }
      ]}
    />
  )
}

ProgressBar

Visual progress indicator.
interface ProgressBarProps {
  value: number // 0-100
  width?: number
  showPercentage?: boolean
  tone?: 'default' | 'accent' | 'success' | 'warning' | 'danger'
}

Example

import { ProgressBar } from '@bunli/tui'
import { useState, useEffect } from 'react'

function Upload() {
  const [progress, setProgress] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setProgress(p => Math.min(p + 10, 100))
    }, 500)
    return () => clearInterval(timer)
  }, [])

  return (
    <box style={{ flexDirection: 'column', gap: 1 }}>
      <text content="Uploading..." />
      <ProgressBar value={progress} showPercentage />
    </box>
  )
}

Form Components

Form

Schema-based form with validation.
interface FormProps<TSchema extends StandardSchemaV1> {
  title: string
  schema: TSchema
  onSubmit: (values: StandardSchemaV1.InferOutput<TSchema>) => void | Promise<void>
  onCancel?: () => void
  onReset?: () => void
  initialValues?: Partial<StandardSchemaV1.InferOutput<TSchema>>
  validateOnChange?: boolean
  children: React.ReactNode
}

Example

import { Form, FormField } from '@bunli/tui'
import { z } from 'zod'

const schema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().positive()
})

function UserForm() {
  return (
    <Form
      title="Create User"
      schema={schema}
      onSubmit={(values) => {
        console.log('Submitted:', values)
      }}
      initialValues={{ name: '', email: '', age: 18 }}
    >
      <FormField name="name" label="Name" />
      <FormField name="email" label="Email" />
      <FormField name="age" label="Age" type="number" />
    </Form>
  )
}

FormField

Generic text input field.
interface FormFieldProps {
  name: string
  label: string
  description?: string
  placeholder?: string
  defaultValue?: string
  type?: 'text' | 'number'
}

SelectField

Dropdown select field.
interface SelectFieldProps<T = string> {
  name: string
  label: string
  options: SelectOption<T>[]
  defaultValue?: T
}

MultiSelectField

Multiple selection field.
interface MultiSelectFieldProps<T = string> {
  name: string
  label: string
  options: SelectOption<T>[]
  defaultValue?: T[]
  min?: number
  max?: number
}

CheckboxField

Boolean checkbox field.
interface CheckboxFieldProps {
  name: string
  label: string
  description?: string
  defaultValue?: boolean
}

PasswordField

Password input with toggle.
interface PasswordFieldProps {
  name: string
  label: string
  description?: string
}

TextareaField

Multiline text input.
interface TextareaFieldProps {
  name: string
  label: string
  rows?: number
  defaultValue?: string
}

Interactive Components

Overlay modal dialog.
interface ModalProps {
  isOpen: boolean
  title: string
  children: ReactNode
  onClose?: () => void
  closeHint?: string
  zIndex?: number
}

Example

import { Modal } from '@bunli/tui'
import { useState } from 'react'

function App() {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <>
      <text content="Press M to open modal" />
      <Modal
        isOpen={isOpen}
        title="Confirmation"
        onClose={() => setIsOpen(false)}
      >
        <text content="Are you sure?" />
      </Modal>
    </>
  )
}
Keyboard-navigable menu.
interface MenuProps<T = string> {
  items: MenuItem<T>[]
  onSelect: (value: T) => void
  selectedValue?: T
}

interface MenuItem<T = string> {
  label: string
  value: T
  disabled?: boolean
  hint?: string
}

Example

import { Menu } from '@bunli/tui'

function Navigation() {
  return (
    <Menu
      items={[
        { label: 'Home', value: 'home' },
        { label: 'Settings', value: 'settings' },
        { label: 'About', value: 'about' }
      ]}
      onSelect={(value) => {
        console.log('Selected:', value)
      }}
    />
  )
}

CommandPalette

Searchable command palette.
interface CommandPaletteProps {
  items: CommandPaletteItem[]
  onSelect: (item: CommandPaletteItem) => void
  onClose?: () => void
  placeholder?: string
}

interface CommandPaletteItem {
  id: string
  label: string
  description?: string
  keywords?: string[]
}

DataTable

Tabular data display.
interface DataTableProps {
  columns: DataTableColumn[]
  data: Record<string, unknown>[]
  onSelect?: (row: Record<string, unknown>) => void
}

interface DataTableColumn {
  key: string
  label: string
  width?: number
}

Example

import { DataTable } from '@bunli/tui'

function UserTable() {
  return (
    <DataTable
      columns={[
        { key: 'name', label: 'Name', width: 20 },
        { key: 'email', label: 'Email', width: 30 },
        { key: 'role', label: 'Role', width: 15 }
      ]}
      data={[
        { name: 'Alice', email: '[email protected]', role: 'Admin' },
        { name: 'Bob', email: '[email protected]', role: 'User' }
      ]}
      onSelect={(row) => console.log(row)}
    />
  )
}

Tabs

Tabbed content switcher.
interface TabsProps {
  tabs: Array<{ label: string; value: string }>
  activeTab: string
  onTabChange: (value: string) => void
  children: ReactNode
}

Best Practices

Layout

Use Stack and Grid for responsive layouts that adapt to terminal size

Forms

Always use schema validation with Zod or similar for type-safe forms

Accessibility

  • Provide keyboard shortcuts for all interactive elements
  • Use semantic component names
  • Include helpful hints and descriptions

Theming

Use theme tokens instead of hardcoded colors for consistent styling

Performance

  • Avoid deep nesting of layout components
  • Use React.memo for expensive components
  • Minimize re-renders in tables and lists

Build docs developers (and LLMs) love