npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mdxui/terminal

v6.0.0

Published

OpenTUI renderer for MDXUI primitives - build CLI dashboards with the same components as web

Downloads

288

Readme

@mdxui/terminal

Universal UI renderer for MDXUI - same components render to terminal, web, and AI agents.

Alpha Status: This package is under active development. Some features are experimental or incomplete. APIs may change.

Multi-tier rendering: Components output to TEXT, MARKDOWN, ASCII, UNICODE, ANSI, or Interactive based on context. AI agents get structured Markdown via MCP. Humans get rich TUIs. UX designers preview in Storybook.

Installation

pnpm add @mdxui/terminal

Quick Start

import { CLI, Box, Text } from '@mdxui/terminal'

async function main() {
  const cli = await CLI()
  cli.render(
    <Box border="single">
      <Text color="cyan">Hello Terminal!</Text>
    </Box>
  )
}

main()

Examples

The package includes complete example applications demonstrating real-world usage patterns. Run them directly or use as templates for your own CLI tools.

Running Examples

# Navigate to the terminal package
cd packages/terminal

# Simple CLI - Basic components, styled text, system info
pnpm example:simple

# Interactive Menu - Multi-screen navigation, forms, VIM bindings
pnpm example:menu

# Dashboard - Metrics cards, data tables, useQuery/useMutation
pnpm example:dashboard

Or run directly with tsx:

npx tsx examples/simple-cli.tsx
npx tsx examples/interactive-menu.tsx
npx tsx examples/dashboard.tsx

Example: Simple CLI

Basic terminal output with styled text, boxes, and system information:

import { CLI, Box, Text, Badge, Panel, List } from '@mdxui/terminal'

async function main() {
  const cli = await CLI({ exitOnCtrlC: true, useAlternateScreen: true })

  cli.render(
    <Box flexDirection="column" gap={1}>
      <Box border="rounded" padding={1}>
        <Text bold color="cyan">Welcome to the Terminal!</Text>
      </Box>

      <Panel title="Status">
        <Badge variant="success">Online</Badge>
      </Panel>
    </Box>
  )
}

main()

Example: Interactive Menu

Multi-screen navigation with keyboard controls and form inputs:

import {
  CLI, Box, Text, Select, Input, Button, Panel,
  useKeyboard, useFocus, FocusProvider,
  VIM_BINDINGS, COMMON_BINDINGS,
} from '@mdxui/terminal'

function MainMenu({ onNavigate }) {
  const [selectedIndex, setSelectedIndex] = useState(0)
  const { isFocused } = useFocus({ autoFocus: true })

  useKeyboard({
    bindings: [...VIM_BINDINGS, ...COMMON_BINDINGS],
    onAction: (action) => {
      if (action === 'move-down') setSelectedIndex(i => i + 1)
      if (action === 'move-up') setSelectedIndex(i => i - 1)
      if (action === 'confirm') onNavigate(menuItems[selectedIndex].value)
    },
    enabled: isFocused,
  })

  return (
    <Panel title="Main Menu">
      <Select
        options={menuItems}
        highlightedIndex={selectedIndex}
        focused={isFocused}
      />
    </Panel>
  )
}

Example: Dashboard with Data Binding

Full dashboard with metrics, tables, and reactive data using useQuery/useMutation:

import {
  CLI, Box, Text, Table, Badge, Panel, Spinner,
  createDB, createCollection, DBProvider,
  useQuery, useMutation,
} from '@mdxui/terminal'
import { z } from 'zod'

// Define schema and collection
const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
  role: z.enum(['admin', 'user', 'guest']),
})

const usersCollection = createCollection({ name: 'users', schema: UserSchema })
const db = createDB({ collections: [usersCollection] })

// Data-bound component
function UserTable() {
  const { data, isLoading, refetch } = useQuery({ from: 'users' })
  const { mutate: deleteUser } = useMutation({
    collection: 'users',
    operation: 'delete',
  })

  if (isLoading) return <Spinner label="Loading..." />

  return (
    <Table
      data={data}
      columns={[
        { key: 'name', header: 'Name' },
        { key: 'email', header: 'Email' },
        { key: 'role', header: 'Role' },
      ]}
    />
  )
}

// App with provider
function App() {
  return (
    <DBProvider db={db}>
      <UserTable />
    </DBProvider>
  )
}

See the examples/ directory for complete, runnable versions of each example.

Architecture Overview

The terminal package implements a UINode-based rendering architecture that decouples component definitions from their output format. This enables the same semantic UI tree to render appropriately across different environments.

