Styling images with Markdown

🚧

Whoa there!

This post contains code that's more than five years old. It might be fine, but you should probably check to make sure this isn't incredibly stupid by today's standards.

One of my top goals while rebuilding this site was to put all of my content into Markdown. The benefit of Markdown as a storage format is that it’s effectively infinitely portable and reusable, should a new CMS or static site generator come along. The downside is that, out of the box, Markdown doesn’t provide a lot of control over the output. Sure, it’s perfectly valid to write HTML inside of Markdown, but that feels like the kind of slippery slope that I’d like to avoid.

There are a lot of good Markdown guides out there, but they’re basically all how-to guides that just tell you what HTML your Markdown will produce, and don’t get into presentation of that HTML — nor should they. A frequent question I see is people asking how they can add classes to their images so they can style them. Unfortunately, that’s generally not possible. Some Markdown parsers might hack in support for something like this, but it’s far from common, and relying on one weird parser’s behavior kills the portability of the content.

The solution is actually surprisingly clean and simple: Hash symbols and CSS.

You can add any hash symbol you want to the end of the image URL, which provides a clean hook for CSS styling using an attribute selector. The possibilities are nearly infinite. Start with a few images, and include a hash symbol followed by anything:

![Block image](https://picsum.photos/id/1020/600/300#block)

![Avatar](https://picsum.photos/id/219/300/300#avatar)

![Align right](https://picsum.photos/id/564/500/300#right)

Next, sprinkle in some CSS. the img[src$="#something"] attribute selector targets src attributes that end with your hash.

img[src$="#block"] {
  display: block;
  margin: 0 auto;
}

img[src$="#avatar"] {
  display: block;
  margin: 0 auto;
  border-radius: 50%;
  max-width: 50%;
}

img[src$="#right"] {
  float: right;
  margin-left: 1em;
  margin-bottom: 1em;
}

Now you’ve got styled images!

Block image

Avatar

Align right

The only real caveat is that you have to control the CSS on the site for this to do any good. If you can’t control the styles for the site where you’re writing, there may still be a simple solution: It’s also perfectly valid to add a style block to your Markdown.

<style>
 /* You know what a style block looks like.
    Just drop your fancy image styles here and put this right in your Markdown, champ! */
</style>

Sometimes the output will be restricted or sanitized, which might remove style blocks, so your mileage may vary. Adding a style block may also come in handy if you need to add presentational styles to accommodate a single piece of content, and you would prefer to not make more styles available globally.

There is a gotcha, however: Images are always placed inside of paragraphs by Markdown. It’s generally not a big deal, but it limits some styling possibilities.

See how the code blocks on this page pop out wider than the main text column? Those are all immediate children of an element with display: grid applied, and styled to align to a different CSS grid column. The images aren’t immediate children of the grid; the paragraphs are. We can’t do that to images inside of paragraphs, at least until CSS subgrid ships, and even then the targeting might be tricky.

Don’t let anybody tell you that using attribute selectors for styling is bad practice. It’s a useful part of the spec, and this approach is simple with no technical overhead. It beats the hell out of hacking class attributes into Markdown-generated HTML or manipulating the markup with Javascript.

Webmentions

  1. Avatar
    Webmention Rocks! •

    This test verifies that you accept a Webmention request that contains a valid source and target URL. To pass this test, your Webmention endpoint must return either HTTP 200, 201 or 202 along with the appropriate headers.

    If your endpoint returns HTTP 201, then it MUST also return a Location header. If it returns HTTP 200 or 202, then it MUST NOT include a Location header.