Skip to content

MarkdownWidget

Renders markdown source text as a composed terminal widget tree. Supports CommonMark headings, inline formatting, fenced code blocks, GFM tables, lists, block quotes, images, and interactive links.

Basic Usage

Pass a markdown string to ctx.Markdown() inside any layout:

csharp
using Hex1b;

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.VScrollPanel(
        ctx.Markdown("""
            # Welcome to Hex1b

            Render **rich markdown** content in your terminal UI with full support
            for headings, *emphasis*, `inline code`, and more.

            ## Features

            - **Bold** and *italic* text formatting
            - Fenced code blocks with line numbers
            - Tables, lists, and block quotes
            - Interactive links with Tab navigation
            - Embedded images via Kitty Graphics Protocol

            ## Code Example

            \`\`\`csharp
            var app = new Hex1bApp(ctx =>
                ctx.Markdown("# Hello, World!")
            );
            await app.RunAsync();
            \`\`\`

            > The MarkdownWidget parses CommonMark-compatible
            > markdown and renders it as a composed widget tree.

            ---

            | Feature        | Status  |
            |:---------------|:-------:|
            | Headings       | ✅ Done |
            | Inline styles  | ✅ Done |
            | Code blocks    | ✅ Done |
            | Tables         | ✅ Done |
            | Links          | ✅ Done |
            """)
    ))
    .Build();

await terminal.RunAsync();

Supported Syntax

Headings

Four heading levels are supported, rendered with decreasing visual weight:

csharp

Text Formatting

Inline styles can be combined freely within paragraphs:

csharp

Supported styles:

  • **bold** — Bold text
  • *italic* — Italic (rendered as dim)
  • ***bold italic*** — Combined bold and italic
  • `code` — Inline code with distinct background
  • ~~strikethrough~~ — Strikethrough text

Code Blocks

Fenced code blocks are rendered as read-only editors with line numbers. The language tag is displayed but syntax highlighting is not yet supported:

csharp

Lists

Unordered, ordered, and task lists are all supported, including nesting:

csharp

Tables

GFM-style tables with column alignment (:---, :---:, ---:):

csharp

Block Quotes

Block quotes support inline formatting and can span multiple lines:

csharp

Images

Inline images (![alt](url)) are rendered using the Kitty Graphics Protocol when an image loader is provided via .OnImageLoad(). When no loader is configured or the loader returns null, the alt text is displayed as a text fallback. See Image Loading for configuration details.

Thematic Breaks

Horizontal rules (---, ***, ___) render as full-width dividers between content sections.

Enable Tab navigation across links with .Focusable(children: true). Links become keyboard-focusable and visually highlight when focused:

csharp
using Hex1b;

var lastActivated = "";

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
        v.VScrollPanel(
            v.Markdown("""
                # Focusable Links Demo

                Use **Tab** and **Shift+Tab** to navigate between links.
                Press **Enter** to activate a focused link.

                ## Navigation

                - [Hex1b on GitHub](https://github.com/mitchdenny/hex1b)
                - [Getting Started](/guide/getting-started)
                - [Widget Documentation](/guide/widgets/)

                ## Intra-Document Links

                Jump to the [Navigation](#navigation) section above,
                or go to [Resources](#resources) below.

                ## Resources

                Check out the [API Reference](/reference/) for details.
                """)
                .Focusable(children: true)
                .OnLinkActivated(args =>
                {
                    lastActivated = $"{args.Kind}: {args.Url}";
                    args.Handled = true;
                })
        ),
        v.Text(string.IsNullOrEmpty(lastActivated)
            ? "Press Tab to focus a link, Enter to activate"
            : $"Activated → {lastActivated}")
    ]))
    .Build();

await terminal.RunAsync();

Handle link clicks with .OnLinkActivated(). The event args include the URL, display text, and a Kind property that classifies the link:

KindDescriptionExample
ExternalHTTP/HTTPS URLshttps://example.com
IntraDocumentHeading anchors#my-heading
CustomEverything elsemailto:, command:

Set args.Handled = true to suppress default behavior (opening browser, scrolling to heading).

Image Loading

Load and display embedded images with .OnImageLoad(). The callback receives a Uri and alt text, and returns pixel data:

csharp
ctx.Markdown(source)
    .OnImageLoad(async (uri, altText) =>
    {
        var bytes = await File.ReadAllBytesAsync(uri.LocalPath);
        // Decode image to RGBA32 pixel data
        return new MarkdownImageData(rgbaData, width, height);
    })

Images are rendered using the Kitty Graphics Protocol. When the loader returns null, the image alt text is shown as a text fallback.

Image Rendering Customization

The .OnImageLoad() callback controls data loading but not how the image widget is rendered. To customize image rendering, use .OnBlock<MarkdownDocument.ParagraphBlock>() to intercept image-only paragraphs. A dedicated image rendering hook is planned — see #242.

Document Source

For live-updating markdown (e.g., an editor preview), pass an IHex1bDocument instead of a string. The widget uses IHex1bDocument.Version for efficient change detection—re-parsing only occurs when the version advances:

csharp

Custom Block Rendering

Override how specific block types are rendered with .OnBlock<TBlock>(). Handlers form a middleware chain—call ctx.Default(block) to fall through to the next handler:

csharp
ctx.Markdown(source)
    .OnBlock<MarkdownDocument.BlockQuote>((ctx, block) =>
        ctx.RootContext.Border(b =>
            [ctx.Default(block)],
            title: "Note"
        ))

Available block types: Heading, Paragraph, FencedCode, BlockQuote, UnorderedList, OrderedList, Table, ThematicBreak, TaskList.

  • Text — Simple text display
  • Hyperlink — Single clickable hyperlinks (OSC 8)
  • Scroll — Scrollable containers for long markdown content
  • KgpImage — Direct pixel image rendering

Released under the MIT License.