Right, so you’ve decided the theme’s default look isn’t going to cut it. Good for you. We’re past simple CSS tweaks and into the real surgery: overriding the actual theme files themselves. This is where you stop asking politely and start telling the framework what to do. It’s powerful, it’s necessary, and if you do it wrong, you’ll create a maintenance nightmare that will haunt your future self. Let’s do it properly.

The core principle here is simple: Hugo’s lookup order is your best friend. When Hugo goes to render a page, it doesn’t just grab the first file it sees with a matching name. It goes on a scavenger hunt through your project, and it stops at the first match it finds. The order is: your project’s directories (layouts, static, etc.) first, and then the theme’s directories. This means any file you place in your project’s structure with the same path and name as a theme file will automatically override the theme’s version. It’s not a suggestion; it’s a law of the Hugo universe.

The Golden Rule: Never Touch the Theme Itself

I mean it. Don’t go into your themes/awesome-theme/layouts directory and start hacking away. The second you do a git pull to get a bug fix from the theme author, your customizations are gone, and you’re left with a broken mess. Instead, you work in your project’s root. No theme file is sacred. You can override templates, partials, data files, static assets like CSS and JS, everything. The directory structure in your project root mirrors the theme’s structure. To override a file, you just replicate its path.

For example, let’s say you’re using the popular ‘Ananke’ theme and you hate the footer. The theme’s footer partial is located at themes/ananke/layouts/partials/site-footer.html. To murder it and replace it with your own brilliant work, you don’t touch that file. You create the same path structure in your project’s layouts folder.

# Don't do this!
# (editing the theme file directly)
code themes/ananke/layouts/partials/site-footer.html

# Do this instead!
# (creating an override in your project)
mkdir -p layouts/partials
touch layouts/partials/site-footer.html

Now, you can write whatever HTML you want in that new, empty site-footer.html file. The next time you run hugo server, it will find your project’s file first and use it, completely ignoring the theme’s version. It’s that straightforward.

Overriding a Specific Block with Partials

Partials are the most common thing you’ll override. Maybe the theme’s head partial doesn’t include the analytics script you need, or its social media links are built with some ancient library. Here’s a pro tip: you don’t have to start from scratch. You can use the theme’s original file as your starting point. Copy the contents from themes/your-theme/layouts/partials/whatever.html into your corresponding project file. Now you have a perfect copy, and you can surgically remove or alter the bits you don’t like. This is far safer than rewriting the entire thing from memory.

Let’s say the theme’s head partial doesn’t load a custom font you’re obsessed with. You copy the theme’s head.html to your layouts/partials/ and then add one line.

<!-- layouts/partials/head.html -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ .Title }}</title>
{{/* ... all the other theme stuff ... */}}

<!-- My brilliant addition -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=My+Awesome+Font">

This way, you get all the crucial meta tags and CSS includes the theme author set up, plus your one perfect addition. You’re not throwing the baby out with the bathwater.

The Nuclear Option: Overriding Entire Page Templates

Sometimes a partial isn’t enough. The entire structure of the single.html (for blog posts) or list.html (for section pages) might be wrong for your content. The same principle applies. Find the template you want to change in the theme’s layouts/_default or layouts/ section directory and copy it to your project.

But here’s the catch—and it’s a big one: theme templates can change. If the theme author updates their single.html template to fix a security issue or add a new feature, your override file won’t get that update. You are now solely responsible for maintaining that entire template. This is the trade-off for absolute control. Before you override a complex template, ask yourself if your goal could be achieved by just overriding a smaller partial within that template instead. Your future self will thank you for minimizing the surface area of your customizations.

Taming Static Assets (CSS, JS, Images)

This one trips everyone up. You want to change the theme’s main.css file. So you naively create static/css/main.css, add your rules, and… nothing happens. Or worse, both files load. Why? Because the theme’s template is still hard-coded to link to its version. You can’t just override the file; you have to override the template that uses the file.

First, override the partial that includes the CSS (often something like partials/head-css.html). Change the link in that partial to point to your new CSS file, which you’ve placed in your project’s static/ directory. Now, you can either completely replace the CSS or, more cleverly, use your new CSS file to extend the theme’s CSS.

<!-- In your overridden head-css.html -->
{{ $styles := resources.Get "css/my-new-main.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $styles.Permalink }}" integrity="{{ $styles.Data.Integrity }}">

The key is using Hugo Pipes (resources.Get) to manage the asset. This ensures it gets processed and fingerprinted correctly. Now you can write my-new-main.css that imports the original theme’s CSS and then adds your overrides on top, keeping everything modular and manageable.

This is the heart of professional Hugo theming. It’s a system built for power users, not tourists. Use it wisely, and you can bend any theme to your will without ever creating a fork you have to maintain.