Library / SDK
markdown-it/markdown-it-container avatar
markdown-it/markdown-it-container

markdown-it-container adds fenced block containers to markdown-it

Fenced container plugin for markdown-it markdown parser

588 stars77 forksJavaScriptMIT

At a glance

What is it?
markdown-it-container is a markdown-it plugin for block level custom containers, so you can write ::: warning blocks and decide exactly how each one is rendered. It is a small extension with a small surface area and a clear job.
Who is it for?
Adopt markdown-it-container if you are already rendering Markdown with markdown-it and want named blocks such as callouts, spoilers or notes to carry their own markup rather than being flattened into paragraphs. Do not adopt it for inline spans, which are a different plugin, and do not expect it to parse the contents of a container, since the content is Markdown and the plugin only handles the fence.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 101 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Fenced blocks that hold Markdown, not literal text

The plugin is described as a plugin for creating block level custom containers for the markdown-it markdown parser, and the syntax it adds is a three colon fence:

code
::: warning
*here be dragons*
:::

The documentation is explicit about the relationship to fenced code blocks. The markup is the same as for fenced code blocks, and the difference is that the marker uses another character and the content is rendered as Markdown markup. That second half is the whole point. A fenced code block keeps its contents literal, while a container parses them, so a container can hold a list, an emphasis run or a paragraph and have that markup come out as HTML. With no renderer defined, a div with the container name as its class is created:

html
<div class="warning">
<em>here be dragons</em>
</div>

So the zero-configuration case is already useful. A document that writes ::: warning blocks gets warning divs with the inner content parsed, and the author does not have to write any HTML at all. The audience is anyone rendering Markdown with markdown-it who needs a small set of named block types, most commonly documentation with callouts, changelogs with a warning block, or a wiki with a spoiler. The plugin is small enough that reading its source is faster than reading its documentation, which is itself a good sign for a dependency of this kind.

Three options, and the validate hook is the interesting one

The API is one call to use with a name and optional options:

js
var md = require('markdown-it')()
            .use(require('markdown-it-container'), name [, options]);

The name is the container name and is mandatory. The options object has three entries. validate is an optional function to validate the tail after the opening marker, and it should return true on success. render is an optional renderer function for opening and closing tokens. marker is an optional character to use in the delimiter, and the default is a colon. The validate hook is the one worth understanding properly, because it decides which blocks belong to your container. The tail after the opening marker is passed to it, so a container can require an argument, a specific word, or a particular set of options, and a block that does not validate is left alone for another plugin or for the default rendering. That is how a plugin avoids swallowing every ::: block in a document, and it is why the default behaviour of matching the container name exactly is not always enough when you have several container types in the same file.

Writing a renderer for a spoiler block

The documented example registers a spoiler container with both a validator and a renderer, and it is the clearest demonstration of how the two hooks fit together:

js
var md = require('markdown-it')();

md.use(require('markdown-it-container'), 'spoiler', {

  validate: function(params) {
    return params.trim().match(/^spoiler\s+(.*)$/);
  },

  render: function (tokens, idx) {
    var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);

    if (tokens[idx].nesting === 1) {
      // opening tag
      return '<details><summary>' + md.utils.escapeHtml(m[1]) + '</summary>\n';

    } else {
      // closing tag
      return '</details>\n';
    }
  }
});

Three things to notice. The validate function and the render function parse the same tail with the same regular expression, which is a small duplication you may want to factor out. The renderer is called for both the opening and the closing token, and it distinguishes them by checking tokens[idx].nesting, where a value of 1 means the opening tag. And the label captured from the tail is escaped with md.utils.escapeHtml before being put into the markup, which is the part you must not skip, since that text comes from the document author. Rendering the result produces a details and summary element with the content parsed as Markdown inside.

Rendering the block and seeing the result

The example ends by rendering a document and showing what comes out, which is the fastest way to see the whole contract in one place:

js
console.log(md.render('::: spoiler click me\n*content*\n:::\n'));

// Output:
//
// <details><summary>click me</summary>
// <p><em>content</em></p>
// </details>

