Markdown

Shows a Markdown string in envUI's typography, and MarkdownEditor writes one.

StableMarkdownEditor: BetaSince 1.0.0
On this page

Import

import {
  Markdown,
  MarkdownEditor,
} from '@envsemble/envui/markdown';

Usage

Markdown and MarkdownEditor come from @envsemble/envui/markdown. Markdown renders the string in children: CommonMark with tables, strikethrough, task lists, automatic links and footnotes.

  • Raw HTML is ignored, except keys written as <kbd>, which render as Kbd.
  • headingOffset adds to the document's heading levels, capped at h6: set it so the first heading sits one level below the page's.
  • codeCopyable gives each code block a copy button.
ProjectNote.tsx
import { Markdown } from '@envsemble/envui/markdown';

const note = `## Launch checklist

Prepare **Northwind** for the spring launch.

- Confirm the pricing with Priya Shah.
- Review the [launch plan](/documents/launch-plan) before sharing it.
  - Keep the original notes.

On launch day:

1. Publish the pricing page.
2. Send the release notes.

> Share the plan once the review is done.

| Task | Owner | Status |
| --- | --- | --- |
| Pricing page | Sam Lee | Ready |
| Release notes | Alex Morgan | In review |

- [x] Outline written
- [ ] Review scheduled

Save a draft with <kbd>Ctrl</kbd>+<kbd>S</kbd>, then build:

\`\`\`sh
npm run build
\`\`\`
`;

export default function ProjectNote() {
  // The note sits under the page's h2, so its headings start one level lower.
  return (
    <Markdown headingOffset={1} codeCopyable>
      {note}
    </Markdown>
  );
}

renderLink renders each link from its safe href and native anchor props, such as to open some in a panel; keep a real anchor with its destination, so modified clicks stay the browser's.

<Markdown
  renderLink={({ href, children, ...props }) => (
    <Link {...props} href={href} onClick={openDocumentLinksInPanel}>
      {children}
    </Link>
  )}
>
  {text}
</Markdown>

Your own elements

components replaces how an element renders, merged over envUI's own, with the application's winning; native props such as id and className reach the element.

  • remarkPlugins run after envUI's own Markdown extensions, and rehypePlugins after raw HTML is dropped; envUI adds no rehype plugins of its own.
  • Don't render untrusted Markdown with a plugin that brings raw HTML back.
<Markdown
  components={{
    blockquote: ({ node: _node, children }) => <Alert title="Rule">{children}</Alert>,
  }}
>
  {text}
</Markdown>

The editor

MarkdownEditor is a Textarea with a toolbar and Write and Preview tabs.

  • Leave it uncontrolled with defaultValue, which a form reset restores, or control it with value and onValueChange, which receives the whole source after typing, formatting, undo or redo; replacing value clears the undo history.
  • It takes Textarea's label, description, error and rows, and variant is outline, the default, or ghost.
  • sizing is fixed, the default, which keeps the height and scrolls, or content, which grows with the text.
  • preview={false} hides the tabs, and previewProps passes headingOffset, renderLink and codeCopyable to the Markdown of the preview.
<MarkdownEditor
  label="Description"
  sizing="content"
  rows={4}
  value={description}
  onValueChange={setDescription}
  previewProps={{ headingOffset: 2 }}
/>

toolbar lists the actions, of heading, bold, italic, inline-code, bullet-list, numbered-list, quote, code-block and link, shown in that order, grouped by kind; undo and redo come with a visible toolbar, and toolbar={false} hides it. pickLink opens the application's own link picker: it receives the selectedText and returns a MarkdownLink, { label, href }, or null to cancel, also as a promise.

<MarkdownEditor
  label="Notes"
  toolbar={['bold', 'italic', 'link']}
  pickLink={({ selectedText }) => pickDocument(selectedText)}
/>

Customize

The editor's root is env-markdown-editor, with the tabs, env-tabs-list, the toolbar, a Toolbar in env-editor-toolbar, the source, env-markdown-editor-input, and the description, env-field-description.

  • className and style reach the root, and ref and the other native props the textarea.
  • A rendered document is env-markdown, with env-prose, and a table in it scrolls in env-markdown-table.
  • Lists in it take envUI's list markers: a small spark, four points with curved sides, in the text color, or the item's number in the muted text color, with a smaller muted spark or a letter one level in.

Accessibility

You provide

  • A label for MarkdownEditor.
  • Link text that names its destination.
  • Translated words through EnvUIProvider's messages.

envUI provides

  • Native headings, lists, quotes, tables and links.
  • Tables in focusable, scrolling regions named after their caption or heading.
  • Task items as read-only checkboxes.
  • A native textarea tied to its label, a named toolbar and named tabs.

Props

Markdown

CommonMark prose with tables, task lists and other extensions, in envUI typography and code. The names of its table regions and task checkboxes come from EnvUIProvider's messages.markdown.

PropTypeDescription
childrenrequired
string
The Markdown string to render.
headingOffsetDefault 0
012345
Add to the document's heading levels; levels are capped at h6.
codeCopyableDefault false
boolean
Gives each code block a copy button; false by default.
components
Components
react-markdown component overrides, merged over envUI's own; the application's win. Native props such as id and className reach the element.
remarkPlugins
Options['remarkPlugins']
remark plugins that run after envUI's own Markdown extensions.
rehypePlugins
Options['rehypePlugins']
rehype plugins, such as heading ids; envUI adds none of its own.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

MarkdownEditor

Native Markdown source editing, shared formatting history, and optional preview. Its toolbar, views and status come from EnvUIProvider's messages: markdownEditor, editor and linkDialog.

PropTypeDescription
ref
Ref<HTMLTextAreaElement>
The native text area of the source, so that a form library can register it and focus it.
value
string
The Markdown source of a controlled editor; replacing it clears the undo history.
defaultValueDefault ''
string
The source an uncontrolled editor starts with, and that a form reset restores.
onValueChange
(value: string) => void
Receives the whole source after typing, formatting, undo or redo.
toolbarDefault ['heading', 'bold', 'italic', 'inline-code', 'bullet-list', 'numbered-list', 'quote', 'code-block', 'link']
readonly MarkdownCommand[]false
Formatting actions; false hides the toolbar. Undo/redo accompany a visible toolbar.
previewDefault true
boolean
Show Write/Preview tabs. Preview uses the same public Markdown renderer.
previewProps
Pick<MarkdownProps, 'headingOffset' | 'renderLink' | 'codeCopyable'>
Options of the Markdown that renders the preview: headingOffset, renderLink and codeCopyable.
label
ReactNode
The field's visible name, such as Project name; it also names the control for assistive technology.
style
CSSProperties
Styles of the field's root element, such as its width in a row.
className
string
The field's class, on its root element, beside env-field, env-text-field and env-textarea.
description
ReactNode
Lasting guidance under the field, such as a format or a limit, linked to the control as its description.
rowsDefault 8
number
The lines the field shows: with fixed sizing, its height in rows, which resizes down to them or to 7.5 rem, whichever is less; with content sizing, the lines it starts at and shrinks back to. Without rows, both start at 7.5 rem.
variantDefault 'outline'
'outline''ghost'
The field's look, as TextField's: outline or ghost. A text area is always tall, with its label resting at the top.
error
ReactNode
A message under the field that says how to correct the value. While it is set, the control is invalid (aria-invalid) and the message joins its description, after the description.
sizingDefault 'fixed'
'fixed''content'
How the height follows the text: fixed keeps its height, scrolls and resizes from the bottom edge, by dragging it or with the arrow keys; content grows and shrinks with the text.

Also accepts the native props of <textarea>, such as ref, className and aria-* attributes.

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

Markdown

env-markdown
The rendered Markdown.
env-markdown-table
The region that scrolls a wide table.

MarkdownEditor

env-markdown-editor
The Markdown editor.
env-markdown-editor-input
The text area that shows the Markdown source as text.
env-markdown-editor-write
The text area for writing.

Strings

EnvUIProvider's messages overrides each, and a component's own label prop wins over it.

messages.editor.heading
"Heading" A command.
messages.editor.bold
"Bold" A command.
messages.editor.italic
"Italic" A command.
messages.editor.strike
"Strikethrough" A command.
messages.editor.bulletList
"Bulleted list" A command.
messages.editor.numberedList
"Numbered list" A command.
messages.editor.taskList
"Task list" A command.
messages.editor.quote
"Quote" A command.
messages.editor.inlineCode
"Inline code" A command.
messages.editor.codeBlock
"Code block" A command.
messages.editor.link
"Insert link" A command.
messages.editor.table
"Insert table" A command.
messages.editor.undo
"Undo" A command.
messages.editor.redo
"Redo" A command.
messages.linkDialog.title
"Insert link" The dialog's title.
messages.linkDialog.insert
"Insert link" The confirm button's label.
messages.linkDialog.text
"Text" The text field's label.
messages.linkDialog.url
"URL or path" The URL field's label.
messages.linkDialog.urlPlaceholder
"https://example.com or /documents/example" The URL field's placeholder, an example of what it takes.
messages.linkDialog.invalidUrl
"Enter a valid URL." The error for an empty destination.
messages.linkDialog.invalidPath
"Enter a valid URL or relative path." The error for a destination that is neither a URL nor a path.
messages.markdown.table
"Table" Names a table region without a caption, a heading or a header row.
messages.markdown.tableNamed
({ name }) => string Names a table region after the heading before it, such as Prices table, or without one, after its first column's header.
messages.markdown.taskDone
"Completed task" A completed task's checkbox.
messages.markdown.taskOpen
"Incomplete task" An incomplete task's checkbox.
messages.markdownEditor.empty
"Nothing to preview." The preview of an empty document.
messages.markdownEditor.toolbar
"Markdown formatting" Names the formatting toolbar.
messages.markdownEditor.views
"Markdown editor views" Names the write and preview tabs.
messages.markdownEditor.write
"Write" The write tab.
messages.markdownEditor.preview
"Preview" The preview tab.
messages.markdownEditor.changedWhilePicking
"The text changed while the picker was open. Insert the link again." Said when the text changed while the application's link picker was open.
messages.markdownEditor.tooLong
"This change would exceed the maximum length, or editing is unavailable." Said when a link would make the text too long, or editing is unavailable.
messages.markdownEditor.tooLongShort
"This change would exceed the maximum length." Said when a formatting command would make the text too long.
messages.markdownEditor.linkFailed
"The link could not be inserted." Said when the link could not be inserted.
messages.markdownEditor.pickerFailed
"The link picker could not finish. Try again." Said when the application's link picker failed.

Changelog

1.0.0

Code in Markdown takes the new code themes, as Code and CodeBlock do. MarkdownEditor takes Textarea's description instead of hint, and its variants outline and ghost: outlined is outline and quiet is ghost, and plain is gone, for the default outline. className and style style the root, env-markdown-editor, rather than the textarea; ref and the other native props still reach the textarea. Its toolbar is a Toolbar, with tooltips instead of native titles, and its words come from EnvUIProvider's messages, as do Markdown's table and task names. Markdown's root carries env-prose beside env-markdown. Markdown is stable; MarkdownEditor stays beta. The editor's source text area, env-markdown-source, is env-markdown-editor-input, a MarkdownEditor part as env-text-field-input is a TextField's.

1.0.0-prerelease.6

Bold text is semibold rather than medium, so it stands out from the body text around it; it is heavier than a heading's medium, which its size sets apart.

Where MarkdownEditor's toolbar is wider than the editor, its groups wrap onto more rows rather than scrolling sideways, so Undo and Redo stay in view. The editor's label takes the size, weight and muted color of a field's floated label.

A table's header cells line up at the start, over their columns' text, rather than in the middle.

1.0.0-prerelease.5

onRequestLink is pickLink: it supplies the link rather than reporting an event.

1.0.0-prerelease.2

MarkdownEditor's underline variant, which plain became in the earlier prereleases, is gone, for the default outline.