Grav Theme Inheritance — Child Themes and Template Overrides
In this tutorial, you'll learn Grav theme inheritance — how to create child themes, override parent templates without modifying core files, extend parent theme functionality, and structure theme customization for maintainability.
What You'll Learn
- What child themes are and why they are essential
- Creating a child theme from a parent theme
- Overriding templates, assets, and configuration
- Extending parent theme blocks and macros
- Best practices for child theme organization
- Updating parent themes without losing customizations
Why It Matters
In WordPress, child themes are the standard way to customize themes without losing changes on update. The same concept applies to Grav. If you edit a parent theme directly, your changes are lost when the parent theme is updated. A child theme inherits everything from the parent but lets you override specific files. This means you can customize any template, CSS file, or configuration while keeping the parent theme installable and updatable.
Real-World Use
A design agency builds sites on the Quark theme (Grav's default). Each client needs custom templates, colors, and layouts. Instead of copying and forking the Quark theme for each client, they create a child theme. The child theme overrides 3 templates and a CSS file. When Quark gets a security update, they apply it without touching the client's customizations. The child theme continues to work because it only overrides specific files, inheriting the rest from the updated parent.
Learning Path
flowchart LR
A["Theme Assets"] --> B["Theme Inheritance
← You are here"]:::current
B --> C["Theme Languages"]
C --> D["Plugin Architecture"]
D --> E["Plugin Events"]
E --> F["Plugin Forms"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
What Is a Child Theme?
A child theme is a theme that inherits all templates, assets, and configuration from a parent theme. You only create files for the parts you want to change.
Parent theme (Quark):
user/themes/quark/
├── quark.yaml
├── blueprints.yaml
├── templates/
│ ├── partials/
│ │ ├── base.html.twig
│ │ ├── header.html.twig
│ │ └── footer.html.twig
│ ├── default.html.twig
│ ├── blog.html.twig
│ └── item.html.twig
├── css/
│ ├── styles.css
│ └── custom.css
├── js/
│ └── main.js
└── images/
Child theme (mytheme):
user/themes/mytheme/
├── mytheme.yaml
├── blueprints.yaml
├── templates/
│ └── partials/
│ └── header.html.twig # ← Only file to override
├── css/
│ └── custom.css # ← Additional CSS
└── child-theme.yaml
Everything else is inherited from Quark.
Creating a Child Theme
Step 1: Create Theme Directory
mkdir -p user/themes/mytheme
Step 2: Create child-theme.yaml
This file tells Grav that this theme is a child of another theme:
user/themes/mytheme/child-theme.yaml:
name: My Child Theme
version: 1.0.0
parent: quark
description: A child theme of Quark for DodaTech documentation
Step 3: Create mytheme.yaml
A minimal mytheme.yaml is needed for theme configuration:
user/themes/mytheme/mytheme.yaml:
enabled: true
Step 4: Enable the Child Theme
Navigate to the Admin panel and set "My Child Theme" as the active theme. Or edit user/config/system.yaml:
pages:
theme: mytheme
Overriding Templates
Any template file you create in the child theme overrides the parent theme's file at the same path.
Override a Single Template
Create user/themes/mytheme/templates/blog.html.twig:
{% extends 'partials/base.html.twig' %}
{% block content %}
<div class="blog-listing custom-listing">
<h1>{{ page.title }}</h1>
{% for child in page.collection %}
<article class="blog-card">
<h2><a href="{{ child.url }}">{{ child.title }}</a></h2>
<p>{{ child.summary }}</p>
</article>
{% endfor %}
</div>
{% endblock %}
This overrides the parent's blog.html.twig completely. All other templates (default.html.twig, item.html.twig, etc.) are inherited from the parent.
Override a Partial
Create user/themes/mytheme/templates/partials/header.html.twig:
<header class="custom-header">
<div class="logo">
<a href="{{ base_url_absolute }}">
<img src="{{ url('theme://images/dodatech-logo.svg') }}" alt="DodaTech" />
</a>
</div>
<nav>
{% include 'partials/navigation.html.twig' %}
</nav>
</header>
Extending Parent Templates
Instead of completely replacing a template, you can extend it and override specific blocks:
templates/default.html.twig in child theme:
{% extends 'default.html.twig' %}
{% block content %}
{{ parent() }} {# Includes parent content #}
<section class="page-footer">
<p>Last updated: {{ page.modified|date('F j, Y') }}</p>
</section>
{% endblock %}
This keeps the parent's content block and adds a "Last updated" section at the bottom.
Overriding Assets
CSS
Create user/themes/mytheme/css/custom.css:
/* Override parent theme styles */
:root {
--primary: #e74c3c;
--secondary: #f39c12;
}
.header {
background: var(--primary);
}
Load it in the child theme's base template override:
{% extends 'partials/base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
{% do assets.addCss('theme://css/custom.css') %}
{% endblock %}
JS
{% block javascripts %}
{{ parent() }}
{% do assets.addJs('theme://js/custom.js') %}
{% endblock %}
Overriding Configuration
Create user/themes/mytheme/blueprints.yaml to add or override parent theme settings:
form:
validation: loose
fields:
custom_logo:
type: file
label: Custom logo
destination: 'theme://images'
Add the child theme's configuration in mytheme.yaml:
enabled: true
custom_logo: 'theme://images/dodatech-logo.svg'
Inheritance Resolution Order
When Grav looks for a template file, it checks:
- Child theme — does the file exist in the child theme directory?
- Parent theme — if not, does it exist in the parent theme directory?
- Error — if neither exists, Twig throws a "template not found" error
This means the child theme does not need to replicate the entire parent theme — only the files it wants to change.
Multiple Levels of Inheritance
Grav supports multi-level theme inheritance:
mytheme-child (child of mytheme)
└── mytheme (child of quark)
└── quark (base theme)
user/themes/mytheme-child/child-theme.yaml:
parent: mytheme
If a file is not found in mytheme-child, Grav checks mytheme, then quark.
Learning Path
flowchart LR
A["Theme Assets"] --> B["Theme Inheritance
← You are here"]:::current
B --> C["Theme Languages"]
C --> D["Plugin Architecture"]
D --> E["Plugin Events"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Common Mistakes
Editing the parent theme directly: Any change to the parent theme is lost when it is updated. Always create a child theme for customizations.
Not creating
child-theme.yaml: Without this file, Grav does not know that the theme is a child theme. It treats it as a standalone theme, and inheritance does not work.Forgetting
parent()when extending blocks: If you override a block and want to keep the parent's content, call{{ parent() }}. Without it, the parent block content is replaced entirely.Wrong template path in child theme: The path in the child theme must exactly match the path in the parent theme. A file at
templates/partials/header.html.twigin the parent must be attemplates/partials/header.html.twigin the child.Overriding too much: Only override files that need changes. Everything else inherits automatically. Overriding unmodified files defeats the purpose of inheritance and creates maintenance burden.
Practice Questions
What file tells Grav that a theme is a child theme? Answer:
child-theme.yamlwith aparent:field specifying the parent theme name. Without this file, inheritance does not work.How does Grav resolve template files between parent and child themes? Answer: Grav checks the child theme first. If the file exists there, it is used. If not, Grav falls back to the parent theme directory. This continues up the inheritance chain.
How do you add new CSS files in a child theme while keeping the parent's CSS? Answer: Override the
{% block stylesheets %}in the base template, call{{ parent() }}to keep parent CSS, then add new assets:{% do assets.addCss('theme://css/custom.css') %}.What happens to a child theme when the parent theme is updated? Answer: The child theme's overridden files are unaffected. New features in the parent theme are inherited automatically by the child theme unless overridden.
Challenge: Create a two-level child theme system. Base theme (quark) with standard templates. First child theme (my-base) that overrides the header template, adds a CSS file, and adds 3 configuration options. Second child theme (my-client) that is a child of my-base and overrides the footer template, adds its own CSS, and sets custom configuration values. Verify that templates resolve correctly at each level of the inheritance chain.
FAQ
Mini Project
Goal: Create a child theme from Quark with 10 customizations.
- Create the child theme directory and
child-theme.yamlpointing to quark - Create
mytheme.yamlandblueprints.yamlwith 3 custom options - Override the header template to use a custom logo
- Override the footer template with custom copyright and social links
- Override
default.html.twigto add a sidebar (using extended block withparent()) - Override
blog.html.twigto use a card-based layout - Override
item.html.twigwith an author bio section - Add a custom CSS file that changes primary colors
- Add a custom JS file for interactive features
- Enable the child theme, verify all overrides work, and confirm unmodified templates are inherited
What's Next
Now you can customize themes without losing updates. Next, learn theme language support:
Continue to Lesson 23: Theme Languages — Multilingual theme strings, translation files, and language-aware templates.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro