Layout Components

April 6, 2026 ยท View on GitHub

Structure, containers, and layout management components.

Layout

Centered dialog overlay with title bar, divider, and Escape-to-close. Renders inside a tui-overlay at the center of the screen.

PropTypeDefaultDescription
visibleboolean--Whether modal is shown (required)
titlestring--Title bar text
childrenReactNode--Modal content (required)
onClose() => void--Called on Escape key
Plus container propsborderStyle, borderColor, padding*, width, margin*, backgroundColor

Basic: Confirmation dialog

import { Modal, Text, Button } from "@orchetron/storm";

<Modal visible={showModal} title="Confirm" onClose={() => setShowModal(false)}>
  <Text>Are you sure you want to delete this item?</Text>
  <Box flexDirection="row" gap={2} marginTop={1}>
    <Button label="Yes" onPress={handleDelete} />
    <Button label="No" onPress={() => setShowModal(false)} />
  </Box>
</Modal>

Advanced: Settings modal

<Modal
  visible={showSettings}
  title="Settings"
  onClose={() => setShowSettings(false)}
  width={60}
  borderStyle="round"
  borderColor="#82AAFF"
  padding={1}
>
  <Form
    fields={[
      { key: "apiKey", label: "API Key", type: "password", required: true },
      { key: "model", label: "Model", placeholder: "demo-model" },
      { key: "maxTokens", label: "Max Tokens", type: "number", placeholder: "4096" },
    ]}
    onSubmit={(values) => { saveSettings(values); setShowSettings(false); }}
    submitLabel="Save"
  />
</Modal>

Modal automatically traps focus. See Common Pitfalls for focus management details.


Tabs

Horizontal tab bar with keyboard navigation. Active tab is bold and colored, others are dim. Left/Right arrows and number keys navigate.

PropTypeDefaultDescription
tabsTab[]--Array of { key, label } (required)
activeKeystring--Currently active tab key (required)
onChange(key: string) => void--Called on tab switch
isFocusedbooleantrueWhether tabs capture input
colorstring | numbercolors.brand.primaryActive tab color
aria-labelstring--Accessibility label
Plus layout propswidth, height, margin*, minWidth, maxWidth

Basic: Simple tab bar

import { Tabs } from "@orchetron/storm";

<Tabs
  tabs={[
    { key: "overview", label: "Overview" },
    { key: "logs", label: "Logs" },
    { key: "config", label: "Config" },
  ]}
  activeKey={activeTab}
  onChange={setActiveTab}
/>

Advanced: Tab bar with content switching

function Dashboard() {
  const [tab, setTab] = useState("overview");

  return (
    <Box flexDirection="column">
      <Tabs
        tabs={[
          { key: "overview", label: "Overview" },
          { key: "metrics", label: "Metrics" },
          { key: "alerts", label: "Alerts" },
        ]}
        activeKey={tab}
        onChange={setTab}
        color="#82AAFF"
      />
      <Box flex={1} marginTop={1}>
        {tab === "overview" && <OverviewPanel />}
        {tab === "metrics" && <MetricsPanel />}
        {tab === "alerts" && <AlertsPanel />}
      </Box>
    </Box>
  );
}

TabbedContent

Combined tab bar and content panels. Renders tabs at the top with automatic content switching based on the active key.

PropTypeDefaultDescription
tabsArray<{ label: string; key: string }>--Tab definitions (required)
activeKeystring--Currently active tab key (required)
onTabChange(key: string) => void--Called on tab switch
childrenReactNode--Content panels (matched by key)
tabColorstring | numbercolors.text.dimInactive tab color
activeTabColorstring | numbercolors.brand.primaryActive tab color
Plus container propsborderStyle, borderColor, padding*, width, margin*

Basic: Tabbed panels

import { TabbedContent, Text } from "@orchetron/storm";

<TabbedContent
  tabs={[
    { key: "code", label: "Code" },
    { key: "tests", label: "Tests" },
  ]}
  activeKey={activeTab}
  onTabChange={setActiveTab}
>
  {activeTab === "code" && <Text>Source code viewer</Text>}
  {activeTab === "tests" && <Text>Test runner output</Text>}
</TabbedContent>

Advanced: Styled tabbed content

<TabbedContent
  tabs={[
    { key: "request", label: "Request" },
    { key: "response", label: "Response" },
    { key: "headers", label: "Headers" },
  ]}
  activeKey={activeTab}
  onTabChange={setActiveTab}
  activeTabColor="#82AAFF"
  tabColor="#505050"
  borderStyle="round"
  borderColor="#505050"
  padding={1}
>
  {activeTab === "request" && <RequestEditor />}
  {activeTab === "response" && <ResponseViewer />}
  {activeTab === "headers" && <HeadersTable />}
</TabbedContent>

Accordion

Collapsible sections with keyboard navigation. Up/Down arrows navigate between headers, Enter/Space toggles. Supports exclusive mode where only one section is open at a time.

PropTypeDefaultDescription
sectionsAccordionSection[]--Section definitions (required)
activeKeysstring[][]Keys of currently open sections
onToggle(key: string) => void--Called when a section is toggled
exclusiveboolean--Only allow one section open at a time
colorstring | numbercolors.brand.primaryIndicator color
Plus container propsborderStyle, borderColor, padding*, width, margin*

AccordionSection type:

PropertyTypeDescription
keystringUnique section identifier
titlestringSection header text
contentReactNodeSection body content

Basic: FAQ accordion

import { Accordion, Text } from "@orchetron/storm";

<Accordion
  sections={[
    { key: "install", title: "Installation", content: <Text>npm install @orchetron/storm</Text> },
    { key: "usage", title: "Basic Usage", content: <Text>Import components and render with storm.</Text> },
  ]}
  activeKeys={openSections}
  onToggle={handleToggle}
/>

Advanced: Exclusive accordion with styling

function SettingsAccordion() {
  const [open, setOpen] = useState<string[]>(["general"]);

  const handleToggle = (key: string) => {
    setOpen(open.includes(key) ? [] : [key]); // Exclusive: one at a time
  };

  return (
    <Accordion
      sections={[
        { key: "general", title: "General", content: <GeneralSettings /> },
        { key: "editor", title: "Editor", content: <EditorSettings /> },
        { key: "terminal", title: "Terminal", content: <TerminalSettings /> },
        { key: "advanced", title: "Advanced", content: <AdvancedSettings /> },
      ]}
      activeKeys={open}
      onToggle={handleToggle}
      exclusive
      color="#82AAFF"
      borderStyle="round"
      borderColor="#505050"
      padding={1}
    />
  );
}

Collapsible

Expand/collapse section with title. Supports controlled and uncontrolled modes with optional animation.

PropTypeDefaultDescription
titlestring--Section title
expandedboolean--Controlled expanded state
onToggle(expanded: boolean) => void--Toggle callback
childrenReactNode--Collapsible content
animatedbooleanfalseAnimate expand/collapse transitions
collapseHintstringpersonality defaultHint text when collapsed
renderHeader(props) => ReactNode--Custom header renderer
<Collapsible title="Details" expanded={open} onToggle={setOpen}>
  <Text>Hidden content here</Text>
</Collapsible>

ContentSwitcher

Shows one child at a time by index. Supports fade and slide transitions.

PropTypeDefaultDescription
activeIndexnumber--Index of the visible child
childrenReactNode--Child elements to switch between
transition"none" | "fade" | "slide""none"Transition effect when switching
<ContentSwitcher activeIndex={tab}>
  <Text>Tab A content</Text>
  <Text>Tab B content</Text>
  <Text>Tab C content</Text>
</ContentSwitcher>

ConfirmDialog

Confirmation dialog overlay with yes/no or multi-action buttons. Focus-trapped with optional auto-timeout.

PropTypeDefaultDescription
visibleboolean--Show/hide the dialog
messagestring--Dialog message
onConfirm() => void--Confirm callback (Y key)
onCancel() => void--Cancel callback (N/Esc key)
confirmLabelstring"Yes"Confirm button label
cancelLabelstring"No"Cancel button label
type"info" | "warning" | "danger""info"Border color variant
timeoutMsnumber--Auto-fire after N ms
timeoutAction"confirm" | "cancel""cancel"Action on timeout
actionsConfirmDialogAction[]--Multi-action buttons (overrides confirm/cancel)
<ConfirmDialog
  visible={showDialog}
  message="Delete this file?"
  type="danger"
  onConfirm={handleDelete}
  onCancel={() => setShowDialog(false)}
