Giving One Hugo Post a DIY Look with Shortcodes


My RV bedroom TV install post looks different from everything else on this blog. It has numbered build steps, a parts list on graph paper, and sticky notes. I worked through it with Claude Code, and the way it fit that styling into this site turned out to be worth a post of its own.

The problem

This site runs on the Beautiful Hugo theme. I keep a copy of the theme inside the repo and patch it by hand when I need to. I wanted a “workshop” look for one post. I did not want to change how the other 190-plus posts look.

There were two easy ways to do it, and both had problems:

  • Edit the theme’s main stylesheet. That loads on every page, so the DIY styles would load everywhere. It would also add one more local patch to the theme.
  • Paste raw HTML and a <style> block into the post. Hugo blocks raw HTML in Markdown by default, and that’s a setting I’d rather keep. It would also make the post hard to read and edit.

The theme also has no built-in option for adding a custom stylesheet. So that wasn’t an option either.

The approach: shortcodes that bring their own CSS

Hugo shortcodes are small templates you call from inside a post. The fix was three new shortcodes, plus one stylesheet that only loads when a page uses them.

The neat part is how the stylesheet gets loaded. The theme’s own figure shortcode already does this. The first time a page calls it, it writes a <link> tag for its CSS and sets a flag. Every later call sees the flag and skips the link. The DIY shortcodes copy that pattern in a small shared partial:

{{- if not (.Scratch.Get "diycss") -}}
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Permanent+Marker&display=swap" />
<link rel="stylesheet" href="{{ "css/diy.css" | absURL }}" />
{{- .Scratch.Set "diycss" true -}}
{{- end -}}

.Scratch is a per-page scratchpad Hugo gives every template. Each shortcode calls this partial first. The stylesheet ends up on the page exactly once, and only on pages that need it. Every other post on the site is unchanged.

All of the new files live in the site’s own layouts/ and static/ folders, not in the theme. Hugo checks the site folders before the theme’s, so no theme files were touched.

The three shortcodes

diy-card is the graph-paper card with a strip of masking tape for a label. I used it for the project summary and the parts list.

{{< diy-card label="Parts & Tools" >}}
- **TV:** Insignia 32" Class FE Series
- A drill
{{< /diy-card >}}
Parts & Tools
  • TV: Insignia 32" Class FE Series
  • A drill

diy-step is a numbered build step. The number sits in a round badge in tape-measure yellow, and a yellow rule runs under the step.

{{< diy-step n="1" title="Find the spot" >}}
Look for the sticker that marks the backing.
{{< /diy-step >}}

Step 1: Find the spot

Look for the sticker that marks the backing.

diy-note is a sticky note for tips and side comments. It’s tilted slightly, like it was stuck on in a hurry.

{{< diy-note label="Cleanup crew" >}}
Pick up the sawdust before someone else finds it.
{{< /diy-note >}}

The text inside each shortcode is still plain Markdown. The shortcodes pass it through Hugo’s RenderString function, so bold text, lists, and links all work like they do anywhere else in a post.

Design choices

The goal was “workshop,” not “theme park.” A few small touches do most of the work:

  • Tape-measure yellow for the step badges and dividers.
  • Kraft paper with a faint blue grid for the cards, like a project sheet on a clipboard.
  • A marker font (Permanent Marker) for labels and step titles only. Body text stays in the theme’s normal font, so the post is still easy to read.
  • Small rotations on the tape labels and sticky notes. One or two degrees is enough to look hand-placed.

On phone-sized screens the sticky notes stop rotating and the padding shrinks a bit. A tilted box on a narrow screen just looks broken.

There’s one accessibility detail too. The big step number is hidden from screen readers. Each step title starts with hidden text that says “Step 1:”, “Step 2:”, and so on. That way a screen reader announces the step number once, in a sentence that makes sense.

Bonus: the photos

The photos needed some work before they went on a public website. My phone saves them at 4032 pixels wide and around 2 MB each. Every one of them also had GPS coordinates stored inside the file. That’s the exact location where the photo was taken.

Each photo was resized so its longest side is 1600 pixels, then re-saved without any of that hidden data. Ten photos totaling about 17 MB came down to nine photos totaling under 1 MB. Then each one was checked again to confirm the GPS data was gone. If you post phone photos anywhere, this is worth doing. Most phones add location data by default.

One small surprise

The first test build didn’t include the TV post at all. Hugo skips posts with a future date unless you tell it not to. The post’s date was set to noon UTC on the day I wrote it, and it wasn’t noon UTC yet. Moving the date earlier fixed it.

Would I do it again?

Yes. The styling is limited to the posts that ask for it. The Markdown stays readable. Nothing in the theme changed. Any future DIY post can use the same three shortcodes.

hugo  css  claude 

See also