Introduction
Parsedown is a lightweight Markdown parser written in PHP. It's included by default in Laravel.
When generating HTML from Markdown it's necessary to add classes to the generated HTML tags,
especially if your whole site is based on a functional CSS framework like Tailwind CSS.
I had the use case that I built an application and some content pages are driven by HTML generated from markdown.
I only wanted to style the HTML on these markdown based content pages in a certain way and
prevent side-effects on other styles of the same HTML elements. Additionally when using a functional CSS framework and don't want to create custom classes this creates a challange.
That's why I want to outline the possibilities I thought about to style the generated HTML with this post.
A concrete example
Let's first look at an example what the resulting HTML looks like of parsed markdown snippet processed through Parsedown:
# Welcome to Markdown
## Explanation
In this post we will show what *Parsedown* generates out of this **markdown** snippet.
We will show what the resulting `HTML` looks like. We will look at:
* Headlines
* Formatting within text
* Lists
* Paragraphs
* Blockquotes
Note that --- not considering the asterisk --- the actual text
content starts indentend.
> If you want to use a quote by someone else you can do this with a
> blockquote like this.
<h1>Welcome to Markdown</h1>
<h2>Explanation</h2>
<p>
In this post we will show what <em>Parsedown</em> generates out of this <strong>markdown</strong> snippet.
We will show what the resulting <code>HTML</code> looks like. We will look at:
</p>
<ul>
<li>Headlines</li>
<li>Formatting within text</li>
<li>Lists</li>
<li>Paragraphs</li>
<li>Blockquotes</li>
</ul>
<p>Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
</p>
<blockquote>
<p>
If you want to use a quote by someone else you can do this with a
blockquote like this.
</p>
</blockquote>
We can see that there are no classes applied to the HTML. With Tailwind this will look pretty unformatted
as tailwind normalizes the styling of the browser with a small set of presets.
The @apply
directive from Tailwind
In many of my solutions it makes sense to use the @apply
directive. That's why I shortly want to introduce it to you. With Tailwind it's possible to combine multiple classes and generate a new custom class out of it using @apply
.
A short example:
.my-custom-class {
@apply mt-8 text-2xl /* apply tailwind classes here */
}
will result in a class defintion that looks like this:
margin-top: 2rem;
font-size: 1.5rem;
line-height: 2rem;
If you don't know this method or want to read more about you can have a look here.
What are the possibilities to style this part of HTML ?
- Adding base styles (
@layer
, or wrting a plugin) - Using CSS specifity to overwrite base styles
- Extending the PHP Class and directly rendering Tailwind classes
- Extending the PHP Class and using
@apply
1. Adding new base styles
Tailwind CSS makes it possible to overwrite the base styles.
This can be done with the @layer
directive or by using a plugin.
Let's see how to define the styles for a headline using the @layer
directive:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
h1 {
@apply mt-8 text-2xl;
}
}
To rewrite the layout of the h1
tag with a plugin one can use the following code:
const plugin = require('tailwindcss/plugin')
module.exports = {
plugins: [
plugin(function({ addBase, theme }) {
addBase({
'h1': { fontSize: theme('fontSize.2xl'), marginTop: '2rem', lineHeight: '2rem' },
})
})
]
}
Pros & Cons
🟡 base styles will apply globally on every page (depending on your situation can be positve or negative)
🟢 the solution is easily maintainable and needs only adjustments in 1 file
🟢 the amount of customization small (uses Tailwind features to customize the result)
🔴 the plugin for that case is in my opinon overengineering
2. Using CSS specifity to overwrite base styles
When rendering our Markdown at a certain point in our template (i.e. a Laravel blade template) we can wrap that part with a div
and add a class to the div
. Here is an example:
<div class="parsedown">
{!! (new Parsedown)->text($markdownText) !!}
</div>
Now we can increase the specifity for that HTML block by writing the following CSS code:
.parsedown h1 {
margin-top: 2rem;
font-size: 1.5rem;
line-height: 2rem;
}
Alternatively we can directly use Tailwind classes here by using the @apply
method:
.parsedown h1 {
@apply mt-8 text-2xl
}
Pros & Cons
🟢 styles will be scoped by using a class (or could also be an id)
🟡 the solution is maintainable and needs adjustments in the CSS and the HTML file
🟢 no real customization needed (to prevent customized CSS from the Tailwind CSS perspectiv @apply
is used)
🟢 only uses standard mechanisms of HTML and CSS
3. Extending the PHP Class and directly rendering Tailwind classes
The PHP class of Parsedown can be easily extended to add additional syntax to the generated HTML part.
Let's suppose we want to add Tailwind CSS classes to a headline.
<?php
namespace App\Utils;
use Parsedown;
class ParseDownTailwind extends Parsedown {
protected function blockHeader($Line) {
$header = parent::blockHeader($Line);
$header['element']['attributes']['class'] = "mt-8 text-2xl";
return $header;
}
}
Parsedown has a protected
function called blockHeader($Line)
which is repsonsible for rendering
all headline tags (h1 - h6).
First we need to make sure that the normal generation keeps working:
$header = parent::blockHeader($Line);
This line makes sure to call the parent method of the original Parsedown class.
After that we are adding the class
attribute to the html.
$header['element']['attributes']['class'] = "mt-8 text-2xl";
In this case we would format all headlines with the Tailwind classes mt-8 text-2xl
.
If we want to differentiate we can do that like this:
if ($header['element']['name'] === 'h1') {
$header['element']['attributes']['class'] = "mt-8 text-2xl";
} else {
$header['element']['attributes']['class'] = "mt-4 text-xl";
}
Pros & Cons
🟢 styles will be scoped depending on the classes you use in your customized PHP class (theoretically multiple classes could be created for different styles in multiple blocks)
🟡 the solution is maintainable however CSS classes are rendered and hardcoded in a PHP class
🟡 a certain of customization of Parsedown is needed
🔴 Against seperation of concerncs (backend code deciding on visual styling)
4. Extending the PHP Class and using @apply
With Tailwind it's possible to combine multiple classes and generate a custom class out of it using @apply
.
If you don't know this method you can have a look here.
Let's suppose we want to create a .markdown-h1
css class from the above definitions.
.markdown-h1 {
@apply mt-8 text-2xl
}
Now we can use the solution again to extend the base Parsedown class.
This time we render the .markdown-h1
css class.
This has the benefit of decoupling our PHP code a little bit more from the CSS code.
if ($header['element']['name'] === 'h1') {
$header['element']['attributes']['class'] = "markdown-h1";
}
Pros & Cons
🟢 styles will be scoped to the point where you call your customized PHP class (theoretically multiple classes could be created for different classes in multiple blocks)
🔴 the solution needs higher maintainance as it requires creating CSS classes and rendering them in the PHP code
🟡 a certain of customization of Parsedown is needed
🟡 In comparison to solution #3 a higher seperation of concerncs (backend code deciding on CSS classes however your decision on how it looks like will be in your CSS file)
Conclusion
I currently have solution #3 implemented in the project. However I might switch to solution #2 as it's less customizing and uses the technologies how they are meant to be used.
Even if this a very specific problem, I wanted to write about it and thought it's a good topic for my first
post. If you have alternative solutions please leave a comment.
I hope this post was interesting and helpful for you.
I am happy for every feedback.
Top comments (4)
If anyone looking for an easy solution for styling markdown with Tailwind, you should definitely try tailwindcss.com/docs/typography-pl.... with simple
<div className="prose">
you get Tailwind styling for wrapped content. It's also highly customizable and configurable. Highly recommend.This is a life saver. Thanks!
very useful for my next-js tailwind config, cheers
So nice. for the rest of the tags we need to setup them css right?