2025, Oct 19 09:00

Fix unstyled Markdown-to-HTML pages in Python: add the .markdown-body wrapper class

Converting Markdown to HTML in Python but CSS looks plain? The github-markdown.css rules need a .markdown-body wrapper. See why styles don’t apply and fix it

When converting Markdown to HTML via Python, a common surprise is that the output looks unstyled even after linking a stylesheet. The HTML is valid, the CSS path is correct, but the page still renders as a clean, basic layout. The reason is simple: the stylesheet targets a specific wrapper class, and without that class in your markup, the rules never match.

Problem setup

The HTML is generated from Markdown, then embedded into a minimal document that links to a stylesheet. The result displays, but the formatting remains plain.

from markdown import markdown as md_convert
md_source = '''
#Example
## Emphasis
**This is bold text**
__This is bold text__
 ...
'''
result_html = md_convert(md_source, extensions=['fenced_code','codehilite'])
stylesheet_url = '"css/github-markdown.css"'
page_html = f'<html><head><link rel="stylesheet" type="text/css" href={stylesheet_url}></head><body>{result_html}</body></html>'
print(page_html)

The resulting HTML follows this pattern and renders fine, but without the intended Markdown styles. Meanwhile, the CSS defines its rules under a class like .markdown-body.

Why the styles don’t apply

The stylesheet uses scoped selectors such as .markdown-body. That means it expects your rendered Markdown to be inside an element carrying this class. If your HTML lacks that class on a wrapper element, the selectors never match and you end up with the browser’s default typography and spacing. The expected structure looks like this:

<body class="markdown-body"><h1>Example</h1> ...</body>

Once the class is present, the selectors take effect across headings, paragraphs, lists, blockquotes, and other elements generated by the Markdown library.

Fixing the page structure

The fix is to add the class required by the stylesheet to the page’s wrapper. Placing it on the body element applies the rules to the entire Markdown content. The example below also includes a minimal document head so the page is a complete document.

from markdown import markdown as md_convert
md_source = '''
#Example
## Emphasis
**This is bold text**
__This is bold text__
 ...
'''
result_html = md_convert(md_source, extensions=['fenced_code','codehilite'])
stylesheet_url = '"css/github-markdown.css"'
page_html = f'''
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href={stylesheet_url}>
    <title>Markdown Viewer</title>
  </head>
  <body class="markdown-body">
    {result_html}
  </body>
</html>
'''
print(page_html)

This ensures the stylesheet’s .markdown-body selectors match, and the page displays with the expected Markdown typography and spacing.

Why this detail matters

When generating HTML programmatically, it’s easy to assume that linking a stylesheet is enough. In practice, many CSS packages scope their rules under a container class to avoid leaking styles globally. Missing that one attribute leaves you with a “clean” layout, which can be confusing during automation and templating. Understanding how the stylesheet scopes its rules saves time and avoids chasing non-issues like wrong paths or broken HTML.

Takeaways

If styles appear to be ignored, inspect the CSS to see whether it’s scoped to a class like .markdown-body. Then ensure your rendered Markdown sits inside an element with that class. With the wrapper in place, the HTML exported by the Markdown library will render as intended.

The article is based on a question from StackOverflow by royer and an answer by royer.