Display rich formatted text using SharpConsoleUI markup syntax.
MarkupControl displays multi-line rich text using SharpConsoleUI's markup syntax. It supports colors and styles (bold, italic, underline, …), RGB/hex colors, inline gradients and animated spinners, clickable + keyboard-navigable links, and inline Markdown — including syntax-highlighted fenced code, and tables that fit the width and wrap their cells. Text is Unicode-correct (emoji/ZWJ/CJK) and wraps by the Unicode line-breaking rules. Selection + copy are opt-in, with a choice of copying the rendered text or the original markup source.
| Property | Type | Default | Description |
|---|---|---|---|
Text |
string |
Empty | Content as a single newline-separated string (get/set). Set content via SetContent(List<string>) or AppendLine/AppendLines. |
Wrap |
bool |
true |
Word-wrap text to the available width |
BackgroundColor |
Color? |
null |
Background color (falls back to container) |
ForegroundColor |
Color? |
null |
Text color (falls back to container) |
EnableSelection |
bool |
false |
Opt-in mouse text selection + copy shortcut (see below) |
SelectionForegroundColor |
Color? |
null |
Foreground color for selected text |
SelectionBackgroundColor |
Color? |
null |
Background color for selected text |
HasSelection |
bool |
false |
(read-only) Whether text is currently selected |
CopyEnabled |
bool |
true |
Whether the keyboard copy shortcut is active |
CopyMode |
MarkupCopyMode |
Rendered |
What a copy returns: Rendered (visible text, markup/markdown stripped) or Source (the raw markup you set, with newlines). See Copy: rendered vs source |
CopyKey |
ConsoleKey |
C |
Key that triggers a copy |
CopyModifiers |
ConsoleModifiers |
Control |
Modifiers required for the copy shortcut |
MarkdownStyle |
MarkdownStyle? |
null |
Per-control style for [markdown] content; null uses MarkdownStyle.Default (see Markdown) |
IsEnabled |
bool |
true |
Whether the control accepts keyboard input (link navigation). A disabled control is never a focus stop |
FocusedLinkForegroundColor |
Color? |
null |
Foreground for the keyboard-focused link highlight; null uses a high-contrast default (see Links) |
FocusedLinkBackgroundColor |
Color? |
null |
Background for the keyboard-focused link highlight; null uses a high-contrast default |
Border |
BorderStyle |
None |
Optional border drawn around the markup (None = no border) |
BorderColor |
Color? |
null |
Border color; null resolves from ColorRole/foreground |
Header |
string? |
null |
Header text shown in the top border (when bordered) |
HeaderAlignment |
TextJustification |
Left |
Header text alignment within the top border |
UseSafeBorder |
bool |
false |
Use ASCII-safe border characters |
Padding |
Padding |
(0,0,0,0) |
Inner padding between border/edge and content |
| Method | Description |
|---|---|
SetContent(List<string>) |
Replaces all content |
SetMarkdown(string) |
Replaces the content with rendered Markdown (see Markdown) |
Markdown(string) |
Alias for SetMarkdown(string) |
AppendLine(string) |
Appends a single markup line |
AppendLines(IEnumerable<string>) |
Appends multiple markup lines |
AppendText(string) |
Appends text. Splits on \n, but keeps a [tag]…[/] region (e.g. a multi-line [markdown] or [yellow] block) together so its style spans the newlines |
GetSelectedText() |
Returns the current selection. CopyMode.Rendered (default) returns plain visible text; CopyMode.Source returns the raw markup you set (see Copy: rendered vs source) |
ClearSelection() |
Clears the current selection |
CopySelectionToClipboard() |
Copies the selection to the clipboard; returns true if anything was copied |
CopyToClipboard() |
Copies the entire content (plain text) to the clipboard |
| Event | Type | Description |
|---|---|---|
LinkClicked |
EventHandler<LinkClickedEventArgs> |
Fires when a rendered link is activated by mouse click or keyboard (Enter); payload exposes Url and Text (see Links) |
SelectionChanged |
EventHandler<string> |
Fires when the selection changes; payload is the selected plain text (empty when cleared) |
TextSelectionChanged |
EventHandler<TextSelectionChangedEventArgs> |
Richer companion carrying HasSelection and SelectedText; fires together with SelectionChanged |
MouseRightClick |
EventHandler<MouseEventArgs> |
Fires on right-click (surface for a context menu — see below) |
var markup = Controls.Markup()
.AddLine("[bold yellow]Welcome to SharpConsoleUI![/]")
.AddLine("")
.AddLine("Features:")
.AddLine(" [green]• Modern UI controls[/]")
.AddLine(" [green]• Async support[/]")
.AddLine(" [green]• Rich formatting[/]")
.AddLine("")
.AddLine("[dim]Press any key to continue...[/]")
.WithAlignment(HorizontalAlignment.Center)
.Build();
window.AddControl(markup);A MarkupControl can draw its own border, header, and padding, so "bordered text" is just
markup — you don't need a separate container around it. For a box that hosts other controls,
use Controls.Panel() (a container) instead.
// A bordered text box — no separate "panel" needed; MarkupControl draws its own border.
var box = Controls.Markup("[cyan]Status:[/] All systems go")
.WithBorder(BorderStyle.Rounded)
.WithHeader("System")
.WithBorderColor(Color.Cyan1)
.WithPadding(1, 0, 1, 0)
.Build();
window.AddControl(box);var markup = new MarkupControl(new List<string>
{
"[bold yellow]Title[/]",
"",
"Regular text",
"[green]Success message[/]",
"[red]Error message[/]"
});
window.AddControl(markup);// Simple text
window.AddControl(Controls.Label("Plain text"));
// Formatted shortcuts
window.AddControl(Controls.Header("Section Title")); // Bold yellow
window.AddControl(Controls.Info("Information")); // Blue
window.AddControl(Controls.Warning("Warning")); // Orange
window.AddControl(Controls.Error("Error")); // Red
window.AddControl(Controls.Success("Success")); // GreenSharpConsoleUI uses its own markup syntax (Spectre-compatible):
"[red]Red text[/]"
"[green]Green text[/]"
"[blue]Blue text[/]"
"[yellow]Yellow text[/]"
"[cyan]Cyan text[/]"
"[magenta]Magenta text[/]"
"[white]White text[/]"
"[black]Black text[/]"
// Extended colors
"[orange3]Orange text[/]"
"[purple]Purple text[/]"
"[grey]Grey text[/]"
// RGB colors
"[rgb(255,128,0)]Custom color[/]"
"[#FF8000]Hex color[/]""[bold]Bold text[/]"
"[italic]Italic text[/]"
"[underline]Underlined text[/]"
"[strikethrough]Strikethrough text[/]"
"[dim]Dimmed text[/]"
"[reverse]Reversed text[/]""[bold red]Bold red text[/]"
"[italic blue]Italic blue text[/]"
"[bold yellow underline]Multiple styles[/]""Use [[double brackets]] to display literal brackets"
"This shows [red]colored[/] and [[red]] literal markup"By default MarkupControl is display-only — its text cannot be selected or copied. Set
EnableSelection = true (opt-in, off by default, WinUI-style) to make the control
mouse-selectable. The user can then:
- Drag to select a range of text
- Double-click to select a word
- Triple-click to select a line
- Press Ctrl+C to copy the selection to the clipboard
By default the copied text is plain text — all markup tags are stripped automatically, and a
selected line soft-wrapped across multiple display rows is copied back as a single line. To copy the
original markup source instead (e.g. the raw [markdown]…[/] you set, with its line breaks), set
CopyMode = MarkupCopyMode.Source — see Copy: rendered vs source.
Selection is coordinated per window: only one control can hold the active selection at a time, so starting a selection in one control clears any selection in another. Ctrl+C is handled at the window level and copies whatever is currently selected. Left-clicking empty space clears the selection; right-click is surfaced to the application (e.g. to show a context menu) and does not affect the selection.
Because selection is off by default, existing applications are unaffected.
// Via property
var markup = new MarkupControl(new List<string> { "[bold]Selectable[/] output" })
{
EnableSelection = true,
SelectionForegroundColor = Color.Black, // optional
SelectionBackgroundColor = new Color(95, 175, 255) // optional
};
// Via fluent builder
var markup = Controls.Markup("[green]Build succeeded[/] in 3.4s")
.WithSelectionEnabled()
.WithSelectionColors(Color.Black, new Color(95, 175, 255)) // optional
.Build();if (markup.HasSelection)
{
string plain = markup.GetSelectedText(); // markup-free
}
markup.SelectionChanged += (sender, selectedText) =>
{
// selectedText is the current plain-text selection ("" when cleared)
};
// Richer event carrying both the state and the text:
markup.TextSelectionChanged += (sender, e) =>
{
// e.HasSelection, e.SelectedText
};
markup.ClearSelection();The control implements ISelectableControl, so it participates in the window's
SelectionManager (window.SelectionManager.ActiveSelection / GetSelectedText()).
MultilineEditControl also implements ISelectableControl, so an editor and selectable markup
controls in the same window share the single-selection behavior.
Copy works locally and over SSH — see Clipboard, Copy & Paste for how OSC 52 carries the copy to the local clipboard over a remote session, and how to configure it.
// Copy the current selection (plain text). Returns false if nothing is selected.
markup.CopySelectionToClipboard();
// Copy the control's entire content (plain text), ignoring the selection.
markup.CopyToClipboard();The keyboard copy shortcut defaults to Ctrl+C and is handled at the window level. It can be remapped or disabled per control (programmatic copy is unaffected):
markup.CopyKey = ConsoleKey.Y; // copy on Ctrl+Y
markup.CopyModifiers = ConsoleModifiers.Control;
markup.CopyEnabled = false; // disable the shortcut entirely
// Or via the builder:
Controls.Markup("...")
.WithSelectionEnabled()
.WithCopyKey(ConsoleKey.Y) // Ctrl+Y
.WithCopyEnabled(true)
.Build();By default a copy returns the rendered text — markup and Markdown are expanded to plain
characters ([markdown]# Title[/] copies as Title, a table copies as its visible glyphs). For
tools that want the original markup back — an agent/IDE copying a rendered Markdown block, for
example — set CopyMode = MarkupCopyMode.Source:
// Property
label.CopyMode = MarkupCopyMode.Source;
// Or via the builder (implies selection):
Controls.Markup()
.Markdown("# Title\n\n- one\n- two")
.WithCopyMode(MarkupCopyMode.Source)
.Build();In Source mode, copying returns the raw markup the control was given — the [markdown]…[/] /
[yellow]…[/] lines the selection touches — with their newlines intact, rather than the rendered
glyphs. The default (Rendered) is unchanged, so existing apps are unaffected.
MarkupCopyModehas two values:Rendered(default) andSource. Source-mode selection is whole-line — a selection touching a markup block copies that block's full source.
markup.AppendLine("[green]New line[/]");
markup.AppendLines(new[] { "line 2", "line 3" });
markup.AppendText("multi\nline\ntext"); // splits on \n (a [tag]…[/] block stays together)Right-click is surfaced via the MouseRightClick event — the control does not show a menu itself,
leaving the app free to present its own (e.g. Copy / Copy All / Clear).
A context menu is typically shown as a portal anchored at the click point, hosting a vertical
MenuControl. The pattern below mirrors the Selectable Text screen in the DemoApp:
// A small reusable portal hosting a vertical MenuControl.
internal sealed class ContextMenuPortal : PortalContentContainer
{
private readonly MenuControl _menu;
public event EventHandler<MenuItem>? ItemSelected;
public ContextMenuPortal(IEnumerable<MenuItem> items, int anchorX, int anchorY,
int windowWidth, int windowHeight)
{
_menu = new MenuControl { Orientation = MenuOrientation.Vertical };
foreach (var item in items) _menu.AddItem(item);
_menu.ItemSelected += (_, mi) => ItemSelected?.Invoke(this, mi);
DismissOnOutsideClick = true; // library auto-dismisses on outside click
BorderStyle = BoxChars.Rounded;
PortalFocusedControl = _menu;
AddChild(_menu);
SetFocusOnFirstChild();
int w = 24, h = _menu /* item count */ is var _ ? 6 : 6;
// Anchor + bounds are in window CONTENT space (0,0 = first content row); Below opens the
// menu one line under the click. Convert window-space click coords with `- 1` (see below).
var pos = PortalPositioner.CalculateFromPoint(
new Point(anchorX, anchorY), new Size(w, h),
new Rectangle(0, 0, windowWidth - 2, windowHeight - 2),
PortalPlacement.Below);
PortalBounds = pos.Bounds;
}
}
// Wiring it to a selectable MarkupControl:
var markup = Controls.Markup("[green]Build succeeded[/]").WithSelectionEnabled().Build();
markup.MouseRightClick += (sender, args) =>
{
var items = new[]
{
new MenuItem { Text = "Copy", Shortcut = "Ctrl+C", IsEnabled = markup.HasSelection },
new MenuItem { Text = "Copy All" },
new MenuItem { IsSeparator = true },
new MenuItem { Text = "Clear Selection", IsEnabled = markup.HasSelection },
};
// args.WindowPosition is window-space (title/border at 0). Subtract the 1-cell border so the
// portal — positioned in content-space — opens exactly one line below the click.
var portal = new ContextMenuPortal(items,
args.WindowPosition.X - 1, args.WindowPosition.Y - 1,
window.Width, window.Height) { Container = window };
var node = window.CreatePortal(markup, portal);
portal.ItemSelected += (_, mi) =>
{
window.RemovePortal(markup, node);
switch (mi.Text)
{
case "Copy": markup.CopySelectionToClipboard(); break;
case "Copy All": markup.CopyToClipboard(); break;
case "Clear Selection": markup.ClearSelection(); break;
}
};
portal.DismissRequested += (_, _) => { /* portal already removed by the library */ };
};Coordinate note: the menu is rendered as a portal arranged in window content space, while
MouseEventArgs.WindowPositionis in window space (the title/border occupies row 0). Subtract the 1-cell border (- 1) from the click coordinates soPortalPlacement.Belowplaces the menu top exactly one row beneath the cursor.
window.AddControl(
Controls.Markup()
.AddLine("")
.AddLine("[bold yellow]╔═══════════════════════════════╗[/]")
.AddLine("[bold yellow]║ Welcome to My Application ║[/]")
.AddLine("[bold yellow]╚═══════════════════════════════╝[/]")
.AddLine("")
.AddLine("[dim]Version 1.0.0[/]")
.WithAlignment(HorizontalAlignment.Center)
.Build()
);// Success
window.AddControl(Controls.Success("File saved successfully!"));
// Error
window.AddControl(Controls.Error("Failed to load file"));
// Warning
window.AddControl(Controls.Warning("Disk space running low"));
// Info
window.AddControl(Controls.Info("Checking for updates..."));window.AddControl(
Controls.Markup()
.AddLine("[bold yellow]Available Commands:[/]")
.AddLine("")
.AddLine(" [green]/help[/] - Show this help message")
.AddLine(" [green]/quit[/] - Exit the application")
.AddLine(" [green]/clear[/] - Clear the screen")
.AddLine(" [green]/settings[/] - Open settings")
.Build()
);var status = Controls.Markup()
.AddLine("[dim]Status: Ready[/]")
.WithName("statusDisplay")
.Build();
window.AddControl(status);
// Later, update the content
var statusControl = window.FindControl<MarkupControl>("statusDisplay");
if (statusControl != null)
{
statusControl.SetContent(new List<string>
{
"[green]Status: Connected[/]",
"[dim]Last updated: " + DateTime.Now.ToString("HH:mm:ss") + "[/]"
});
}void UpdateProgress(int percent, MarkupControl control)
{
int barWidth = 30;
int filled = (int)((percent / 100.0) * barWidth);
string bar = new string('█', filled) + new string('░', barWidth - filled);
control.SetContent(new List<string>
{
$"[bold]Progress:[/] {percent}%",
$"[cyan]{bar}[/]"
});
}
var progress = Controls.Markup()
.WithName("progress")
.Build();
window.AddControl(progress);
// Update progress
for (int i = 0; i <= 100; i += 10)
{
var progressControl = window.FindControl<MarkupControl>("progress");
if (progressControl != null)
{
UpdateProgress(i, progressControl);
}
await Task.Delay(100);
}var logDisplay = Controls.Markup()
.WithName("logs")
.Build();
window.AddControl(logDisplay);
// Add log entries
void AddLog(string level, string message)
{
var logs = window.FindControl<MarkupControl>("logs");
if (logs == null) return;
var color = level switch
{
"ERROR" => "red",
"WARN" => "yellow",
"INFO" => "blue",
"DEBUG" => "grey",
_ => "white"
};
var timestamp = DateTime.Now.ToString("HH:mm:ss");
var lines = logs.Text.Split('\n').ToList();
lines.Add($"[dim]{timestamp}[/] [{color}]{level}[/] {message}");
// Keep only last 20 lines
if (lines.Count > 20)
lines.RemoveAt(0);
logs.SetContent(lines);
}
AddLog("INFO", "Application started");
AddLog("WARN", "Configuration file not found");
AddLog("ERROR", "Failed to connect to database");window.AddControl(
Controls.Markup()
.AddLine("[bold]Name Age City[/]")
.AddLine("[dim]─────────────────────────────────[/]")
.AddLine("Alice 30 New York")
.AddLine("Bob 25 London")
.AddLine("Charlie 35 Tokyo")
.Build()
);window.AddControl(
Controls.Markup()
.AddLine("[bold cyan on blue] [/]")
.AddLine("[bold cyan on blue] MY APPLICATION - v2.0.0 [/]")
.AddLine("[bold cyan on blue] [/]")
.WithAlignment(HorizontalAlignment.Center)
.Build()
);window.AddControl(
Controls.HorizontalGrid()
.Column(col => col.Add(
Controls.Markup()
.AddLine("[bold yellow]Left Column[/]")
.AddLine("Content on")
.AddLine("the left side")
.Build()
))
.Column(col => col.Add(
Controls.Markup()
.AddLine("[bold yellow]Middle Column[/]")
.AddLine("Content in")
.AddLine("the middle")
.Build()
))
.Column(col => col.Add(
Controls.Markup()
.AddLine("[bold yellow]Right Column[/]")
.AddLine("Content on")
.AddLine("the right side")
.Build()
))
.Build()
);See Links below for clickable, keyboard-navigable links (both hand-written markup
and links inside [markdown] content). A quick example:
var docs = Controls.Markup()
.AddLine("[bold]Documentation:[/]")
.AddLine("")
.AddLine("[link=https://github.com/example/docs]Repository[/]")
.AddLine("[link=https://example.com/guide]User Guide[/]")
.OnLinkClicked((sender, e) => OpenInBrowser(e.Url))
.Build();
window.AddControl(docs);var markup = window.FindControl<MarkupControl>("myMarkup");
if (markup != null)
{
markup.SetContent(new List<string>
{
"[green]New content[/]",
"Line 2"
});
}var markup = window.FindControl<MarkupControl>("myMarkup");
if (markup != null)
{
markup.SetContent("[yellow]Single line update[/]");
}var markup = window.FindControl<MarkupControl>("myMarkup");
if (markup != null)
{
var lines = markup.Text.Split('\n').ToList();
lines.Add("[green]New line appended[/]");
markup.SetContent(lines);
}var markup = window.FindControl<MarkupControl>("myMarkup");
if (markup != null)
{
markup.SetContent(new List<string>());
}- Use semantic helpers: Prefer
Controls.Header(),Controls.Info(), etc. for common patterns - Don't over-format: Too many colors/styles can be distracting
- Consistent colors: Use same colors for same meaning (red = error, green = success)
- Test readability: Ensure text is readable with different themes
- Escape brackets: Use
[[and]]to display literal brackets - Let it wrap: With
Wrap = true(default), long lines wrap correctly following the Unicode line-breaking rules — words and numbers are never split mid-token, CJK/wide text breaks per character to fill the line, and mixed scripts wrap correctly. You don't need to pre-break lines - Update efficiently: Use
SetContent()instead of recreating controls
window.AddControl(
Controls.Header("Application Settings")
.WithAlignment(HorizontalAlignment.Center)
.Build()
);
window.AddControl(
Controls.Label("Configure your preferences below")
.WithAlignment(HorizontalAlignment.Center)
.Build()
);window.AddControl(
Controls.Label("[dim]Ready | ESC: Exit | F1: Help[/]")
.StickyBottom()
.WithAlignment(HorizontalAlignment.Center)
.Build()
);window.AddControl(
Controls.Markup()
.AddLine("[bold red]Error:[/]")
.AddLine("")
.AddLine("Failed to connect to server.")
.AddLine("[dim]Please check your network connection.[/]")
.Build()
);Common colors available in SharpConsoleUI:
- Basic: red, green, blue, yellow, cyan, magenta, white, black
- Grays: grey, grey0-grey100 (0=black, 100=white)
- Extended: orange, orange3, purple, lime, aqua, fuchsia, maroon, navy, olive, teal, silver
- Custom: rgb(r,g,b) or #RRGGBB
MarkupControl content may contain the [spinner] tag, which renders an animated spinner glyph inline:
var status = Controls.Markup("Loading [yellow][spinner][/] please wait").Build();The spinner animates automatically while the window system is running. See Markup Syntax → Spinner for all styles.
MarkupControl supports clickable, keyboard-navigable links. A link is written with the
[link=<url>]…[/] tag and renders as styled text (by default the Markdown link color, underlined),
exactly as it looks today — but the URL is now preserved and the link is interactive.
var c = Controls.Markup()
.AddLine("See the [link=https://example.com/docs]documentation[/] for details.")
.OnLinkClicked((sender, e) => OpenInBrowser(e.Url)) // e.Url, e.Text
.Build();Links also come for free from Markdown — a Markdown [text](url) inside a [markdown] region (or
via SetMarkdown / Controls.Markdown(...)) is translated into a [link=…] span by the Markdown
parser, so rendered Markdown links are clickable too. Bare autolinks (<https://example.com>) work
as well.
- Mouse: clicking a link raises
LinkClicked. (Clicking does not move keyboard focus — mouse behavior is unchanged from a display-only control.) - Keyboard: when a control contains at least one link it becomes a single Tab stop. Once
focused, Left/Right arrows move the highlight between links (in document order), and Enter
activates the focused link (raising
LinkClicked). Tab/Shift+Tab move focus to the next/previous control; Up/Down/PageUp/PageDown are passed through to an enclosing scroll container.
markup.LinkClicked += (sender, e) =>
{
// e.Url — the link target (already unescaped)
// e.Text — the visible link text
// e.Mouse — the originating MouseEventArgs, or null for keyboard activation
OpenInBrowser(e.Url);
};LinkClickedEventArgs is the same type used by HtmlControl, so link handling is
consistent across both controls.
- A MarkupControl with no links is not focusable and is skipped by Tab traversal — its behavior is identical to a plain display label. This preserves backward compatibility for existing display-only markup.
- A MarkupControl that has links automatically becomes keyboard-reachable (one Tab stop). No flag
to set. If you need to suppress it for a specific control, set
IsEnabled = false(it then accepts no keyboard input and is not a focus stop) — or hide it (Visible = false). - When the focused link is scrolled out of view inside a
ScrollablePanelControl(even several container levels up), arrow-navigating to it scrolls it into view automatically.
The keyboard-focused link is drawn with a definite high-contrast highlight (a swap of the link's own colors would be unreadable over a transparent background). Override per control or via the builder:
var markup = new MarkupControl(new List<string> { "[link=u]link[/]" })
{
FocusedLinkForegroundColor = Color.Black,
FocusedLinkBackgroundColor = new Color(235, 175, 60) // amber (the default)
};
// Or via the builder:
Controls.Markup("[link=https://x]click[/]")
.WithFocusedLinkColors(Color.Black, new Color(235, 175, 60))
.OnLinkClicked((_, e) => OpenInBrowser(e.Url))
.Build();The URL inside [link=…] is percent-escaped so characters like ], [, space, and % cannot break
the markup parser; this is handled automatically by the Markdown translator and by LinkUrl.Escape.
Hand-written [link=…] tags should likewise percent-escape a ] in the URL as %5D (rare in
practice).
MarkupControl content may contain the [markdown]…[/] tag, which parses its inner text as Markdown (headings, lists, emphasis, code blocks, blockquotes, links, tables) and renders it as native markup. Markdown links ([text](url)) and autolinks become clickable [link=…] spans — see Links. By default copied text is the rendered plain text; use CopyMode.Source to copy the Markdown source back (see Copy: rendered vs source).
var c = Controls.Markdown("# Report\n\n**Status:** OK\n\n- one\n- two").Build();
c.SetMarkdown("# Updated\n\nNew content"); // live update (Markdown(...) is an alias)Styling is controlled by MarkdownStyle (per-control via MarkupControl.MarkdownStyle or builder .WithMarkdownStyle(...), globally via MarkdownStyle.Default). See Markup Syntax → Markdown for the full reference.
Fenced code blocks are syntax-highlighted. A fenced block with a language tag is tokenized and
coloured (highlighting carries across multi-line constructs like block comments and strings). Built-in
languages: C#, JavaScript, JSON, YAML, XML, HTML, CSS, Bash, Dockerfile, Razor, SLN, Diff, Markdown.
You can register your own highlighter for a language via MarkdownStyle.CodeHighlighters.
Tables fit the available width. A Markdown table is fitted to the render width — columns are capped (only over-long columns shrink; short columns keep their natural width) and long cell content wraps onto multiple lines within its column, so the box stays intact instead of overflowing. Inline styling inside a cell is preserved on every wrapped line.
Streaming / progressive rendering. You do not need to wait for the closing [/]: an open,
unclosed [markdown] block renders as Markdown as soon as content arrives and keeps re-rendering as
more is appended. This works when you set Text at once or stream it in with
AppendLine/AppendText — useful for an agent/LLM UI that streams a Markdown response token by
token:
label.AppendLine("[markdown]# Streaming Response");
label.AppendLine("");
label.AppendLine("- first point");
label.AppendLine("- second point");
// …no [/] yet — it already renders progressively as a heading + list…- Markup Syntax Reference - Complete markup syntax guide with all colors and decorations
- FigleControl - For large ASCII art text
- SpectreRenderableControl - For Spectre.Console widgets
- LogViewerControl - For log display with filtering