┌─────────────────────────────────────────────────────────────┐
│                    Input Sources                            │
├─────────────────────────────────────────────────────────────┤
│  JSX Components          │  JSON/API Responses              │
│  <Dashboard>             │  { type: 'dashboard', ... }      │
│    <Metrics />           │                                  │
│  </Dashboard>            │                                  │
└────────────┬─────────────┴──────────────┬───────────────────┘
             │                            │
             ▼                            ▼
     ┌───────────────┐            ┌───────────────┐
     │  compileJSX() │            │ parseUINode() │
     │  JSX Compiler │            │ JSON Parser   │
     └───────┬───────┘            └───────┬───────┘
             │                            │
             └────────────┬───────────────┘
                          ▼
              ┌───────────────────────┐
              │       UINode          │
              │  Universal UI Tree    │
              │  (type, props, data,  │
              │   children, key)      │
              └───────────┬───────────┘
                          │
    ┌─────────┬───────────┼───────────┬─────────┬─────────────┐
    ▼         ▼           ▼           ▼         ▼             ▼
┌───────┐ ┌───────┐ ┌─────────┐ ┌─────────┐ ┌──────┐ ┌─────────────┐
│ TEXT  │ │  MD   │ │  ASCII  │ │ UNICODE │ │ ANSI │ │ INTERACTIVE │
│Tier 1 │ │Tier 2 │ │ Tier 3  │ │ Tier 4  │ │Tier 5│ │   Tier 6    │
└───────┘ └───────┘ └─────────┘ └─────────┘ └──────┘ └─────────────┘

Core Types

// The universal UI tree node
interface UINode {
  type: string                    // Component type ('text', 'box', 'table', etc.)
  props: Record<string, unknown>  // Component configuration
  children?: UINode[]             // Nested components
  data?: unknown                  // Bound query data for data-driven components
  key?: string                    // React-style reconciliation key
}

// Rendering capability level
type RenderTier = 'text' | 'markdown' | 'ascii' | 'unicode' | 'ansi' | 'interactive'

// Context passed during rendering
interface RenderContext {
  tier: RenderTier
  width: number      // Terminal width in columns
  height: number     // Terminal height in rows
  depth: number      // Current nesting depth
  theme: ThemeTokens // Semantic color tokens
  interactive: boolean
}

Package Exports

The package provides several entry points for different use cases:

| Entry Point | Description | |-------------|-------------| | @mdxui/terminal | Main entry - CLI API, components, hooks, compiler, parser | | @mdxui/terminal/renderers | All tier renderers (text, markdown, ascii, unicode, ansi, interactive) | | @mdxui/terminal/data | Data layer with TanStack DB-like API | | @mdxui/terminal/components | Terminal UI components | | @mdxui/terminal/keyboard | Keyboard and focus management | | @mdxui/terminal/theme | Theme utilities and color support | | @mdxui/terminal/storybook | Storybook integration helpers |

The 6 Render Tiers

| Tier | Use Case | Capabilities | Example Output | |------|----------|--------------|----------------| | text | Logs, piping, CI | Plain text only | Users: 1234, API Calls: 45K | | markdown | AI agents, MCP | Tables, links, formatting | \| Users \| 1,234 \| +12% \| | | ascii | Legacy terminals | ASCII box drawing (+, -, \|) | +------+-------+ | | unicode | Modern terminals | Unicode box drawing | ┌──────┬───────┐ | | ansi | Full terminal | Colors, bold, dim | Colored output with ANSI escapes | | interactive | Human TUI | Keyboard, focus, real-time | Full interactive application |

Using Renderers

Import renderers from the dedicated entry point:

import {
  renderText,
  renderMarkdown,
  renderASCII,
  renderUnicode,
  renderANSI,
  createInteractiveRenderer
} from '@mdxui/terminal/renderers'

// Compile JSX to UINode tree
import { compileJSX } from '@mdxui/terminal'
const uiTree = compileJSX(<Dashboard metrics={data} />)

// Render to different tiers
const textOutput = renderText(uiTree)           // Tier 1: Plain text
const mdOutput = renderMarkdown(uiTree)         // Tier 2: Markdown
const asciiOutput = renderASCII(uiTree)         // Tier 3: ASCII art
const unicodeOutput = renderUnicode(uiTree)     // Tier 4: Unicode
const ansiOutput = renderANSI(uiTree)           // Tier 5: ANSI colors
// Tier 6: Interactive requires a renderer instance

JSX Compiler

The compiler converts React JSX trees into UINode trees. It handles:

  • Fragment flattening
  • Null/undefined/boolean child filtering (React semantics)
  • Key preservation for list reconciliation
  • Deep tree support via iterative compilation
import { compileJSX, compileJSXDeep } from '@mdxui/terminal'

// Standard compilation (recursive)
const node = compileJSX(
  <Box padding={2}>
    <Text bold>Hello</Text>
    <Text>World</Text>
  </Box>
)
// Result:
// {
//   type: 'Box',
//   props: { padding: 2 },
//   children: [
//     { type: 'Text', props: { bold: true }, children: ['Hello'] },
//     { type: 'Text', props: {}, children: ['World'] }
//   ]
// }

