Jekyll Theme Setup and Customization
Jekyll themes are distributed in two ways: as gem packages (installed via Bundler) and as regular repositories (copied directly). The customization mechanism differs, but the override principle is the same: files in the project root override theme files.
Gem-based themes: how they work
When a theme is installed as a gem, its files are located in the Ruby gems directory, not in the project:
bundle info --path minima
# => /path/to/gems/minima-2.5.1
To view theme files:
bundle exec jekyll theme-files
To override: copy the needed file from the gem to the project root, preserving folder structure:
# Copy the theme layout to the project
cp $(bundle info --path minima)/_layouts/post.html _layouts/post.html
# Copy a partial
cp $(bundle info --path minima)/_includes/header.html _includes/header.html
After this, edit the copied file — Jekyll will automatically use it instead of the original.
Configuration through _config.yml
Most themes read parameters from the config. Example for the popular Minimal Mistakes theme:
# Theme
remote_theme: "mmistakes/[email protected]"
# Theme skin
minimal_mistakes_skin: "air" # "default", "air", "aqua", "contrast", "dark", "dirt", "neon", "mint", "plum", "sunrise"
# Site parameters
locale: "en-US"
title: "Site Title"
name: "Team"
description: "Description"
url: "https://example.com"
# Default author
author:
name: "Name"
avatar: "/assets/images/avatar.jpg"
bio: "Brief description"
links:
- label: "Email"
icon: "fas fa-fw fa-envelope-square"
url: "mailto:[email protected]"
- label: "LinkedIn"
icon: "fab fa-fw fa-linkedin"
url: "https://linkedin.com/in/username"
# Navigation
navigation:
- title: "Home"
url: /
- title: "Blog"
url: /blog/
- title: "About"
url: /about/
# Analytics
analytics:
provider: "google-gtag"
google:
tracking_id: "G-XXXXXXXXXX"
anonymize_ip: false
# Comments
comments:
provider: "disqus"
disqus:
shortname: "mysite"
Style customization
Gem themes usually allow adding custom styles through a special file. For Minima:
/* assets/css/style.scss */
---
---
@import "minima";
/* Your overrides */
:root {
--base-font-size: 16px;
--base-line-height: 1.7;
--brand-color: #2563eb;
--brand-color-dark: #1e40af;
}
.site-header {
border-top: 4px solid var(--brand-color);
background: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
.post-title {
font-size: 2.25rem;
line-height: 1.2;
letter-spacing: -0.02em;
}
For Minimal Mistakes — through _sass/minimal-mistakes/_variables.scss (need to copy from gem):
/* _sass/minimal-mistakes/_variables.scss */
$sans-serif: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !default;
$monospace: "JetBrains Mono", "Fira Code", monospace !default;
$primary-color: #2563eb !default;
$success-color: #22c55e !default;
$warning-color: #f59e0b !default;
$danger-color: #ef4444 !default;
$border-radius: 6px !default;
$box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1) !default;
Liquid template overrides
Example: add a table of contents (TOC) to a post layout:
<!-- _layouts/post.html -->
---
layout: default
---
<article class="post h-entry">
<header class="post-header">
<h1 class="post-title p-name">{{ page.title | escape }}</h1>
<p class="post-meta">
<time class="dt-published" datetime="{{ page.date | date_to_xmlschema }}">
{{ page.date | date: "%d %B %Y" }}
</time>
{% if page.author %}· {{ page.author }}{% endif %}
</p>
</header>
{% if page.toc %}
<aside class="toc">
<h2>Contents</h2>
{% include toc.html html=content %}
</aside>
{% endif %}
<div class="post-content e-content">
{{ content }}
</div>
{% if page.tags.size > 0 %}
<div class="post-tags">
{% for tag in page.tags %}
<a href="/tags/{{ tag | slugify }}/" class="tag">#{{ tag }}</a>
{% endfor %}
</div>
{% endif %}
</article>
Adding custom partials
If a theme doesn't provide a needed component, create it in _includes/:
<!-- _includes/cta-banner.html -->
{% assign bg = include.bg | default: "primary" %}
{% assign title = include.title %}
{% assign subtitle = include.subtitle %}
{% assign button_text = include.button_text | default: "Learn more" %}
{% assign button_url = include.button_url | default: "/contact/" %}
<section class="cta-banner cta-banner--{{ bg }}">
<div class="container">
<h2>{{ title }}</h2>
{% if subtitle %}
<p>{{ subtitle }}</p>
{% endif %}
<a href="{{ button_url }}" class="btn btn--primary">{{ button_text }}</a>
</div>
</section>
Usage on a page or layout:
{% include cta-banner.html
title="Ready to start a project?"
subtitle="Let's discuss your goals and find a solution"
button_text="Get in touch"
button_url="/contact/"
bg="dark"
%}
Navigation management
For complex menus — through _data/navigation.yml:
main:
- title: "Services"
url: /services/
children:
- title: "Web development"
url: /services/web/
- title: "Mobile apps"
url: /services/mobile/
- title: "Blog"
url: /blog/
- title: "Contact"
url: /contact/
<!-- _includes/header.html -->
<nav>
{% for item in site.data.navigation.main %}
<div class="nav-item {% if item.children %}has-dropdown{% endif %}">
<a href="{{ item.url | relative_url }}"
{% if page.url == item.url %}aria-current="page"{% endif %}>
{{ item.title }}
</a>
{% if item.children %}
<ul class="dropdown">
{% for child in item.children %}
<li><a href="{{ child.url | relative_url }}">{{ child.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
</nav>
Timeline
Configuring a gem theme through config and custom CSS — 1–2 days. Overriding templates, adding custom partials and data — 3–5 days.







