Markdown

Render a markdown string using Corn UI styles, available as vanilla JS or React.

import { ... } from '@corndev/ui-markdown'

Markdown component

Renders bold, italic, and inline code with Corn UI styles.

Features

  • CommonMark + GFM support
  • Styled headings, lists, and tables
  • Syntax-highlighted code blocks

Blockquotes are styled as info-tone callouts.

Column AColumn B
Value 1Value 2
Value 3Value 4

HTML

<div class="corn-markdown"><!-- rendered markdown --></div>

Classes

Class
.corn-markdown-blockquote
.corn-markdown-list
.corn-markdown-item
.corn-markdown-link
.corn-markdown-divider
.corn-markdown-strong
.corn-markdown-em
.corn-markdown-del
.corn-markdown-img
.corn-markdown-table
.corn-markdown-th
.corn-markdown-td
.corn-markdown-table-wrap

Vanilla JS

Required for behaviour — adds parses the markdown string and builds the DOM.

import { createMarkdown } from '@corndev/ui-markdown';
import '@corndev/ui/theme';

const el = document.querySelector('#target');
const instance = createMarkdown(el, { /* options */ });

Options

OptionTypeDefaultDescription
contentstring''Raw markdown source to render.

React

Markdown — from @corndev/ui-markdown-react.

Both runtimes share one parser. renderMarkdown produces the HTML and createMarkdown writes it into an element; the React component renders the same string, so the two cannot drift in what they emit. GFM tables and strikethrough work in both. Raw HTML is dropped rather than rendered — inline SVG and embeds go through img as a data URI.

Props

PropTypeDefaultDescription
contentstringrequiredRaw markdown source.
classNamestringundefinedMerged with the Corn UI classes.

@corndev/ui-markdown parses markdown and outputs HTML styled entirely with Corn UI design tokens. It ships two entry points — one for vanilla JS projects and one for React — so you only pull in what you need.

Installation

npm install @corndev/ui-markdown

Then import the shared stylesheet once in your app:

import '@corndev/ui-markdown/styles';

Vanilla JS

import { createMarkdown } from '@corndev/ui-markdown';
import '@corndev/ui-markdown/styles';

const el = document.getElementById('content');
const md = createMarkdown(el, { content: '# Hello\n\nThis is **Corn UI** markdown.' });

// Update later
md.update({ content: '# Updated\n\nNew content.' });

// Tear down
md.destroy();

createMarkdown(el, options) mounts a rendered markdown document into el and returns an instance with two methods:

MethodDescription
update(options)Re-render with new options
destroy()Remove rendered content and clean up classes

React

Import from the /react sub-path to avoid bundling the vanilla JS renderer:

import { Markdown } from '@corndev/ui-markdown-react';
import '@corndev/ui-markdown/styles';

function App() {
  return <Markdown content="# Hello\n\nThis is **Corn UI** markdown." />;
}

Props

PropTypeDescription
contentstringRaw markdown string to render

Supported syntax

Both entry points support standard CommonMark plus GitHub Flavored Markdown (GFM) extensions:

ElementRendered as
# H1 – ###### H6corn-heading at each level
Paragraphcorn-text
`inline code`corn-code
```block```corn-code + data-block
**bold**corn-markdown-strong
_italic_corn-markdown-em
~~strike~~corn-markdown-del
> blockquoteInfo-styled blockquote
- list / 1. listStyled unordered / ordered list
[link](url)Primary-coloured underlined link
![img](url)Responsive image with rounded corners
---Subtle horizontal rule
GFM tableBordered corn-paper table

Styling rendered content

The container carries corn-markdown and nothing styles it on its own; every element inside is styled. It is there as a scope for your own rules:

.corn-markdown h2 { margin-block-start: var(--corn-spacing-xl); }

Heading anchors

Every heading gets an id, so a link to #its-slug in the same document resolves:

# Pet Store

See [the store](#pet-store).

The slug is lower case, with letters and digits kept and everything else collapsed to single hyphens: GET /pets/{id} becomes get-pets-id. A repeated heading takes -1, -2 and so on, in document order. A generator writing the links can import the same function:

import { slugify } from '@corndev/ui-markdown';

const link = `[${name}](#${slugify(name)})`;

URLs are checked as the markdown renders. Links may point to relative URLs, # anchors, http, https, mailto and tel; images may also use data:image/*. A link or image with any other URL, javascript: included, renders as its text. Raw HTML in the source is dropped. Untrusted markdown is therefore safe to render.

To route clicks yourself, for example opening external links in the system browser from a desktop app, listen on the container:

<div onClick={event => {
  const link = event.target.closest('a');
  if (!link) return;
  const href = link.getAttribute('href');
  if (/^https?:/.test(href)) {
    event.preventDefault();
    openExternal(href);
  }
}}>
  <Markdown content={source} />
</div>

There is no option to rewrite links as they render. Unsafe URLs never reach the page, so what is left is a decision about what a click does, and only a listener can make that.