Effortless Markdown Styling with Tailwind, Pygments, and Key Plugins

Published on November 17, 2024, 11:00 UTC 5 minutes 628 views

I’ve been running this blog for about five years, and during that time, it’s undergone several transformations. Initially, it was built with plain Markdown, HTML, and CSS. Over time, I experimented with various frameworks before finally settling on Tailwind CSS. The blog has been using Tailwind for a while now, and I’m happy with the results. In addition to Tailwind, I’ve incorporated other tools that enhance the blog’s functionality and aesthetics, such as Pygments for syntax highlighting and Medium Zoom for an interactive image zooming experience. Now that I’ve settled on a stack that I’m happy with, I thought it would be a good idea to document it. This post will cover the tools I use for my blog, including Tailwind, Pygments, Medium Zoom, and other tips & tricks.

Tailwind

Tailwind is a utility first CSS framework that is very popular. It is the best framework by far for me that uses plain HTML and CSS which I use for my website since I use Django and Wagtail. The framework provides also provides plugins for various things such as typography which we use in the blog for Markdown text.

The Tailwind setup I use is the following:

module.exports = {
  theme: {
    extend: {
      fontFamily: {
        sans: ['Noto Sans', 'Inter var'],
      },
    },
  },
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/line-clamp'),
    require('@tailwindcss/aspect-ratio'),
    require('daisyui'),
  ]
}

The most relevant plugins for Markdown are @tailwindcss/typography and daisyui The typography plugin is used for styling the Markdown text. The daisyui plugin provides a component library for Tailwind and custom styling for Tailwind. The typography plugin is documented here.

The usage of the typography plugin is as follows:

<article class="prose font-['Noto_Sans']">{{ page.body|markdown }}</article>

Note: The font-['Noto_Sans'] class is used to set the font for the Markdown text. The prose class is used to style the Markdown text. The syntax {{ page.body|markdown }} is used to render the Markdown text in the Django template.

The font comes from Google Fonts and is imported in the base.html file:

<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900" rel="stylesheet" />

With the above Tailwind/DaisyUI setup any text that has the prose class will be styled according to the Tailwind typography plugin which is great for Markdown text. It provides a nice and clean look for the blog posts and is minimal effort to integrate it.

Pygments

Pygments is a syntax highlighting tool that is used to highlight code snippets in the blog posts. It supports all languages and has several themes. To generate Pygments CSS you’ll use the following command:

pygmentize -f html -a .codehilite > <my_directory>/pygments.css>

The generated CSS file can be included in the Django template to style the code snippets. The CSS file is included in the blog.html file:

<link rel="stylesheet" href="{% static 'css/pygments.css' %}" />

The code snippets are styled using the codehilite class:

<pre><code class="codehilite">print("Hello, World!")</code></pre>

However, for a large Markdown file, it is better to use the markdown library in Python to render markdown. The Python library is a Python implementation of John Grueber’s markdown which converts markdown to html. The markdown library supports the codehilite extension which can be used to highlight code snippets. The markdown library can be used as follows:

markdown.markdown(
    markdown_text, extensions=["extra", "codehilite", "tables"]
)

The above will transform the markdown_text to html and highlight the code snippets using the codehilite extension. The tables extension is used to render tables in the markdown text. This is what the wagtail-markdown library does under the hood when you use the markdown filter in the Django template, and you can do the same in any of your projects.

Theming

Pygments also supports several themes. I prefer the github-dark theme to align my blog with GitHub’s theming. You can generate a CSS file for a specific theme using the following command:

pygmentize -S github-dark -f html -a .codehilite > <my_directory>/pygments.css

As you can see all code snippets in this blog are styled using Pygments’ with the GitHub theme.

Medium Zoom

