Markdown text
<gui-markdown-text> renders Markdown as HTML: headings, lists, links, quotes, code and tables,
styled to match the other elements. It ships no parser: you give it the one your app already uses.
import type { SanitizedHtml } from '@golemui/gui-components';import snarkdown from 'snarkdown';import { GuiMarkdownText } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
const dependencies = { markdown: { parse: (md: string) => snarkdown(md) as SanitizedHtml },};
const notes = `## What's new in 2.4
Shared projects are here. Invite **Grace Hopper** or anyone on your team, and edit together.
- Comments on every task- Due dates in the calendar- Faster search
Read the [full changelog](#changelog).`;
export function MarkdownTextBasic() { return ( <GuiMarkdownText id="release-notes" dependencies={dependencies} md={notes} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { SanitizedHtml } from '@golemui/gui-components';import snarkdown from 'snarkdown';import '@golemui/gui-components/markdown-text';
@Component({ selector: 'app-markdown-text-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-markdown-text id="release-notes" [dependencies]="dependencies" [md]="notes" ></gui-markdown-text> `,})export class MarkdownTextBasicComponent { dependencies = { markdown: { parse: (md: string) => snarkdown(md) as SanitizedHtml }, }; notes = `## What's new in 2.4
Shared projects are here. Invite **Grace Hopper** or anyone on your team, and edit together.
- Comments on every task- Due dates in the calendar- Faster search
Read the [full changelog](#changelog).`;}<script setup lang="ts">import type { SanitizedHtml } from '@golemui/gui-components';import snarkdown from 'snarkdown';import '@golemui/gui-components/markdown-text';import '@golemui/gui-components/index.css';
const dependencies = { markdown: { parse: (md: string) => snarkdown(md) as SanitizedHtml },};
const notes = `## What's new in 2.4
Shared projects are here. Invite **Grace Hopper** or anyone on your team, and edit together.
- Comments on every task- Due dates in the calendar- Faster search
Read the [full changelog](#changelog).`;</script>
<template> <gui-markdown-text id="release-notes" :dependencies.prop="dependencies" :md="notes" ></gui-markdown-text></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { SanitizedHtml } from '@golemui/gui-components';import snarkdown from 'snarkdown';import '@golemui/gui-components/markdown-text';import '@golemui/gui-components/index.css';
const dependencies = { markdown: { parse: (md: string) => snarkdown(md) as SanitizedHtml },};
const notes = `## What's new in 2.4
Shared projects are here. Invite **Grace Hopper** or anyone on your team, and edit together.
- Comments on every task- Due dates in the calendar- Faster search
Read the [full changelog](#changelog).`;
@customElement('markdown-text-basic')export class MarkdownTextBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-markdown-text id="release-notes" .dependencies=${dependencies} .md=${notes} ></gui-markdown-text> `; }}<gui-markdown-text id="release-notes"></gui-markdown-text>
<script type="module"> // In your app: import snarkdown from 'snarkdown'; import snarkdown from 'https://cdn.jsdelivr.net/npm/snarkdown@2/+esm';
const notes = document.querySelector('#release-notes'); notes.dependencies = { markdown: { parse: snarkdown } }; notes.md = `## What's new in 2.4
Shared projects are here. Invite **Grace Hopper** or anyone on your team, and edit together.
- Comments on every task- Due dates in the calendar- Faster search
Read the [full changelog](#changelog).`;</script>The element’s entry point is @golemui/gui-components/markdown-text, which registers
<gui-markdown-text> and nothing else. To install the package and load the stylesheet, see
Installation.
The @golemui/gui-components/react components have typed props and an on* prop for each event. React 18 or later.
import { GuiMarkdownText } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function MarkdownTextExample() { return ( <GuiMarkdownText md="## Release notes" /> );}Add CUSTOM_ELEMENTS_SCHEMA to the component that uses the tags, and the stylesheet to styles in angular.json.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/markdown-text';
@Component({ selector: 'app-markdown-text-example', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-markdown-text md="## Release notes" ></gui-markdown-text> `,})export class MarkdownTextExampleComponent {}"styles": ["node_modules/@golemui/gui-components/lib/styles/index.css"]Tell Vue's compiler that gui-* tags are custom elements, and add the typings once for typed props and @gui-* handlers.
<script setup lang="ts">import '@golemui/gui-components/markdown-text';import '@golemui/gui-components/index.css';</script>
<template> <gui-markdown-text md="## Release notes" /></template>vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('gui-') } },});import type {} from '@golemui/gui-components/vue';The page's stylesheet styles the gui-* elements, so it only reaches them outside a shadow root. Render the host component into the light DOM too, as below, or load the stylesheet into its shadow root.
import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/markdown-text';import '@golemui/gui-components/index.css';
@customElement('markdown-text-example')export class MarkdownTextExample extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-markdown-text md="## Release notes" ></gui-markdown-text> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-markdown-text md="## Release notes"></gui-markdown-text>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/markdown-text';import '@golemui/gui-components/index.css';It renders nothing until you give it a parser in its dependencies property, below.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
md | string | – | The Markdown to render. |
dependencies | { markdown?: MarkdownParser } | – | Provides the markdown parser that turns md into HTML. The element inserts that HTML without sanitizing it, see MarkdownParser. |
CSS custom properties
| Property | Default | Description |
|---|---|---|
--gui-md-text-color | – | Text color. |
--gui-md-line-height | – | Line height. |
--gui-md-heading-color | – | Heading color. |
--gui-md-heading-weight | – | Heading font weight. |
--gui-md-link-color | – | Link color. |
--gui-md-link-hover-color | – | Link color on hover. |
--gui-md-blockquote-bg | – | Quote background. |
--gui-md-blockquote-border-color | – | Quote border color. |
--gui-md-blockquote-color | – | Quote text color. |
--gui-md-code-bg | – | Code background. |
--gui-md-code-color | – | Code text color. |
--gui-md-code-radius | – | Code corner radius. |
--gui-md-hr-color | – | Horizontal rule color. |
Examples
Section titled “Examples”The parser
Section titled “The parser”dependencies takes an object with a markdown parser: any object with a parse(markdown)
method that returns its HTML, synchronously. Set it as a property, in a script or with your
framework’s property binding. The element runs parse again each time md or dependencies
change.
In TypeScript, parse returns SanitizedHtml, a string type that only a cast creates. The cast
marks the place where you sanitize the HTML, or trust where it comes from:
import type { SanitizedHtml } from '@golemui/gui-components';import '@golemui/gui-components/markdown-text';import snarkdown from 'snarkdown';
const notes = document.querySelector('gui-markdown-text')!;// The Markdown is in your own code, so the cast alone is enough.notes.dependencies = { markdown: { parse: (md) => snarkdown(md) as SanitizedHtml },};Most libraries fit with a small wrapper, such as marked.parse(md, { async: false }) for
marked. The examples on this page use snarkdown, a tiny parser, and load it from a CDN only so
they run in the page.
Untrusted Markdown
Section titled “Untrusted Markdown”The element puts the parser’s HTML into the page as it is, without sanitizing it. When the
Markdown can come from your users or from stored data, sanitize the parser’s output, for example
with DOMPurify, so no script or event handler gets into your page. Snarkdown and Marked keep raw
HTML and javascript: links. Micromark and markdown-it escape both with their default options.
When all the Markdown comes from your own code, the cast alone is enough.
import type { SanitizedHtml } from '@golemui/gui-components';import DOMPurify from 'dompurify';import snarkdown from 'snarkdown';import { GuiMarkdownText } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
const dependencies = { markdown: { parse: (markdown: string) => DOMPurify.sanitize(snarkdown(markdown)) as SanitizedHtml, },};
// A visitor's comment. The sanitizer drops the onmouseover handler and keeps the text.const comment = `Loved the **Lisbon** tour! <span onmouseover="alert('Gotcha')">Hover me</span>`;
export function MarkdownTextSanitize() { return ( <GuiMarkdownText id="guest-comment" dependencies={dependencies} md={comment} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { SanitizedHtml } from '@golemui/gui-components';import DOMPurify from 'dompurify';import snarkdown from 'snarkdown';import '@golemui/gui-components/markdown-text';
@Component({ selector: 'app-markdown-text-sanitize', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-markdown-text id="guest-comment" [dependencies]="dependencies" [md]="comment" ></gui-markdown-text> `,})export class MarkdownTextSanitizeComponent { dependencies = { markdown: { parse: (markdown: string) => DOMPurify.sanitize(snarkdown(markdown)) as SanitizedHtml, }, }; // A visitor's comment. The sanitizer drops the onmouseover handler and keeps the text. comment = `Loved the **Lisbon** tour! <span onmouseover="alert('Gotcha')">Hover me</span>`;}<script setup lang="ts">import type { SanitizedHtml } from '@golemui/gui-components';import DOMPurify from 'dompurify';import snarkdown from 'snarkdown';import '@golemui/gui-components/markdown-text';import '@golemui/gui-components/index.css';
const dependencies = { markdown: { parse: (markdown: string) => DOMPurify.sanitize(snarkdown(markdown)) as SanitizedHtml, },};
// A visitor's comment. The sanitizer drops the onmouseover handler and keeps the text.const comment = `Loved the **Lisbon** tour! <span onmouseover="alert('Gotcha')">Hover me</span>`;</script>
<template> <gui-markdown-text id="guest-comment" :dependencies.prop="dependencies" :md="comment" ></gui-markdown-text></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { SanitizedHtml } from '@golemui/gui-components';import DOMPurify from 'dompurify';import snarkdown from 'snarkdown';import '@golemui/gui-components/markdown-text';import '@golemui/gui-components/index.css';
const dependencies = { markdown: { parse: (markdown: string) => DOMPurify.sanitize(snarkdown(markdown)) as SanitizedHtml, },};
// A visitor's comment. The sanitizer drops the onmouseover handler and keeps// the text.const comment = `Loved the **Lisbon** tour! <span onmouseover="alert('Gotcha')">Hover me</span>`;
@customElement('markdown-text-sanitize')export class MarkdownTextSanitize extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-markdown-text id="guest-comment" .dependencies=${dependencies} .md=${comment} ></gui-markdown-text> `; }}<gui-markdown-text id="guest-comment"></gui-markdown-text>
<script type="module"> // In your app: import snarkdown from 'snarkdown'; import DOMPurify from 'dompurify'; import snarkdown from 'https://cdn.jsdelivr.net/npm/snarkdown@2/+esm'; import DOMPurify from 'https://cdn.jsdelivr.net/npm/dompurify@3/+esm';
const comment = document.querySelector('#guest-comment'); comment.dependencies = { markdown: { parse: (markdown) => DOMPurify.sanitize(snarkdown(markdown)) }, }; // A visitor's comment. The sanitizer drops the onmouseover handler and keeps the text. comment.md = `Loved the **Lisbon** tour! <span onmouseover="alert('Gotcha')">Hover me</span>`;</script>Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<gui-markdown-text> renders the parser’s HTML in the light DOM, with no wrapper of its own, and
the stylesheet styles the tags inside it:
<gui-markdown-text> <h2>What's new in 2.4</h2> Shared projects are here. Invite <strong>Grace Hopper</strong> or anyone on your team, and edit together.<br /> <ul> <li>Comments on every task</li> <!-- …one li per item --> </ul> <br />Read the <a href="#changelog">full changelog</a>.</gui-markdown-text>What’s inside the element is whatever your parser writes; this is snarkdown’s output for the first example.
| Class | Part |
|---|---|
gui-markdown-text | On an ancestor of the element, not on the element: turns its styles on. |
Inside, the stylesheet styles the plain tags the parser writes: h1 to h6, p, a, strong,
em, code, pre, blockquote, ul, ol, li, hr, img, table, th and td. Override
them with selectors such as .gui-markdown-text gui-markdown-text h2.
Tokens
Section titled “Tokens”The element has tokens of its own for its typography, the --gui-md-* tokens listed in its API
above and with their defaults in Tokens. It
also reads these design tokens:
| Token | What it styles |
|---|---|
--gui-font-sm | The text size, and the size of code blocks. |
--gui-font-3xl … --gui-font-base | The sizes of the headings, h1 to h6. |
--gui-space-1 … --gui-space-6 | The space around headings, paragraphs and lists. |
--gui-radius-md | The corners of code blocks, quotes and images. |
--gui-border-default | The borders of the table cells. |
--gui-bg-surface-hover | The background of the table headers. |
/* Roomier text, with brand links, in the help panel only. */.help-panel { --gui-md-line-height: 1.8; --gui-md-link-color: #d96c3f; --gui-md-link-hover-color: #b4532b;}To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode.
Accessibility
Section titled “Accessibility”- Headings. The Markdown’s headings are real headings in your page’s outline. Start the
Markdown at the level that fits where it sits, such as
##under the page’sh1. - Links and images. Write link text that makes sense out of context, and alt text for every
image:
. - Content only. The element adds no role, name or announcement. When the Markdown changes, screen readers don’t announce it.
The element takes no focus and has no keys of its own. Links inside it work as anywhere else.
What every element shares is in Accessibility.