Skip to content

Drupal Themes, Layouts & Extensions — Complete Theming Guide

DodaTech Updated Jun 6, 2026 8 min read

Drupal uses Twig (the templating engine from Symfony) for theme templates. If you’ve used Jinja2 in Python or Liquid in Shopify, Twig will feel familiar. Think of a theme as the costume your content wears — the same article can look completely different depending on which theme wraps around it.

What You’ll Learn

  • Drupal’s theme structure — .info.yml, .libraries.yml, Twig templates
  • Creating a custom theme and sub-theme
  • Using Layout Builder for drag-and-drop page layouts
  • Installing and managing contributed modules via Composer
  • Creating a custom module with routing and hooks

Why Theming and Extensibility Matter

Drupal’s theming system separates content from presentation. Your editors create structured content (articles with fields) and your theme decides how to display that content (grid, list, card, table). This means you can redesign your entire site by switching themes without touching a single piece of content.

The same separation powers DodaTech’s tutorial platform — structured content in the database, Twig templates deciding the HTML output. Even Durga Antivirus Pro uses template-based rendering for its documentation.

    flowchart LR
    A["Menus, Blocks & Taxonomies"] --> B["Themes, Layouts & Extensions<br/><strong>You are here</strong>"]:::current
    B --> C["Users, Security & Administration"]

    classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px;
  
Prerequisites: Drupal 10 installed, familiar with the admin UI. Basic knowledge of HTML, CSS, and PHP is helpful for custom theme and module development.

Theme Structure

A Drupal theme lives in themes/custom/ and consists of:

mytheme/
├── mytheme.info.yml           # Theme metadata & configuration
├── mytheme.libraries.yml      # Asset library definitions
├── mytheme.theme              # Theme hook overrides (PHP)
├── templates/                 # Twig template overrides
│   ├── node.html.twig
│   ├── page.html.twig
│   └── block.html.twig
├── dist/                      # Compiled assets
│   ├── css/
│   └── js/
└── src/                       # Source files (SCSS, JS)
    ├── scss/
    └── js/

The .info.yml File

This is the identity card of your theme. WordPress uses style.css headers; Drupal uses a YAML file.

name: 'My Theme'
type: theme
core_version_requirement: ^10
description: 'Custom theme for My Site'
base theme: false

# Define regions where blocks can be placed
regions:
  header: Header
  primary_menu: 'Primary menu'
  secondary_menu: 'Secondary menu'
  content: Content
  sidebar_first: 'Left sidebar'
  sidebar_second: 'Right sidebar'
  footer: Footer

# Load libraries globally
libraries:
  - mytheme/global-styles

Asset Libraries

Every CSS and JS file must be declared in .libraries.yml:

global-styles:
  version: 1.x
  css:
    theme:
      dist/css/style.css: { minified: true }
  js:
    dist/js/main.js: { minified: true }

Default Themes

Drupal 10 ships with three themes:

  • Olivero — Modern, accessible front-end theme (default)
  • Claro — Administration theme designed for usability
  • Stark — Bare-bones theme for debugging (no CSS, no JS)

Twig Templates

Twig is Drupal’s templating engine. Templates follow a strict naming hierarchy:

node--article--teaser.html.twig  → Article content type, teaser view mode
node--article.html.twig          → Article content type, any view mode
node.html.twig                   → Any node, any view mode
page.html.twig                   → Page-level template
block.html.twig                  → Block template
{# Override node.html.twig for the Article content type as a teaser #}
{# File: templates/node/node--article--teaser.html.twig #}
<article{{ attributes.addClass('article-teaser') }}>
  {{ title_prefix }}
  {% if not page %}
    <h2{{ title_attributes }}>
      <a href="{{ url }}" rel="bookmark">{{ label }}</a>
    </h2>
  {% endif %}
  {{ title_suffix }}

  {% if display_submitted %}
    <footer class="author">
      {{ author_picture }}
      <div{{ author_attributes }}>
        {% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %}
        {{ metadata }}
      </div>
    </footer>
  {% endif %}

  {% if content.field_image|render %}
    <div class="teaser-image">{{ content.field_image }}</div>
  {% endif %}

  <div{{ content_attributes }}>
    {{ content.body|render|striptags|slice(0, 300) }}...
  </div>
</article>

Sub-themes

A sub-theme inherits all templates and assets from a parent theme. Use a sub-theme when customizing Olivero or a contributed theme — your changes survive parent updates.

# my_subtheme.info.yml
name: 'My Subtheme'
type: theme
core_version_requirement: ^10
base theme: olivero

libraries:
  - my_subtheme/global

Create a new templates/ folder in your sub-theme and copy only the templates you need to override. Everything else comes from the parent.

Layout Builder

Layout Builder is Drupal’s drag-and-drop page builder — in core, no extra module needed.

Enabling Layout Builder

  1. Extend → Layout Builder and Layout Discovery (both core modules)
  2. Structure → Content types → Manage display → Layout
  3. Click Use Layout BuilderSave

Building Layouts

Once enabled, content editors can:

  • Add sections (one, two, three, or four columns)
  • Drag blocks into section regions
  • Configure per-block visibility and styling
  • Override layout on individual content items (different layout per article)
{# Layout Builder section template override #}
{# templates/layouts/layout--twocol-section.html.twig #}
{%
  set classes = [
    'layout',
    'layout--twocol-section',
    'layout--twocol-section--' ~ settings.percentage,
  ]
%}
<div{{ attributes.addClass(classes) }}>
  {% if content.first %}
    <div {{ region_attributes.first.addClass('layout__region', 'layout__region--first') }}>
      {{ content.first }}
    </div>
  {% endif %}
  {% if content.second %}
    <div {{ region_attributes.second.addClass('layout__region', 'layout__region--second') }}>
      {{ content.second }}
    </div>
  {% endif %}
</div>

Extensions & Modules

Modules add functionality. Core modules ship with Drupal; contributed modules come from drupal.org; custom modules are built by your team.

Installing Contributed Modules

Always use Composer:

# Install Pathauto (automatic URL aliases)
composer require drupal/pathauto

# Install Admin Toolbar (better admin navigation)
composer require drupal/admin_toolbar

# Install a specific version
composer require drupal/token:^1.12

Essential Contributed Modules

ModulePurpose
Admin ToolbarBetter admin navigation
PathautoAutomatic URL aliases
TokenToken replacement system
RedirectURL redirect management
WebformForm builder
ParagraphsFlexible content components
MetatagSEO meta tags
XML SitemapSEO sitemap generation

Creating a Custom Module

# mymodule/mymodule.info.yml
name: 'My Module'
type: module
core_version_requirement: ^10
description: 'Example custom module'
<?php
// mymodule/mymodule.module

use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 */
function mymodule_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.mymodule':
      return '<p>This is my custom module!</p>';
  }
}

/**
 * Implements hook_preprocess_node().
 */
function mymodule_preprocess_node(array &$variables) {
  if ($variables['node']->bundle() === 'article') {
    $variables['attributes']['class'][] = 'article--custom';
  }
}
# mymodule/mymodule.routing.yml
mymodule.hello:
  path: '/hello'
  defaults:
    _controller: '\Drupal\mymodule\Controller\HelloController::hello'
    _title: 'Hello World'
  requirements:
    _permission: 'access content'

Common Mistakes

1. Editing Core or Contributed Themes Directly

Changes to Olivero, Claro, or contributed themes are lost on update. Always create a custom theme or sub-theme.

2. Ignoring the Asset Library System

Drupal requires libraries declared in .libraries.yml. Adding CSS via #attached in a template or deprecated drupal_add_css() won’t work properly.

3. Not Using Cache Tags

When creating custom block or plugin output, attach cache tags so Drupal’s cache invalidation works:

$build['#cache']['tags'][] = 'node:' . $node->id();

4. Overriding Too Many Templates

Start with minimal overrides. Use hook_preprocess_HOOK for small changes instead of copying entire template files.

5. Not Clearing Cache After Module Changes

After installing or updating modules, run drush cr — otherwise Drupal’s cache won’t recognize the new module’s routes and hooks.

Practice Questions

  1. What is the difference between a module and a theme?
    Answer: A module provides functionality (business logic, API endpoints, data processing). A theme provides visual presentation (templates, CSS, JavaScript). Modules can alter themes, but themes should not contain business logic.

  2. What is the Template: line in a child theme’s info.yml?
    Answer: In Drupal, it’s called base theme. It tells Drupal which theme is the parent. Templates not present in the child are automatically loaded from the parent.

  3. Why should you use Composer for contributed modules?
    Answer: Composer manages dependencies, versions, and updates. Downloading modules manually makes updates difficult and can lead to version conflicts.

  4. Challenge: Create a sub-theme of Olivero with custom CSS that changes the site’s primary color to a shade of blue. Enable Layout Builder for Articles and create a two-column layout with an image in the left column and text in the right column.

FAQ

What is a sub-theme and when should I use one?
: A sub-theme inherits all resources from a parent theme while letting you override specific files. Use one when customizing Olivero or any contributed theme — your changes survive parent updates.
Can I use Bootstrap with Drupal?
: Yes. The Bootstrap contributed theme or Bootstrap Barrio base theme provide Bootstrap 5 integration. You can also manually include Bootstrap in your custom theme’s libraries.
What is Drush’s theme command?
: drush theme:enable mytheme enables the theme. drush config:set system.theme default mytheme sets it as default. drush pml lists all installed themes and modules.
How do I include a third-party JavaScript library?
: Declare it in your theme’s .libraries.yml file. For CDN-hosted libraries, use the remote option. For local libraries, place them in the theme’s js/ or libraries/ folder.
What is the Twig naming convention for template suggestions?
: Templates follow {template}--{suggestion}.html.twig. For example, node--article--teaser.html.twig targets Article nodes displayed as teasers. More specific templates override less specific ones.

Try It Yourself

  1. Create a custom theme with a single CSS file that changes heading colors
  2. Enable and set it as the default theme via Drush
  3. Install Admin Toolbar via Composer and enable it
  4. Enable Layout Builder for the Basic Page content type
  5. Create a page with a custom two-column layout using Layout Builder

What’s Next

TopicDescription
Users, Security & AdministrationUser roles, permissions, security hardening, multilingual
Drupal Developer ReferenceHooks, entities, render arrays, Drush commands
CSSStyling Drupal themes
PHPCustom module development
HTMLUnderstanding Drupal’s template output

What’s Next

Congratulations on completing this Drupal Themes Layouts Extensions tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro