This article explains what Drupal themes are, how they work, and how they fit into the Drupal rendering system.
It focuses on real-world usage, not just files — so you understand when and why to use themes while building or debugging Drupal sites.
1. What Is a Drupal Theme? (Beginner Friendly)
A Drupal theme controls how the site looks, not how it behaves.
Themes are responsible for:
- HTML markup
- Layout and structure
- CSS styling
- JavaScript behavior
- Twig templates
In simple terms:
Modules decide what data exists.
Themes decide how that data is displayed.
2. What a Theme Should and Should NOT Do
Themes SHOULD:
- Control layout and markup
- Style content with CSS
- Render data using Twig
- Attach frontend libraries (CSS/JS)
Themes SHOULD NOT:
- Query the database
- Contain business logic
- Call external APIs
- Decide access permissions
If logic lives in a theme, it belongs in a module.
3. Types of Drupal Themes
Drupal supports three main theme types:
Core Themes
Provided by Drupal core
Examples:
- Olivero
- Claro
Location:
core/themes/
Contributed Themes
Downloaded from drupal.org
Examples:
- Bootstrap
- Barrio
- Radix
Location:
themes/contrib/
Custom Themes
Built for a specific project
Location:
themes/custom/
Most production sites use custom themes.
4. Minimal Theme Structure
Every Drupal theme must have:
example_theme.info.yml
example_theme.info.yml
name: Example Theme
type: theme
base theme: olivero
core_version_requirement: ^10
libraries:
- example_theme/global
This tells Drupal:
- It is a theme
- Which base theme it extends
- Which libraries to load
5. Twig | How Drupal Outputs HTML
Drupal uses Twig for templating.
Example:
<h1>{{ title }}</h1>
{{ content }}
Twig:
- Receives data from modules
- Escapes output by default
- Keeps logic out of templates
Twig templates live in:
/templates
6. Theme Hook Suggestions (Why They Matter)
Drupal chooses templates dynamically using theme hook suggestions.
Example for a node:
node--article.html.twig
node--page.html.twig
node.html.twig
Drupal picks the most specific template available.
This allows precise control without duplicating logic.
7. Libraries | CSS and JavaScript the Drupal Way
Themes attach CSS and JS using libraries.
example_theme.libraries.yml
global:
css:
theme:
css/style.css: {}
js:
js/main.js: {}
Libraries are attached:
- Globally
- Per template
- Per component
No hardcoded <link> or <script> tags.
8. Real-World Scenario – Event Listing Page
Scenario:
- A module provides event data
- A theme controls how events look
Flow:
Module → Controller → Render Array → Theme → Preprocess Functions → Twig → HTML
The theme:
- Chooses layout
- Styles event cards
- Handles responsive design
The module:
- Loads data
- Handles permissions
- Returns structured data
Clear separation of responsibilities.
9. Where Does Twig Data Come From? (Very Important)
This is a common and very important question.
Twig does not magically know data.
Everything available in Twig comes from Drupal’s render pipeline.
Let’s break it down step by step.
Step 1: Controller or Plugin Creates a Render Array
Most data starts in:
- Controllers
- Blocks
- Views
- Plugins
Example controller:
public function page() {
return [
'#theme' => 'event_list',
'#title' => 'Upcoming Events',
'#events' => $events,
];
}
This render array defines:
- Which theme hook to use (
#theme) - What data should be passed
At this point, there is no Twig yet.
Step 2: Theme Hook Defines Available Variables
The theme hook tells Drupal what variables exist.
Defined via:
hook_theme()in a module- Or auto-generated (nodes, views, blocks)
Example:
function event_hosting_theme() {
return [
'event_list' => [
'variables' => [
'title' => NULL,
'events' => [],
],
],
];
}
This is where Drupal officially says:
These variables are allowed in Twig.
Step 3: Preprocess Functions Modify Variables
Before Twig runs, Drupal calls preprocess functions.
Example:
function example_theme_preprocess_event_list(&$variables) {
$variables['event_count'] = count($variables['events']);
}
Preprocess functions can:
- Add new variables
- Clean or format data
- Combine values
They run after the render array, but before Twig.
Step 4: Twig Receives Final Variables
Finally, Twig gets the prepared variables:
<h1>{{ title }}</h1>
<p>Total events: {{ event_count }}</p>
<ul>
{% for event in events %}
<li>{{ event.name }}</li>
{% endfor %}
</ul>
Twig:
- Cannot query data
- Cannot call services
- Only renders what it receives
What About JSON Responses?
If a controller returns JSON:
return new JsonResponse($data);
Then:
- Twig is skipped entirely
- No preprocess runs
- Drupal sends raw JSON
Render arrays and Twig are only used for HTML responses.
Simple Mental Model
Controller / Plugin
→ Render Array
→ Theme Hook
→ Preprocess Functions
→ Twig Template
→ HTML
If data is missing in Twig, the issue is always upstream.
10. Themes vs Modules (Quick Comparison)
| Themes | Modules |
|---|---|
| Control presentation | Control behavior |
| Twig, CSS, JS | PHP, services, hooks |
| Layout and styling | Business logic |
| No data loading | Data and APIs |
Why Themes Matter
Understanding themes helps you:
- Debug markup issues
- Customize layouts safely
- Avoid logic in Twig
- Work cleanly with frontend teams
- Build upgrade-safe UIs
If you understand themes, you control how Drupal looks without breaking how it works.
Quick Summary
- Themes control presentation
- Modules control logic
- Twig renders HTML
- Libraries manage CSS and JS
- Preprocess bridges data and templates
- Clean separation = maintainable Drupal