Two things are visible there. The label text, click me, comes from the tail of the opening marker and lands in the summary element, and the body is wrapped in a paragraph because the content was parsed as Markdown rather than passed through as raw text. If you had used a fenced code block, the emphasis would have come out as literal asterisks. That difference is the entire feature. For a project already rendering Markdown with markdown-it, adding this plugin means you can introduce a named block in a document and have the HTML structure decided in one place, in your renderer, rather than scattered through the content.

What the plugin does not do, and where the boundary is

The scope is narrow, and knowing the edges saves time. This is a block level plugin, so it handles fenced blocks with a marker line, not inline spans, and there is a separate community plugin for the inline case. It also does not parse the contents of a container in any special way. The content is ordinary Markdown that markdown-it renders normally, so if you want a container whose body is treated as raw text or as a different language, that is not what this plugin does. Another constraint is that a container is a fence with a fixed marker character, and the default is a colon, so a document that already uses triple colons for something else will conflict. The marker option exists for that, but changing the marker for one container changes it for the delimiter parser generally, so a document mixing conventions needs care. The dependency on markdown-it itself is the real one. The documentation states that version 2 and later of the plugin requires markdown-it v5 or newer, so if you are pinned to an older markdown-it this package is not for you, and if you upgrade markdown-it you should check the plugin alongside it. The package itself has no runtime dependencies at all, which is why it is small.

Packaging, versions and the shape of the repository

The repository is a single plugin with a test directory, a CHANGELOG, an eslint configuration and a rollup config. The package is published as markdown-it-container at version 4.0.0 under the MIT licence, with no runtime dependencies declared, only dev tooling. The build uses rollup with babel, node-resolve and terser plugins, and the module output is index.mjs alongside a CommonJS build at dist/index.cjs.js. The exports map handles both, and the wildcard export pattern for subpaths means a consumer can reach the internal files as well, which is a small sign of a package that expects to be extended rather than only consumed. The test script runs lint, then build, then mocha under c8 with text, html and lcov coverage, and the prepublish script runs lint and build. The dev dependencies are the usual markdown-it ecosystem set, with markdown-it itself at 13, mocha as the runner, c8 for coverage, eslint with the standard config, and markdown-it-testgen, which generates parser conformance fixtures from a specification. There are no GitHub releases, the default branch is master, and the last push to the repository was on 2026-07-14, so the work is current but you track the master branch rather than a tag.

Editorial conclusion

Adopt markdown-it-container if you are already rendering Markdown with markdown-it and want named blocks such as callouts, spoilers or notes to carry their own markup rather than being flattened into paragraphs. Do not adopt it for inline spans, which are a different plugin, and do not expect it to parse the contents of a container, since the content is Markdown and the plugin only handles the fence. Before you start, check which markdown-it major version you are on, because version 2 and later of the plugin require markdown-it v5 or newer, and read the default render output before you write your own renderer, because the default already gives you a classed div.

Frequently asked questions

What does markdown-it-container add to markdown-it?

It adds block level custom containers, written as a three colon fence around content, so you can create blocks such as ::: warning with a chosen name. Unlike a fenced code block, the content inside a container is rendered as Markdown markup, and with no renderer defined a div with the container name as its class is produced.

How do I install markdown-it-container and use it?

Install it with `npm install markdown-it-container --save`, or with bower for a browser workflow. Then call use on the parser with the container name and optional options, for example `md.use(require('markdown-it-container'), 'spoiler', { validate, render })`.

What options can I pass when registering a container?

There are three. validate is an optional function that receives the tail after the opening marker and should return true on success, render is an optional renderer function for the opening and closing tokens, and marker is an optional character to use in the delimiter, with a colon as the default.

Which markdown-it versions does markdown-it-container support?

Version 2 and later of the plugin requires markdown-it v5 or newer, according to the project documentation, and the package is currently at version 4.0.0 with markdown-it 13 in its development dependencies. The plugin itself declares no runtime dependencies.

How do I write a custom renderer for a container?

The render function is called for both the opening and the closing token, and you tell them apart by checking tokens[idx].nesting, where 1 is the opening tag. Parse the label from tokens[idx].info and escape it with md.utils.escapeHtml before putting it into your markup, as the spoiler example does with a details and summary element.

Official sources

  1. Issues
  2. License: MIT
  3. markdown-it/markdown-it-container on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/markdown-it-markdown-it-container.svg)](https://hysenlabs.com/projects/markdown-it-markdown-it-container)

Was this useful?