npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

markdown-it-container-details-heading

v1.0.2

Published

Plugin to add heading to summary of details in markdown-it-container that to show it in the outline or contents

Downloads

45

Readme

markdown-it-container-details-heading

npm GitHub License: MIT

A markdown-it plugin that adds headings to the <summary> element inside <details> containers, so they appear in the page outline and table of contents.

Why Use This Plugin?

This plugin is especially important for FAQ pages.

In a typical FAQ page, each question is placed inside a <details> element so that only the questions are visible when the page loads, and users click on a question to expand the answer. However, since the question text sits directly inside a <summary> element rather than a proper heading element (<h1><h6>), it suffers from two major discoverability problems:

  1. Missing from the outline / table of contents. The page outline (or TOC sidebar) only lists heading elements. Because a plain <summary> is not a heading, FAQ questions are invisible in the outline. Users cannot scan or jump directly to a specific question from the outline.

  2. Search won't take you there. Document search features (such as VitePress's built-in search or Algolia) typically match and navigate to heading elements, not body text. Even if a search query matches the text inside a <summary>, the page will only scroll to the nearest parent heading above the <details> block — not to the question itself. The user is left scanning the page manually to find the answer they searched for.

With this plugin, you can optionally designate the summary text as a heading by prefixing it with markdown heading syntax (##, ###, etc.). This injects a real heading element inside the <summary>, making it visible in the outline and directly targetable by search — without changing the visual appearance.

[!IMPORTANT]
Using a heading inside <summary> is entirely opt-in. If you omit the # prefix, the plugin falls back to the original <details> / <summary> behavior. This means you can use heading behavior on FAQ pages where it matters, while keeping the default behavior everywhere else.

Installation

# npm
npm install markdown-it-container-details-heading

# yarn
yarn add markdown-it-container-details-heading

# pnpm
pnpm add markdown-it-container-details-heading

Usage

import markdownit from "markdown-it";
import containerPlugin from "markdown-it-container";
import containerDetailsHeadingPlugin from "markdown-it-container-details-heading";

const md = markdownit();

// The `markdown-it-container` plugin is required as a dependency.
md.use(containerPlugin);
// Register this plugin after `markdown-it-container` and `markdown-it-attrs` (if used).
md.use(containerDetailsHeadingPlugin, {
  /* options */
});

With VitePress

// .vitepress/config.js
import containerDetailsHeadingPlugin from "markdown-it-container-details-heading";

export default {
  markdown: {
    config: md => {
      md.use(containerDetailsHeadingPlugin);
    },
  },
};

How It Works

Inside your markdown files, use the details container as usual. Prefix the summary text with markdown heading syntax (# through ######) to enable heading behavior:

Without heading (original behavior — no heading element, stays as-is):
::: details Plain Summary Text
Content goes here.
:::

With heading (summary becomes <h2> with an id):
::: details ## I Am a Level-2 Heading
Content goes here.
:::

The heading level you specify determines the HTML tag used (#<h1>, ##<h2>, ..., ######<h6>). The heading text is automatically slugified into an id attribute, turning the heading into a navigable anchor.

Rendered HTML

<!-- Without heading: original behavior preserved -->
<details class="details custom-block">
  <summary>Plain Summary Text</summary>
  <p>Content goes here.</p>
</details>

<!-- With heading: a heading element is injected -->
<details class="details custom-block details-h2">
  <summary><h2 id="i-am-a-level-2-heading">I Am a Level-2 Heading</h2></summary>
  <p>Content goes here.</p>
</details>

Markdown Inside Headings

Inline markdown syntax (bold, strikethrough, code, etc.) inside the heading text is rendered correctly:

::: details ### ~~This Is Outdated~~ and **Still Useful**
Content
:::

::: details #### Why Can't `process.env` Get Variables?
Content
:::

Custom ID

You can manually specify an id using the markdown-it-attrs syntax. When provided, it takes precedence over the auto-generated slug:

::: details ## Custom Title {#my-custom-id}
Content
:::

Options

All class options can be either a string or a callback function that receives the heading level and text:

type ContainerDetailsHeadingPluginClassHandler = (
  headingLevel: number | null,
  headingText: string | null,
  rawText: string,
) => string;

[!IMPORTANT]
If the <summary> of a <details> element is not a heading, the headingLevel and headingText parameter will be null.
So you have to manually handle them to make sure that the class(es) of a normal <details> element should work properly.

You can import the type of the plugin options and the callback function from the package if you are using TypeScript.

detailsClass

  • Type: string | ContainerDetailsHeadingPluginClassHandler
  • Default:
    • Heading Details: `details custom-block h${headingLevel}`
    • Normal Details: "details custom-block"

CSS class(es) for the <details> element. The default value matches the VitePress default theme, so the plugin works out of the box with VitePress. If you do not need this default class, simply set this option to an empty string ("").

// Remove the default class:
md.use(containerDetailsHeadingPlugin, { detailsClass: "" });

// Use a custom static class:
md.use(containerDetailsHeadingPlugin, { detailsClass: "my-details" });

// Compute the class dynamically:
md.use(containerDetailsHeadingPlugin, {
  detailsClass: (level, text, raw) =>
    level ? `custom-details h${level}` : "custom-details",
});

summaryClass

  • Type: string | ContainerDetailsHeadingPluginClassHandler
  • Default: ""

CSS class(es) for the <summary> element.

headingClass

  • Type: string | ContainerDetailsHeadingPluginClassHandler
  • Default: ""

CSS class(es) for the heading element (<h1><h6>) inside the summary.

Compatibility with markdown-it-attrs

This plugin is fully compatible with markdown-it-attrs. You can add custom attributes to the <details> element using the {...} syntax. When a heading is used, the id attribute is automatically moved from the <details> to the heading element, while all other attributes remain on the <details>:

::: details ## Custom Title {#my-custom-id .additional-class data-custom-attr="true" open}
Content
:::
<details class="additional-class details custom-block details-h2" data-custom-attr="true" open>
  <summary><h2 id="my-custom-id">Custom Title</h2></summary>
  <p>Content</p>
</details>

Improved Theme Styles

This package ships with an improved CSS theme for the <details> element that looks better than VitePress's default styling:

import "markdown-it-container-details-heading/vitepress-theme.css";

The theme features:

  • Smooth expand/collapse animation with a rotating chevron icon.
  • Clean divider lines between adjacent details blocks.
  • Hover effects on the summary and border.
  • Headings inside summaries are visually reset to inherit from the summary, so they blend in seamlessly.

You can use this stylesheet as-is or use it as a starting point for your own custom styles.

Nested Details

Nested <details> blocks are fully supported:

:::: details ## Parent Heading
Parent content
::: details ### Child Heading
Child content
:::
::::

License

MIT