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 🙏

© 2025 – Pkg Stats / Ryan Hefner

remark-directive-mdx

v0.1.1

Published

Remark plugin to transform directives to MDX JSX elements

Downloads

227

Readme

remark-directive-mdx

remark plugin to integrate remark-directive with MDX.

What is this?

This package is a unified (remark) plugin that converts Markdown directives to MDX JSX elements. Markdown directives are first parsed with remark-directive, which needs to be used before this plugin.

unified is a project that transforms content with abstract syntax trees (ASTs). remark adds support for Markdown to unified, and has a rich plugin ecosystem. MDX enables the use of JSX components inside Markdown, and uses remark internally. This plugin operates on the Markdown AST used by remark, known as mdast.

When should I use this?

This package is useful when you are using MDX and want to turn Markdown directives parsed by remark-directive into MDX JSX components.

Additionally, this plugin can work with any application that uses remark-rehype and rehype-recma. If that doesn't mean anything to you, it's probably fine to ignore it.

Install

This package is ESM only. In Node.js (version 16+), install with npm or pnpm:

npm install remark-directive-mdx # or
pnpm add remark-directive-mdx

In Deno with esm.sh:

import remarkDirectiveMdx from 'https://esm.sh/[email protected]'

In browsers with esm.sh:

<script type="module">
  import remarkDirectiveMdx from 'https://esm.sh/[email protected]'
</script>

Usage

Given the following file, example.mdx:

import CustomList from './CustomList.jsx'
import ListItem from './ListItem.jsx'
import Emphasis from './Emphasis.jsx'

:::CustomList{type=unordered bulletType=square}
::ListItem[The :Emphasis[unified] collective is an ecosystem that deals with content via ASTs.]{first}
::ListItem[:Emphasis[remark] adds support for Markdown to unified.]
::ListItem[:Emphasis[MDX] leverages remark to bring JSX support to Markdown.]
:::

And the following example.mjs:

import { compile } from '@mdx-js/mdx'
import remarkDirective from 'remark-directive'
import remarkDirectiveMdx from 'remark-directive-mdx'
import { read } from 'to-vfile'
import { format } from 'prettier'

// Compile MDX to JSX
const compiled = await compile(await read('./example.mdx'), {
  remarkPlugins: [remarkDirective, remarkDirectiveMdx],
  jsx: true,
})

// Format the JSX
const formatted = await format(String(compiled), { parser: 'acorn' })

console.log(formatted)

Running node example.mjs yields (only relevant parts shown):

// -- snip -- //
import CustomList from './CustomList.jsx'
import ListItem from './ListItem.jsx'
import Emphasis from './Emphasis.jsx'
function _createMdxContent(props) {
  return (
    <CustomList type="unordered" bulletType="square">
      <ListItem first="">
        {'The '}
        <Emphasis>{'unified'}</Emphasis>
        {' collective is an ecosystem that deals with content via ASTs.'}
      </ListItem>
      <ListItem>
        <Emphasis>{'remark'}</Emphasis>
        {' adds support for Markdown to unified.'}
      </ListItem>
      <ListItem>
        <Emphasis>{'MDX'}</Emphasis>
        {' leverages remark to bring JSX support to Markdown.'}
      </ListItem>
    </CustomList>
  )
}
// -- snip -- //

API & Configuration

This package is fully typed. Use TypeScript for ideal results.

We provide some configuration options. Markdown directives were initially designed to express arbitrary HTML (if you want to convert directives to HTML, not JSX, use remark-directive-rehype). There are many subtle differences between JSX and HTML, and some configuration options are intended to bridge this gap.

function remarkDirectiveMdx

Type: unified.Plugin<[Option?], mdast.Root>

This is the default export and exposes a unified (remark) plugin. It optionally accepts one argument of type Options, which we will describe below.

interface Options

This is the means to configure this plugin. It has the following fields:

skipTransformed?: boolean (default: true)

If true, skip transforming any directives that are already transformed for remark-rehype (i.e. have their data.hName field set) by preceding plugins.

filter?: (node: mdast.Parent) => boolean

If supplied, the plugin additionally uses this function to filter out directives it will not transform.

handleLabel?: (node: mdx.MdxJsxFlowElement, label: mdast.PhrasingContent[]) => void

Markdown container directives have an optional "label" part that can accept arbitrary inline Markdown. This does not map cleanly to JSX:

:::Directive[this is the **label**]
... Content ...
:::

By default, this plugin drops the label entirely:

function _createMdxContent(props) {
  return (
    <Directive>
      <p>{'... Content ...'}</p>
    </Directive>
  )
}

However, some JSX frameworks might have a "slots" mechanism that allows you to include this label in a dedicated slot. Astro is such an example. In this case, you can set handleLabel to the astroHandleLabel function that this plugin provides, which produces this output instead:

function _createMdxContent(props) {
  return (
    <Directive>
      <p>{'... Content ...'}</p>
      <Fragment slot="label">
        {'this is the '}
        <strong>{'label'}</strong>
      </Fragment>
    </Directive>
  )
}

You can also write a custom function for this field to handle the label however you like. This function should modify node in-place, or use Object.assign() to replace the node.

transformTag?: (tag: string) => string

If provided, this function transforms the directive name before making it a JSX tag. This could be useful if e.g. you want to use kebab-case in your directives, but your components come in PascalCase (which is the prevalent convention). We provide a normalizeTag function that normalizes all non-HTML tags to a specific casing convention.

transformAttribute?: (tag: string, attr: string) => string

If provided, this function transforms the attribute names from directives before putting them into JSX tags. The transformation can also depend on the (transformed) tag name.

This is useful if you want to use kebab-case for your attributes but your JSX framework uses camelCase for all attributes, for example React and Solid. For frameworks that mix kebab-case and camelCase (e.g. Astro, Svelte), you need to be very careful if you use this field.

It is also useful for this specific scenario: remark-directive always translates the class shorthand {.myclass} to class="myclass". If you use React, you can use this function to transform all class to className.

We provide a normalizeAttribute function that normalizes all attributes to a specific casing convention, and optionally transforms class to className.

function astroHandleLabel

Type: (node: mdx.MdxJsxFlowElement, label: mdast.PhrasingContent[]) => void

Pass this function to Options.handleLabel to instruct the plugin to append the label of a container directive as a fragment in the label slot. Only works with @astrojs/mdx.

function normalizeTag

Type: (casing: Casing = 'kebab') => (tag: string) => string

Calling this function returns a function that you can pass to Options.transformTag. I.e.:

{
  transformTag: normalizeTag(),
}

By default, this normalizes all HTML tags to lowercase, and all non-HTML tags to PascalCase. Optionally, provide a specific casing convention for non-HTML tags:

{
  transformTag: normalizeTag('kebab'),
}

function normalizeAttribute

Type: (casing: Casing = 'kebab', {className = false} = {}) => (tag: string, attr: string) => string

Calling this function returns a function that you can pass to Options.transformAttribute. I.e.:

{
  transformAttribute: normalizeAttribute(),
}

By default, this normalizes all attributes to camelCase. Optionally, provide a specific casing convention. Also optionally, instruct it to transform class to className:

{
  transformAttribute: normalizeAttribute('kebab'), // or
  transformAttribute: normalizeAttribute('camel', { className: true })
}

type Casing

The casing conventions that normalizeTag and normalizeAttribute support. Available values are:

  • 'pascal', 'kebab', 'camel', 'snake';
  • 'none' for leaving the input as-is.

Internally they use tiny-case.

Testing & Contribution

This plugin is unit tested, as well as tested on Astro. We appreciate you testing this plugin on other JSX frameworks and submitting a bug report if it doesn't work there.

Contributions are welcome for use cases that could be reasonably supported by this plugin.

License

This package is provided under the permissive BSD 3-Clause "New" or "Revised" License (BSD-3-Clause).