How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Set up a base template and extend it

One base template holds the document, the head, the chrome and the script queue, and defines blocks. Every page-type template extends it and fills the blocks. Both engines do this the same way.

Context

Context. Every page on a site shares its document shell, head tags, header, footer and script tags. Put them in one base template with named blocks, and make every page-type template extend it. A layout's template is then a short file that fills the blocks, which is what keeps forty page types consistent. Both engines have inheritance with the same shape.

Steps

  1. Create templates/includes/frontend/base-template.html with the whole document and blocks for head, content and scripts.
  2. Create a page template that extends it and fills content. Put nothing outside blocks except assignments.
  3. Where a child wants to add to a block rather than replace it, call the parent block inside it.
  4. Point the layout at the child template; the base template is never a layout's template itself.

Code

Template code
{* templates/includes/frontend/base-template.html *}
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{$pageTitle|default:$_currentSite->getName()|escape}</title>
  <link rel="stylesheet" href="/dist/app.css?v={$_currentSite->getLastVcsHash()}">
  {block name='head'}{/block}
</head>
<body>
  {include 'includes/frontend/header.html'}
  <main>{block name='content'}{/block}</main>
  {include 'includes/frontend/footer.html'}
  <script nonce="{$_inlineScriptNonce}" src="/dist/app.js?v={$_currentSite->getLastVcsHash()}" defer></script>
  {block name='scripts'}{/block}
</body>
</html>
{# templates/includes/frontend/base-template.html #}
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ (pageTitle|default(_currentSite.getName()))|e }}</title>
  <link rel="stylesheet" href="/dist/app.css?v={{ _currentSite.lastVcsHash }}">
  {% block head %}{% endblock %}
</head>
<body>
  {{ include('includes/frontend/header.html') }}
  <main>{% block content %}{% endblock %}</main>
  {{ include('includes/frontend/footer.html') }}
  <script nonce="{{ _inlineScriptNonce }}" src="/dist/app.js?v={{ _currentSite.lastVcsHash }}" defer></script>
  {% block scripts %}{% endblock %}
</body>
</html>

The page template fills the blocks and adds to one:

Template code
{* templates/pages/pages/article.html *}
{extends 'includes/frontend/base-template.html'}
{$pageTitle = $page->getName()}
{block name='head'}{$smarty.block.parent}<meta name="description" content="{$page->getAreaText('summary')|escape}">{/block}
{block name='content'}
  <h1>{$page->heading}</h1>
  {$page->body}
{/block}
{# templates/pages/pages/article.html #}
{% extends 'includes/frontend/base-template.html' %}
{% set pageTitle = page.getName() %}
{% block head %}{{ parent() }}<meta name="description" content="{{ page.getAreaText('summary')|e('html_attr') }}">{% endblock %}
{% block content %}
  <h1>{{ page.heading }}</h1>
  {{ page.body }}
{% endblock %}

Why this and not that

Not an include of a header partial and a footer partial from every page template: that works until the document shell changes, and then forty files change. Not a base template chosen as a layout's template: the base has no content of its own. Name the base by its path when extending, never with a ./ relative path, so a template anywhere in the tree can extend it.

Go deeper

Was this page helpful?

Your answer goes to the docs team.