How Markdown Here Ensures Explicit CSS Styling for Rendered Markdown in Email Clients
Markdown Here converts stylesheet rules into inline style attributes via the makeStylesExplicit function to ensure styling survives email client sanitization.
When rendering Markdown inside email compose windows, the open-source extension Markdown Here (adam-p/markdown-here) faces a critical constraint: major webmail clients like Gmail strip out external <style> blocks. To guarantee consistent visual formatting, the tool implements a robust inline CSS conversion system that embeds styles directly into HTML elements.
The Email Client Sanitization Challenge
Email clients aggressively sanitize HTML content to prevent security risks and ensure consistent rendering. Gmail and other providers commonly remove <style> tags from incoming or composed messages, which would normally break any CSS-dependent Markdown formatting. According to comments in the source at lines 94-96 of markdown-here.js, this sanitization makes external stylesheets unreliable for email-based Markdown rendering.
The makeStylesExplicit Conversion Process
At the core of the solution is the makeStylesExplicit function located in src/common/markdown-here.js. This utility transforms stylesheet-based CSS into element-level inline styles through a systematic four-step pipeline.
Step 1: Rendering to HTML and CSS
The process begins when the Markdown renderer produces two distinct outputs: the rendered HTML content (mdHtml) and the corresponding CSS rules (mdCss). This separation allows the system to maintain clean semantic markup while preserving stylistic information for later inline application.
Step 2: DOM Insertion
The rendered HTML is inserted into the email compose area using the replaceRange function, which wraps the content in a container element. This wrapper serves as the boundary for style application, ensuring that only the Markdown content receives inline styling without affecting the rest of the email interface.
Step 3: Stylesheet Processing
Immediately after insertion, the code calls makeStylesExplicit(wrapper, mdCss) at lines 496-97. This function creates a temporary <style> element via getMarkdownStylesheet (defined at lines 28-60) to parse the CSS text into accessible rules:
function makeStylesExplicit(wrapperElem, css) {
var stylesheet = getMarkdownStylesheet(wrapperElem, css);
for (var i = 0; i < stylesheet.cssRules.length; i++) {
var rule = stylesheet.cssRules[i];
var selectorMatches = wrapperElem.parentNode.querySelectorAll(rule.selectorText);
// Processing continues...
}
}
Step 4: Inline Style Application
For each CSS rule, the function queries for matching elements within the wrapper and applies the styles directly to the DOM:
selectorMatches.forEach(function (elem) {
if (!elem.closest('.markdown-here-wrapper')) return;
var styleAttr = elem.getAttribute('style') || '';
if (styleAttr && !/;\s*$/.test(styleAttr)) styleAttr += '; ';
styleAttr += rule.style.cssText;
elem.setAttribute('style', styleAttr);
});
This ensures that even if the email client subsequently removes all <style> tags, the visual formatting remains intact on each individual element.
Key Implementation Details
The getMarkdownStylesheet helper function safely instantiates a temporary stylesheet object from the CSS text string, enabling standard DOM CSSOM methods to parse and iterate through rules. The main rendering orchestration in src/common/markdown-render.js coordinates this pipeline, while utility functions in src/common/utils.js handle safe HTML insertion.
The conversion logic specifically checks for existing inline styles using elem.getAttribute('style') and intelligently appends new declarations with proper semicolon separation, preventing syntax errors when multiple rules target the same element.
Summary
- Markdown Here uses the
makeStylesExplicitfunction insrc/common/markdown-here.jsto convert CSS rules into inlinestyleattributes - The process involves creating a temporary stylesheet, iterating through
cssRules, and applying each rule'scssTextto matching elements viasetAttribute - This approach guarantees that rendered Markdown retains its intended appearance even when email clients like Gmail strip external
<style>blocks - The implementation safely handles pre-existing inline styles and ensures proper CSS syntax through semicolon separation
Frequently Asked Questions
Why can't Markdown Here rely on external CSS stylesheets in email clients?
Webmail providers like Gmail aggressively sanitize HTML by removing <style> blocks to prevent security vulnerabilities and styling conflicts with their own interfaces. As noted in the source code comments, this makes external stylesheets unreliable, necessitating the conversion to inline styles that cannot be stripped without removing the element itself.
How does makeStylesExplicit preserve existing inline styles?
The function retrieves any pre-existing style attribute content using getAttribute('style'), checks for proper semicolon termination with a regex, and appends the new CSS rules while maintaining valid syntax. This ensures that elements with multiple CSS declarations receive all styles without string concatenation errors.
Which source files contain the explicit CSS styling logic?
The primary implementation resides in src/common/markdown-here.js, specifically within the makeStylesExplicit and getMarkdownStylesheet functions. The rendering pipeline that feeds CSS into this system is orchestrated by src/common/markdown-render.js, while src/common/utils.js provides helper functions for safe DOM manipulation.
Does inline CSS conversion affect rendering performance?
While the conversion requires DOM traversal and attribute manipulation, it executes only once during the Markdown-to-HTML transformation process. The temporary stylesheet creation and querySelectorAll operations complete quickly for typical email-length content, resulting in negligible performance impact while ensuring cross-client compatibility.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →