Markdown Viewer
The MarkdownViewer component renders markdown content with syntax highlighting, interactive task lists, code suggestions, and customizable image handling. It’s suited to documentation, comments, READMEs, and code-review content. It renders standard markdown syntax including headings, lists, links, images, tables, blockquotes, and code blocks.
Variations
With border
Add a bordered container around the markdown content using the withBorder prop.
Max height
Control the maximum height of the markdown container to create scrollable content.
Code blocks with syntax highlighting
Code blocks receive syntax highlighting based on the language specified in the fence.
Code blocks with line numbers
Enable line numbers for code blocks using the showLineNumbers prop. This is especially useful for code reviews and documentation. Line numbers appear only when a code block has more than one line.
Interactive task lists
The MarkdownViewer supports interactive checkboxes in task lists. Use the onCheckboxChange prop to handle state updates — it receives the full updated markdown source.
Variant sizes
The MarkdownViewer supports two variants: default and sm for different typography scales.
Muted text
Use the muted prop to render markdown content with the subdued text-cn-3 foreground color. It defaults to false.
Code suggestions
The MarkdownViewer can render special code suggestion blocks with custom headers and footers. Use the suggestionBlock, suggestionTitle, and suggestionFooter props.
Image URL transform
Transform image URLs before rendering using the imageUrlTransform prop. Useful for handling relative paths or proxying images.
Tables and blockquotes
The MarkdownViewer fully supports markdown tables and blockquotes.
External links
External links automatically open in a new tab with security attributes (nofollow, noreferrer, noopener).
Video embeds
The MarkdownViewer supports video file embeds for common video formats.
Loading state
Display a loading state to disable interactive elements while content is being updated.
Comment system example
A practical example combining multiple features for a comment/discussion system.
Installation
import { MarkdownViewer } from '@harnessio/ui/components'
//...
return ( <MarkdownViewer source="# Hello World\n\nThis is **markdown** content." />)Anatomy
MarkdownViewer takes a markdown string via source and renders it. It’s built on @uiw/react-markdown-preview, with a set of remark/rehype plugins layered on for line breaks, sanitization, video embeds, and external-link handling.
<MarkdownViewer source="# Hello" withBorder />- Source —
source(required) is the markdown string to render. Standard syntax is supported: headings, lists, links, images, emphasis, tables, blockquotes, and fenced code blocks. - Container —
withBorderwraps the content in a bordered, padded container.maxHeightcaps the container height for scrollable content.classNamestyles the outer wrapper;markdownClassNamestyles the rendered markdown element. - Typography —
variant(default/sm) selects the typography scale;mutedrenders the text in a muted color. - Code blocks — fenced code is syntax-highlighted by the underlying previewer.
showLineNumbersadds line numbers, but only for blocks with more than one line. - Task lists —
- [ ]/- [x]items render as real, focusable checkboxes. When you passonCheckboxChange, toggling a checkbox calls it with the full updated markdown source string (not an event), so you can persist the change.isLoadingdisables the checkboxes while content is updating. - Code suggestions — a
```suggestionfence renders through a dedicated suggestion block.suggestionTitlesets its header andsuggestionFootersupplies footer content (e.g. Accept / Reject buttons);suggestionBlocksupplies the diff source. - Images —
imageUrlTransformrewrites each imagesrcbefore render (useful for relative paths or proxying). Clicking an image opens it in a new tab. - Typewriter —
speed(ms per character) reveals the source progressively instead of all at once.
Plugins & security
- Plugins —
remark-breaks(line breaks →<br>),rehype-sanitize(HTML sanitization),rehype-video(video-file embeds), andrehype-external-links(external-link attributes). - Sanitization — output is sanitized; image event handlers and unsafe attributes are stripped as XSS hardening.
- External links — get
target="_blank"andrel="nofollow noreferrer noopener". Internal links and#anchors are intercepted and routed/scrolled instead of doing a full navigation.
Best practices
Do
- Use
withBorderwhen the markdown is a distinct block in the UI (a comment, a README preview); leave it off when the content flows inline with the page. - Set
maxHeightfor long, user-generated content so it doesn’t dominate the layout. - Use
variant="sm"in dense contexts like comment threads and inline review notes.
Don’t
- Don’t hand
MarkdownVieweruntrusted HTML expecting it to pass through — HTML is sanitized by design. - Don’t rely on
showLineNumbersfor single-line snippets; line numbers only appear when a block has multiple lines.
Content
- Write the
sourceas plain markdown — keep headings meaningful and lists parallel, since the viewer renders structure faithfully. - For task lists, keep item text short and action-oriented (“Add unit tests”), and persist toggles through
onCheckboxChangerather than treating them as read-only.
Accessibility
Labeling
- The viewer renders whatever semantics the markdown implies (headings, lists, tables). Author the
sourcewith a sensible heading hierarchy so the rendered output is navigable. - Images use their markdown
alttext; provide meaningful alt text in the source.
Focus & states
- Task-list checkboxes are genuine native inputs — they’re keyboard-focusable and toggleable, and are disabled when
isLoadingis set.
Known limitations
- No added ARIA. Beyond native element semantics and the sanitize schema, the component doesn’t add roles or
aria-*. There’s no live-region announcement when content updates (e.g. during typewriter reveal or loading). - Decorative alert icons. Markdown alert blocks (
> [!NOTE]etc.) render a decorative icon with no accessible label; the meaning comes from the adjacent text.
Properties
MarkdownViewer
Prop | Required | Default | Type |
|---|---|---|---|
| source | true | string | |
| variant | false | 'default' | 'default' | 'sm' |
| muted | false | false | boolean |
| maxHeight | false | string | number | |
| withBorder | false | false | boolean |
| className | false | string | |
| markdownClassName | false | string | |
| muted | false | false | boolean |
| showLineNumbers | false | false | boolean |
| onCheckboxChange | false | (source: string) => void | |
| isLoading | false | false | boolean |
| speed | false | 0 | number |
| imageUrlTransform | false | (src: string) => string | |
| suggestionBlock | false | SuggestionBlock | |
| suggestionTitle | false | string | |
| suggestionFooter | false | ReactNode | |
| suggestionCheckSum | false | string |
SuggestionBlock
interface SuggestionBlock { source: string; // The suggestion diff source lang?: string; // Optional language for highlighting}