Grav Themes & Customization — From Quark to Your Own Design
In this tutorial, you'll learn how theming works in Grav — from tweaking the default Quark theme to creating a fully custom design.
What You'll Learn
- How Grav's theme system works
- How to create a child theme
- How to override templates and CSS
- Theme configuration and inheritance
- Installing themes via GPM
Why It Matters
Every site needs its own look. A default theme tells visitors "this is a template." A custom theme tells them "this is a professional product." Grav's theme system lets you override just the files you need — full control without rewriting everything.
Real-World Use
DodaTech's product documentation sites each have a branded theme built as a child of Quark. The child theme overrides the color scheme, fonts, and header layout but inherits all the responsive grid and typography from the parent. When Quark updates, the child theme gets bug fixes automatically.
The Default Theme: Quark
Grav ships with the Quark theme — a clean, Responsive Design based on the Spectre CSS framework.
Location: user/themes/quark/
quark/
├── templates/ # Twig templates
│ ├── partials/ # Reusable partials (header, footer)
│ ├── default.html.twig
│ ├── blog.html.twig
│ └── item.html.twig
├── css/ # Compiled CSS
├── js/ # JavaScript
├── images/ # Theme images
├── blueprints.yaml # Theme configuration definition
└── quark.yaml # Default theme config
To set Quark as your theme, user/config/system.yaml should have:
pages:
theme: quark
Creating a Child Theme
Never edit the parent theme directly. Updates will overwrite your changes. Instead, create a child theme.
Step 1: Create the Folder Structure
user/themes/
└── mytheme/
├── templates/
├── css/
├── js/
├── blueprints.yaml
└── mytheme.yaml
Step 2: Create blueprints.yaml
user/themes/mytheme/blueprints.yaml:
name: My Theme
version: 1.0.0
description: Child theme of Quark
author:
name: Your Name
email: you@example.com
extends: quark
Step 3: Create mytheme.yaml
user/themes/mytheme/mytheme.yaml:
enabled: true
Step 4: Activate the Theme
In user/config/system.yaml:
pages:
theme: mytheme
Clear cache and refresh:
bin/grav cache
Your site should now use mytheme — it looks identical to Quark because that's the parent.
Step 5: Override Specific Templates
Copy only the templates you want to change from user/themes/quark/templates/ to user/themes/mytheme/templates/:
# Copy the header partial to your child theme
cp user/themes/quark/templates/partials/header.html.twig \
user/themes/mytheme/templates/partials/header.html.twig
Edit the copy. Now your custom header renders instead of Quark's. All other templates still inherit from Quark.
Customizing CSS
Add Custom CSS
Create user/themes/mytheme/css/custom.css:
:root {
--primary-color: #3b82f6;
--secondary-color: #06b6d4;
--font-family: 'Inter', sans-serif;
}
.header {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
padding: 1rem 2rem;
}
.header a {
color: white;
text-decoration: none;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin: 2rem 0;
}
.card {
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 1.5rem;
transition: box-shadow 0.2s;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
Include It in the Template
Copy base.html.twig from Quark to your child theme and add the CSS link:
{% block stylesheets %}
{% do assets.addCss('theme://css/custom.css', 10) %}
{{ parent() }}
{% endblock %}
The {{ parent() }} call includes everything the parent template includes (Quark's CSS), plus your custom CSS on top.
Template Override Order
When Grav looks for a template, it checks in this order:
- Page-specific template — Based on the
templatefrontmatter field - Filename-based template — Based on the
.mdfilename - Child theme templates — Your theme's templates/
- Parent theme templates — Quark's templates/ (if your theme extends it)
- Fallback: default.html.twig — If nothing else matches
This cascade means you can override at any level.
Theme Configuration
Themes can expose configuration options. In your child theme's mytheme.yaml:
enabled: true
color_scheme: dark
header_style: sticky
Access in templates:
{% if theme.color_scheme == 'dark' %}
<body class="dark-theme">
{% endif %}
Users can override these values in user/config/themes/mytheme.yaml.
Installing Third-Party Themes
# Search for themes
bin/gpm search theme
# Install a theme
bin/gpm install afterburner2
After installation, set it as active in system.yaml:
pages:
theme: afterburner2
Common Theming Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| Editing parent theme | Changes lost on update | Create a child theme instead |
Missing extends in blueprints |
Theme loads but has no templates | Add extends: quark to blueprints.yaml |
| Cache not cleared | Old template still renders | bin/grav cache |
| CSS not loading | Wrong path in template | Use theme://css/custom.css |
| Template not found | 404 or blank page | Check template filename matches exactly |
Learning Path
flowchart LR A["What is Grav?"] --> B["Installation"] B --> C["Pages & Content"] C --> D["Navigation"] D --> E["Twig Templating"] E --> F["Themes
← You are here"]:::current F --> G["Taxonomy & Blog"] G --> H["Plugins & Admin"] H --> I["Configuration & Caching"] I --> J["Deployment & Maintenance"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Practice Questions
Why should you use a child theme instead of editing Quark directly? Answer: Updates to Quark will overwrite your changes. A child theme inherits from Quark but keeps your customizations separate.
How does Grav find a template when rendering a page? Answer: It checks page-specific templates first, then filename-based, then child theme, then parent theme, and falls back to
default.html.twig.How do you add custom CSS to a child theme? Answer: Create a
.cssfile incss/, then add it via{% do assets.addCss('theme://css/custom.css') %}in the base template.What does
{{ parent() }}do in a Twig block? Answer: It renders the parent theme's block content, allowing you to add to the block rather than replace it entirely.Challenge: Create a child theme that changes the header background to a gradient, adds a custom font, and displays a logo in the top-left corner. Override only the minimum templates needed.
What's Next
Your site has a unique look. Now let's add blog functionality:
Continue to Lesson 7: Taxonomy & Blogging — Tags, categories, and blog listing pages.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro