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 A | Column B |
|---|---|
| Value 1 | Value 2 |
| Value 3 | Value 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
| Option | Type | Default | Description |
|---|---|---|---|
content | string | '' | 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
| Prop | Type | Default | Description |
|---|---|---|---|
content | string | required | Raw markdown source. |
className | string | undefined | Merged 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:
| Method | Description |
|---|---|
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
| Prop | Type | Description |
|---|---|---|
content | string | Raw markdown string to render |
Supported syntax
Both entry points support standard CommonMark plus GitHub Flavored Markdown (GFM) extensions:
| Element | Rendered as |
|---|---|
# H1 – ###### H6 | corn-heading at each level |
| Paragraph | corn-text |
`inline code` | corn-code |
```block``` | corn-code + data-block |
**bold** | corn-markdown-strong |
_italic_ | corn-markdown-em |
~~strike~~ | corn-markdown-del |
> blockquote | Info-styled blockquote |
- list / 1. list | Styled unordered / ordered list |
[link](url) | Primary-coloured underlined link |
 | Responsive image with rounded corners |
--- | Subtle horizontal rule |
| GFM table | Bordered 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)})`;
Links and safety
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.