// helpers/alerts.js: a blockquote that starts with [!NOTE] gets class="alert alert-note"
export function alerts(md) {
md.core.ruler.push('alerts', (state) => {
const tokens = state.tokens;
for (let i = 0; i < tokens.length; i++) {
if (tokens[i].type !== 'blockquote_open') continue;
// blockquote_open, paragraph_open, then the paragraph's text
const inline = tokens[i + 2];
const match = inline?.type === 'inline' && inline.content.match(/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]/);
if (!match) continue;
tokens[i].attrJoin('class', `alert alert-${match[1].toLowerCase()}`);
// Remove the marker and the line break after it
const children = inline.children ?? [];
if (children[0]?.type === 'text') {
children[0].content = children[0].content.slice(match[0].length).trimStart();
if (!children[0].content) children.splice(0, children[1]?.type === 'softbreak' ? 2 : 1);
}
}
});
}
// stati.config.ts
import { alerts } from './helpers/alerts.js';
export default defineConfig({
// site and the rest…
markdown: {
configure(md) {
md.use(alerts);
},
},
});
> [!WARNING]
> `stati build --clean` deletes `dist/` and `.stati/`.
That becomes a <blockquote class="alert alert-warning"> without the marker, and the same Markdown still reads right on GitHub. An ordinary quote stays an ordinary quote. The colours, from Sweetie 16:
.alert { border-left: 4px solid; padding: 0.25rem 1rem; }
.alert-note { border-color: #41a6f6; }
.alert-tip { border-color: #38b764; }
.alert-important { border-color: #5d275d; }
.alert-warning { border-color: #ef7d57; }
.alert-caution { border-color: #b13e53; }
In the Stati docs: Creating a plugin and Custom configuration.