// For very deep trees (>1000 levels), use iterative version
const deepNode = compileJSXDeep(veryDeepElement)

JSON Parser

Parse JSON strings or objects into validated UINode trees with Zod schemas.

import { parseUINode, ParseError } from '@mdxui/terminal'

// Parse JSON string
const node = parseUINode('{"type":"text","props":{"content":"Hello"}}')

// Parse object (validates structure)
const node = parseUINode({
  type: 'table',
  props: { columns: [{ key: 'name', header: 'Name' }] },
  data: [{ name: 'Alice' }, { name: 'Bob' }]
})

// Handle validation errors
try {
  parseUINode({ props: {} }) // Missing 'type'
} catch (error) {
  if (error instanceof ParseError) {
    console.error('Invalid UINode:', error.message)
  }
}

Renderer Examples

Text Renderer (Tier 1)

Plain text output with no formatting. Ideal for logs, CI output, or piping.

import { renderText } from '@mdxui/terminal/renderers'

const output = renderText({
  type: 'dashboard',
  props: { title: 'Metrics' },
  children: [
    { type: 'text', props: { content: 'Active Users: 1,234' } }
  ]
})
// Output:
// Metrics
//
// Active Users: 1,234

Markdown Renderer (Tier 2)

Structured markdown for AI agents consuming via MCP. Tables, links, and formatting preserved.

import { renderMarkdown } from '@mdxui/terminal/renderers'

const output = renderMarkdown({
  type: 'table',
  props: {
    columns: [
      { key: 'name', header: 'Name' },
      { key: 'value', header: 'Value' }
    ],
    data: [
      { name: 'Users', value: '1,234' },
      { name: 'Revenue', value: '$45K' }
    ]
  }
})
// Output:
// | Name | Value |
// | --- | --- |
// | Users | 1,234 |
// | Revenue | $45K |

ASCII Renderer (Tier 3)

Pure ASCII characters for maximum compatibility. No unicode, no ANSI codes.

import { renderASCII } from '@mdxui/terminal/renderers'

const output = renderASCII({
  type: 'box',
  props: { border: 'single' },
  children: [{ type: 'text', props: { content: 'Hello' } }]
})
// Output:
// +-------+
// | Hello |
// +-------+

Unicode Renderer (Tier 4)

Beautiful box-drawing characters for modern terminals. No colors.

import { renderUnicode } from '@mdxui/terminal/renderers'

const output = renderUnicode({
  type: 'box',
  props: { border: 'rounded' },
  children: [{ type: 'text', props: { content: 'Hello' } }]
})
// Output:
// ╭───────╮
// │ Hello │
// ╰───────╯

ANSI Renderer (Tier 5)

Full color support with 16, 256, or truecolor modes. Supports theming and color degradation.

import { renderANSI } from '@mdxui/terminal/renderers'

const output = renderANSI(
  { type: 'text', props: { content: 'Error!', color: 'red', bold: true } },
  { colorSupport: 'truecolor', theme: 'dark' }
)
// Output: Bold red text with ANSI escape codes

Interactive Renderer (Tier 6)

Full TUI with keyboard navigation, focus management, and real-time updates.

import { createInteractiveRenderer } from '@mdxui/terminal/renderers'

const renderer = await createInteractiveRenderer({
  vimBindings: true,
  wrapFocus: true
})

// Register focusable elements
renderer.registerFocusable('btn-1', { tabIndex: 0, onActivate: handleClick })

// Handle keyboard input
renderer.onKeyPress('enter', () => console.log('Pressed!'))

// Start the TUI
renderer.start()

Supported Components

Primitives

| Component | Description | Key Props | |-----------|-------------|-----------| | text | Styled text | content, bold, italic, color | | box | Container with borders | border, padding, width, height | | list | Bullet or numbered list | items, numbered, bullet | | table | Data table | columns, data, headers, rows | | code | Code block | code, language | | link | Hyperlink | text, href | | button | Interactive button | label, hotkey, action |

Layout

| Component | Description | Key Props | |-----------|-------------|-----------| | panel | Titled panel | title, collapsible, collapsed | | card | Content card | title, border | | sidebar | Navigation sidebar | nav, sections | | breadcrumb | Navigation path | items, separator | | dialog | Modal dialog | open, title, actions |

Data Display

| Component | Description | Key Props | |-----------|-------------|-----------| | metrics | Metric cards | metrics: [{ label, value, trend }] | | badge | Status badge | variant, children | | progress | Progress bar | value, max, width | | spinner | Loading indicator | label, frame |

Page Templates

| Component | Description | |-----------|-------------| | dashboard | Dashboard with metrics and panels | | settings | Settings page with sections | | hero | Landing page hero section | | features | Feature showcase | | pricing | Pricing tiers | | faq | FAQ accordion | | footer | Page footer |

Data Layer

The data layer provides reactive data management with offline support, available via @mdxui/terminal/data.

import {
  createDB,
  createCollection,
  createDOSync,
  DBProvider,
  useQuery,
  useMutation
} from '@mdxui/terminal/data'
import { z } from 'zod'

// Define schema
const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
  role: z.enum(['admin', 'user', 'guest'])
})

// Create collection
const usersCollection = createCollection({
  name: 'users',
  schema: UserSchema
})

// Create database with Durable Objects sync
const db = createDB({
  collections: [usersCollection],
  sync: createDOSync({
    namespaceUrl: 'https://api.example.com.ai/do/workspace',
    reconnect: { enabled: true, maxAttempts: 10 }
  })
})

// Use in React components
function UserList() {
  const { data, isLoading } = useQuery({
    from: 'users',
    where: { role: 'admin' }
  })

  const { mutate } = useMutation({
    collection: 'users',
    operation: 'insert'
  })

  if (isLoading) return <Spinner label="Loading users..." />

  return (
    <Table
      columns={[
        { key: 'name', header: 'Name' },
        { key: 'email', header: 'Email' },
        { key: 'role', header: 'Role' }
      ]}
      data={data}
    />
  )
}

Built-in SaaS Collections

Pre-defined schemas for common SaaS patterns:

import {
  UsersCollection,
  APIKeysCollection,
  WebhooksCollection,
  TeamsCollection,
  UsageCollection
} from '@mdxui/terminal/data'

const db = createDB({
  collections: [
    UsersCollection(),
    APIKeysCollection(),
    WebhooksCollection(),
    TeamsCollection(),
    UsageCollection()
  ]
})

Sync Adapter Features

The createDOSync adapter provides:

  • WebSocket connection management with automatic reconnection
  • Exponential backoff with jitter to prevent thundering herd
  • Offline mutation queue - changes are queued when offline and synced on reconnect
  • Connection state observable for UI feedback
  • Conflict resolution strategies - server-wins, client-wins, merge, throw, or custom
const sync = createDOSync({
  namespaceUrl: 'https://api.example.com.ai/do/workspace',
  authToken: 'jwt-token',
  reconnect: {
    enabled: true,
    maxAttempts: 10,
    initialDelay: 1000,
    maxDelay: 30000
  },
  conflictResolution: 'server-wins',
  requestTimeout: 10000
})

// Monitor connection state
sync.onConnectionStateChange((state) => {
  // 'disconnected' | 'connecting' | 'connected' | 'reconnecting'
  updateStatusIndicator(state)
})

// Check pending changes
const { count, oldestAt } = sync.getQueueStats()
if (count > 0) {
  showPendingBanner(`${count} changes pending`)
}

Terminal Hooks

| Hook | Status | Description | |------|--------|-------------| | useTerminalSize | Implemented | Returns terminal dimensions (uses @opentui/react with fallbacks) | | useTerminal | Implemented | Returns terminal context including dimensions and color support | | useFocus | Implemented | Focus management via @mdxui/terminal/keyboard | | useTheme | Implemented | Access current theme and colors | | useKeyboard | Implemented | Low-level keyboard event handling | | createKeyboardManager | Implemented | Create keyboard managers with presets (VIM_BINDINGS, COMMON_BINDINGS) |

API Reference

Compiler

// Compile JSX to UINode (recursive)
function compileJSX(element: React.ReactElement): UINode

// Compile JSX to UINode (iterative, for deep trees)
function compileJSXDeep(element: React.ReactElement): UINode

Parser

// Parse JSON string or object to UINode
function parseUINode(input: string | unknown): UINode

// Parse JSON string only
function parseUINodeFromJSON(json: string): UINode

// Error class for parse failures
class ParseError extends Error {}

Renderers (from @mdxui/terminal/renderers)

// Text tier
function renderText(node: UINode, options?: TextRenderOptions): string

// Markdown tier
function renderMarkdown(node: UINode, options?: MarkdownRenderOptions): string

// ASCII tier
function renderASCII(node: UINode, context?: RenderContext): string

// Unicode tier
function renderUnicode(node: UINode, context?: RenderContext): string

// ANSI tier
function renderANSI(node: UINode, options?: ANSIRenderOptions): string

// Interactive tier
function createInteractiveRenderer(config?: InteractiveRendererConfig): Promise<InteractiveRenderer>

Zod Schemas

import { UINodeSchema, RenderTierSchema, ThemeTokensSchema, RenderContextSchema } from '@mdxui/terminal'

// Validate a UINode
const result = UINodeSchema.safeParse(data)
if (result.success) {
  const node: UINode = result.data
}

License

MIT