Toolium

The Markdown Patterns I Reach For Daily

Hemanth Gedda7 min read

Markdown has a thousand-page spec when you include every flavor and extension, but in practice the patterns you actually use day-to-day are a small set. This post is the working subset I have memorized from years of writing READMEs, blog posts, and chat messages. Plus a story about the XSS bug that I shipped into the Toolium Markdown Preview tool early on and how I fixed it.

The 10 patterns I actually use

1. Headings. One # per heading level. Use them for document structure. Most renderers use the H1 as the page title; subsequent headings should start at H2.

# Heading 1
## Heading 2
### Heading 3

2. Bold and italic. Asterisks for both: double for bold, single for italic. Underscores work too but asterisks are the convention.

**bold text**
*italic text*
***bold italic***

3. Links. Square brackets for the link text, parentheses for the URL. Optional third part for hover text in quotes.

[Toolium](https://devtoolium.com)
[link with title](https://example.com "hover text")

4. Images. Same as links with a leading exclamation mark. The text in brackets is the alt text (for accessibility), not the visible caption.

![alt text for screen readers](path/to/image.png)

5. Lists. Hyphens or asterisks for unordered, numbers followed by periods for ordered. Indent for nesting. The actual numbers in ordered lists do not matter - markdown renumbers them - so "1. first / 1. second / 1. third" produces "1. first / 2. second / 3. third".

- Unordered item
- Another item
  - Nested item (two spaces of indent)

1. Ordered item
2. Second item

6. Code spans and code blocks. Backticks for inline code; triple backticks for fenced blocks. Add a language identifier after the opening backticks for syntax highlighting.

This is `inline code`.

```javascript
function hello() {
  console.log("hi");
}
```

7. Blockquotes. A greater-than sign at the start of each line. Useful for callouts.

> A quote or callout.
> Multiple lines work.

8. Tables (GFM extension). Pipes between columns, hyphens for the header separator. The number of hyphens does not matter; one is fine.

| Column | Other Column |
|--------|--------------|
| Cell 1 | Cell 2       |
| Cell 3 | Cell 4       |

9. Task lists (GFM extension). Brackets after the list marker, with x for checked or space for unchecked.

- [x] Done
- [ ] Todo

10. Horizontal rules. Three or more hyphens on a line by themselves.

---

GitHub Flavored Markdown (GFM)

The original Markdown spec from 2004 is intentionally minimal. GitHub extended it with features that have since become universal: tables, task lists, strikethrough (~~text~~), fenced code blocks with language hints, and autolinks (typing a URL turns it into a link without explicit syntax).

GFM is what most modern markdown renderers support, including the Toolium Markdown Preview. If you write content for any modern destination - GitHub, GitLab, Notion, Obsidian, most documentation sites - you can assume GFM features work.

The DOMPurify story

Markdown allows raw HTML embedded inside it. This is intentional - you can drop in an <img> tag with custom attributes if the standard image syntax does not give you what you need. But it is also a security problem if you are rendering markdown from untrusted sources.

The first version of the Toolium Markdown Preview rendered the parsed HTML directly via dangerouslySetInnerHTML in React, without sanitizing. A friend pointed out that I had shipped a classic stored-XSS vulnerability - paste a markdown source like <script>alert(1)</script> and the script would execute when the preview rendered.

The fix was to add DOMPurify between the markdown parser and the React render. DOMPurify is a battle-tested HTML sanitizer that strips script tags, event handlers (onclick, onerror, etc.), javascript: URLs in href attributes, and a long list of other XSS vectors. The preview now sanitizes all rendered HTML, which means it is safe to paste untrusted markdown - the worst that can happen is the preview ignores some malicious HTML.

The trade-off: some custom HTML you might legitimately want gets stripped. <style> tags are removed, iframe elements are blocked, and unusual attributes get scrubbed. For 99% of markdown writing this is invisible. For the 1% case, the workaround is to copy the rendered HTML out of the preview and apply your custom HTML manually in the destination.

Patterns I deliberately do not use

Some less common markdown features I never reach for:

  • Setext headings (underlining with === or ---). Less readable in plaintext than # headings, and not all renderers support them.
  • Reference-style links. Defining links with [identifier]: URL at the bottom of the document. Better for documents read as plaintext, but most markdown is read as rendered.
  • HTML entities in markdown. Some special characters need entity encoding to render. But this gets ugly fast and is usually not worth it.
  • Embedded raw HTML for layout. Tables, columns, custom containers. The output is unportable across renderers. If you need complex layout, you have probably outgrown markdown.

Testing markdown

The Toolium Markdown Preview is split-pane: source on the left, rendered output on the right, updating in real time. It runs entirely in your browser. Paste your draft README and verify it renders correctly before committing. The renderer uses the same library (marked) that GitHub uses, so what you see should match what shows up after you push.

Try the tool mentioned in this article

Open tool