marked-attributes
v1.0.0
Published
Add custom HTML attributes to any marked token
Maintainers
Readme
marked-attributes
A marked.js extension that allows you to add custom HTML attributes (including CSS classes, data attributes, IDs, etc.) to tokens during the rendering process.
Why Use This?
- Programmatic Control: Add attributes dynamically based on token properties or external conditions data—without modifying your markdown source
- Dynamic Logic: Generate IDs from counters, add classes based on heading depth, inject tracking attributes conditionally
- Simpler Than Custom Renderers: No need to override 20+ renderer methods manually; works automatically for all token types
- Clean Markdown: Keep your markdown portable and readable without inline syntax like
{.class #id}required
Installation
npm install marked-attributesBasic Setup
import { marked } from 'marked'
import { markedAttributes } from 'marked-attributes'
// Enable attribute rendering
marked.use(markedAttributes())
// Now use marked as normal
const html = marked.parse('**bold text**')Adding Attributes with walkTokens
The simplest way to add attributes is using marked's walkTokens hook:
import { marked } from 'marked'
import { markedAttributes } from 'marked-attributes'
marked.use(markedAttributes())
// Add unique IDs to all tokens
let idCounter = 0
marked.use({
walkTokens(token) {
token.attributes = {
'data-token-id': `token-${++idCounter}`
}
}
})
const html = marked.parse('This is **bold** text')<p data-token-id="token-1">This is <strong data-token-id="token-2">bold</strong> text</p>TypeScript Support
Import and use the TokenWithAttributes for type assertion:
import { marked } from 'marked'
import { markedAttributes, type TokenWithAttributes } from 'marked-attributes'
marked.use(markedAttributes())
marked.use({
walkTokens(token: TokenWithAttributes) {
token.attributes = { 'class': 'my-class' } // ✓ TypeScript recognizes .attributes
}
})Usage Examples
Dynamic Attributes Based on Token Properties
marked.use(markedAttributes())
let headingCounter = 0
marked.use({
walkTokens(token) {
// Dynamic IDs for headings
if (token.type === 'heading') {
headingCounter++
token.attributes = {
'id': `heading-${headingCounter}`,
'class': `level-${token.depth}`,
'data-level': token.depth.toString()
}
}
// Add analytics tracking to external links only
if (token.type === 'link' && token.href.startsWith('http')) {
token.attributes = {
'data-analytics-action': 'click',
'data-analytics-label': token.href,
'rel': 'noopener noreferrer',
'target': '_blank'
}
}
}
})
const html = marked.parse('# Title\n\nVisit [example](https://example.com)')Output:
<h1 id="heading-1" class="level-1" data-level="1">Title</h1>
<a href="..." data-analytics-action="click" rel="noopener noreferrer" target="_blank">example</a>Hierarchical Token IDs
Useful for contenteditable markdown editors to track cursor positions:
marked.use(markedAttributes())
const addHierarchicalIds = (tokens, prefix = '') => {
tokens.forEach((token, index) => {
const id = prefix ? `${prefix}.${index + 1}` : `${index + 1}`
token.attributes = { 'data-token-id': id }
if (token.tokens) {
addHierarchicalIds(token.tokens, id)
}
})
}
const tokens = marked.lexer('This is **bold and _italic_** text')
addHierarchicalIds(tokens)
const html = marked.parser(tokens)
// Outputs hierarchical IDs like: 1, 1.1, 1.1.1, etc.Use Cases
// CSS Styling: Custom classes for headings, blockquotes, code blocks
if (token.type === 'heading') token.attributes = { 'class': `heading-${token.depth}` }
if (token.type === 'blockquote') token.attributes = { 'class': 'callout-box' }
// Contenteditable Editors: Track cursor positions
token.attributes = { 'data-token-id': generateUniqueId() }
// E2E Testing: Stable test IDs
token.attributes = { 'data-testid': `${token.type}-${index}` }
// Analytics: Track interactions
if (token.type === 'link') token.attributes = { 'data-analytics-action': 'click' }
// Accessibility: ARIA attributes
if (token.type === 'heading') token.attributes = { 'aria-level': token.depth.toString() }
// Interactive Features: Data attributes for JS
token.attributes = { 'data-interactive': 'true', 'data-component': 'markdown-element' }Credits
Wanting to implement post-render handlers and processes, and this helpful dicussion.
