Twig Inheritance and Layouts
Twig Inheritance and Layouts
~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Without inheritance, EVERY template would have to repeat navigation, footer, and the HTML skeleton. Twig's inheritance system solves this elegantly – EXACTLY the pattern we already anticipated for flash messages in chapter 12.
The base template: base.html.twig
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Task Manager{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
<nav>
<a href="{{ path('project_index') }}">Projects</a>
</nav>
{% for label, messages in app.flashes %}
{% for message in messages %}
<div class="alert alert-{{ label }}">{{ message }}</div>
{% endfor %}
{% endfor %}
<main>
{% block body %}{% endblock %}
</main>
{% block javascripts %}{% endblock %}
</body>
</html>{% block name %}...{% endblock %} defines a NAMED spot that child templates can OVERRIDE. path('project_index') resolves a route name (chapter 7) to the actual URL – EXACTLY like redirectToRoute() in the controller, never hardcode a URL.
A child template: extends
{% extends 'base.html.twig' %}
{% block title %}Projects – {{ parent() }}{% endblock %}
{% block body %}
<h1>Projects</h1>
<ul>
{% for project in projects %}
<li>{{ project.name }}</li>
{% endfor %}
</ul>
{% endblock %}{% extends %} MUST be the template's first line. {{ parent() }} inside a block inserts the PARENT block's CONTENT – here: Projects – Task Manager instead of completely replacing the base title.
Reusable partial templates: include
For building blocks that appear on SEVERAL pages independent of the inheritance hierarchy (e.g. a project card), {% include %} fits better than inheritance:
<div class="project-card">
<h3>{{ project.name }}</h3>
<a href="{{ path('project_show', {id: project.id}) }}">Details</a>
</div>{% for project in projects %}
{{ include('project/_card.html.twig', {project: project}) }}
{% endfor %}The leading underscore (_card.html.twig) is pure convention (not a Symfony requirement), but signals "this template only gets INCLUDED, not rendered directly from a controller".
path('project_show', {id: project.id}) shows how route parameters (chapter 9) get passed from Twig – as a second argument, an object literal with the placeholder names as keys.
Inheritance vs. include: a rule of thumb
| Mechanism | Use case |
|---|---|
| extends | For the ENTIRE page structure – ONE base, many pages fill its blocks. |
| include | For REUSABLE building blocks WITHIN a page, independent of the block hierarchy – e.g. a card, a form fragment. |
Including CSS and JavaScript: the asset() function
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('styles/app.css') }}">
{% endblock %}Tipp: asset('styles/app.css') resolves the path relative to public/ and automatically respects configured "asset versioning" (cache-busting via query string) – more robust than a hardcoded /styles/app.css path.