Sometimes you really need a span
A Python-Markdown extension that enables inline <span> elements with attribute lists:
[content]{#id .class key="value"}- Wraps inline content in
<span>elements with appended attributes - Fully compatible with Python-Markdown base and extended syntax, including:
- Inline links
- Reference links
- Wikilinks
- Supports nested spans, links, and nesting inside links
- Renders correctly HTML elements inside attributes (e.g., for MkDocs Material tooltips)
- No parsing overhead — delegates attribute handling to the
attr_listextension
$ pip install markdown-span-attrEnable alongside attr_list in Python:
import markdown
md = markdown.Markdown(extensions=['attr_list', 'span_attr'])Or in mkdocs.yml:
markdown_extensions:
- attr_list
- span_attr[content]{#id .class key="value"}Renders as:
<span class="class" id="id" key="value">content</span>See the official extension for attribute lists syntax.
[horace]: #
[[Horace][horace]{.p} is [daring](#quint1){.cit}]{#s1} # nested reference/inline links
[[audax]{.foreign} [daring]]{#s2} # nested spans and unescaped brackets
[[[Poets]] daring in [[words]]{title="Words"}]{#s3} # wikilinksRenders as:
<span id="s1"><a class="p" href="#">Horace</a> is <a class="cit" href="#quint1">daring</a></span>
<span id="s2"><span class="foreign">audax</span> [daring]</span>
<span id="s3"><a class="wikilink" href="/Poets/">Poets</a> daring in <a class="wikilink" href="/words/" title="Words">words</a></span>[transilire lineas impune]{: .q title="Varro, <em>De Lingua Latina</em> IX 5" }Renders as:
<span class="q" title="Varro, <em>De Lingua Latina</em> IX 5">transilire lineas impune</span>Many would say that supporting arbitrary <span> elements in Markdown syntax goes against Markdown philosophy, and perhaps it is true. But sometimes you really need a readable span in Markdown, and decadence is inescapable, anyway. So at least be safe with span_attr not to break compatibility with other extensions.
The extension uses a greedy-safe regular expression to match [content]{: attr-list} spans, supporting nesting while avoiding premature matches:
\[([^\[\]]*|(?:[^\[\]]*\[[^\[\]]*\](?!\{)[^\[\]]*)*)\](?=\{\:?[ ]*([^\}\n ][^\n]*)[ ]*\})What it does:
- Matches the innermost span first to support nesting (no other attribute span is allowed inside).
- Allows only content with none or balanced square brackets.
- Uses a lookahead to ensure a valid
{}attribute list follows.
It matches only [content], while attribute parsing is delegated to attr_list.
The processor is registered with priority 72. This is chosen to:
- Run after
wikilinks(75), so links are parsed before wrapping them in spans. - Run before
attr_list(8), so<span>elements are inserted beforeattr_listdecorates them.
This placement ensures the extension is compatible with link handling and HTML escaping.
Inline Processors Priority Table (source)
| Priority | Pattern Name | Description |
|---|---|---|
| 190 | backtick |
Code spans |
| 180 | escape |
Backslash escapes |
| 175 | footnotes* |
Footnote references |
| 170 | reference |
Reference-style links |
| 160 | link |
Inline links |
| 150 | image_link |
Inline images |
| 140 | image_reference |
Reference-style images |
| 130 | short_reference |
Shortcut reference-style links |
| 125 | short_image_ref |
Shortcut reference-style images |
| 120 | autolink |
Automatic links |
| 110 | automail |
Automatic email links |
| 100 | linebreak |
Hard line breaks |
| 90 | html |
Inline HTML |
| 80 | entity |
HTML entities |
| 75 | wikilinks* |
Wiki-style links |
| 72 | span_attr* |
(this extension) |
| 70 | not_strong |
Prevent misparsed emphasis |
| 60 | em_strong |
*-style emphasis |
| 50 | em_strong2 |
_-style emphasis |
| 8 | attr_list* |
Attribute lists |
| 7 | abbr* |
Abbreviations |
*: Extension