/>

Full-width header bar with title, optional subtitle, and thick border lines.

PropTypeDefaultDescription
titlestring--Header title
subtitlestring--Subtitle (dim, after separator)
borderStyle"single" | "double" | "none"personality defaultBorder line style
widthnumber--Header width
rightstring | ReactNode--Right-aligned content
showBorderbooleantrueShow border lines
<Header title="Dashboard" subtitle="v2.1" right="10:30 AM" />

Full-width footer bar with a top border. Supports key bindings display and left/right content slots.

PropTypeDefaultDescription
childrenReactNode--Footer content
borderStyle"single" | "double" | "none"--Top border style
widthnumber--Footer width
bindingsFooterBinding[]--Key bindings [{key, label}]
leftstring | ReactNode--Left-aligned content
rightstring | ReactNode--Right-aligned content
<Footer bindings={[{ key: "q", label: "Quit" }, { key: "?", label: "Help" }]} />

FocusGroup

Groups focusable children and manages arrow-key or tab navigation within the group. Supports focus trapping for modals.

PropTypeDefaultDescription
childrenReactNode--Child elements
idstringautoUnique group ID
trapbooleanfalseTrap Tab cycling within group
direction"vertical" | "horizontal"--Arrow key navigation direction
onFocusChange(index: number) => void--Focus change callback
isActivebooleantrueWhether the group is interactive
<FocusGroup direction="vertical" trap>
  <Button>Option A</Button>
  <Button>Option B</Button>
</FocusGroup>

ErrorBoundary

React Error Boundary that catches render errors in the child tree and shows fallback UI.

PropTypeDefaultDescription
fallbackReactNode | (error, reset) => ReactNodeerror messageFallback UI or render function
onError(error, errorInfo) => void--Error callback
childrenReactNode--Child elements
<ErrorBoundary fallback={(err, reset) => <Text color="red">{err.message}</Text>}>
  <MyComponent />
</ErrorBoundary>

Static

Renders a list of items where previously rendered items are never re-rendered. Ideal for append-only output like build logs.

PropTypeDefaultDescription
itemsT[]--Items to render
children(item: T, index: number) => ReactNode--Render function for each item
<Static items={completedTasks}>
  {(task, i) => <Text key={i}>{task.name} completed</Text>}
</Static>

AnimatePresence

Manages mount/unmount animations for children. Keeps removed children rendered long enough for the exit animation to play.

PropTypeDefaultDescription
childrenReactNode--Child elements (must have unique key props)
exitType"fade" | "slide-up" | "collapse""fade"Animation type for exiting children
exitDurationnumberpersonality defaultDuration of exit animation in ms
<AnimatePresence exitType="fade" exitDuration={200}>
  {items.map(item => (
    <Box key={item.id}><Text>{item.label}</Text></Box>
  ))}
</AnimatePresence>

Transition

Declarative enter/exit animation wrapper with multiple transition types.

PropTypeDefaultDescription
showboolean--Whether children are visible
type"fade" | "slide-down" | "slide-up" | "slide-right" | "collapse""fade"Animation type
enter{ duration?, easing? }personality defaultsEnter timing config
exit{ duration?, easing? }personality defaultsExit timing config
childrenReactNode--Content to animate

Easing options: "linear", "easeIn", "easeOut", "easeInOut".

<Transition show={isOpen} type="slide-down" enter={{ duration: 200, easing: "easeOut" }}>
  <Box><Text>Animated panel</Text></Box>
</Transition>

Welcome

First-run welcome screen with app name, version, and getting-started hints.

PropTypeDefaultDescription
titlestring--App name
versionstring--Version string
hintsstring[]--Getting-started tips
<Welcome title="My App" version="1.0.0" hints={["Press ? for help", "Ctrl+C to quit"]} />

Back to Components