Twig has been Drupal's theming engine since Drupal 8, and in Drupal 10 and 11 it runs on Twig 3.x. The syntax that made Twig approachable hasn't changed much. What has changed is where theming logic belongs: Single Directory Components, the in-core component system stable since Drupal 10.3, has quietly absorbed several of the patterns developers used to hand-build in templates. These Twig techniques cover both: the Twig fundamentals that still earn their keep, and the ones worth retiring in favor of components.
The most useful Twig techniques in Drupal 11 are: enabling debug through the Development settings page (not the services file), using template suggestions and the hook_theme_suggestions_HOOK_alter() hook for targeted overrides, applying filters and functions like path() and include(), building reusable UI with Single Directory Components, managing markup with the attributes object, and using template inheritance through extends and block. In Drupal 10.3+, components handle reuse and asset attachment that templates once did by hand.
This post is written for architects and dev leads making theming decisions on enterprise Drupal (multisite universities, government portals, nonprofit platforms), where consistency and maintainability matter more than any single clever template.
How do you enable Twig debugging in Drupal 11?
Enabling Twig debug mode on a Varbase (Drupal 11) install, at Configuration → Development → Development settings. Toggling "Twig development mode" reveals and switches on debug mode and disable Twig cache.
Enable Twig debugging in Drupal 11 through the Development settings page at /admin/config/development/settings, not by editing the primary services.yml. The Development settings page, added in Drupal 10.1, exposes a "Twig development mode" checkbox that turns on debug output, auto-reload, and the dump() function in a few clicks. The settings are stored in the key-value store rather than as configuration, so they can't be accidentally committed and pushed to production.
Enabling Twig debug mode on a Varbase (Drupal 11) install, at Configuration → Development → Development settings. Toggling "Twig development mode" reveals and switches on debug mode and disable Twig cache.
On Drupal 10.3 and later, avoid setting twig.config: debug in the primary services.yml. That file takes precedence over the admin UI, which means you can no longer toggle debugging from the browser. For committed local config, use development.services.yml referenced by your settings.local.php instead.
If you work from the command line, Drush 13.6+ adds drush theme:dev on, which enables Twig debugging and disables caching and CSS/JS aggregation in one command, and drush theme:dev off to reverse it. Once debug is on, {{ dump(content) }} inspects a variable; the Devel module's kint() renders the same output more legibly. Never leave debug enabled in production, since it alters markup and can break Views.
How do template suggestions work in Drupal theming?
Template suggestions let you override one template for a narrow case without bloating it with conditionals. Drupal generates a cascade of candidate filenames and uses the most specific one that exists. The base node.html.twig can be specialized to a content type with node--article.html.twig, then further to a view mode with node--article--teaser.html.twig, each overriding the more general fallback.
For static, predictable cases, the filename is all you need: create the file and clear the cache. For dynamic logic, the cleaner pattern since Drupal 8 is the dedicated alter hook rather than pushing onto theme_hook_suggestions inside a preprocess function:
Keeping suggestion logic in the alter hook, and presentation in the matching Twig file, is what stops templates from accumulating the branching conditionals that make a theme hard to maintain across a large site.
Which Twig filters and functions are worth knowing?
Twig filters manipulate a variable inline, which keeps simple formatting out of preprocess functions. The ones that come up most in Drupal theming are string and date filters, length checks, and default to guard against empty values:
For multilingual sites, the t filter ({{ 'Read more'|t }}) is not optional. It routes interface strings through Drupal's translation system, which matters the moment a theme has to serve English and Arabic from the same templates.
Functions do the heavier lifting. path() builds an internal, root-relative path and url() returns a fully qualified URL, both from a route name:
The include() function pulls in a partial or component, attach_library() loads a theme library on demand, and create_attribute() builds a fresh Attribute object for dynamic markup. Twig's own documentation now recommends the include() function over the {% include %} tag, since it accepts named arguments and behaves more predictably.
How do you build reusable components with Twig and SDC?
Build reusable UI in Drupal 10.3+ with Single Directory Components rather than hand-rolled Twig partials. Single Directory Components (SDC) became stable in Drupal core in 10.3, and they package a component's Twig, a .component.yml schema defining its props and slots, and optional CSS and JavaScript in one directory. Drupal builds and attaches the component's asset library automatically the moment it's used, so you stop writing and attaching libraries by hand for component-scoped styles.
You render a component with the include() function, referencing it by ID, which is the providing theme or module name, a colon, then the component name:
with_context = false keeps the surrounding template's variables out of the component, which prevents accidental coupling. When a component exposes slots as Twig blocks, use the {% embed %} tag instead so you can fill them. As of Drupal 11.2, SDC also supports first-class component variants, so one card component can define named layouts (vertical, horizontal, featured) without a separate template for each. SDC is also the substrate Drupal Canvas, the visual page builder released at 1.0 in December 2025, uses to assemble pages, which means a well-built component library now feeds both hand-theming and visual authoring.
How do you keep attributes and conditionals clean in Twig?
Drupal's attributes object is the right way to manage classes and attributes dynamically, instead of concatenating strings. It carries the classes Drupal already assigned and lets you add to them safely:
How do template inheritance and asset attachment work in Twig?
Template inheritance lets a child template fill named regions defined by a parent, which keeps shared structure in one place. A base template declares blocks, and a child extends it and overrides only what it needs:
For assets, attach_library() loads a library defined in your *.libraries.yml only on the templates that need it, rather than site-wide:
{{ attach_library('mytheme/hero-banner') }}
The distinction that matters in 2026: for assets tied to a Single Directory Component, you no longer attach the library yourself. SDC detects the component's .css and .js files and attaches them automatically. Reserve manual attach_library() calls for theme-level assets that aren't owned by a single component.
Vardot's view: the tricks are becoming architecture
Our view: the highest-value Twig skill in 2026 is no longer knowing the most filters. It's deciding what becomes a component, what stays a template, and what belongs in a preprocess function. Several techniques that defined Drupal 8-era theming, hand-built reusable partials and manual library attachment among them, are now first-class behavior in SDC. The clever template trick that saved an afternoon in 2018 is, on a large install, the inconsistency that costs a week in 2026.
The pattern we see consistently on enterprise builds is that theming pain rarely traces to a missing filter. It traces to logic scattered across templates that should have lived in one component. On a multilingual government or nonprofit install, the same card has to flip for right-to-left Arabic, stay WCAG-conformant across dozens of subsites, and render identically whether a developer places it in a template or an editor drops it through Canvas. That is a component-governance problem, not a Twig-syntax problem, and it's where consistency either holds or quietly breaks.
A framework for where theming logic should live
Decide where theming logic belongs by asking what kind of work it is, then placing it in the layer built for that work. The four layers, in the order to reach for them:
If the logic is...
Put it in...
Why
Reusable UI with its own markup, styles, and behavior
A Single Directory Component
One definition, auto-attached assets, usable in templates and Canvas
Data shaping: formatting, deriving, or restructuring variables
A preprocess function
Keeps templates declarative; logic stays testable in PHP
Targeting a specific entity, bundle, or view mode
A template suggestion or the alter hook
Narrow overrides without conditionals in markup
Site structure editors should control
Configuration, blocks, or layout
Not a code concern; belongs to site builders
The test for any piece of theming logic: if a sentence describing it includes the word "and," it's probably two concerns that belong in two layers. A card that "shows a title and changes color by section and hides on mobile" is a component with a variant prop, not three conditionals in a node template.
What are the performance guardrails for Twig in production?
Keep Twig fast in production by disabling debug, caching aggressively, and reusing markup instead of duplicating it. Debug mode and disabled caches are development-only, and the Development settings toggles and drush theme:dev make it easy to flip them off before deploying. Favor include() and components over copy-pasted markup so a fix lands in one place. And let Drupal's render cache do its job: avoid pushing uncacheable, per-request logic into templates, since that's what forces cache misses and slows pages under load.
Where to take this next
If you're standing up or refactoring a theme on enterprise Drupal, the leverage is in the component library, not the template count. Vardot builds and audits Drupal theming systems for organizations where consistency across many sites and languages is the requirement: multisite universities, public-sector portals, and nonprofits. If a theming audit or a component-system review would help, our team can take a look.
Drupal
Twig
Drupal Planet
About the Author
Ahmad Estaitia
Associate Software Engineer
Ahmad Estaitia is an Associate Software Engineer at Vardot with experience in Drupal development, frontend theming, and building accessible, responsive web solutions. He works on digital platforms and content-driven applications, contributing to scalable implementations, UI components, and performance improvements across web projects.
Enable Twig debug mode in Drupal 11 from the Development settings page at /admin/config/development/settings by checking "Twig development mode," or run drush theme:dev on. Both turn on debug output, template suggestions in HTML comments, and the dump() function. On Drupal 10.3 and later, avoid setting debug in the primary services.yml, which overrides the admin UI; use development.services.yml for committed local config. Never enable debug in production.
The Twig include() function renders a template or component and outputs its result, and it's the recommended choice for most Drupal theming because it accepts named arguments. The {% embed %} tag is for cases where you need to fill a component's slots using Twig blocks, since embed lets you override block content inside the included template. For Single Directory Components, use include() when passing props and embed when filling slots.
No, Single Directory Components do not replace Twig templates in Drupal; they sit alongside them. Drupal templates still use naming conventions to render entities, blocks, and fields, and they call components with include() or embed. SDC, stable in core since Drupal 10.3, handles reusable UI and auto-attaches component assets, so templates get shorter and reuse improves, but the template layer remains how Drupal maps content to markup.
Create dynamic CSS classes in Twig by building an array of class names and passing it to the attributes object or create_attribute(), using the clean_class filter to sanitize values. For example, set a classes array with conditional entries, then output {{ attributes.addClass(classes) }}. This keeps class logic readable and avoids string concatenation. The attributes object also preserves classes Drupal already assigned to the element.
You do not need attach_library() for assets that belong to a Single Directory Component, because Drupal automatically detects the component's .css and .js files and attaches them whenever the component renders. Reserve attach_library() for theme-level assets not owned by a single component: global styles, a typeface, or a script that spans the whole site. This split keeps component styling self-contained and prevents loading assets on pages that don't use them.