Medium Zoom is a JavaScript library that is used to zoom images on the blog. It works by zooming the image when it is clicked. This allows us to have large images in blog posts and any time we click it, we will see all details. It really works great! Adding it to your blog is simple, you just need to include the library in the HTML file and then initialize it for the images you want to zoom. The following is an example of how to include the library and initialize it:

  <script src="https://cdnjs.cloudflare.com/ajax/libs/medium-zoom/1.0.6/medium-zoom.min.js"
          integrity="sha512-N9IJRoc3LaP3NDoiGkcPa4gG94kapGpaA5Zq9/Dr04uf5TbLFU5q0o8AbRhLKUUlp8QFS2u7S+Yti0U7QtuZvQ=="
          crossorigin="anonymous"
          referrerpolicy="no-referrer"></script>
  <script>
      const images = Array.from(document.querySelectorAll(".prose img"));
      images.forEach(img => {
          mediumZoom(img, {
              margin: 0,
              /* The space outside the zoomed image */
              scrollOffset: 40,
              /* The number of pixels to scroll to close the zoom */
              container: null,
              /* The viewport to render the zoom in */
              template: null /* The template element to display on zoom */
          });
      });

The prose class is used to select the images in the Markdown text. The prose class is used to style the Markdown text. The img tag is used to select the images in the Markdown text. The mediumZoom function is used to initialize the zoom for the images. If you do not use the prose class you need to select the images to zoom using a different way.

You can click on the image below from my Comprehensive Kubernetes Autoscaling Monitoring with Prometheus and Grafana to see the zoom in action:

Karpenter Overview

Clipboard.js

The clipboard.js library is used to copy code snippets to the clipboard. It’s a simple library that allows you to copy text to the clipboard with a single click. The library can be used as follows when combined with Tailwind/DaisyUI and font icons from iconify:

  <script type="text/javascript"
           src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js"></script>

Install the script using a CDN as in the preceding example or download it and include it in your project. The following is an example of how to use the library:

const codeBlocks = document.querySelectorAll('.prose pre code');

codeBlocks.forEach((codeBlock) => {
   const wrapper = document.createElement('div');
   wrapper.style.position = 'relative';
   wrapper.classList.add('code-wrapper');

   const pre = codeBlock.parentElement;
   pre.parentNode.insertBefore(wrapper, pre);
   wrapper.appendChild(pre);

   const button = document.createElement('button');
   button.className = 'btn btn-sm btn-ghost btn-square absolute top-1 right-1 copy-btn';
   button.setAttribute('data-clipboard-text', codeBlock.textContent);
   button.setAttribute('aria-label', 'Copy code');

   // Add Iconify icon inside the button
   button.innerHTML = `<div data-icon="mdi-content-copy" class="text-lg iconify"></div>`;

   wrapper.appendChild(button);
});

// Init clipboard
const clipboard = new ClipboardJS('.copy-btn');

// Optional: Feedback
clipboard.on('success', (e) => {
   const btn = e.trigger;
   btn.innerHTML = `<div data-icon="mdi-check" class="text-lg iconify text-green-500"></div>`;
   setTimeout(() => {
       btn.innerHTML = `<div data-icon="mdi-content-copy" class="iconify text-lg"></div>`;
   }, 1500);
});

Conclusion

This post covered the tools I use for my blog, including Tailwind, Pygments, Medium Zoom, and other tips & tricks. This is the stack I use for my blog, and I’m quite happy with it. It is minimal compared to all the custom HTML and CSS I used to write, and it is easy to maintain and implement.

Related Posts

Recipes when building a headless CMS with Wagtail's API

Recently I built a headless CMS using Wagtail’s API as a backend with NextJS/React/Redux as a frontend. Building the API I ran into some small issues with Image URL data, the API representation of snippets and creating a fully customized page representation. I’ll show some simple recipes which will hopefully simplify it for anyone encountering these issues.

Best Practises for A Performant Django Admin

The admin interface that comes with Django is one of the great things about Django. It comes with a ton of features out of the box and has many open source packages that extend the base functionality even more. Well documented and works very well, the only pain point I’ve found when using the admin and its features is when I’ve had large tables containing millions of objects. In that case, searching, sorting, ordering, counting and other features cause the admin to load slowly and creates database pressure. At that point we need to optimize the admin and there are many small changes that you can do that will speed up your admin load times and reduce any additional database load. This blog post will describe approaches to the common performance problems I’ve experienced when having a large database.

Custom Django Error Pages

Django comes with default views for 400, 403, 404 and 500 pages which is great. However, the default templates for these views are minimal - they just indicate what error it is. This blog post will walk through how to add custom templates and views for the error pages.

Shynet