Back to Sentry

Markdown

static/app/components/core/markdown/markdown.mdx

26.7.19.4 KB
Original Source

import {Markdown} from '@sentry/scraps/markdown';

import * as Storybook from 'sentry/stories';

import {CustomComponentsDemo, StreamingDemo, TagDemo} from './stories/components';

export const documentation = import('!!type-loader!@sentry/scraps/markdown/markdown');

The <Markdown> component renders markdown strings as native React elements using a token-based pipeline. Instead of dangerouslySetInnerHTML, it lexes the input with marked and renders each token as a React component. This enables:

  • Component overrides via the components prop (replace any element with a custom React component)
  • Streaming variant with a glyph-shuffle decode animation for real-time AI/LLM output
jsx
<Markdown raw="**Hello**, world!" />

Basic Usage

Pass a markdown string to the raw prop.

<Storybook.Demo minHeight="max-content"> <Markdown raw={`# Issue Grouping

Sentry uses fingerprints to group similar events into issues. Each issue tracks frequency, error count impact, and affected users. Use `sentry.set_fingerprint()` to customize grouping.

Visit the Sentry docs for more on issue triage.

```python import sentry_sdk

sentry_sdk.set_tag("region", "us-east") sentry_sdk.capture_exception(err) ```

Custom fingerprints are useful when:

  • The default stack trace grouping is too broad or too narrow
  • You want to group errors by business logic rather than code path

See the grouping best practices guide for advanced configuration.

Events are grouped by stack trace similarity by default. Custom fingerprints override this behavior.

Resolution Strategies

  1. Resolve immediately
  2. Resolve in the next release
  3. Resolve in a specific commit
StatusDescription
UnresolvedActive and needs triage
ResolvedFixed in a release
RegressedReappeared after being resolved

Triage Checklist

  • Assign an owner via ownership rules
  • Review suspect commits
  • Set up alert rules

Hard line break:\ New line here.

<mark>Note:</mark> Reprocessing events requires uploading new debug files. `} />

</Storybook.Demo>

jsx
<Markdown raw={markdownString} />

Component Overrides

The components prop lets you replace any rendered element with a custom React component. Keys are named after design system primitives: Heading, Paragraph, Link, InlineCode, CodeBlock, Text, etc.

Every override receives a Default prop — a reference to the built-in component for that element. This lets you conditionally customize rendering without importing internal defaults.

Falling Back to the Default

Use the Default prop to apply custom behavior for specific cases and fall through to the built-in rendering for everything else.

jsx
<Markdown
  raw={content}
  components={{
    Heading: ({children, level, Default}) => {
      if (level > 3) return <strong>{children}</strong>;
      return <Default level={level}>{children}</Default>;
    },
  }}
/>

Use components.Link to control how links render (e.g., client-side navigation). Use components.Text to intercept plain text and inject patterns like issue linkification.

<Storybook.Demo> <CustomComponentsDemo /> </Storybook.Demo>

jsx
<Markdown
  raw="The issue SENTRY-1234 was caused by a race condition..."
  components={{
    Link: ({href, children}) => <RouterLink to={href}>{children}</RouterLink>,
    Text: ({children}) => linkifyIssueShortIds(children),
  }}
/>

Suppress Headings

Override Heading to render headings as bold text instead of <h1>-<h6>.

<Storybook.Demo> <Markdown raw={# Heading becomes bold\n\nParagraph stays normal.} components={{ Heading: ({children}) => <strong>{children}</strong>, }} /> </Storybook.Demo>

jsx
<Markdown
  raw={content}
  components={{
    Heading: ({children}) => <strong>{children}</strong>,
  }}
/>

Streaming

Set variant="streaming" to enable animation for new content. As the raw string grows, new text appears to be decoded progressively. The consumer accumulates chunks into a string and passes it as raw—the component handles the animation imperatively.

  • New block-level elements animate with a staggered character reveal
  • Growing blocks re-animate only the new tail (already-visible text is preserved)
  • When a newer block arrives, any in-progress animation on the previous block settles immediately
  • Long blocks are capped at a max duration, with remaining text fading in gracefully
  • Respects prefers-reduced-motion (animation is skipped entirely)

<Storybook.Demo minHeight="520px" align="flex-start"> <StreamingDemo /> </Storybook.Demo>

jsx
function StreamingExample() {
  const [text, setText] = useState('');

  useEffect(() => {
    // Accumulate chunks into a string
    let buffer = '';
    const reader = getStreamReader();
    reader.onChunk = chunk => {
      buffer += chunk;
      setText(buffer);
    };
  }, []);

  return <Markdown raw={text} variant="streaming" />;
}

Tags

Tags are a minimal Markdown syntax extension for custom embedded components. Inspired by Markdoc tag syntax, they let consumers wire up their own integration surface — for example, Seer uses tags to embed entity refs and generated artifacts inline in streamed markdown.

Two syntax forms are supported:

Self-closing — inline within text, no body:

The crash is caused by {% ref type="issue" id="PROJ-ABC" /%} in the auth middleware.

Block — with a JSON body between opening and closing tags:

{% artifact type="root-cause" %}
{"description":"Race condition in session refresh","severity":"high"}
{% /artifact %}

The Tag component receives four props:

  • name — the tag name (e.g. "ref", "artifact", or domain-specific conventions)
  • level"block" or "inline", reflecting where the parser encountered the tag (not the syntax form)
  • attrskey="value" pairs from the opening tag, parsed as Record<string, string>
  • data — the parsed JSON body (undefined for self-closing tags)

By default, Tag renders nothing. Provide a Tag component override to handle tags:

<Storybook.Demo> <TagDemo /> </Storybook.Demo>

jsx
<Markdown
  raw={seerOutput}
  components={{
    Tag: ({name, attrs, data}) => {
      if (name === 'ref' && attrs.type === 'issue') {
        return <IssueBadge shortId={attrs.id} snapshot={data} />;
      }
      return null;
    },
  }}
>

During streaming, partial tag syntax (e.g. {% ref type="issue" before the closing /%} arrives) is automatically suppressed to avoid flashing raw source text.

Security

The component applies the same security model as the existing MarkedText:

  • Links are validated against a safe protocol allowlist (https:, mailto: only). Unsafe protocols like javascript: render as plain text.
  • Images are stripped by default. Provide a custom Image component to opt in.
  • Raw HTML in markdown is sanitized with DOMPurify using a strict allowlist of tags and attributes.
  • React escaping handles all other content — no dangerouslySetInnerHTML for markdown tokens.

Available Component Keys

Every override receives a Default prop in addition to the props listed below (except Image, which has no built-in default).

KeyDefaultProps
Paragraph<Text as="p">children
Heading<Heading as="h1-h6">children, level (1-6)
CodeBlock<CodeBlock>children (string), lang
InlineCode<InlineCode>children (string)
Link<Link> / <ExternalLink>children, href, title
Blockquote<Quote>children
Strong<strong>children
Emphasis<em>children
Strikethrough<Text strikethrough>children
Imagestrippedsrc, alt, title
Textpassthroughchildren (string)
OrderedList<Stack as="ol">children
UnorderedList<Stack as="ul">children
ListItem<Container as="li">children
TaskList<Stack as="ul"> (no bullets)children
TaskListItem<Container as="li">children, checked (bool)
Tablestyled <table>children
Tagnull (silent)name, level, attrs, data
HorizontalRule<Separator>-
LineBreak`
`-
HtmlDOMPurify sanitizedhtml (string)