Colored Text in Markdown: Which Apps Keep It, Which Strip the Style, and the Tag GitHub Deletes Outright
Markdown has no colour syntax. Every attempt — <span style="color:red">, <font color="red">, LaTeX $\color{red}{\text{...}}$ — is raw HTML that each tool's sanitizer decides to keep, strip, or escape. On GitHub, the style attribute disappears silently; the <font> tag is deleted entirely. Seven setups, measured on 2026-10-29, produced seven different results from one source file.1
Six Stack Overflow questions on colour and alignment in Markdown have accumulated roughly 4.2 million views combined — the top question alone reaching 1,741,117 views2 — making colour the most-asked formatting feature Markdown does not have. The answers scattered across those threads share a flaw: they tell you which tag to write, not which tool will honour it. The same <span style="color:red"> that renders red in Obsidian prints as escaped text in a default markdown-it or micromark pipeline, vanishes silently on GitHub, and passes through pandoc untouched. The difference is not the Markdown — it is the sanitizer downstream.
This post measures each method across seven setups and explains the allowlist mechanics that determine the outcome. For underline (<u>) and highlight (<mark>) across the same renderers, see the companion piece on inline marks in Markdown. For what Google Docs does when it edits a .md file — including dropping font colours on save — see the Google Docs rewrite audit.
The problem in concrete terms
Markdown is a plain-text format with no colour syntax. When a writer adds <span style="color:red">text</span> to a .md file, they are embedding raw HTML inside a Markdown document. The spec does not say what happens to that HTML — and different tools make different choices, often silently.
John Gruber's original 2004 Markdown syntax describes the rule plainly: HTML block-level elements and span-level elements pass through the parser unchanged. The key word is "pass through" — the parser does nothing. What happens after the parse is left to the consuming application. That is where the divergence begins.
In a static site generator with no sanitization, the raw HTML passes all the way to the browser and the colour works. In a security-conscious platform like GitHub, the rendered HTML goes through a sanitizer that removes anything on its blocklist before display. In a JavaScript renderer configured for safety, the tags never become HTML at all — they are escaped to visible text (<span>). The same .md file, read by these three systems, produces three completely different outputs. The colour you wrote lives in your file unchanged; whether anyone sees it depends on the application opening it.
The test: one note, seven setups
One source file containing six colour and alignment methods was passed through seven setups on 2026-10-29. The results are the primary data for this post.1
| Method | GitHub (POST /markdown, gfm) | commonmark.js 0.31.2 default | commonmark.js safe:true | markdown-it 15.0.3 default | micromark 4.0.3 default | pandoc 3.13 -f markdown | Python-Markdown 3.11.2 | JupyterLab (sanitizer.ts @2c685d3) |
|---|---|---|---|---|---|---|---|---|
<span style="color:red"> | tag kept, style stripped | passed through | <!-- raw HTML omitted --> | escaped (visible text) | escaped (visible text) | passed through | passed through | style allowed (COLOR) |
<font color="red"> | tag deleted, text kept | passed through | omitted | escaped | escaped | passed through | passed through | tag allowed (color attr) |
<span class="red"> | tag kept, class stripped | passed through | omitted | escaped | escaped | passed through | passed through | class stripped (not in allowlist) |
<p style="text-align:center"> | tag kept, style stripped | passed through | omitted | escaped | escaped | passed through | passed through | text-align allowed |
<div align="center"> | kept | passed through | omitted | escaped | escaped | rewritten → data-align | passed through | align attr not in allowlist |
$\color{red}{\text{...}}$ | → <math-renderer> (MathJax, colour client-side) | literal $...$ | literal $...$ | literal $...$ | literal $...$ | → MathML (colour in annotation only) | literal $...$ | literal (no math plugin) |
#ff0000 in backticks | colour swatch — issues/PRs/discussions only, not READMEs | code element | code element | code element | code element | code element | code element | code element |
Sources: own receipts S1–S6 (GitHub API, local JS/Python runs, pandoc 3.13, 2026-10-29); JupyterLab sanitizer.ts Q22–Q25 (commit @2c685d3, confirmed unchanged on main branch 2026-10-29).
Three facts stand out from the matrix immediately:
- GitHub removes
styleandclassattributes but keeps the tag. A<span style="color:red">text</span>becomes<span>text</span>— the word is there, the colour is gone. - GitHub deletes
<font>entirely while preserving its text content. The tag disappears; only the wrapped word survives. <div align="center">survives GitHub but is the one case pandoc quietly rewrites — to<div data-align="center">, an attribute no browser centres on.
The mechanism: sanitizers, not Markdown
GitHub's approach is not specific to colour — it applies to every HTML attribute that could be misused. The github/markup README is explicit: "The HTML is sanitized, aggressively removing things that could harm you and your kin—such as script tags, inline-styles, and class or id attributes."3 The same README notes that the markup library itself does not sanitize: "In particular, markup itself does no sanitization of the resulting HTML, as it expects that to be covered by whatever pipeline is consuming the HTML."3
This architecture — parse in one step, sanitize in a separate step — means the sanitizer's allowlist is the real arbiter of what survives. On GitHub, the html-pipeline sanitizer maintains a per-tag allowlist. The <span> tag is allowed; the style and class attributes are not. The <font> tag is not on the allowlist at all — so GitHub strips the tag and falls back to its text content, which is why <font color="red">hello</font> renders as hello on GitHub: the tag is gone, the word is not.
Obsidian documents the same design on its own side. Its help page states: "To prevent malicious code from doing harm, Obsidian sanitizes any HTML in your notes."4 Then — in the same paragraph — it gives an example of what does work: "using <span style="font-family: cursive">your text</span> can allow you to quickly change your font."4 Obsidian's sanitizer is configured differently from GitHub's: it allows style attributes on <span>. The principle is the same; the allowlist differs.
On the JavaScript renderer side, safety is opt-in rather than opt-out. markdown-it documents its stance in its type definitions: "Enable HTML tags in source. @defaultValue false (true for 'commonmark' preset only)."5 Its README summarises this as "Safe by default."6 micromark uses the same inversion: "Whether to allow (dangerous) HTML (boolean, default: false)."7 Both renderers, in their default configuration, do not pass HTML through at all — they escape the angle brackets to visible text. commonmark.js documents its safe mode option: "safe: if true, raw HTML will not be passed through to HTML output (it will be replaced by comments)."8 With safe:true, every HTML case in the test note became <!-- raw HTML omitted -->.
GitLab's documentation makes the limit explicit: "Markdown does not support changing text color."9 It also notes a narrow exception: "In the GitLab application (but not the GitLab documentation) color codes in backticks display a color chip next to the color code."10 That chip is a UI annotation, not coloured text. GitLab and GitHub share a sanitizer lineage but differ in configuration.
Method by method
The six methods in the test note produce different results for different reasons. Understanding why each one behaves as it does is more useful than memorising the outcome — because the outcome changes as renderer configurations and versions change.
<span style="color:red">
This is the most-recommended approach across Stack Overflow answers on colour in Markdown, and the most misleading. The <span> tag is broadly allowed: GitHub keeps it, commonmark.js passes it through, pandoc keeps it, Python-Markdown keeps it. But the style attribute is on GitHub's blocklist — so on GitHub, <span style="color:red">text</span> becomes <span>text</span>. The word survives; the colour does not.
The only platforms measured here that allow style on <span> for the color property are JupyterLab (explicit allowlist in sanitizer.ts)11 and pandoc (which does not sanitize). Obsidian documents it as working but was not in the automated test; its help page implies style is allowed.4
<font color="red">
The <font> element is classified as obsolete in the WHATWG HTML Living Standard, which lists it among elements that "should not be used": "basefont big blink center font marquee multicol nobr spacer tt — Use appropriate elements or CSS instead."12 Browsers still parse it — the Standard notes that "when a font element has a color attribute, its value is expected to be parsed using the rules for parsing a legacy color value"13 — so the colour works in a raw HTML context. But GitHub's sanitizer does not include <font> in its tag allowlist, so the tag is deleted entirely, leaving only its text content. The result differs from a <span> outcome: with <span>, the empty tag persists; with <font>, nothing persists except the words.
JupyterLab's sanitizer.ts explicitly includes font with its color, face, and size attributes in the allowlist.11 In JupyterLab Markdown cells, <font color="red"> works.
<span class="red">
Class-based colouring depends on a stylesheet defining .red { color: red }. As a Markdown method it is structurally fragile: even if the sanitizer keeps the <span> tag, it will strip the class attribute. Both GitHub and JupyterLab strip class. The colour only works if the consuming page happens to have a stylesheet that defines the class — which is true on exactly zero generic Markdown platforms.
Alignment: <div align="center"> and <p style="text-align:center">
Alignment and colour are two sides of the same problem: Markdown has no alignment syntax, so writers reach for raw HTML. The WHATWG Living Standard specifies that "<div align='center'>... are expected to center text within themselves."14 GitHub honours this: <div align="center"> and <p align="center"> are both kept in the GitHub API response with the align attribute intact.1
Pandoc's behaviour reveals a subtlety. With -f markdown, pandoc rewrites <div align="center"> to <div data-align="center">. Its manual documents the general rule: "Unknown attributes are passed through as custom attributes, with data- prepended."15 No browser centres on data-align. A note converted from .md to HTML via pandoc -f markdown loses its centring silently — the content is there, the layout is not. With -f gfm, pandoc keeps align without the rewrite.1
<p style="text-align:center"> follows the same pattern as <span style="color:red">: GitHub strips the style attribute, keeping <p> bare. JupyterLab's sanitizer.ts allowlists text-align in the style property, so alignment works in Jupyter Markdown cells.11
LaTeX math colour: $\color{red}{\text{...}}$
LaTeX \color{} works only where a math renderer is present. GitHub Docs explain: "GitHub's math rendering capability uses MathJax; an open source, JavaScript-based display engine."16 The rendering is available "in GitHub Issues, GitHub Discussions, pull requests, wikis, and Markdown files."16 The GitHub API in gfm mode wraps the expression in <math-renderer class="js-inline-math">, delegating the colour rendering to the MathJax client-side script — which is why colour appears in the browser but is absent from the raw API response.1
A real GitHub gist containing $\color{red}{...}$ expressions confirmed 29 such expressions rendered as coloured math in the browser, validating that client-side rendering does apply the colour.17
Pandoc takes a different path: its default math method is MathML. The manual documents this: "The default value is mathml."18 In the pandoc output, $\color{red}{\text{math red}}$ becomes MathML <mtext mathvariant="normal">math red</mtext> — the \color command appears in an annotation block only, not in the visible MathML tree. The visual colour is lost.1
Every other renderer in the test — commonmark.js, markdown-it, micromark, Python-Markdown — has no math extension loaded by default. The $...$ expression is output as literal text.
The backtick colour swatch
GitHub Docs describe a separate mechanism: "A supported color model within backticks will display a visualization of the color."19 This is a colour chip next to the code, not coloured text. It is also strictly scoped: "The visualization of the color is only supported in issues, pull requests, and discussions."19 It does not work in READMEs or Markdown files. GitLab documents a parallel feature for the GitLab application (not its documentation site).10 No other renderer in the test produces a colour swatch — they all emit a standard <code> element.
Losing colour on export: pandoc to Word
When a Markdown file is converted to .docx via pandoc, colour and alignment are both dropped regardless of source method. The pandoc 3.13 Word export of the test note produced zero w:color elements and zero w:jc (justification) elements — all words were kept, all styling was gone.1
This matters for any workflow that uses Markdown as the canonical source and Word as the delivery format. Colour added with HTML in .md is invisible in the resulting .docx. The only reliable way to pass colour through to Word is to use Word's own styles, which pandoc supports via a reference document (--reference-doc). The Markdown source then stays plain; the styling is defined in the reference template and applied at export time.
The accessibility floor
Markdown colour markup reaching its destination does not end the problem. WCAG 2.2 Success Criterion 1.4.1 (Use of Color) sets a hard rule: "Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element."20
Roughly 8% of men and 0.4% of women have red-green colour vision deficiency in European Caucasian populations — a proportion documented in Birch (2012) in the Journal of the Optical Society of America A.21 For a reader with red-green deficiency, a note where critical content is coloured red against a white background may be indistinguishable from the surrounding text.
The practical rule follows directly from WCAG 1.4.1: colour that carries meaning must be accompanied by a word, a label, a callout heading, or another non-colour signal that communicates the same information to a reader who cannot see — or cannot process — the colour. Colour as decoration is unaffected. Colour as the sole signal for "this is important" or "this is wrong" is not accessible, regardless of whether the sanitizer kept the markup.
A coloured word that survives every renderer but remains unreadable by 8% of the male audience has cleared the technical bar and failed the human one.
Frequently Asked Questions
How do I color text in Markdown?
Markdown has no native colour syntax. The only method is raw HTML: <span style="color:red">text</span>. Whether it renders depends entirely on the application. GitHub strips the style attribute, leaving unstyled text. Renderers like markdown-it and micromark escape the tag to visible characters by default. Obsidian and JupyterLab allow the style attribute.
Why doesn't my <span style="color:red"> work on GitHub?
GitHub's sanitizer removes inline-styles from all HTML tags before rendering. The tag itself (<span>) is on the allowlist; the style attribute is not. The word renders; the colour does not. GitHub's own documentation on this is in the github/markup README, which states the sanitizer aggressively removes inline-styles and class or id attributes.3
Does <font color="red"> work in GitHub Markdown?
No. GitHub's sanitizer does not include <font> in its tag allowlist. The tag is deleted; only its text content is kept. <font color="red">hello</font> renders as hello — no tag, no colour. Browsers still honour <font color> natively (it is a legacy presentational hint in the HTML Standard13), but GitHub never passes the tag to the browser.
Does $\color{red}{\text{...}}$ work for coloring text on GitHub?
In GitHub Issues, pull requests, discussions, wikis, and Markdown files — yes, via MathJax. GitHub Docs confirm that mathematical expressions rendering is available across these surfaces.16 The colour is applied client-side by MathJax, not in the server-rendered HTML. In a default markdown-it, micromark, or Python-Markdown pipeline with no math plugin, the expression renders as literal $...$ text. In pandoc, it converts to MathML where the \color command is in an annotation, not in the visible output.
How do I center text in Markdown?
There is no Markdown syntax for centring. <div align="center"> survives GitHub's sanitizer and GitHub does render centred content. <p style="text-align:center"> does not survive GitHub (the style attribute is stripped). If your pipeline uses pandoc with the -f markdown reader, <div align="center"> is rewritten to <div data-align="center">, which browsers do not centre — use -f gfm to preserve the align attribute through pandoc.
Does the GitHub #ff0000 backtick colour swatch work in READMEs?
No. GitHub Docs state explicitly: "The visualization of the color is only supported in issues, pull requests, and discussions."19 In a README or a Markdown file rendered on the repository page, the backtick colour code renders as a standard code element with no chip.
Which setup keeps the most colour and alignment methods?
Python-Markdown 3.11.2 and pandoc 3.13 (with -f markdown) pass all six HTML cases through to their output without modification — they do no sanitization. JupyterLab's sanitizer explicitly allowlists color on font and span and text-align on styled elements, so colour and alignment both work in Jupyter Markdown cells.11 GitHub keeps the most of any major platform that does sanitize: it keeps <div align> and <p align>, keeps <span> and <p> tags (minus style), keeps <span> minus class, and delegates math colour to MathJax.
The colour you write in a Markdown file travels with the file — sanitizers never touch the source. What does not travel is the guarantee that any particular reader will see it. The rendering depends on the tool, the tool's configuration, and how carefully its sanitizer allowlist was designed. A file is not a rendered page.
MNMNOTE is a local-first Markdown editor that stores notes on your own device. The colour markup in your files travels intact in the raw text — whether it renders is a decision made by whatever opens the file. mnmnote.com
References
Footnotes
-
Own measurement — GitHub REST API
POST /markdown(mode=gfm), commonmark.js 0.31.2, markdown-it 15.0.3, micromark 4.0.3 + gfm 3.0.0, pandoc 3.13, Python-Markdown 3.11.2; receipts inmaterials/receipts/(gh_gfm.html, js.txt, pd_py.txt, pd313.txt, pd313_docx.txt), 2026-10-29T01:17Z. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
Stack Overflow API v2.3 (
api.stackexchange.com/2.3/questions?tagged=markdown&sort=votes), six questions measured 2026-10-29: q/35465557 (1,741,117 views), q/11509830 (558,652), q/19746350 (410,009), q/14051715 (610,899), q/12090472 (604,064), q/35077507 (312,918) — total ~4,237,659. ↩ -
github/markup, README, GitHub Inc., last commit 2026-10-19. https://github.com/github/markup ↩ ↩2 ↩3
-
Obsidian Help, HTML content, Obsidian. https://help.obsidian.md/html ↩ ↩2 ↩3
-
markdown-it type definitions (mdit_dts.mts), markdown-it 15.0.3. https://unpkg.com/[email protected]/dist/markdown-it.d.mts ↩
-
markdown-it README. https://github.com/markdown-it/markdown-it ↩
-
micromark README, micromark 4.0.3. https://github.com/micromark/micromark/tree/main/packages/micromark ↩
-
commonmark.js README, commonmark.js 0.31.2. https://github.com/commonmark/commonmark.js ↩
-
GitLab Docs, GitLab Flavored Markdown, "Markdown does not support changing text color." https://docs.gitlab.com/user/markdown/ ↩
-
GitLab Docs, GitLab Flavored Markdown, color chips in backticks. https://docs.gitlab.com/user/markdown/ ↩ ↩2
-
JupyterLab
packages/apputils/src/sanitizer.ts, allowlist entries:font: ['color', 'face', 'size'];color: [CssProp.COLOR];'text-align': [CssProp.TEXT_ALIGN]. Pinned @2c685d3; confirmed unchanged on current main branch 2026-10-29. https://github.com/jupyterlab/jupyterlab/blob/2c685d3c2be4db4b4a44a0f27c15f0dfed9f1cc2/packages/apputils/src/sanitizer.ts ↩ ↩2 ↩3 ↩4 -
WHATWG HTML Living Standard, Obsolete elements, listing
fontamong elements to avoid. https://html.spec.whatwg.org/multipage/obsolete.html ↩ -
WHATWG HTML Living Standard, Rendering — The
fontelement, presentational hint forcolorattribute. https://html.spec.whatwg.org/multipage/rendering.html ↩ ↩2 -
WHATWG HTML Living Standard, Rendering — centering,
<div align="center">expected to center text. https://html.spec.whatwg.org/multipage/rendering.html ↩ -
Pandoc User's Guide (MANUAL), Unknown attributes. https://pandoc.org/MANUAL.html ↩
-
GitHub Docs, Writing mathematical expressions, Working with advanced formatting. https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions ↩ ↩2 ↩3
-
Own receipt — gist.github.com/luigiMinardi/4574708d404cdf4fe0da7ac6fe2314db, 2026-10-29T01:18Z; 70
<math-renderer>elements, 29 starting with$\color. ↩ -
Pandoc User's Guide (MANUAL),
--mathml. https://pandoc.org/MANUAL.html ↩ -
GitHub Docs, Supported color models, Basic writing and formatting syntax. https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax ↩ ↩2 ↩3
-
W3C, WCAG 2.2 Success Criterion 1.4.1 Use of Color. https://www.w3.org/TR/WCAG22/ ↩
-
Birch, J. (2012). Worldwide prevalence of red-green color deficiency. Journal of the Optical Society of America A, 29(3), 313–320. DOI 10.1364/JOSAA.29.000313. https://europepmc.org/article/MED/22472762 ↩