A robust Next.js newsletter Next.js Weekly is sponsoring me π

An outstanding Git-based CMS Outstatic is also sponsoring me π

A warm thanks π to @ErfanEbrahimnia, @avitorio, @recepkyk, and @LSeaburg for the support π
This package is a unified (remark) plugin to add custom containers with customizable properties in markdown.
unified is a project that transforms content with abstract syntax trees (ASTs) using the new parser micromark. remark adds support for markdown to unified. mdast is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree.
This plugin is a remark plugin that transforms the mdast.
remark-flexible-containers is useful if you want to add a custom container in markdown, for example, in order to produce a callout or an admonition.
container node, with custom tag name, custom class name and also additional properties.title node inside the container, if the title is provided, with custom tag name, custom class name and also additional properties.remark-flexible-containers supports nested containers.
This package is suitable for ESM only. In Node.js (version 16+), install with npm:
npm install remark-flexible-containers
or
yarn add remark-flexible-containers
Say we have the following file, example.md, which consists a flexible container. Each container should be closed with the same length colon ::: at the end.
It works also with more colons than tripple colon as well. (4-colon, or 5-colon and more)
Below, the container type is "warning", specified after the triple colon :::; and the container title is "title".
::: warning title
My paragraph with **bold text**
:::
[!TIP] You don't need to put empty lines inside the container.
[!CAUTION] There must be empty lines before and after the container in order to parse the markdown properly.
<!--- here must be empty line --->
::: warning Title
My paragraph with **bold text**
:::
<!--- here must be empty line --->
And our module, example.js, looks as follows:
import { read } from "to-vfile";
import remark from "remark";
import gfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import remarkFlexibleContainers from "remark-flexible-containers";
main();
async function main() {
const file = await remark()
.use(gfm)
.use(remarkFlexibleContainers)
.use(remarkRehype)
.use(rehypeStringify)
.process(await read("example.md"));
console.log(String(file));
}
Now, running node example.js yields:
(The type of the container is also added as a classname into the container node and the title node)
<div class="remark-container warning">
<div class="remark-container-title warning">Title</div>
<p>My paragraph with <strong>bold text</strong></p>
</div>
Without remark-flexible-containers, youβd get:
<p>::: warning Title
My paragraph with <strong>bold text</strong>
:::</p>
As of version ^1.2.0, remark-flexible-containers supports syntax for specific identifiers (tagname, id, classnames, attributes) for both container and title nodes. For example:
::: info {section#foo.myclass} Title {span#baz.someclass @disabled}
<!-- content -->
:::
<section id="foo" class="remark-container info myclass">
<span id="baz" class="remark-container-title info someclass" disabled>Title</span>
<!-- content -->
</section>
For more information, go to detailed explanation.
As of version ^1.4.0, remark-flexible-containers works with more colons (4-colon or 5-colon and more) as well. It requires just the length of the colons is identical at the opening and closing.
::: info Title
<!-- content -->
:::
:::: info Title
<!-- content -->
::::
::::: info Title
<!-- content -->
:::::
As of version ^1.4.0, remark-flexible-containers supports nested containers. It requires just more colons at the outer than the inner.
::::: outer
:::: inner
::: deeper
<!-- content -->
:::
::::
:::::
:::: tab-group Main Title
::: tab First Title
<!-- content -->
:::
::: tab Second Title
<!-- content -->
:::
::::
<details> <summary> elementAll you have to do is that you set a specific type details.
::: details Title
<!-- content -->
:::
<details class="remark-details">
<summary class="remark-summary">Title</summary>
<!-- content -->
</details>
If you don't provide a title, the default is Details for <summary> element.
::: details
<!-- content -->
:::
<details class="remark-details">
<summary class="remark-summary">Details</summary>
<!-- content -->
</details>
If you want <details> element is opened, add specific identifer {@open} attribute just after the type details.
::: details {@open} Title
<!-- content -->
:::
<details class="remark-details" open>
<summary class="remark-summary">Title</summary>
<!-- content -->
</details>
All options are optional and have default values.
interface Properties {
[PropertyName: string]: boolean | number | string | null | undefined | Array<string | number>;
}
type TitleFunction = (type?: string, title?: string) => string | null | undefined;
type TagNameFunction = (type?: string, title?: string) => string;
type ClassNameFunction = (type?: string, title?: string) => string[];
type PropertyFunction = (type?: string, title?: string) => Omit<Properties, 'className'> & { className?: never };
use(remarkFlexibleContainers, {
containerTagName?: string | TagNameFunction; // default is "div"
containerClassName?: string | ClassNameFunction; // default is "remark-container"
containerProperties?: PropertyFunction;
title?: TitleFunction;
titleTagName?: string | TagNameFunction; // default is "div"
titleClassName?: string | ClassNameFunction; // // default is "remark-container-title"
titleProperties?: PropertyFunction;
} as FlexibleContainerOptions);
containerTagNameIt is a string or a callback (type?: string, title?: string) => string option for providing custom HTML tag name for the container node.
By default, it is div.
use(remarkFlexibleContainers, {
containerTagName: "section";
});
Now, the container tag names will be section.
<section class="...">
<!-- content -->
</section>
The option can take also a callback function, which has two optional arguments type and title, and returns string representing a custom tag name.
use(remarkFlexibleContainers, {
containerTagName: (type, title) => {
return type === "details" ? "details" : "div";
}
});
Now, the container tag names will be div or details. It is a good start for creating details-summary HTML elements in markdown.
::: details Title
<!-- content -->
:::
::: warning Title
<!-- content -->
:::
<details class="...">
<!-- content -->
</details>
<div class="...">
<!-- content -->
</div>
containerClassNameIt is a string or a callback (type?: string, title?: string) => string[] option for providing custom class name for the container node.
By default, it is remark-container, and all container nodes' classnames will contain remark-container.
A container node contains also a secondary class name representing the type of the container, like warning or info. If there is no type of the container, then the secondary class name will not present.
::: danger Title
<!-- content -->
:::
<div class="remark-container danger">
<!-- content -->
</div>
use(remarkFlexibleContainers, {
containerClassName: "custom-container";
});
Now, the container nodes will have custom-container as a className, and the secondary class names will be the type of the container, if exists.
::: danger Title
<!-- content -->
:::
<div class="custom-container danger">
<!-- content -->
</div>
The option can take also a callback function, which has two optional arguments type and title, and returns array of strings representing class names.
use(remarkFlexibleContainers, {
containerClassName: (type, title) => {
return [`remark-container-${type ?? "note"}`]
};
});
Now, the container class names will contain only one class name like remark-container-warning, remark-container-note etc.
:::
<!-- content -->
:::
<div class="remark-container-note">
<!-- content -->
</div>
[!WARNING] If you use the
containerClassNameoption as a callback function, it is your responsibility to define class names, add the type of the container into class names etc. in an array.
containerPropertiesIt is a callback (type?: string, title?: string) => Record<string, unknown> & { className?: never } option to set additional properties for the container node.
The callback function that takes the type and the title as optional arguments and returns object which is going to be used for adding additional properties into the container node.
The className key is forbidden and effectless in the returned object.
use(remarkFlexibleContainers, {
containerProperties(type, title) {
return {
["data-type"]: type,
["data-title"]: title,
};
},
});
Now, the container nodes which have a type and a title will contain data-type and data-title properties.
::: danger Title
<!-- content -->
:::
<div class="remark-container danger" data-type="danger" data-title="Title">
<!-- content -->
</div>
titleIt is a callback (type?: string, title?: string) => string | null | undefined option to set the title with a callback function.
remark-flexible-containers adds a title node normally if a title is provided in markdown.
::: danger Title
<!-- content -->
:::
<div class="remark-container danger">
<div class="remark-container-title">Title</div>
<!-- content -->
</div>
if the callback function returns null title: () => null, the plugin will not add the title node.
use(remarkFlexibleContainers, {
title: () => null,
});
<div class="remark-container danger">
<!-- There will be NO title node -->
<!-- content -->
</div>
On the other hand, the option title: () => null doesn't affect if the type is details, so <summary> element will have definetly a value.
The callback function takes the type and the title as optional arguments and returns string | null | undefined. For example if there is no title you would want the title is the type of the container as a fallback.
use(remarkFlexibleContainers, {
title: (type, title) => {
return title ?? type ? type.charAt(0).toUpperCase() + type.slice(1) : "Fallback Title";
},
});
::: info Title
<!-- content -->
:::
::: info
<!-- content -->
:::
:::
<!-- content -->
:::
<div class="remark-container info">
<div class="remark-container-title info">Title</div>
<!-- content -->
</div>
<div class="remark-container info">
<div class="remark-container-title info">Info</div>
<!-- content -->
</div>
<div class="remark-container">
<div class="remark-container-title">Fallback Title</div>
<!-- content -->
</div>
titleTagNameIt is a string or a callback (type?: string, title?: string) => string option for providing custom HTML tag name for the title node.
By default, it is div.
use(remarkFlexibleContainers, {
titleTagName: "span";
});
Now, the title tag names will be span.
<div class="...">
<span class="...">Title</span>
<!-- content -->
</div>
The option can take also a callback function, which has two optional arguments type and title, and returns string representing a custom tag name.
use(remarkFlexibleContainers, {
containerTagName: (type, title) => {
return type === "details" ? "details" : "div";
},
titleTagName: (type, title) => {
return type === "details" ? "summary" : "span";
}
});
Now, the container tag names will be span or summary. It is a good complementary for creating details-summary HTML elements in markdown.
::: details Title
<!-- content -->
:::
::: warning Title
<!-- content -->
:::
<details class="...">
<summary class="...">Title</summary>
<!-- content -->
</details>
<div class="...">
<span class="...">Title</span>
<!-- content -->
</div>
titleClassNameIt is a string or a callback (type?: string, title?: string) => string[] option for providing custom class name for the title node.
By default, it is remark-container-title, and all title nodes' classnames will contain remark-container-title.
A title node contains also a secondary class name representing the type of the container, like warning or info. If there is no type of the container, then the secondary class name will not present.
::: danger Title
<!-- content -->
:::
<div class="...">
<div class="remark-container-title danger">Title</div>
<!-- content -->
</div>
use(remarkFlexibleContainers, {
titleClassName: "custom-container-title";
});
Now, the title nodes will have custom-container-title as a className, and the secondary class names will be the type of the container, if exists.
::: danger Title
<!-- content -->
:::
<div class="...">
<div class="custom-container-title danger">Title</div>
<!-- content -->
</div>
The option can take also a callback function, which has two optional arguments type and title, and returns array of strings representing class names.
use(remarkFlexibleContainers, {
titleClassName: (type, title) => {
return type ? [`container-title-${type}`] : ["container-title"]
};
});
Now, the container class names will contain only one class name like container-title-warning, container-title etc.
::: tip Title
<!-- content -->
:::
:::
<!-- content -->
:::
<div class="...">
<div class="container-title-tip">Title</div>
<!-- content -->
</div>
<div class="...">
<!-- No title node because there is no title in the second container
and no fallback title with `title` option -->
<!-- content -->
</div>
[!WARNING] If you use the
titleClassNameoption as a callback function, it is your responsibility to define class names, add the type of the container into class names etc. in an array.
titlePropertiesIt is a callback (type?: string, title?: string) => Record<string, unknown> & { className?: never } option to set additional properties for the title node.
The callback function that takes the type and the title as optional arguments and returns object which is going to be used for adding additional properties into the title node.
The className key is forbidden and effectless in the returned object.
use(remarkFlexibleContainers, {
titleProperties(type, title) {
return {
["data-type"]: type,
};
},
});
Now, the title nodes which have a type will contain data-type property.
::: danger Title
<!-- content -->
:::
<div class="...">
<div class="..." data-type="danger">Title</div>
<!-- content -->
</div>
::: info The Title of Information
Some information
:::
use(remarkFlexibleContainers);
is going to produce as default:
<div class="remark-container info">
<div class="remark-container-title info">The Title of Information</div>
<p>Some information</p>
</div>
() => nulluse(remarkFlexibleContainers, {
title: () => null,
});
is going to produce the container without title node (even if the the title is provided in markdown):
<div class="remark-container info">
<p>Some information</p>
</div>
use(remarkFlexibleContainers, {
containerTagName: "section",
containerClassName: "remark-custom-wrapper",
containerProperties(type, title) {
return {
["data-type"]: type,
["data-title"]: title,
};
},
title: (type, title) => title ? title.toUpperCase() : "Fallback Title";
titleTagName: "span",
titleClassName: "remark-custom-wrapper-title",
titleProperties: (type, title) => {
["data-type"]: type,
},
});
is going to produce the container section element like below:
<section class="remark-custom-wrapper info" data-type="info" data-title="The Title of Information">
<span class="remark-custom-wrapper-title info" data-type="info">THE TITLE OF INFORMATION</span>
<p>Some information</p>
</section>
use(remarkFlexibleContainers, {
containerTagName(type) {
return type === "details" ? "details" : "section";
},
containerClassName(type) {
return type === "details" ? ["remark-details"] : ["remark-container", type ?? ""];
},
titleTagName(type) {
return type === "details" ? "summary" : "span";
},
titleClassName(type) {
return type === "details" ? ["remark-summary"] : ["remark-container-title", type ?? ""];
},
});
With above options you can create details-summary HTML element in addition to containers easily.
::: details Title
Some information
:::
::: warning Title
Some information
:::
<details class="remark-details">
<summary class="remark-summary">Title</summary>
<p>Some information</p>
</details>
<section class="remark-container warning">
<span class="remark-container warning">Title</span>
<p>Some information</p>
</section>
You can use the plugin syntax without providing a type and a title.
:::
Some information
:::
It will not add a title node since it is not provided (assume that title option is not provided for a fallback title, as well), and it will also not add the type as a classname into the container:
<div class="remark-container">
<p>Some information</p>
</div>
The flexible container can contain also HTML elements and MDX components:
::: tip Title
This package is so _**cool** and **flexible**_
::it does not confuse with double colons::
==marked text via plugin `remark-flexible-markers`==
<mark>marked text via HTML element in markdown</mark>
<MarkRed>marked text via custom marker in support of MDX</MarkRed>
<details>
<summary>HTML tag works too</summary>
<p>I am working</p>
</details>
other paragraph *italic content* and,
some **bold content** without stress
:::
remark-flexible-containers supports syntax for specific identifiers (tagname, id, classnames, attributes) for both container and title nodes. For example:
::: info {section#foo.myclass @open @data-type=expandable} Title Of Information {span#baz.someclass}
<!-- content -->
:::
<section id="foo" class="remark-container info myclass" open data-type="expandable">
<span id="baz" class="remark-container-title info someclass">
Title Of Information
</span>
<!-- content -->
</section>
The identifiers (tagname, id, classnames, attributes) must be inside curly braces.
Syntax is very simple.
tagname is to be compatible HTML tag name, and may present only once,id is to start with hash #, and may present only once,classnames are to start with dot ., and may present many.attributes are to start with at sign @, and may present many, can be either bare / boolean attribute or key=value attribute.There are two groups of identifiers. Each group is optional, may present or not.
type) is for container node.title) is for title node.Here are some example usages. For simplicity, I omitted the container contents and ending syntax, just put the beginning syntax in the examples. All are valid usage for specific identifiers.
[!TIP] These identifiers can be placed as all, any three, any two, or just any of them in the desired order, with or without a space between them. This is why the "flexibility" term comes from.
However, make sure that no character other than a space appears before at sign (
@), because if you are using a package likeremark-gfm, it may interpret something like{span@open.someclass}as an autolink. For this reason, you should insert a space before at sign@if necessary.
::: info {section#foo.myclass.second-class} Title {span#baz.someclass.other-class}
::: info {section#foo.myclass} Title {span#baz.someclass}
::: info {section #foo .myclass .second-class} Title {span #baz .someclass .other-class}
::: info {section #foo .myclass} Title {span #baz .someclass}
::: info {section.myclass#foo} Title {span.someclass#baz}
::: info {.myclass#foo} Title {.someclass#baz}
::: info {.myclass #foo} Title {.someclass #baz}
::: info {.myclass #foo section} Title {.someclass #baz span}
::: info {#foo section} Title {#baz span}
::: info {.myclass} Title {#baz}
::: info {#foo} Title {.someclass}
::: info {#foo} Title
::: info {#foo}
::: info {section#foo.myclass}
::: info Title {.someclass}
::: info Title {span#baz.someclass}
::: info Title {#baz @data-attr=something}
::: info Title {span#baz @open}
::: info Title {@open span#baz}
::: details {@open}
::: details {@open} Title {.someclass}
You should consider that specific identifiers for title breaks the option title: () => null, and the title will take place for that individual container.
Another consideration is that if the content is MDX format, you may need to escape the opening curly braces, otherwise mdx parser of your integration may interpret curly braces as a MDX expression.
::: info \{#foo} Title \{.someclass}
<!-- content -->
:::
::: details \{@open} Summary Title
<!-- content -->
:::
This plugin only modifies the mdast (markdown abstract syntax tree) as explained.
This package is fully typed with TypeScript. The plugin options' type is exported as FlexibleContainerOptions.
This plugin works with unified version 6+ and remark version 7+. It is compatible with mdx version 2+.
Use of remark-flexible-containers does not involve rehype (hast) or user content so there are no openings for cross-site scripting (XSS) attacks.
I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to have a look my plugins.
If you find remark-flexible-containers or any of my projects is useful and helpful, please consider supporting my work. Your sponsorship means a lot to me and keeps these projects alive and updated! π
My sponsors are going to be featured at the very top of the page and proudly displayed on my Sponsor Wall.
Thank you for supporting open source! π
remark-flexible-code-titles
β Remark plugin to add titles or/and containers for the code blocks with customizable propertiesremark-flexible-containers
β Remark plugin to add custom containers with customizable properties in markdownremark-ins
β Remark plugin to add ins element in markdownremark-flexible-paragraphs
β Remark plugin to add custom paragraphs with customizable properties in markdownremark-flexible-markers
β Remark plugin to add custom mark element with customizable properties in markdownremark-flexible-toc
β Remark plugin to expose the table of contents via vfile.data or via an option referenceremark-mdx-remove-esm
β Remark plugin to remove import and/or export statements (mdxjsEsm)remark-mdx-remove-expressions
β Remark plugin to remove MDX expressions within curlybraces {} in MDX contentrehype-pre-language
β Rehype plugin to add language information as a property to pre elementrehype-highlight-code-lines
β Rehype plugin to add line numbers to code blocks and allow highlighting of desired code linesrehype-code-meta
β Rehype plugin to copy code.data.meta to code.properties.metastringrehype-image-toolkit
β Rehype plugin to enhance Markdown image syntax ![]() and Markdown/MDX media elements (<img>, <audio>, <video>) by auto-linking bracketed or parenthesized image URLs, wrapping them in <figure> with optional captions, unwrapping images/videos/audio from paragraph, parsing directives in title for styling and adding attributes, and dynamically converting images into <video> or <audio> elements based on file extension.recma-mdx-escape-missing-components
β Recma plugin to set the default value () => null for the Components in MDX in case of missing or not provided so as not to throw an errorrecma-mdx-change-props
β Recma plugin to change the props parameter into the _props in the function _createMdxContent(props) {/* */} in the compiled source in order to be able to use {props.foo} like expressions. It is useful for the next-mdx-remote or next-mdx-remote-client users in nextjs applications.recma-mdx-change-imports
β Recma plugin to convert import declarations for assets and media with relative links into variable declarations with string URLs, enabling direct asset URL resolution in compiled MDX.recma-mdx-import-media
β Recma plugin to turn media relative paths into import declarations for both markdown and html syntax in MDX.recma-mdx-import-react
β Recma plugin to ensure getting React instance from the arguments and to make the runtime props {React, jsx, jsxs, jsxDev, Fragment} is available in the dynamically imported components in the compiled source of MDX.recma-mdx-html-override
β Recma plugin to allow selected raw HTML elements to be overridden via MDX components.recma-mdx-interpolate
β Recma plugin to enable interpolation of identifiers wrapped in curly braces within the alt, src, href, and title attributes of markdown link and image syntax in MDX.I also build low-level utilities and plugins for the Unified ecosystem that can be used across Remark, Rehype, Recma, and other unist-based abstract syntax trees (ASTs).
unist-util-find-between
β Unist utility to find the nodes between two nodes.unified-log-tree
β Unified plugin to log abstract syntax trees (ASTs) for debugging without mutating.MIT License Β© ipikuka
154 commits
1 commits
TypeScript
98.5%
JavaScript
1.5%
A robust Next.js newsletter Next.js Weekly is sponsoring me π

An outstanding Git-based CMS Outstatic is also sponsoring me π

A warm thanks π to @ErfanEbrahimnia, @avitorio, @recepkyk, and @LSeaburg for the support π
This package is a unified (remark) plugin to add custom containers with customizable properties in markdown.
unified is a project that transforms content with abstract syntax trees (ASTs) using the new parser micromark. remark adds support for markdown to unified. mdast is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree.
This plugin is a remark plugin that transforms the mdast.
remark-flexible-containers is useful if you want to add a custom container in markdown, for example, in order to produce a callout or an admonition.
container node, with custom tag name, custom class name and also additional properties.title node inside the container, if the title is provided, with custom tag name, custom class name and also additional properties.remark-flexible-containers supports nested containers.
This package is suitable for ESM only. In Node.js (version 16+), install with npm:
npm install remark-flexible-containers
or
yarn add remark-flexible-containers
Say we have the following file, example.md, which consists a flexible container. Each container should be closed with the same length colon ::: at the end.
It works also with more colons than tripple colon as well. (4-colon, or 5-colon and more)
Below, the container type is "warning", specified after the triple colon :::; and the container title is "title".
::: warning title
My paragraph with **bold text**
:::
[!TIP] You don't need to put empty lines inside the container.
[!CAUTION] There must be empty lines before and after the container in order to parse the markdown properly.
<!--- here must be empty line --->
::: warning Title
My paragraph with **bold text**
:::
<!--- here must be empty line --->
And our module, example.js, looks as follows:
import { read } from "to-vfile";
import remark from "remark";
import gfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import remarkFlexibleContainers from "remark-flexible-containers";
main();
async function main() {
const file = await remark()
.use(gfm)
.use(remarkFlexibleContainers)
.use(remarkRehype)
.use(rehypeStringify)
.process(await read("example.md"));
console.log(String(file));
}
Now, running node example.js yields:
(The type of the container is also added as a classname into the container node and the title node)
<div class="remark-container warning">
<div class="remark-container-title warning">Title</div>
<p>My paragraph with <strong>bold text</strong></p>
</div>
Without remark-flexible-containers, youβd get:
<p>::: warning Title
My paragraph with <strong>bold text</strong>
:::</p>
As of version ^1.2.0, remark-flexible-containers supports syntax for specific identifiers (tagname, id, classnames, attributes) for both container and title nodes. For example:
::: info {section#foo.myclass} Title {span#baz.someclass @disabled}
<!-- content -->
:::
<section id="foo" class="remark-container info myclass">
<span id="baz" class="remark-container-title info someclass" disabled>Title</span>
<!-- content -->
</section>
For more information, go to detailed explanation.
As of version ^1.4.0, remark-flexible-containers works with more colons (4-colon or 5-colon and more) as well. It requires just the length of the colons is identical at the opening and closing.
::: info Title
<!-- content -->
:::
:::: info Title
<!-- content -->
::::
::::: info Title
<!-- content -->
:::::
As of version ^1.4.0, remark-flexible-containers supports nested containers. It requires just more colons at the outer than the inner.
::::: outer
:::: inner
::: deeper
<!-- content -->
:::
::::
:::::
:::: tab-group Main Title
::: tab First Title
<!-- content -->
:::
::: tab Second Title
<!-- content -->
:::
::::
<details> <summary> elementAll you have to do is that you set a specific type details.
::: details Title
<!-- content -->
:::
<details class="remark-details">
<summary class="remark-summary">Title</summary>
<!-- content -->
</details>
If you don't provide a title, the default is Details for <summary> element.
::: details
<!-- content -->
:::
<details class="remark-details">
<summary class="remark-summary">Details</summary>
<!-- content -->
</details>
If you want <details> element is opened, add specific identifer {@open} attribute just after the type details.
::: details {@open} Title
<!-- content -->
:::
<details class="remark-details" open>
<summary class="remark-summary">Title</summary>
<!-- content -->
</details>
All options are optional and have default values.
interface Properties {
[PropertyName: string]: boolean | number | string | null | undefined | Array<string | number>;
}
type TitleFunction = (type?: string, title?: string) => string | null | undefined;
type TagNameFunction = (type?: string, title?: string) => string;
type ClassNameFunction = (type?: string, title?: string) => string[];
type PropertyFunction = (type?: string, title?: string) => Omit<Properties, 'className'> & { className?: never };
use(remarkFlexibleContainers, {
containerTagName?: string | TagNameFunction; // default is "div"
containerClassName?: string | ClassNameFunction; // default is "remark-container"
containerProperties?: PropertyFunction;
title?: TitleFunction;
titleTagName?: string | TagNameFunction; // default is "div"
titleClassName?: string | ClassNameFunction; // // default is "remark-container-title"
titleProperties?: PropertyFunction;
} as FlexibleContainerOptions);
containerTagNameIt is a string or a callback (type?: string, title?: string) => string option for providing custom HTML tag name for the container node.
By default, it is div.
use(remarkFlexibleContainers, {
containerTagName: "section";
});
Now, the container tag names will be section.
<section class="...">
<!-- content -->
</section>
The option can take also a callback function, which has two optional arguments type and title, and returns string representing a custom tag name.
use(remarkFlexibleContainers, {
containerTagName: (type, title) => {
return type === "details" ? "details" : "div";
}
});
Now, the container tag names will be div or details. It is a good start for creating details-summary HTML elements in markdown.
::: details Title
<!-- content -->
:::
::: warning Title
<!-- content -->
:::
<details class="...">
<!-- content -->
</details>
<div class="...">
<!-- content -->
</div>
containerClassNameIt is a string or a callback (type?: string, title?: string) => string[] option for providing custom class name for the container node.
By default, it is remark-container, and all container nodes' classnames will contain remark-container.
A container node contains also a secondary class name representing the type of the container, like warning or info. If there is no type of the container, then the secondary class name will not present.
::: danger Title
<!-- content -->
:::
<div class="remark-container danger">
<!-- content -->
</div>
use(remarkFlexibleContainers, {
containerClassName: "custom-container";
});
Now, the container nodes will have custom-container as a className, and the secondary class names will be the type of the container, if exists.
::: danger Title
<!-- content -->
:::
<div class="custom-container danger">
<!-- content -->
</div>
The option can take also a callback function, which has two optional arguments type and title, and returns array of strings representing class names.
use(remarkFlexibleContainers, {
containerClassName: (type, title) => {
return [`remark-container-${type ?? "note"}`]
};
});
Now, the container class names will contain only one class name like remark-container-warning, remark-container-note etc.
:::
<!-- content -->
:::
<div class="remark-container-note">
<!-- content -->
</div>
[!WARNING] If you use the
containerClassNameoption as a callback function, it is your responsibility to define class names, add the type of the container into class names etc. in an array.
containerPropertiesIt is a callback (type?: string, title?: string) => Record<string, unknown> & { className?: never } option to set additional properties for the container node.
The callback function that takes the type and the title as optional arguments and returns object which is going to be used for adding additional properties into the container node.
The className key is forbidden and effectless in the returned object.
use(remarkFlexibleContainers, {
containerProperties(type, title) {
return {
["data-type"]: type,
["data-title"]: title,
};
},
});
Now, the container nodes which have a type and a title will contain data-type and data-title properties.
::: danger Title
<!-- content -->
:::
<div class="remark-container danger" data-type="danger" data-title="Title">
<!-- content -->
</div>
titleIt is a callback (type?: string, title?: string) => string | null | undefined option to set the title with a callback function.
remark-flexible-containers adds a title node normally if a title is provided in markdown.
::: danger Title
<!-- content -->
:::
<div class="remark-container danger">
<div class="remark-container-title">Title</div>
<!-- content -->
</div>
if the callback function returns null title: () => null, the plugin will not add the title node.
use(remarkFlexibleContainers, {
title: () => null,
});
<div class="remark-container danger">
<!-- There will be NO title node -->
<!-- content -->
</div>
On the other hand, the option title: () => null doesn't affect if the type is details, so <summary> element will have definetly a value.
The callback function takes the type and the title as optional arguments and returns string | null | undefined. For example if there is no title you would want the title is the type of the container as a fallback.
use(remarkFlexibleContainers, {
title: (type, title) => {
return title ?? type ? type.charAt(0).toUpperCase() + type.slice(1) : "Fallback Title";
},
});
::: info Title
<!-- content -->
:::
::: info
<!-- content -->
:::
:::
<!-- content -->
:::
<div class="remark-container info">
<div class="remark-container-title info">Title</div>
<!-- content -->
</div>
<div class="remark-container info">
<div class="remark-container-title info">Info</div>
<!-- content -->
</div>
<div class="remark-container">
<div class="remark-container-title">Fallback Title</div>
<!-- content -->
</div>
titleTagNameIt is a string or a callback (type?: string, title?: string) => string option for providing custom HTML tag name for the title node.
By default, it is div.
use(remarkFlexibleContainers, {
titleTagName: "span";
});
Now, the title tag names will be span.
<div class="...">
<span class="...">Title</span>
<!-- content -->
</div>
The option can take also a callback function, which has two optional arguments type and title, and returns string representing a custom tag name.
use(remarkFlexibleContainers, {
containerTagName: (type, title) => {
return type === "details" ? "details" : "div";
},
titleTagName: (type, title) => {
return type === "details" ? "summary" : "span";
}
});
Now, the container tag names will be span or summary. It is a good complementary for creating details-summary HTML elements in markdown.
::: details Title
<!-- content -->
:::
::: warning Title
<!-- content -->
:::
<details class="...">
<summary class="...">Title</summary>
<!-- content -->
</details>
<div class="...">
<span class="...">Title</span>
<!-- content -->
</div>
titleClassNameIt is a string or a callback (type?: string, title?: string) => string[] option for providing custom class name for the title node.
By default, it is remark-container-title, and all title nodes' classnames will contain remark-container-title.
A title node contains also a secondary class name representing the type of the container, like warning or info. If there is no type of the container, then the secondary class name will not present.
::: danger Title
<!-- content -->
:::
<div class="...">
<div class="remark-container-title danger">Title</div>
<!-- content -->
</div>
use(remarkFlexibleContainers, {
titleClassName: "custom-container-title";
});
Now, the title nodes will have custom-container-title as a className, and the secondary class names will be the type of the container, if exists.
::: danger Title
<!-- content -->
:::
<div class="...">
<div class="custom-container-title danger">Title</div>
<!-- content -->
</div>
The option can take also a callback function, which has two optional arguments type and title, and returns array of strings representing class names.
use(remarkFlexibleContainers, {
titleClassName: (type, title) => {
return type ? [`container-title-${type}`] : ["container-title"]
};
});
Now, the container class names will contain only one class name like container-title-warning, container-title etc.
::: tip Title
<!-- content -->
:::
:::
<!-- content -->
:::
<div class="...">
<div class="container-title-tip">Title</div>
<!-- content -->
</div>
<div class="...">
<!-- No title node because there is no title in the second container
and no fallback title with `title` option -->
<!-- content -->
</div>
[!WARNING] If you use the
titleClassNameoption as a callback function, it is your responsibility to define class names, add the type of the container into class names etc. in an array.
titlePropertiesIt is a callback (type?: string, title?: string) => Record<string, unknown> & { className?: never } option to set additional properties for the title node.
The callback function that takes the type and the title as optional arguments and returns object which is going to be used for adding additional properties into the title node.
The className key is forbidden and effectless in the returned object.
use(remarkFlexibleContainers, {
titleProperties(type, title) {
return {
["data-type"]: type,
};
},
});
Now, the title nodes which have a type will contain data-type property.
::: danger Title
<!-- content -->
:::
<div class="...">
<div class="..." data-type="danger">Title</div>
<!-- content -->
</div>
::: info The Title of Information
Some information
:::
use(remarkFlexibleContainers);
is going to produce as default:
<div class="remark-container info">
<div class="remark-container-title info">The Title of Information</div>
<p>Some information</p>
</div>
() => nulluse(remarkFlexibleContainers, {
title: () => null,
});
is going to produce the container without title node (even if the the title is provided in markdown):
<div class="remark-container info">
<p>Some information</p>
</div>
use(remarkFlexibleContainers, {
containerTagName: "section",
containerClassName: "remark-custom-wrapper",
containerProperties(type, title) {
return {
["data-type"]: type,
["data-title"]: title,
};
},
title: (type, title) => title ? title.toUpperCase() : "Fallback Title";
titleTagName: "span",
titleClassName: "remark-custom-wrapper-title",
titleProperties: (type, title) => {
["data-type"]: type,
},
});
is going to produce the container section element like below:
<section class="remark-custom-wrapper info" data-type="info" data-title="The Title of Information">
<span class="remark-custom-wrapper-title info" data-type="info">THE TITLE OF INFORMATION</span>
<p>Some information</p>
</section>
use(remarkFlexibleContainers, {
containerTagName(type) {
return type === "details" ? "details" : "section";
},
containerClassName(type) {
return type === "details" ? ["remark-details"] : ["remark-container", type ?? ""];
},
titleTagName(type) {
return type === "details" ? "summary" : "span";
},
titleClassName(type) {
return type === "details" ? ["remark-summary"] : ["remark-container-title", type ?? ""];
},
});
With above options you can create details-summary HTML element in addition to containers easily.
::: details Title
Some information
:::
::: warning Title
Some information
:::
<details class="remark-details">
<summary class="remark-summary">Title</summary>
<p>Some information</p>
</details>
<section class="remark-container warning">
<span class="remark-container warning">Title</span>
<p>Some information</p>
</section>
You can use the plugin syntax without providing a type and a title.
:::
Some information
:::
It will not add a title node since it is not provided (assume that title option is not provided for a fallback title, as well), and it will also not add the type as a classname into the container:
<div class="remark-container">
<p>Some information</p>
</div>
The flexible container can contain also HTML elements and MDX components:
::: tip Title
This package is so _**cool** and **flexible**_
::it does not confuse with double colons::
==marked text via plugin `remark-flexible-markers`==
<mark>marked text via HTML element in markdown</mark>
<MarkRed>marked text via custom marker in support of MDX</MarkRed>
<details>
<summary>HTML tag works too</summary>
<p>I am working</p>
</details>
other paragraph *italic content* and,
some **bold content** without stress
:::
remark-flexible-containers supports syntax for specific identifiers (tagname, id, classnames, attributes) for both container and title nodes. For example:
::: info {section#foo.myclass @open @data-type=expandable} Title Of Information {span#baz.someclass}
<!-- content -->
:::
<section id="foo" class="remark-container info myclass" open data-type="expandable">
<span id="baz" class="remark-container-title info someclass">
Title Of Information
</span>
<!-- content -->
</section>
The identifiers (tagname, id, classnames, attributes) must be inside curly braces.
Syntax is very simple.
tagname is to be compatible HTML tag name, and may present only once,id is to start with hash #, and may present only once,classnames are to start with dot ., and may present many.attributes are to start with at sign @, and may present many, can be either bare / boolean attribute or key=value attribute.There are two groups of identifiers. Each group is optional, may present or not.
type) is for container node.title) is for title node.Here are some example usages. For simplicity, I omitted the container contents and ending syntax, just put the beginning syntax in the examples. All are valid usage for specific identifiers.
[!TIP] These identifiers can be placed as all, any three, any two, or just any of them in the desired order, with or without a space between them. This is why the "flexibility" term comes from.
However, make sure that no character other than a space appears before at sign (
@), because if you are using a package likeremark-gfm, it may interpret something like{span@open.someclass}as an autolink. For this reason, you should insert a space before at sign@if necessary.
::: info {section#foo.myclass.second-class} Title {span#baz.someclass.other-class}
::: info {section#foo.myclass} Title {span#baz.someclass}
::: info {section #foo .myclass .second-class} Title {span #baz .someclass .other-class}
::: info {section #foo .myclass} Title {span #baz .someclass}
::: info {section.myclass#foo} Title {span.someclass#baz}
::: info {.myclass#foo} Title {.someclass#baz}
::: info {.myclass #foo} Title {.someclass #baz}
::: info {.myclass #foo section} Title {.someclass #baz span}
::: info {#foo section} Title {#baz span}
::: info {.myclass} Title {#baz}
::: info {#foo} Title {.someclass}
::: info {#foo} Title
::: info {#foo}
::: info {section#foo.myclass}
::: info Title {.someclass}
::: info Title {span#baz.someclass}
::: info Title {#baz @data-attr=something}
::: info Title {span#baz @open}
::: info Title {@open span#baz}
::: details {@open}
::: details {@open} Title {.someclass}
You should consider that specific identifiers for title breaks the option title: () => null, and the title will take place for that individual container.
Another consideration is that if the content is MDX format, you may need to escape the opening curly braces, otherwise mdx parser of your integration may interpret curly braces as a MDX expression.
::: info \{#foo} Title \{.someclass}
<!-- content -->
:::
::: details \{@open} Summary Title
<!-- content -->
:::
This plugin only modifies the mdast (markdown abstract syntax tree) as explained.
This package is fully typed with TypeScript. The plugin options' type is exported as FlexibleContainerOptions.
This plugin works with unified version 6+ and remark version 7+. It is compatible with mdx version 2+.
Use of remark-flexible-containers does not involve rehype (hast) or user content so there are no openings for cross-site scripting (XSS) attacks.
I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to have a look my plugins.
If you find remark-flexible-containers or any of my projects is useful and helpful, please consider supporting my work. Your sponsorship means a lot to me and keeps these projects alive and updated! π
My sponsors are going to be featured at the very top of the page and proudly displayed on my Sponsor Wall.
Thank you for supporting open source! π
remark-flexible-code-titles
β Remark plugin to add titles or/and containers for the code blocks with customizable propertiesremark-flexible-containers
β Remark plugin to add custom containers with customizable properties in markdownremark-ins
β Remark plugin to add ins element in markdownremark-flexible-paragraphs
β Remark plugin to add custom paragraphs with customizable properties in markdownremark-flexible-markers
β Remark plugin to add custom mark element with customizable properties in markdownremark-flexible-toc
β Remark plugin to expose the table of contents via vfile.data or via an option referenceremark-mdx-remove-esm
β Remark plugin to remove import and/or export statements (mdxjsEsm)remark-mdx-remove-expressions
β Remark plugin to remove MDX expressions within curlybraces {} in MDX contentrehype-pre-language
β Rehype plugin to add language information as a property to pre elementrehype-highlight-code-lines
β Rehype plugin to add line numbers to code blocks and allow highlighting of desired code linesrehype-code-meta
β Rehype plugin to copy code.data.meta to code.properties.metastringrehype-image-toolkit
β Rehype plugin to enhance Markdown image syntax ![]() and Markdown/MDX media elements (<img>, <audio>, <video>) by auto-linking bracketed or parenthesized image URLs, wrapping them in <figure> with optional captions, unwrapping images/videos/audio from paragraph, parsing directives in title for styling and adding attributes, and dynamically converting images into <video> or <audio> elements based on file extension.recma-mdx-escape-missing-components
β Recma plugin to set the default value () => null for the Components in MDX in case of missing or not provided so as not to throw an errorrecma-mdx-change-props
β Recma plugin to change the props parameter into the _props in the function _createMdxContent(props) {/* */} in the compiled source in order to be able to use {props.foo} like expressions. It is useful for the next-mdx-remote or next-mdx-remote-client users in nextjs applications.recma-mdx-change-imports
β Recma plugin to convert import declarations for assets and media with relative links into variable declarations with string URLs, enabling direct asset URL resolution in compiled MDX.recma-mdx-import-media
β Recma plugin to turn media relative paths into import declarations for both markdown and html syntax in MDX.recma-mdx-import-react
β Recma plugin to ensure getting React instance from the arguments and to make the runtime props {React, jsx, jsxs, jsxDev, Fragment} is available in the dynamically imported components in the compiled source of MDX.recma-mdx-html-override
β Recma plugin to allow selected raw HTML elements to be overridden via MDX components.recma-mdx-interpolate
β Recma plugin to enable interpolation of identifiers wrapped in curly braces within the alt, src, href, and title attributes of markdown link and image syntax in MDX.I also build low-level utilities and plugins for the Unified ecosystem that can be used across Remark, Rehype, Recma, and other unist-based abstract syntax trees (ASTs).
unist-util-find-between
β Unist utility to find the nodes between two nodes.unified-log-tree
β Unified plugin to log abstract syntax trees (ASTs) for debugging without mutating.MIT License Β© ipikuka
154 commits
1 commits
TypeScript
98.5%
JavaScript
1.5%