Drupal Sub-themes — How to Create and Customize a Sub-theme
In this tutorial, you'll learn how to create a Drupal sub-theme — inheriting from a base theme like Olivero or Bootstrap 5, overriding templates, adding custom CSS and JavaScript, and following best practices for maintainable sub-themes.
What You'll Learn
- What sub-themes are and why they are preferred over modifying base themes directly
- Choosing the right base theme for your project
- Creating a sub-theme directory structure and .info.yml file
- Overriding parent templates and libraries
- Adding custom CSS, JavaScript, and regions
Why It Matters
Modifying a contributed theme directly is risky. When the theme updates, your changes are overwritten. Sub-themes solve this by inheriting everything from a parent theme while keeping your customizations in a separate, update-safe location. Sub-themes let you leverage existing theme code — often hundreds of hours of development — and customize only what you need. This is standard practice in professional Drupal development and the only safe way to customize contributed themes.
Real-World Use
A digital agency builds Drupal sites for multiple clients, all using the Bootstrap 5 base theme. Each client needs different colors, fonts, and layouts. The agency creates a sub-theme for each client that inherits from Bootstrap 5. The sub-theme overrides only the CSS variables file and a few specific templates. When Bootstrap 5 releases a security update, the agency updates it via Composer, and all client sub-themes inherit the fixes automatically.
Learning Path
flowchart LR A[Twig Templating] --> B[Sub-themes] B --> C[Template Suggestions] C --> D[Asset Libraries] D --> E[Module Management] E --> F[Essential Modules] F --> G[Caching]
What Is a Sub-theme?
A sub-theme is a Drupal theme that inherits resources from a parent theme. The sub-theme contains only the files that differ from the parent. When Drupal renders a page, it first looks for templates and assets in the sub-theme, then falls back to the parent theme.
Sub-theme (my_custom_theme)
├── my_custom_theme.info.yml (declares base theme)
├── css/style.css (overrides parent CSS)
└── templates/
└── node.html.twig (overrides parent template)
Parent theme (olivero)
├── olivero.info.yml
├── css/
│ ├── components/
│ └── layout/
├── js/
└── templates/
├── node.html.twig (fallback if sub-theme lacks it)
├── page.html.twig
└── block.html.twig
Choosing a Base Theme
The base theme determines your starting point. Consider these options:
Olivero — Drupal 10's default front-end theme. Accessible, modern, good starting point for content-focused sites. Limited layout options without customization.
Bootstrap 5 — responsive framework with built-in grid, components, and utility classes. Best for sites that need rapid prototyping and Responsive Design.
Stable 9 — minimal base theme with almost no CSS. Best for experienced developers who want complete control and minimal bloat.
Classy — provides more default styles than Stable. Good for beginners who need visible progress without writing much CSS.
# Example .info.yml with different base themes:
# Olivero sub-theme:
base theme: olivero
# Bootstrap 5 sub-theme:
base theme: bootstrap5
# Stable 9 sub-theme:
base theme: stable9
Sub-theme Directory Structure
A minimal sub-theme needs only an .info.yml file. Additional files are added as needed.
themes/custom/my_custom_theme/
├── my_custom_theme.info.yml
├── my_custom_theme.libraries.yml
├── my_custom_theme.breakpoints.yml
├── my_custom_theme.schema.yml
├── screenshot.png
├── css/
│ └── style.css
├── js/
│ └── main.js
└── templates/
├── node.html.twig
├── page.html.twig
└── block.html.twig
Sub-theme .info.yml
The .info.yml file is the only required file. It must include the base theme key pointing to the parent theme.
# my_custom_theme.info.yml
name: 'My Custom Theme'
type: theme
core_version_requirement: ^10 || ^11
description: 'A custom sub-theme based on Olivero.'
base theme: olivero
package: Custom
version: 1.0.0
libraries:
- my_custom_theme/global
libraries-override:
olivero/global:
css:
theme:
css/style.css: css/custom-style.css
libraries-extend:
core/drupal.dropbutton:
- my_custom_theme/dropbutton
regions:
header: 'Header'
primary_menu: 'Primary Menu'
content: 'Content'
sidebar: 'Sidebar'
footer: 'Footer'
regions_hidden: []
Inheriting Templates
Sub-themes automatically inherit all templates from the parent theme. You only need to create a template file in your sub-theme if you want to override it.
To override node.html.twig:
- Find the original template in the parent theme at
themes/contrib/olivero/templates/node.html.twig - Copy it to
themes/custom/my_custom_theme/templates/node.html.twig - Modify the copied template
The template path in the sub-theme must match the relative path used in the parent theme.
# Locate a template in the parent theme:
find themes/contrib/olivero -name "node.html.twig"
# Copy to sub-theme:
cp themes/contrib/olivero/templates/node.html.twig \
themes/custom/my_custom_theme/templates/node.html.twig
Overriding Libraries
Use libraries-override in .info.yml to replace or modify parent theme libraries.
# Replace a parent CSS file with your own:
libraries-override:
olivero/global:
css:
theme:
css/style.css: css/custom-style.css
# Remove a parent library entirely:
libraries-override:
olivero/fonts: false
# Override a JavaScript file:
libraries-override:
olivero/navigation:
js:
js/navigation.js: js/custom-navigation.js
Adding Custom CSS and JS
Define new libraries in your sub-theme's .libraries.yml file.
# my_custom_theme.libraries.yml
global:
version: 1.0
css:
component:
css/components/cards.css: {}
theme:
css/custom-style.css: {}
js:
js/main.js: {}
js/custom.js:
attributes:
defer: true
homepage:
version: 1.0
css:
component:
css/components/hero.css: {}
js:
js/hero-animation.js:
attributes:
defer: true
Load these libraries in .info.yml globally or in Twig templates:
# In .info.yml to load globally:
libraries:
- my_custom_theme/global
{# In a specific template: #}
{{ attach_library('my_custom_theme/homepage') }}
Libraries-Extend
The libraries-extend key adds your libraries to existing parent libraries. Use this to add CSS or JS to specific parent theme behaviors.
libraries-extend:
# Add our custom styles to the parent's dropbutton library
core/drupal.dropbutton:
- my_custom_theme/dropbutton
# Extend Olivero's global library with our custom print styles
olivero/global:
- my_custom_theme/print-styles
Sub-theme Regions Override
A sub-theme can define different regions than its parent. The regions from the sub-theme completely replace the parent's regions.
# Sub-theme with custom regions:
regions:
header_top: 'Header Top'
header_bottom: 'Header Bottom'
highlighted: 'Highlighted'
content: 'Content'
sidebar_left: 'Left Sidebar'
sidebar_right: 'Right Sidebar'
footer_first: 'Footer First'
footer_second: 'Footer Second'
footer_third: 'Footer Third'
Sub-theme Settings
Create a .schema.yml file to provide custom theme settings with a UI.
# my_custom_theme.schema.yml
my_custom_theme.settings:
type: theme_settings
label: 'My Custom Theme settings'
mapping:
layout_width:
type: string
label: 'Layout Width'
header_style:
type: string
label: 'Header Style'
footer_columns:
type: integer
label: 'Footer Columns'
Sub-theme Screenshot
A screenshot.png in the sub-theme root appears on the Appearance page. Size it at 293 by 68 pixels.
# Create a simple screenshot:
convert -size 293x68 "xc:#0044cc" -fill white \
-pointsize 20 -gravity center \
-annotate 0 "My Custom Theme" \
screenshot.png
Best Practices
Do not override every template. Override only what you need. Each override creates maintenance work when the parent theme updates. Document every override by adding comments at the top of each overridden file noting the date and reason for the override. Test after every parent theme update to ensure overrides still work. Use libraries-override sparingly — prefer libraries-extend when you only need to add assets. Keep the sub-theme minimal. A sub-theme with only an .info.yml file is valid if you only need to change theme settings.
Common Mistakes
Modifying the parent theme directly: Changes to contributed themes are lost on update. Always create a sub-theme for customizations.
Missing the base theme key: Without
base themein .info.yml, the sub-theme is treated as a standalone theme and inherits nothing.Wrong template path: Templates in the sub-theme must match the same relative path as in the parent. A template in the wrong path is ignored.
Not clearing cache after adding templates: Twig caches template locations. Run
drush crafter adding new template files to the sub-theme.Overriding too much: Copying every parent template defeats the purpose of a sub-theme. Override only the templates you need to customize.
Practice Questions
What is the advantage of creating a sub-theme instead of modifying a contributed theme directly?
How does the
libraries-overridekey in .info.yml differ fromlibraries-extend?When you create a template file in a sub-theme, how does Drupal decide whether to use the sub-theme template or fall back to the parent?
Challenge: Plan a sub-theme for a corporate website using Bootstrap 5 as the base theme. List the templates you would override (at least 3), the libraries you would add, and the regions you would define. Explain why each override is necessary and how you would handle Bootstrap 5 updates.
FAQ
Mini Project
Goal: Create a complete sub-theme based on Olivero.
- Create
themes/custom/my_olivero/directory - Create
my_olivero.info.ymlwith base theme as olivero, name, description, and one region override (add a "promo" region) - Create
my_olivero.libraries.ymlwith a global library containing custom-style.css - Create
css/custom-style.cssthat changes the primary color to a custom hex value - Override
templates/page.html.twigto add the promo region above the content - Create
screenshot.pngfor the theme - Enable the sub-theme on the Appearance page
- Verify it inherits Olivero's templates but uses your custom color
What's Next
Now that you can create sub-themes, proceed to template suggestions for fine-grained control over which template renders each piece of content. After that, explore asset libraries to manage CSS and JavaScript efficiently.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro