How to Customize a Jekyll Theme (Complete Guide 2026)
Learn how to customize any Jekyll theme — override layouts, change colours and fonts, modify SCSS, and add custom functionality without breaking your theme.
Customizing a Jekyll theme doesn’t mean editing the theme files directly — that approach breaks every time you update the theme. The right way is to use Jekyll’s override system: copy only the files you want to change into your project, and Jekyll will use your version instead.
This guide covers everything from changing colours to overriding layouts to adding completely new features.
How Jekyll Theme Overrides Work
Jekyll loads files in this priority order:
- Your project files (highest priority)
- Theme gem files
- Jekyll defaults
This means if you create _layouts/post.html in your project, Jekyll uses it instead of the theme’s version — without touching the original.
1. Change Colours and Fonts (SCSS Override)
Most modern themes expose SCSS variables you can override. Here is the standard approach:
Step 1: Find the theme’s variable file
bundle info --path minimal-mistakes-jekyll
This shows the gem path. Look for _sass/minimal-mistakes/_variables.scss.
Step 2: Create your override file
Create assets/css/main.scss in your project:
---
---
// Override theme variables BEFORE importing the theme
$primary-color: #2563eb;
$font-family-sans-serif: "Inter", sans-serif;
$border-radius: 8px;
// Then import the theme
@import "minimal-mistakes";
Step 3: Rebuild
bundle exec jekyll serve
Your colour and font changes apply site-wide instantly.
2. Override a Layout
To modify a theme’s layout without editing the gem:
Step 1: Copy the layout file
cp $(bundle info --path minimal-mistakes-jekyll)/_layouts/post.html _layouts/post.html
Step 2: Edit your local copy
Open _layouts/post.html and make your changes. Jekyll will use this file automatically.
3. Override an Include
Includes work the same way as layouts:
cp $(bundle info --path your-theme)/_includes/header.html _includes/header.html
Edit _includes/header.html freely — your version takes precedence.
4. Customize _config.yml
Most themes expose configuration options in _config.yml. This is the safest place to make changes since it never conflicts with theme updates:
# Minimal Mistakes example
minimal_mistakes_skin: "dark"
author:
name: "Your Name"
bio: "Your bio here"
avatar: "/assets/images/avatar.jpg"
# Typography
title_separator: "—"
words_per_minute: 200
Consult your theme’s documentation for available options.
5. Add Custom CSS Without Overriding SCSS
For small tweaks, create a custom CSS file and include it in your layout:
/* assets/css/custom.css */
.site-header {
border-bottom: 2px solid #2563eb;
}
.post-title {
font-size: 2.5rem;
}
Then reference it in your _includes/head.html override:
<link rel="stylesheet" href="{{ '/assets/css/custom.css' | relative_url }}">
6. Add Google Fonts
Override _includes/head.html and add:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
Then set the font in your SCSS override:
$font-family-sans-serif: "Inter", system-ui, sans-serif;
7. Add a Custom Navigation Menu
Most themes read navigation from _data/navigation.yml:
main:
- title: "Blog"
url: /blog/
- title: "Themes"
url: /themes/
- title: "About"
url: /about/
8. Modify the Home Page
To customise the homepage, create index.html or index.md in your project root. Most themes support front matter options to configure sections:
---
layout: home
hero_title: "My Jekyll Site"
hero_subtitle: "Writing about code and design"
show_posts: true
posts_limit: 6
---
Best Practices for Customizing Jekyll Themes
- Never edit gem files directly — changes are lost on
bundle update - Override only what you need — fewer overrides = easier theme updates
- Use
_config.ymloptions first — most themes expose more than you expect - Keep a
_sass/custom.scss— a single file for all your tweaks is easier to maintain - Test with
bundle exec jekyll serve --livereload— see changes instantly
Popular Themes and Their Customization Docs
| Theme | Customization Guide |
|---|---|
| Minimal Mistakes | Docs |
| Chirpy | Docs |
| Just the Docs | Docs |
| Hydejack | Docs |
References
Understanding the override philosophy in depth
Jekyll’s override system is elegant once you fully grasp it. When Jekyll builds your site, it merges files from two sources: the installed theme gem and your project directory. Your project always wins. This gives you a clear separation between “theme code I did not write” and “customisations I own.”
The practical implication: you should only copy files into your project when you intend to modify them. Every file you copy becomes a file you maintain. If the theme releases an important bug fix to _layouts/post.html and you have overridden that file, you will not receive the fix automatically — you need to merge the changes manually.
Keep overrides minimal. For most customisations, changing SCSS variables or editing _config.yml settings is sufficient without copying any layout or include files at all.
Customising typography
Beyond changing the font family, typography customisation typically involves adjusting the type scale, line heights, and spacing to suit your content.
Most themes define typography in SCSS variables. A common set:
/* Add to your SCSS override before importing the theme */
$font-size-base: 1.0625rem; /* 17px — slightly larger than the default 16px */
$font-size-lg: 1.1875rem; /* 19px */
$line-height-base: 1.75; /* More generous for long-form reading */
$headings-line-height: 1.2; /* Tighter for headings */
$font-weight-bold: 700;
/* Heading sizes */
$h1-font-size: 2.5rem;
$h2-font-size: 1.75rem;
$h3-font-size: 1.375rem;
If the theme does not expose these as SCSS variables, override the element selectors directly in a custom stylesheet:
/* _sass/_custom.scss */
body {
font-size: 1.0625rem;
line-height: 1.75;
}
h1 { font-size: clamp(1.8rem, 4vw, 2.5rem); }
h2 { font-size: clamp(1.4rem, 3vw, 1.75rem); }
.post-content {
max-width: 68ch; /* Optimal reading line length */
}
The clamp() function is a modern CSS technique that scales headings smoothly between a minimum size on small screens and a maximum on large screens — no media queries needed.
Overriding the navigation
Navigation customisation is one of the most common requests. Most themes read navigation from _data/navigation.yml:
# _data/navigation.yml
main:
- title: "Browse Themes"
url: /themes/
- title: "Blog"
url: /blog/
- title: "Showcase"
url: /showcase/
- title: "About"
url: /about/
footer:
- title: "Privacy"
url: /privacy/
- title: "Terms"
url: /terms/
- title: "Contact"
url: /contact/
For themes that hard-code navigation links in _includes/nav.html, override that file and switch to a data-driven approach — it saves you from editing the include every time you add a page.
For dropdown menus, extend the data structure:
main:
- title: "Themes"
url: /themes/
children:
- title: "Blog Themes"
url: /themes/?category=blog
- title: "Portfolio Themes"
url: /themes/?category=portfolio
- title: "Documentation Themes"
url: /themes/?category=documentation
Then update _includes/nav.html to render children as a dropdown.
Customising the post layout for your content type
The default post layout works for most blogs but may not suit specialised content. If you write long technical tutorials, you might want a table of contents, estimated reading time, a “last updated” date, and a series navigation widget.
Override _layouts/post.html and add these elements:
---
layout: default
---
<article class="post">
<header class="post-header">
<div class="post-meta">
<time datetime="{{ page.date | date_to_xmlschema }}">
{{ page.date | date: "%B %-d, %Y" }}
</time>
{% if page.last_modified_at and page.last_modified_at != page.date %}
<span class="post-updated">
Updated {{ page.last_modified_at | date: "%B %-d, %Y" }}
</span>
{% endif %}
<span class="read-time">
{{ content | number_of_words | divided_by: 200 | plus: 1 }} min read
</span>
</div>
<h1>{{ page.title }}</h1>
{% if page.description %}<p class="post-lead">{{ page.description }}</p>{% endif %}
</header>
{% if page.toc %}
<nav class="toc">
<h2>Contents</h2>
{{ content | toc_only }}
</nav>
{% endif %}
<div class="post-content">
{{ content }}
</div>
</article>
Adding a light/dark mode toggle to any theme
If your theme does not include dark mode, you can add it yourself by overriding a few files. The approach:
First, override the root stylesheet to use CSS custom properties. Second, add a [data-theme="dark"] rule set. Third, add the toggle button via _includes/header.html override. Fourth, add a small JavaScript file.
The full implementation is covered in detail in the dark mode guide. For most themes, the process takes about two hours and the result is indistinguishable from a theme with built-in dark mode.
When to switch themes entirely
Customisation has limits. If you find yourself overriding every layout and include, rewriting the entire stylesheet, and fighting the theme’s assumptions at every turn, the theme is the wrong fit. At that point, the right answer is to switch to a more appropriate theme or build a minimal custom theme from scratch.
Signs you have outgrown a theme: more than half the theme’s CSS is overridden, the theme’s _config.yml settings do not cover what you need, and updates break things because your overrides conflict with the theme’s changes.
Browse Jekyll themes on JekyllHub to find one that requires fewer overrides for your specific use case. The right theme customisation strategy is: pick a theme that is 80-90% right, then use overrides to close the remaining gap — not pick any theme and try to build your vision from scratch on top of it.
Handling theme updates after customisation
When a theme releases an update, you need a strategy for integrating changes without losing your customisations. This is the central tension of using a gem-based theme: you benefit from upstream fixes and new features, but those changes can conflict with your overrides.
A reliable update workflow:
First, review the theme’s changelog before updating. Look for changes to files you have overridden — these require manual merging. Changes to files you have not overridden are applied automatically and you do not need to do anything.
To see what changed in a theme gem between versions, use git to compare the gem files. Most themes publish their source on GitHub, making it easy to compare tags:
# View what changed in Minimal Mistakes between v4.24 and v4.25
https://github.com/mmistakes/minimal-mistakes/compare/4.24.0...4.25.0
Look at the diff for any layout or include file you have overridden. If the upstream change is a bug fix or improvement you want, merge those changes into your override file manually.
For stylistic changes (the theme changed a colour, adjusted a font size), check whether you would prefer to keep your customisation or adopt the upstream change. Since your SCSS override file sets variables before importing the theme, your values take precedence automatically — theme updates to the same variables do not override yours.
Keeping a CUSTOMISATIONS.md file in your repository helps greatly during updates. List every file you have overridden and why. When updating the theme, this checklist tells you exactly what to review.
Customising error pages
The 404 page often gets overlooked during theme customisation. A well-designed 404 page includes your site’s full navigation and provides useful paths forward: a search box, links to popular content, a link to the homepage.
Override the 404 page by creating 404.md or 404.html at your project root:
---
layout: default
title: "Page Not Found"
permalink: /404.html
---
# 404 — Page Not Found
The page you were looking for does not exist.
- [Browse all Jekyll themes](/themes/)
- [Read the blog](/blog/)
- [Return home](/)
Most Jekyll themes provide a default 404 page, but overriding it with a custom version that matches your site’s design and links to relevant content is a small investment with noticeable returns — users who land on a 404 are about to leave your site, and a useful 404 page can retain some of them.
Performance implications of customisation
CSS overrides add weight. A few practices keep the stylesheet lean:
Remove variables you are overriding from the theme’s existing CSS rather than layering your values on top. If the theme defines --color-primary: #0366d6 and you declare --color-primary: #2563eb later, both rules are in the CSS, though only the last one takes effect. Tools like PurgeCSS remove unused CSS rules but cannot detect that the first declaration is functionally dead.
If you add Google Fonts via <link> tag, consider using the font-display: swap display option and limiting the number of weights you load. Loading every weight variant of a typeface adds 200-500KB to your page. Most sites need only two weights: regular (400) and bold (700).
Audit your total CSS size periodically with browser DevTools (Network tab, filter by CSS). If it exceeds 200KB uncompressed, something is wrong — either you have accumulated many overrides without cleaning up, or the base theme is unusually large.
Summary: the customisation hierarchy
From least risky to most risky:
1. _config.yml settings — safest. Change without touching any file the theme owns.
2. SCSS variable overrides — very safe. Declare variables before importing the theme; updates do not affect them.
3. _data/ files — safe. Navigation, settings, feature flags. Theme updates rarely touch these.
4. Include overrides — moderate risk. Copy an include when you need to change its structure; merge upstream updates manually.
5. Layout overrides — higher risk. Copy a layout when the include approach is insufficient. Upstream layout changes require manual merging.
6. Asset replacement — low risk for images, no risk for custom JS.
Work from the top of this list downward. Use the most conservative approach that achieves your goal.
Browse Jekyll themes on JekyllHub with the assurance that every theme’s gem-based distribution makes customisation — and future updates — straightforward.