A Lossless Group package · MIT

Lossless Flavored Markdown

MDX power without MDX's opinions. A polyglot extended-markdown pipeline — authors keep authoring, LFM normalizes the variations.

Let content creators be content creators. Identify the syntaxes worth supporting — and the variations on intent inside each one — then parse and render beautifully. LFM flexibly handles the rest.

$ pnpm add jsr:@lossless-group/lfm

STC — Syntax · Trigger · Component

Three steps from your markdown to your renderer.

  1. 01

    Declare Syntax

    Add the syntax you want rendered as a component, plus the params or props it should carry.

  2. 02

    Parse Trigger

    Wire the parser to recognize it — a peer remark plugin file that emits one canonical AST node.

  3. 03

    Component Pipeline

    Map the AST node to a component, or a chain of them. Authors keep authoring; the renderer takes over.

Live example

Video Embeds

live
Syntax

bare URL or directive

  • bare youtu.be/share={id}
  • directive :::youtube-share[https://youtu.be/jCe2wg1ulus?si=oplqTdsbv8sv2JfH]
Parse
src/plugins/lfm-link-preview.ts
Component
components/YoutubeShareEmbed--Base.astro

Features

What's in the box

Triggers, normalizers, and build-time enrichment. All wired together by remarkLfm; cherry-pick what you need.

remark-gfm

Stable

Tables, task lists, strikethrough, autolinks — GitHub-flavored markdown enabled by default.

GFM is the floor, not the ceiling. Tables and task lists come for free; they’re also the substrate for the bare-link auto-unfurl detector — a paragraph with a single autolink child is the GFM signal that LFM uses to dispatch embeds.

GFMTables

remark-directive

Stable

Directive syntax for tagging blocks, leaves, and inline runs as components.

Block, leaf, and text directives — :::box, ::badge, :abbr — give authors a typographically lightweight way to name a region. LFM’s normalizers emit directive nodes from many input syntaxes, so directives are also the lingua franca your renderer dispatches on.

DirectivesSyntax

remark-callouts

Stable

Obsidian `> [!type] Title` blocks normalized into directive nodes — same downstream shape, two upstream syntaxes.

Authors writing in Obsidian get callout previews in their editor. Authors writing in plain markdown can use :::callout{type="warning"}. Both end up as the same :::callout directive node — your <Callout> component renders one shape regardless of which tool wrote the file.

CalloutsObsidian

remark-citations

Stable

Hex-code footnotes get renumbered to display indices and lifted into a structured citation dataset.

Author with stable hex IDs ([^a1b2c3]); LFM renumbers them to readable indices at parse time and assembles the full dataset on tree.data.citations.ordered — parsed dates, source domains, raw text — so a Sources component can render the canonical bibliography at the bottom of the article.

CitationsFootnotes

Bare-link provider catalog

Beta

A URL on its own line becomes the embedded player or rich card the author meant. The catalog ships in the package.

src/plugins/Bare-Link-Provider-Catalog.md is the canonical record of supported providers. v0.2.2 ships YouTube video / shorts / playlist and Vimeo as stable, with Loom, Spotify, and SoundCloud planned.

The matching rule is strict: the paragraph must have exactly one child that’s a link whose visible text equals its URL. So Check this out https://... mid-sentence stays a clickable link; only an isolated bare URL becomes an embed.

EmbedsYouTubeVimeo

lfm-task-metadata

Beta

Assign tasks with a plain checkbox line, then roll a whole repo's tasks into one file for GitHub.

- [ ] @mpstaton Task title #Docs 2026-11-15 reads correctly in Obsidian and on GitHub with no plugin. Turn on taskMetadata and LFM also exposes the assignees, tags, estimate and due date as data, leaving the text as written. The lfm-tasks script then rolls a repo’s tasks into one file, each linking to its source line.

TasksGitHub

remark-code-fences

Beta

A fence registry that ships empty — you name the formats you want, and pay for nothing else.

Diagram fences normally mean a routing table inside somebody’s renderer, edited once per format and reimplemented once per site. This inverts it: the plugin knows no formats at all, and a handler is plain data plus an optional pure function.

Seven ship, none adding a dependency — yang and jsonSchema render as trees, plantuml encodes to a server URL with no renderer at all, vegaLite parses for a client chart, mermaid and graphviz just claim their language.

See them rendered →

Code-fence formats

Diagrams, schemas and UML — from a registry that ships empty

remarkCodeFences knows no formats until you name them. Seven handlers are included and none add a dependency, because "support a diagram language" means three different things depending on who does the drawing.

Handler Fences Produces Who draws it
yang yang RFC 8340 tree diagram nobody — it renders as text
json-schema json-schemajsonschema schema tree, $refs expanded nobody — it renders as text
plantuml plantumlpumluml server URL a PlantUML server, via <img>
vega-lite vega-litevegalitevl spec + mark/channels summary vega-embed, client-side
mermaid mermaid claims the language mermaid.js, client-side
graphviz graphvizdot claims the language @viz-js/viz (WASM)
jsoncanvas jsoncanvasjson-canvascanvas normalized nodes + edges your canvas renderer

See them rendered → YANG in depth

Recent

What shipped lately

Read the full changelog →