Skip to main content
xriptxr

Markdown

Info:Content
37

Renders markdown as themed HTML, as a document or as an inline label, with an optional source view.

md gfm rich text prose render commonmark editor

Live demo

live · @xtyle/astro

Markdown

A document

Headings ride the type scale, rules and quotes ride the border ramp, and fences borrow the same --code-* family the Code component owns.

Derivation

An algorithm maps a few anchors and knobs into a full, internally consistent token set. The algorithm is the durable asset; a theme is a materialized invocation of one. See the derivation model for the long version.

What it covers

  1. the anchors you pin
  2. the knobs you turn
  3. everything else, derived

A theme is a print. The algorithm is the press.

Inline --accent reads the same as a fence:

const theme = derive({ bg: "#0f1115", accent: "#5b8cff" });

Rendered with a sanitizer no sanitizer at all.

Tables and task lists

GFM is on by default. A wide table scrolls inside its own box rather than pushing the document sideways.

component state notes
tooltip fixed tracks its trigger through scroll
swatch fixed same promotion, same defects
markdown new this one
  • escape raw HTML to text
  • allowlist link and image URLs
  • name the version

Autolinks too: https://xtyle.dev

An inline label

inline renders emphasis with no blocks and no paragraph wrapper, so it inherits whatever type surrounds it. Note the last one: a leading # stays literal text instead of erupting a heading into the row.

Fix tooltip placement Refactor AnchorTracker in xtyle Drop keep the swatch fix Review PR #12 # Heading in a label

The same label inside running text: a task titled Fix tooltip in AnchorTracker inherits this sentence's size, weight, and color rather than imposing its own.

Untrusted input

Success:no sanitizer

Raw HTML is escaped to text and refused URLs lose their href — so nothing here is live. The markup below is inert text, and the first link is a real <a> with no destination.

This came from an LLM and is rendered as-is:

<script>alert("this never runs")</script>

<img src=x onerror=alert("nor this")>

a link that looks fine and one that is fine.

An editable source view

editable adds a toggle between the render and its markdown. The switch is a real node in the fragment fill, so a mod can reword it, make it an icon, or move it.

Edit me

Switch to source with the toggle, change something, and switch back.

  • the textarea is the source of truth while editing
  • input fires with the markdown as you type

Markdown renders GitHub-Flavored Markdown into HTML that themes entirely from the token register: headings ride the type scale, rules and quotes ride the border and surface ramps, and fenced code borrows the same --code-* family the Code component owns, so a fence inside a document and an <xtyle-code> beside it agree in any theme. GFM is on — tables, task lists, strikethrough, and autolinks all render.

inline switches to a label render: emphasis, code, links and strikethrough, but no blocks and no paragraph wrapper, so it drops into a tab title or a chip and inherits its type — a generated label that opens with # stays text instead of erupting a heading into a tab strip. editable adds a source view the reader can switch to, emitting input as it is typed. It ships no sanitizer, by design. Raw HTML in the source is escaped to text rather than rendered, and link and image URLs are written from a scheme allowlist, so everything that reaches the DOM is markup the renderer generated itself from a closed token set. There is no allow-html escape hatch: it would reintroduce the arbitrary-HTML problem the design exists to avoid.

When to use

How this component composes with the rest of the set.

Reach for inline whenever the markdown is a label rather than a document — a tab title, a chip, a table cell, a menu item. It inherits the type around it instead of imposing its own.
Untrusted markdown needs no extra handling: raw HTML is escaped and URLs are allowlisted, so an LLM-authored or user-authored string is safe to pass straight in.
Pair editable with the input event to keep your own state in sync; the event's detail.source carries the markdown as it's typed.
A fenced block inside a document borrows the same --code-* tokens as <xtyle-code>, so the two agree without any configuration.

Props

4 props, straight from the manifest.

PropTypeDefaultBindingsDescription
source string
html svelte astro
The markdown to render. Optional for html/svelte, where the element's text content is used instead.
inline boolean false
html svelte astro
Render as a label rather than a document: emphasis, code, links and strikethrough, with no blocks and no paragraph wrapper. It flows with the surrounding text and inherits its type, so it fits a tab title, a chip, or a table cell. Block syntax stays literal instead of erupting.
editable boolean false
html svelte astro
Offer a source view the reader can switch to, via a toggle the fragment fill draws.
editing boolean false
html svelte astro
Whether the source view is showing. Only meaningful alongside `editable`; setting it alone would strand the reader in a box with no way out, so it is ignored.

Appearance

States

editing

.xtyle-markdown__toggle[aria-pressed='true']

The source view is showing: the body is swapped for the textarea and the toggle reads as pressed.

inert-link

.xtyle-markdown__body a:not([href])

A link whose URL the renderer refused — a javascript: href, say. It keeps its text but loses its href, and reads dimmed and unclickable rather than lying about being a link.

Anatomy

The named parts that make up the component, with their selectors.

body

.xtyle-markdown__body

Where the rendered markdown lands. Every rule is scoped inside it and styles element types rather than named parts, since the content's structure is the author's, not ours — so nothing here reaches a consumer's own headings.

--fg-0 --font-sans --text-body --leading-normal --space-4

heading

.xtyle-markdown__body h1, .xtyle-markdown__body h2

Document headings, on the type scale; h1 and h2 carry a rule beneath them.

