Skip to main content

Overview

The MarkdownRenderable component renders Markdown content with full support for tables, code blocks, inline formatting, and more. It uses the marked parser internally and provides syntax highlighting for code blocks via Tree-sitter.

Basic Usage

import { MarkdownRenderable, SyntaxStyle } from "@opentui/core"

const syntaxStyle = SyntaxStyle.fromStyles({
  "markup.heading": { fg: parseColor("#58A6FF"), bold: true },
  "markup.link": { fg: parseColor("#58A6FF"), underline: true },
  default: { fg: parseColor("#E6EDF3") },
})

const markdown = new MarkdownRenderable(renderer, {
  content: "# Hello World\n\nThis is **bold** text.",
  syntaxStyle,
  conceal: true,
})

Props

content
string
default:"''"
Markdown content to render.
syntaxStyle
SyntaxStyle
required
Syntax styling configuration for markdown elements and code blocks.
conceal
boolean
default:"true"
Hide formatting markers (e.g., **, *, “, []()).When true: **bold**boldWhen false: **bold** → **bold**
treeSitterClient
TreeSitterClient
Tree-sitter client for syntax highlighting code blocks. Uses default client if not provided.
streaming
boolean
default:"false"
Enable streaming mode for incremental content updates. Keeps trailing tokens unstable to handle incomplete content.
tableOptions
MarkdownTableOptions
Configuration for rendered markdown tables.
renderNode
(token: Token, context: RenderNodeContext) => Renderable | undefined
Custom node renderer. Return a Renderable to override default rendering, or undefined to use default.

Table Options

tableOptions.widthMode
'content' | 'full'
default:"'full'"
Column sizing strategy:
  • 'content': Columns fit to intrinsic content width
  • 'full': Columns expand to fill available width
tableOptions.columnFitter
'uniform' | 'proportional'
default:"'proportional'"
Column fitting method when shrinking constrained tables.
tableOptions.wrapMode
'none' | 'char' | 'word'
default:"'word'"
Text wrapping strategy for table cell content.
tableOptions.cellPadding
number
default:"0"
Padding applied on all sides of each table cell.
tableOptions.borders
boolean
default:"true"
Enable/disable table border rendering.
tableOptions.borderStyle
BorderStyle
default:"'single'"
Border style for markdown tables ('single', 'double', etc.).
tableOptions.borderColor
ColorInput
Border color. Defaults to conceal style color.
tableOptions.selectable
boolean
default:"true"
Enable selection support on markdown tables.

Supported Markdown Features

Headings

# Heading 1
## Heading 2
### Heading 3

Text Formatting

**bold text**
*italic text*
~~strikethrough~~
`inline code`
[Link text](https://example.com)
![Image alt](https://example.com/image.png)

Lists

- Unordered item 1
- Unordered item 2

1. Ordered item 1
2. Ordered item 2

Blockquotes

> This is a blockquote
> with multiple lines

Code Blocks

```typescript
const message = "Hello World"
console.log(message)
```

Tables

| Feature | Status | Priority |
|---|---|---|
| Table alignment | Done | High |
| Conceal mode | Working | Medium |

Horizontal Rules

---

Methods

clearCache()

Force re-render by clearing the parse cache.
markdown.clearCache()

refreshStyles()

Re-render blocks with updated styles without rebuilding parse state.
markdown.syntaxStyle = newSyntaxStyle
markdown.refreshStyles()

Examples

GitHub Dark Theme

const syntaxStyle = SyntaxStyle.fromStyles({
  "markup.heading": { fg: parseColor("#58A6FF"), bold: true },
  "markup.heading.1": { fg: parseColor("#00FF88"), bold: true, underline: true },
  "markup.heading.2": { fg: parseColor("#00D7FF"), bold: true },
  "markup.bold": { fg: parseColor("#F0F6FC"), bold: true },
  "markup.strong": { fg: parseColor("#F0F6FC"), bold: true },
  "markup.italic": { fg: parseColor("#F0F6FC"), italic: true },
  "markup.raw": { fg: parseColor("#A5D6FF"), bg: parseColor("#161B22") },
  "markup.link": { fg: parseColor("#58A6FF"), underline: true },
  "markup.link.url": { fg: parseColor("#58A6FF"), underline: true },
  "punctuation.special": { fg: parseColor("#8B949E") },
  default: { fg: parseColor("#E6EDF3") },
})

const markdown = new MarkdownRenderable(renderer, {
  content: markdownContent,
  syntaxStyle,
  conceal: true,
})

Markdown with Tables

const content = `
# Project Status

| Feature | Status | Priority |
|---|---|---|
| Table alignment | **Done** | High |
| Conceal mode | *Working* | Medium |
| Theme switching | **Done** | Low |
`

const markdown = new MarkdownRenderable(renderer, {
  content,
  syntaxStyle,
  tableOptions: {
    widthMode: "full",
    cellPadding: 1,
    borders: true,
    borderStyle: "single",
  },
})

Streaming Mode

const markdown = new MarkdownRenderable(renderer, {
  content: "",
  syntaxStyle,
  streaming: true, // Enable incremental updates
})

// Simulate streaming content
let position = 0
const fullContent = "# Streaming\n\nThis content arrives gradually..."

const interval = setInterval(() => {
  const chunk = fullContent.slice(0, position + 10)
  markdown.content = chunk
  position += 10
  
  if (position >= fullContent.length) {
    markdown.streaming = false // Finalize
    clearInterval(interval)
  }
}, 100)

Custom Table Styling

const markdown = new MarkdownRenderable(renderer, {
  content: markdownWithTables,
  syntaxStyle,
  tableOptions: {
    widthMode: "content",
    wrapMode: "word",
    cellPadding: 2,
    borders: true,
    borderStyle: "double",
    borderColor: "#4ECDC4",
    selectable: true,
  },
})

Conceal Toggle

const markdown = new MarkdownRenderable(renderer, {
  content: "**bold** and *italic* text",
  syntaxStyle,
  conceal: true,
})

// Toggle conceal on keypress
renderer.keyInput.on("keypress", (key) => {
  if (key.name === "c") {
    markdown.conceal = !markdown.conceal
  }
})

Custom Node Renderer

const markdown = new MarkdownRenderable(renderer, {
  content: markdownContent,
  syntaxStyle,
  renderNode: (token, context) => {
    // Custom rendering for specific token types
    if (token.type === "heading" && token.depth === 1) {
      // Return custom renderable for h1 elements
      return new CustomHeaderRenderable(renderer, {
        text: token.text,
      })
    }
    // Return undefined to use default rendering
    return undefined
  },
})

Syntax Style Groups

Markdown uses the following syntax style groups:
  • markup.heading, markup.heading.1 through markup.heading.6
  • markup.bold, markup.strong
  • markup.italic
  • markup.list
  • markup.quote
  • markup.raw, markup.raw.block, markup.raw.inline
  • markup.link, markup.link.label, markup.link.url
  • punctuation.special
  • conceal (for concealed formatting markers)
  • default

Properties

content

Get or set the markdown content.
markdown.content = "# New Content"
const current = markdown.content

syntaxStyle

Get or set the syntax style.
markdown.syntaxStyle = newSyntaxStyle

conceal

Get or set conceal mode.
markdown.conceal = false // Show formatting markers

streaming

Get or set streaming mode.
markdown.streaming = true

tableOptions

Get or set table rendering options.
markdown.tableOptions = {
  widthMode: "content",
  cellPadding: 2,
}

Performance

  • Uses AST-based parsing with marked for accurate Markdown parsing
  • Implements caching for repeated content to avoid redundant parsing
  • Smart width calculation accounting for concealed characters
  • Incremental rendering in streaming mode
  • Efficient table rendering with change detection
  • Text - Plain text display
  • Code - Syntax-highlighted code blocks
  • TextTable - Table component (used internally)

Build docs developers (and LLMs) love