Brewdown
Brewdown is the markdown converter that powers this site. It extends standard Markdown with media embedding, spoilers, collapsible sections, interactive forms, timestamps, and template variables. This page documents the syntax used across Coffee Byte Dev and what the converter does with it.
How It Works
Every page on this site loads brewdown.js in its <head> with the defer attribute. On page load, the script scans the DOM for two patterns and rewrites them to HTML in place:
1. A <div class="brewdown"> with markdown inside it. The div's text content is converted; the div gets the additional class brewdown-rendered after processing.
<div class="brewdown">
# Hello World
This is **Brewdown** in action.
</div>
2. A <script> tag with a data-brewdown attribute. Used either with inline markdown or to load an external .md file. The script tag is replaced with a div containing the rendered HTML.
<script type="text/markdown" data-brewdown>
# Hello World
This is **Brewdown** in action.
</script>
<script data-brewdown="path/to/file.md"></script>
<script data-brewdown> tags also work inside a <div class="brewdown">. The script is preserved through the parent div's render pass and processed in a second pass — useful for embedding external chapter files inside a collapsible (>>>) block.
If highlight.js is also on the page, Brewdown calls hljs.highlightAll() after rendering so code blocks pick up syntax colors. This is why highlight.min.js must load before brewdown.js — if it loads after, the check fails and code blocks render plain.
Text Formatting
**bold**
*italic*
~~strikethrough~~
__underline__
`inline code`
Escape Characters
Use \ before any character to prevent Brewdown from processing it.
\*not italic\*
\[not a link\](url)
Inside code blocks, a backslash before a backtick escapes it so it displays literally.
Headings
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
Links
[Link Text](https://example.com)
External links (http/https, magnet, .pdf) automatically open in a new tab. http/https and .pdf links also get a 🔗 indicator prepended; magnet links do not.
Any link whose URL ends in .zip also gets a 💾 indicator prepended (regardless of whether it's external or local). This is independent of 🔗 — an external .zip URL gets both.
External links carry title="Click to follow external link" and .zip links carry title="Click to save" as a hover tooltip. Magnet and internal links carry no title.
Media Embedding
Use image syntax — Brewdown detects the file type by extension and renders the appropriate HTML element.
 -> <img>
 -> <video controls>
 -> <audio controls>
 -> opens in new tab
Image: jpg, jpeg, png, gif, webp, svg, bmp, ico, avif
Video: mp4, webm, ogg, mov
Audio: mp3, wav, flac, aac, m4a
Other: opens in a new tab (browser renders or downloads)
Code Blocks
```javascript
function hello() {
console.log("Hello, world!");
}
```
Works with highlight.js if loaded on the page.
Blockquotes
> This is a blockquote.
> It can span multiple lines.
Tables
| Name | Role |
|--------|--------|
| Alice | Admin |
| Bob | User |
Checkboxes
[ ] Unchecked
[x] Checked
Fill-in Blanks
Three or more underscores create a text input field. An optional placeholder follows the underscores with no spaces; underscores inside the placeholder render as spaces.
___ -> blank input
___hello -> input with placeholder "hello"
___help_me -> input with placeholder "help me"
___for_example_here -> input with placeholder "for example here"
Note: a literal space in the markdown terminates the placeholder. Use underscores to insert spaces.
Horizontal Rules
---
Timestamps
Converts a date to the user's local format with a semantic <time> element.
@@2026.03.28@@ -> March 28, 2026
@@2026.03.28.14.30@@ -> March 28, 2026 at 2:30 PM
Spoilers
Click-to-reveal hidden text.
!!This text is hidden until clicked.!!
Wrapped in <span class="spoiler" title="Click to reveal">. Requires CSS for the .spoiler and .spoiler.revealed classes.
Click-to-Copy
Click the rendered text to copy its content to the clipboard. A 📋 indicator is prepended automatically.
^^npm install foo^^
Wraps content in <span class="copy-text" title="Click to copy this text"> with a click handler that writes data-copy to the clipboard via navigator.clipboard.writeText.
Collapsible Sections
Wraps content in a <details>/<summary> element. The <details> element gets an id auto-slugified from its title, and the title is auto-added to the table of contents (unless the title is empty).
>>> Click to expand
Content goes here.
More content.
<<<
Supports nesting:
>>> Outer
>>> Inner
Nested content.
<<<
<<<
Table of Contents
H1, H2, and H3 headers automatically collect into a table of contents, but the TOC is only rendered when you explicitly write ::toc:: in the source — opt-in, not automatic, so pages with lots of incidental headers (like the changelog) don't sprout unwanted TOCs. Every header (H1-H6) and every >>> collapsible gets an id="..." attribute auto-slugified from its text, so links can scroll to them — but >>> collapsibles are NOT auto-listed in the TOC (use a header or ::label:: above the collapsible if you want a TOC entry that scrolls to it).
::toc::
## First Section
Content here.
## Second Section
More content.
For non-header anchors, wrap any inline label in ::label:: markers. The label gets a span with an ID and is added to the TOC.
Some prose with ::a special anchor:: inline.
Renders TOC as <pre class="brewdown-toc"> with a bold TABLE OF CONTENTS header followed by one anchor per line. H3 and >>> entries are indented two spaces under their parent H2. Clicking any link scrolls to the anchor via native browser behavior.
Template Variables
Placeholders that render as <span> elements with IDs for JavaScript to target.
Server status: {{server-status}}
Set the default text before brewdown.js loads (since brewdown.js is deferred, an inline Brewdown.defaultVar = … in the head would run before Brewdown exists). Use the window.brewdownDefaultVar stash instead — brewdown.js reads it at init:
<script>window.brewdownDefaultVar = 'Loading...';</script>
After brewdown loads, Brewdown.defaultVar = 'text' works as expected from any non-deferred code (e.g. DOMContentLoaded handlers, button clicks).
HTML Passthrough
Raw HTML lines are passed through as-is.
API
Brewdown exposes these methods on the global Brewdown object for code that needs to call it directly (rather than relying on the automatic page-load processing):
// Convert markdown string to HTML
const html = Brewdown.brewdown(markdownText);
// With options
const html = Brewdown.brewdown(markdownText, {
wrapInContainer: true,
containerClass: 'my-class'
});
// Parse inline formatting only
const html = Brewdown.parseInlineFormatting(text);
// Re-process script tags or brewdown divs
Brewdown.processScriptTags();
Brewdown.processBrewdownDivs();
// Set default text for template variables
Brewdown.defaultVar = 'Loading...';