--text-2xl --text-xl --weight-semibold --leading-tight --field-border

code

.xtyle-markdown__body code, .xtyle-markdown__body pre

Inline code and fenced blocks, borrowing the Code component's --code-fg / --code-bg rather than deriving a second opinion about what code looks like.

--code-fg --code-bg --font-mono --radius-md --field-border

table

.xtyle-markdown__body table

A GFM table: striped rows, a filled header, and its own horizontal scroll so a wide table never pushes the document sideways.

--bg-1 --bg-2 --field-border --text-sm --weight-semibold

quote

.xtyle-markdown__body blockquote

A blockquote, marked by a leading edge and dimmed a step off the body text.

--fg-1 --field-border --space-4

editor

.xtyle-markdown__editor

The source textarea shown while editing, in the mono face on the field surface.

--field-bg --field-border --font-mono --text-sm --radius-md --accent

toggle

.xtyle-markdown__toggle

The edit/view switch. Chrome the component invents, so it is a real node in the fragment fill: a mod can reword it, make it an icon, or move it, and the element keeps working.

--neutral-bg --neutral-text --field-border --radius-sm --text-xs --accent-text

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 37/37 consumed tokens produced default register: 305 tokens

Live coverage check against the xtyle-default register (derive(xtyleDefault, { anchors })coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--accent --accent-text --bg-1 --bg-2 --border-normal --border-thin --code-bg --code-fg --duration-fast --ease-standard --fg-0 --fg-1 --fg-2 --field-bg --field-border --font-mono --font-sans --leading-normal --leading-tight --neutral-bg --neutral-text --radius-md --radius-sm --space-1 --space-2 --space-3 --space-4 --space-5 --space-6 --text-2xl --text-body --text-lg --text-sm --text-xl --text-xs --weight-bold --weight-semibold

Slots

default
html svelte

The markdown as text content (html/svelte). For Astro, pass it via the source prop instead.

Accessibility

Renders semantic document structure — real <h1><h6>, <ul>/<ol>, <table> with <th>, <blockquote> — so assistive tech announces the content's shape rather than a wall of text.
inline emits no headings or landmarks, which is what makes it safe to drop into a tab title or a chip: a label never injects document structure into a strip of them.
The edit/view toggle is a real <button> carrying aria-pressed, so its state is announced; the source textarea has an accessible name.
A link whose URL was refused loses its href, so it is skipped by link navigation rather than being announced as a link that does nothing.
A wide table scrolls inside its own box rather than the page, so the document never scrolls horizontally (WCAG 1.4.10).

Code

A document

The block render: headings, emphasis, links and lists, themed from the register.

<xtyle-markdown source="## Release notes

The **tooltip** now tracks its trigger. See [the changelog](/changelog).

- fixed placement under scroll
- `Escape` dismisses a hover-raised hint"></xtyle-markdown>
<script lang="ts">
	import { Markdown } from "@xtyle/svelte";

	const notes = `## Release notes

The **tooltip** now tracks its trigger. See [the changelog](/changelog).`;
</script>

<Markdown source={notes} />
---
import Markdown from "@xtyle/astro/Markdown.astro";

const notes = `## Release notes

The **tooltip** now tracks its trigger. See [the changelog](/changelog).`;
---

<Markdown source={notes} />

An inline label

inline renders emphasis without blocks and without a paragraph wrapper, so a generated tab title or chip inherits its surroundings. Block syntax stays literal — a leading # is text, not a heading.

<!-- a label, not a document: emphasis renders, blocks stay literal -->
<xtyle-markdown inline source="Fix **tooltip** in `AnchorTracker`"></xtyle-markdown>
<script lang="ts">
	import { Markdown } from "@xtyle/svelte";

	// a tab title an LLM formatted, dropped straight into a tab strip
	export let title = "Fix **tooltip** in `AnchorTracker`";
</script>

<Markdown inline source={title} />
---
import Markdown from "@xtyle/astro/Markdown.astro";
---

<Markdown inline source="Fix **tooltip** in `AnchorTracker`" />

Tables and task lists

GFM is on: tables, task lists, strikethrough and autolinks all render. A wide table scrolls inside its own box.

<xtyle-markdown source="| component | state |
| --- | --- |
| tooltip | fixed |
| swatch | fixed |

- [x] track the anchor
- [ ] name the version"></xtyle-markdown>
<script lang="ts">
	import { Markdown } from "@xtyle/svelte";
</script>

<Markdown source={`| component | state |
| --- | --- |
| tooltip | fixed |

- [x] track the anchor
- [ ] name the version`} />
---
import Markdown from "@xtyle/astro/Markdown.astro";
---

<Markdown source={`| component | state |
| --- | --- |
| tooltip | fixed |

- [x] track the anchor`} />

An editable source view

editable adds a toggle between the render and its markdown source, emitting input with the source as it's typed.

<xtyle-markdown editable source="# Draft

Switch to the source and edit it."></xtyle-markdown>
<script lang="ts">
	import { Markdown } from "@xtyle/svelte";

	let draft = "# Draft\n\nSwitch to the source and edit it.";
</script>

<Markdown editable source={draft} on:input={(e) => (draft = e.detail.source)} />
---
import Markdown from "@xtyle/astro/Markdown.astro";
---

<Markdown editable source={"# Draft\n\nSwitch to the source and edit it."} />