A custom element for rendering stylable (light DOM) Markdown

Overview

Motivation

There are many web components these days to render Markdown to HTML. Here are a few:

However, all render the resulting Markdown in Shadow DOM, making it painful to style like a regular part of the page, which my use cases required. <zero-md> supports opt-in light DOM rendering, but it's tedious to add an extra attribute per element.

I also wanted a few more things existing web components didn't have. Plus, making stuff is fun. 😅

So I made my own. Feel free to use it. Or don't. 🤷🏽‍♀️ I primarily wrote it to scratch my own itch anyway! 😊

Features

  • Zero dependencies (except marked, obvs, which is only loaded if a <md-block> or <md-span> element is actually used on the page)
  • Styleable with regular selectors, just like the rest of the page
  • Load external Markdown files or render inline content
  • Customize start heading level (e.g. so that # Foo becomes a <h3> and not an <h1>)
  • Also comes with <md-span>, for lightweight inline markdown
  • Prism is automatically used for syntax highlighting, if included (but can be included dynamically too)

View demos

Usage

Via HTML:

<script type="module" src="https://md-block.verou.me/md-block.js"></script>

In JS:

import {MarkdownBlock, MarkdownSpan, MarkdownElement} from "https://md-block.verou.me/md-block.js";

Of course you can also use npm if that's your jam:

npm install md-block
import {MarkdownBlock, MarkdownSpan, MarkdownElement} from "md-block";

Importing the module in any of these ways also registers two custom elements: <md-block> for block level content and <md-span> for inline content. If you additionally want to use other tag names, you can.

API

Both <md-block> and <md-span>

Attribute Property Type Description
- mdContent String Actual Markdown code initially read from the HTML or fetched from src. Can also be set to render new Markdown code
rendered rendered (Read-only) String Added to the element after Markdown has been rendered. Thus, you can use md-block:not([rendered]) in your CSS to style the element differently before rendering and minimize FOUC
untrusted untrusted (Read-only) Boolean Sanitize contents. Read more

<md-block>

Attribute Property Type Description
src src String or URL External Markdown file to load. If specified, original element content will be rendered and displayed while the file is loading (or if it fails to load).
hmin hmin Number Minimum heading level
hlinks hlinks String Whether to linkify headings. If present with no value, the entire heading text becomes the link, otherwise the symbol provided becomes the link. Note that this is only about displaying links, headings will get ids anyway

<md-span>

(No attributes or properties at the moment)

Recipes

Updating the Markdown

While you can provide initial Markdown inline, after the element is rendered, changing its contents will not cause it to re-render, since its contents are now the parsed HTML (this is a disadvantage of this approach, compared to the Shadow DOM ones).

If you need to update its contents dynamically, use element.mdContent. You can also read that property to get access to the Markdown code that was last rendered, whether it came from the element's contents, or fetched from a URL.

Note that setting mdContent will override any remote URL provided via src.

Minimizing FOUC

md-block adds a rendered attribute to elements whose Markdown has been rendered. This allows you to style unrendered content however you please, by using a md-block:not([rendered]) CSS selector.

  • You could hide it entirely via md-block:not([rendered]) { display: none }
  • You could apply white-space: pre-line to it so that at least paragraphs are not all smushed together
  • …or you could do something fancier.

I'd recommend you consider how it fails before deciding what to do. It's the Internet, 💩 happens. Do you want your content to not be visible if a script doesn't load?

When loading remote content, there are two renders: First, any fallback content renders, then the remote content. Because we often want to style the element differently until the remote content renders, the rendered attribute has keyword values, depending on what happened:

  • fallback when only fallback content has been rendered
  • remote if remote content has been rendered
  • content if element content has been rendered and there is no src attribute present
  • property if content has been rendered by setting this.mdContent directly

Using different tag names

By default, md-block registers two custom elements: <md-block> for block-level content and <md-span> for inline content. You can use different names, but since each class can only be associated with one tag name, you need to create your own subclass:

import {MarkdownBlock, MarkdownSpan, MarkdownElement} from "https://md-block.verou.me/md-block.js"

customElements.define("md-content", class MarkdownContent extends MarkdownBlock {});

Handling untrusted content

By default md-block does not santize the Markdown you provide, since in most use cases the content is trusted.

If you need to render untrusted content use the untrusted attribute, which will dynamically load DOMPurify and use it. This is not dynamic, you need to add it in your actual markup (or before the element is connected, if dynamically generated). The reason is that it's unsafe to add it later: if the content has been already rendered once and treated as safe, it's pointless to sanitize it afterwards and re-render.

Important: Do not rely on the untrusted attribute for inline Markdown! This is mainly useful for content linked via the src attribute. If there is potentially malicious code in the inline Markdown you are using, it will be picked up by the browser before md-block has the chance to do anything about it. Instead, use a regular <md-block> element, and MarkdownElement.sanitize() for the untrusted content.

Using different URLs for marked and DOMPurify

By default, md-block dynamically loads marked and DOMPurify from a CDN. If you want to use different versions, there is a number of ways:

Probably the easiest is if you use the versions of these libraries that create a global, md-block will use that instead of loading them.

The URLs md-block uses to fetch these libraries reside on a separate URLs export. So theoretically you could do something like this:

import {URLs as MdBlockURLS, MarkdownBlock, MarkdownSpan, MarkdownElement} from "./md-block.js";

MdBlockURLS.marked = "./marked.js";
MdBlockURLS.DOMPurify = "./purify.es.js";

But it's uncertain whether the new URLs will be picked up before the default ones load. In my tests that seems to work for DOMPurify but not marked. These libraries are loaded when the element is connected, so you could add the <md-block> elements dynamically to the document after you set the URLs, but that's a bit of a hassle.

You can also use the URLs export to import these modules yourself, in case you want to add plugins or whatnot.

Loading Prism dynamically

By default md-block will use Prism if it's available, but won’t load it dynamically if it isn't. You could tell it to load Prism dynamically, only if there are actual code elements, by providing a Prism URL:

import {URLs as MdBlockURLS, MarkdownBlock, MarkdownSpan, MarkdownElement} from "./md-block.js";

MdBlockURLS.Prism = "./prism.js";
// You can optionally also provide a Prism CSS URL:
MdBlockURLS.PrismCSS = "./prism.css";

<md-block> inception

Did you know you can actualy use <md-block> inside your Markdown and it works correctly?

For a cool example of this, check out the Stretchy docs

How to set different Markdown options/flavor?

Right now, this element uses GFM as a Markdown flavor and doesn’t expose a whole lot of options (besides hmin and hlinks). That’s because I originally wrote it for my own needs, and that’s what I needed. I’m not opposed to adding more customizability, if there are actual use cases that require it. If you have such use cases, please open an issue.

Comments
  • Adding markdown-it renderer to match VS Code parser?

    Adding markdown-it renderer to match VS Code parser?

    This is a great library, thanks for putting it together! We've been using it but have noticed small variations between our multiple systems where we edit and render Markdown. In HTML we use <md-block> but in CLI code generators we use markdown-it rendering because VS Code also uses it.

    Since VS Code uses markdown-it and we use VSC for editing our markdown files we were thinking of forking <md-block> to support markdown-it. I was curious if you were thinking of allow different renderers (through configuration) for <md-block>? How hard might it be if we sponsored that work?

    opened by shah 4
  • Custom heading levels

    Custom heading levels

    Hi Lea, thank you for making this project! Its something I constantly rebuild in projects and am excited by a web component approach that I can style in the normal DOM!

    with headings, I often struggle with visual distinction of h3's vs h2's. So on my site i have done something like this:

    image

    and it's really helped me with my "heading anxiety". i wonder if that could be incorporated here in a more generic way?

    opened by sw-yx 2
  • Add citation support.

    Add citation support.

    Hi ! I was using this library for a forum website when I noticed that notations are not supported, could you add it ? I think it would be simple also, as it's just a <blockquote> tag.

    Exemple of citation I'm needing.

    opened by Ayfri 1
  • How would I use the footnotes plugin with md-block?

    How would I use the footnotes plugin with md-block?

    Everything's working great with md-block but I need footnotes, which seem to be supported with this github.com/xiaoliwang/markedjs-extra plugins. Is there any support for adding plugins to the rendering process?

    opened by shah 1
  • Code block syntax highlighting

    Code block syntax highlighting

    How would one achieve this? I've tried highlight.js, which didn't appear to do anything at all.

    Here's my attempt;

    <!DOCTYPE html>
    <html>
    <head>
      <script type="module" src="https://md-block.verou.me/md-block.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css" />
    </head>
    
    <body>
      <md-block>
        ```cs
        Console.WriteLine("Hello, world!");
        ```
      </md-block>
    </body>
    </html>
    
    opened by just-ero 0
  • Block quotes don't work inside <md-block> contents

    Block quotes don't work inside contents

    <md-block> ignores Markdown block quote syntax inside its contents.

    For example:

    <md-block>
    > Block quote
    > doesn’t work
    > as expected
    </md-block>
    

    renders in-browser as a <p> containing:

    > Block quote > doesn’t work > as expected
    

    (Probably HTML tag ambiguity with >?)

    As a workaround, HTML tags work fine inside <md-block> contents:

    <blockquote>This is fine</blockquote>
    

    This also only appears to affect Markdown inside an <md-block> tag's contents. The same Markdown renders block quotes as expected when loaded remotely via the src attribute.

    Codepen example: https://codepen.io/oznogon/pen/XWYVmLG?editors=1110

    bug 
    opened by oznogon 1
  • Parse Markdown inside inline HTML

    Parse Markdown inside inline HTML

    Apparently marked doesn't offer a way to do this (see https://github.com/markedjs/marked/issues/488 ) except in a restricted way, so it would need to be implemented ad hoc.

    opened by LeaVerou 0
  • How to handle textContent?

    How to handle textContent?

    Right now, textContent doesn't do anything special, and one needs to use mdContent to re-render Markdown on the component. However, that means that <md-block> doesn't work well with libraries that handle HTML in an agnostic way (e.g. Mavo, Vue, Angular etc). However, if textContent (and innerText?) becomes an alias to mdContent, then how does one get the actual textContent of the element?

    Another alternative would be if textContent sets mdContent on write, but returns the actual element textContent on read. This would allow libraries that work with textContent to update its content, but reading textContent would still work. Is there any precedent for such behavior? Is it confusing?

    opened by LeaVerou 0
  • Hopefully Helpful Suggestions

    Hopefully Helpful Suggestions

    Ola. Big fan of markdown and this caught my eye by way of the dreaded orange beast. Some suggestions for the project that are hopefully received as helpful:

    • ESLint and/or Prettier (this will surely help contributors)
    • Tests! (ava/uvu/jest/etc anything with snapshots will probably be useful)
    • Minification (terser would probably suffice)

    🍻

    opened by shellscape 1
Releases(v0.0.1)
Owner
Lea Verou
Elected @w3ctag member, CSS WG Invited Expert, HCI researcher at MIT CSAIL. Founder of many open source projects, PrismJS probably being the most popular one.
Lea Verou
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
Custom Vitest matchers to test the state of the DOM, forked from jest-dom.

vitest-dom Custom Vitest matchers to test the state of the DOM This library is a fork of @testing-library/jest-dom. It shares that library's implement

Chance Strickland 14 Dec 16, 2022
This experimental library patches the global custom elements registry to allow re-defining or reload a custom element.

Redefine Custom Elements This experimental library patches the global custom elements registry to allow re-defining a custom element. Based on the spe

Caridy Patiño 21 Dec 11, 2022
Kuldeep 2 Jun 21, 2022
A Fast & Light Virtual DOM Alternative

hyper(HTML) ?? Community Announcement Please ask questions in the dedicated discussions repository, to help the community around this project grow ♥ A

Andrea Giammarchi 3k Dec 30, 2022
A docsify.js plugin for rendering LaTeX math blocks from markdown

docsify-latex A docsify.js plugin for typesetting LaTeX with display engines from markdown. Docsify + LaTeX = ❤️ Installation Add JavaScript LaTeX dis

Scruel Tao 8 Dec 25, 2022
A custom Chakra UI component that adds ready-made styles for rendering remote HTML content.

Chakra UI Prose Prose is a Chakra UI component that adds a ready-made typography styles when rendering remote HTML. Installation yarn add @nikolovlaza

Lazar Nikolov 50 Jan 3, 2023
A table component for your Mantine data-rich applications, supporting asynchronous data loading, column sorting, custom cell data rendering, row context menus, dark theme, and more.

Mantine DataTable A "dark-theme aware" table component for your Mantine UI data-rich applications, featuring asynchronous data loading support, pagina

Ionut-Cristian Florescu 331 Jan 4, 2023
🌗 1 line of code to apply auto dark / light theme and support custom theme for your website. Super fast and lightweight theme library.

themes.js A super lightweight and fast Theme library with auto system color scheme detection in JavaScript. Features Auto detect Dark / Light mode by

SerKo 4 Nov 29, 2022
An extension of DOM-testing-library to provide hooks into the shadow dom

Why? Currently, DOM-testing-library does not support checking shadow roots for elements. This can be troublesome when you're looking for something wit

Konnor Rogers 28 Dec 13, 2022
Create DOM element and bind observables on it.

rx-domh Create DOM element and bind observables on it. Inspired by Binding.scala and react-flyd, I made this. Just a simple todo example: /** @jsx h *

xialvjun 4 Feb 6, 2018
I forgot about el.outerHTML so I made this, it takes a DOM element and returns its html as string

htmlToString Convert html/DOM element to string Works with rendered and virtual DOM Installation npm install htmltostring Or using CDN <script src="ht

Shuvo 4 Jul 22, 2022
A JavaScript library to shuffle the text content of a DOM element with an animated effect.

shuffle-letters.js A JavaScript library to shuffle the text content of a DOM element with an animated effect. NOTE: This library is a port to vanilla

George Raptis 6 Jun 2, 2022
A little JavaScript plugin to generate PDF, XLS, CSV and DOC from JavaScript Object or DOM element only from the frontend!

?? JavaScript Object to csv, xls, pdf, doc and DOM to html generator ?? A little JavaScript plugin to generate PDF, XLS, CSV and DOC from JavaScript O

null 61 Jan 7, 2023
Creates a table of contents in a DOM element optionally linked to with anchors. No jQuery or other dependencies.

HTML-Contents Creates a table of contents in a DOM element optionally linked to with anchors. No dependencies. @psalmody Get It We're on npm: npm i ht

Michael Tallino 3 Oct 25, 2022
JavaScript micro-library: pass in an element and a callback and this will trigger when you click anywhere other than the element

Add a click listener to fire a callback for everywhere on the window except your chosen element. Installation run npm install @lukeboyle/when-clicked-

Boyleing Point 5 May 13, 2021
A plugin for the Obsidian markdown note application, adding functionality to render markdown documents with multiple columns of text.

Multi-Column Markdown Take your boring markdown document and add some columns to it! With Multi Column Markdown rather than limiting your document lay

Cameron Robinson 91 Jan 2, 2023