Fluid Templates

To understand the following section you need basic knowledge about how to use the Fluid templating engine and TypoScript.

This chapter is based on the following steps:

After this tutorial you have created Fluid templates and split them into manageable pieces.

Create the Fluid templates

Copy the main static HTML file from Resources/Public/StaticTemplate/default.html to Resources/Private/Templates/Pages/Default.html. You can override the file created in step Minimal site package - The TYPO3 Fluid version.

The template name Default.html is used as a fall back if no other template names are defined. Do not change it for now.

Even though this file ends on .html it will be interpreted by the templating engine Fluid.

TYPO3 takes care of creating the outermost HTML structure of the site, including the <html> and <head> tags therefore they need to be removed from the template:

Resources/Private/Templates/Pages/Default.html (difference)
-<!doctype html>
-<html lang="en" data-bs-theme="auto">
-<head>
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <title>TYPO3 site package example</title>
-    <link href="../Libaries/bootstrap-5.3.3-dist/css/bootstrap.min.css" rel="stylesheet">
-    <link href="../Css/main.css" rel="stylesheet">
-</head>
-<body>
 <main>
     ...
 </main>
-<script src="../Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js"></script>
-<script src="../JavaScript/main.js"></script>
-</body>
-</html>
Copied!

The Fluid template Default.html now contains only the HTML code inside the body:

Resources/Private/Templates/Pages/Default.html
<main>
    <nav class="navbar navbar-expand-lg bg-body-tertiary mb-4">
        <div class="container">
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuToggler"
                    aria-controls="menuToggler" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="menuToggler">
                <a href="#" class="navbar-brand">
                    <img src="../Images/logo.svg" alt="Logo" height="50px" class="pe-3">
                    Site name
                </a>

                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item"><a href="#" class="nav-link active" aria-current="page">Home</a></li>
                    <li class="nav-item"><a href="#" class="nav-link">Features</a></li>
                    <li class="nav-item"><a href="#" class="nav-link">Pricing</a></li>
                    <li class="nav-item"><a href="#" class="nav-link">FAQs</a></li>
                    <li class="nav-item"><a href="#" class="nav-link">About</a></li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container">
        <div class="p-5 mb-4 bg-body-tertiary">
            <div class="container-fluid py-5">
                <h1 class="display-5 fw-bold">Custom jumbotron</h1>
                <p class="col-md-8 fs-4">This jumbotron contains a button to demonstrate that JavaScript is working: </p>
                <button class="btn btn-dark btn-lg" role="button" href="#" id="exampleButton">Example button</button>
            </div>
        </div>
    </div>
    <div class="container">
        <h2>Start page content</h2>
        <p>The content of the start page is displayed here. This content should be generated from the content element of the startpage </p>
    </div>
    <div class="container">
        <footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top">
            <div class="col-md-4 d-flex align-items-center">
                <a href="#" class="mb-3 me-2 mb-md-0 text-body-secondary text-decoration-none lh-1">
                    <img src="../Images/logo.svg" alt="Logo" height="24" class="bi">
                </a>
                <span class="mb-3 mb-md-0 text-body-secondary">&copy; 2024 Site name</span>
            </div>

            <ul class="nav col-md-4 justify-content-end">
                <li class="nav-item"><a href="#" class="nav-link px-2 text-body-secondary">Data privacy</a></li>
                <li class="nav-item"><a href="#" class="nav-link px-2 text-body-secondary">Imprint</a></li>
            </ul>
        </footer>
    </div>
</main>
Copied!

Flush the caches and preview the page. You should now see a pure HTML page without any styles or images. We will add them in a further step.

Load assets (CSS, JavaScript)

Load all CSS which had been removed in step Create the Fluid templates using the Asset.css ViewHelper <f:asset.css>.

Replace <script> tags in the body by using the Asset.script ViewHelper <f:asset.script>.

Resources/Private/Templates/Pages/Default.html (difference)
+ <f:asset.css identifier="bootstrap" href="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/css/bootstrap.min.css" />
+ <f:asset.css identifier="main" href="EXT:site_package/Resources/Public/Css/main.css" />
  <main>
      ...
  </main>
- <script src="../Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js"></script>
- <script src="../JavaScript/main.js"></script>
+ <f:asset.script identifier="bootstrap" src="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js" />
+ <f:asset.script identifier="main" src="EXT:site_package/Resources/Public/JavaScript/main.js" />
Copied!

The path to the assets will be resolved by TYPO3. EXT: tells TYPO3 that this is an extension path. site_package is the Extension name defined in the composer.json.

Flush all caches and preview the page. .. todo: Link to cache and preview pages in getting started once they exist

When you load your page and inspect it with the developer tools of your browser you will notice that the assets are loaded from paths like /_assets/99a57ea771f379715c522bf185e9a315/Css/main.css?1728057333. You must never try to use these path directly, for example as absolute paths. They can change at any time. Only use the EXT: syntax.

When you now preview the page you will notice that the your page is loaded with the dummy content from the template and functioning CSS and JavaScript. The logo however is not found.

Load images in Fluid

Replace all <img> tags in the template with the Image ViewHelper <f:image>:

Resources/Private/Templates/Pages/Default.html (difference)
-<img src="../Images/logo.svg" alt="Logo" height="50px" class="pe-3">
+<f:image src="EXT:site_package/Resources/Public/Images/logo.svg" alt="Logo" class="pe-3" />
Copied!

Just like happened with the CSS paths in step Load assets (CSS, JavaScript) the path to the image is now replaced in the output by a path like /_assets/99a57ea771f379715c522bf185e9a315/Images/logo.svg?1728057333.

Split up the template into partials

If you compare the two static templates Resources/Public/StaticTemplate/default.html and Resources/Public/StaticTemplate/subpagepage.html they share many parts like the footer or the header with the menu. In order to reuse those parts we extract them to their own Fluid files. These are called partials and stored in path Resources/Private/Templates/Partials.

We can use the Render ViewHelper <f:render> to render the partial in the correct place.

Remove the header from the template and replace it with a render ViewHelper:

Resources/Private/Templates/Pages/Default.html (difference)
 <main>
     <nav class="navbar navbar-expand-lg bg-body-tertiary mb-4">
-        <div class="container">
-            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuToggler"
-                    aria-controls="menuToggler" aria-expanded="false" aria-label="Toggle navigation">
-                <span class="navbar-toggler-icon"></span>
-            </button>
-            <div class="collapse navbar-collapse" id="menuToggler">
-                <a href="#" class="navbar-brand">
-                    <img src="../Images/logo.svg" alt="Logo" height="50px" class="pe-3">
-                    Site name
-                </a>
-
-                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-                    <li class="nav-item"><a href="#" class="nav-link active" aria-current="page">Home</a></li>
-                    <li class="nav-item"><a href="#" class="nav-link">Features</a></li>
-                    <li class="nav-item"><a href="#" class="nav-link">Pricing</a></li>
-                    <li class="nav-item"><a href="#" class="nav-link">FAQs</a></li>
-                    <li class="nav-item"><a href="#" class="nav-link">About</a></li>
-                </ul>
-            </div>
-        </div>
-    </nav>
+    <f:render partial="Header" arguments="{_all}"/>
     ...
 </main>
Copied!

Move the Fluid code you just remove to a file called sitepackage/Resources/Private/Templates/Partials/Header.html.

Do the same with the jumbotron, the breadcrumb, and the footer.

You should now have the following files:

  • EXT:my_sitepackage/Resources/Private/Templates
  • Pages

    • Default.html
    • Subpage.html
  • Partials

    • Footer.html
    • Header.html
    • Jumbotron.html

The Fluid template Resources/Private/Templates/Pages/Default.html should now look like this:

Resources/Private/Templates/Pages/Default.html
<f:asset.css identifier="bootstrap" href="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/css/bootstrap.min.css" />
<f:asset.css identifier="main" href="EXT:site_package/Resources/Public/Css/main.css" />
<main>
    <f:render partial="Header" arguments="{_all}"/>
    <f:render partial="Jumbotron" arguments="{records: content.jumbotron.records}"/>
    <div class="container">
        <h2>Start page content</h2>
        <p>The content of the start page is displayed here. This content should be generated from the content element of the startpage </p>
    </div>
    <f:render partial="Footer" arguments="{_all}"/>
</main>
<f:asset.script identifier="bootstrap" src="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js" />
<f:asset.script identifier="main" src="EXT:site_package/Resources/Public/JavaScript/main.js" />
Copied!

You will learn how to display the dynamic content in chapter Display the content elements on your page.

Extract the menu into a partial

Partials can also be rendered from within another partial. We move the menu in the partial Resources/Private/Templates/Partials/Header.html to its own partial, Resources/Private/Templates/Partials/Navigation/Menu.html:

EXT:site_package/Resources/Private/Templates/Partials/Header.html (Difference)
 <nav class="navbar navbar-expand-lg bg-body-tertiary mb-4">
     <div class="container">
         <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuToggler"
                 aria-controls="menuToggler" aria-expanded="false" aria-label="Toggle navigation">
             <span class="navbar-toggler-icon"></span>
         </button>
         <div class="collapse navbar-collapse" id="menuToggler">
             <a href="#" class="navbar-brand">
                 <f:image src="{settings.mySitepackage.logo}" alt="{settings.mySitepackage.logo-alt}" class="pe-3" />
                 Site name
             </a>
-            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-                <li class="nav-item"><a href="#" class="nav-link active" aria-current="page">Home</a></li>
-                <li class="nav-item"><a href="#" class="nav-link">Features</a></li>
-                <li class="nav-item"><a href="#" class="nav-link">Pricing</a></li>
-                <li class="nav-item"><a href="#" class="nav-link">FAQs</a></li>
-                <li class="nav-item"><a href="#" class="nav-link">About</a></li>
-            </ul>
             <f:render partial="Navigation/Menu.html" arguments="{_all}"/>
         </div>
     </div>
 </nav>
Copied!

The Render ViewHelper <f:render> is used the same like from within the template.

Chapter Main menu will teach you how to make the menu work.

Move the content into a section

You can also move a part of the template into a section, surrounded by a Section ViewHelper <f:section> and use the Render ViewHelper <f:render> with argument section to render it.

We move the content, including the Jumbotron into such a section:

Resources/Private/Templates/Pages/Default.html (difference)
 <f:asset.css identifier="bootstrap" href="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/css/bootstrap.min.css" />
 <f:asset.css identifier="main" href="EXT:site_package/Resources/Public/Css/main.css" />
 <main>
-    <f:render partial="Header" arguments="{_all}"/>
-    <f:render partial="Jumbotron" arguments="{records: content.jumbotron.records}"/>
-    <div class="container">
-        <h2>Start page content</h2>
-        <p>The content of the start page is displayed here. This content should be generated from the content element of the startpage </p>
-    </div>
+    <f:render section="Main"/>
     <f:render partial="Footer" arguments="{_all}"/>
 </main>
 <f:asset.script identifier="bootstrap" src="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js" />
 <f:asset.script identifier="main" src="EXT:site_package/Resources/Public/JavaScript/main.js" />

+<f:section name="Main">
+    <f:render partial="Jumbotron" arguments="{_all}"/>
+    <div class="container">
+        <h2>Start page content</h2>
+        <p>The content of the start page is displayed here. This content should be generated from the content element of the startpage </p>
+    </div>
+</f:section>
Copied!

The result looks like this:

Resources/Private/Templates/Pages/Default.html
<f:asset.css identifier="bootstrap" href="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/css/bootstrap.min.css" />
<f:asset.css identifier="main" href="EXT:site_package/Resources/Public/Css/main.css" />
<main>
    <f:render partial="Header" arguments="{_all}"/>
    <f:render section="Main"/>
    <f:render partial="Footer" arguments="{_all}"/>
</main>
<f:asset.script identifier="bootstrap" src="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js" />
<f:asset.script identifier="main" src="EXT:site_package/Resources/Public/JavaScript/main.js" />

<f:section name="Main">
    <f:render partial="Jumbotron" arguments="{records: content.jumbotron.records}"/>
    <div class="container">
        <h2>Start page content</h2>
        <p>The content of the start page is displayed here. This content should be generated from the content element of the startpage </p>
    </div>
</f:section>
Copied!

You will learn how to display the dynamic content in chapter Display the content elements on your page.

The Fluid template for the subpage

We can repeat the above steps for the subpage and write such a template:

Resources/Private/Templates/Pages/Subpage.html
<f:asset.css identifier="bootstrap" href="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/css/bootstrap.min.css" />
<f:asset.css identifier="main" href="EXT:site_package/Resources/Public/Css/main.css" />
<main>
    <f:render partial="Header" arguments="{_all}"/>
    <f:render section="Main"/>
    <f:render partial="Footer" arguments="{_all}"/>
</main>
<f:asset.script identifier="bootstrap" src="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js" />
<f:asset.script identifier="main" src="EXT:site_package/Resources/Public/JavaScript/main.js" />

<f:section name="Main">
    <f:render partial="Jumbotron" arguments="{_all}"/>
    <f:render partial="Navigation/Breadcrumb" arguments="{_all}"/>
    <div class="container">
        <div class="row">
            <div class="col-md-8">
                <div class="h-100 p-5 text-bg-white">
                    <h2>Page content</h2>
                    <p>The content of each page can be displayed here. </p>
                </div>
            </div>
            <div class="col-md-4">
                <div class="h-100 p-5 bg-body-tertiary">
                    <h2>Sidebar </h2>
                    <p>Place for some shared content</p>
                </div>
            </div>
        </div>
    </div>
</f:section>
Copied!

Extract the repeated part to a layout

Lines 1-9 of file Subpage.html in step The Fluid template for the subpage step are exactly the same like in file Resources/Private/Templates/Pages/Default.html.

We can extract these lines into a so called Fluid layout and load them with the Layout ViewHelper <f:layout>:

Resources/Private/Templates/Pages/Subpage.html (difference)
-<f:asset.css identifier="bootstrap" href="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/css/bootstrap.min.css" />
-<f:asset.css identifier="main" href="EXT:site_package/Resources/Public/Css/main.css" />
-<main>
-    <f:render partial="Header" arguments="{_all}"/>
-    <f:render section="Main"/>
-    <f:render partial="Footer" arguments="{_all}"/>
-</main>
-<f:asset.script identifier="bootstrap" src="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js" />
-<f:asset.script identifier="main" src="EXT:site_package/Resources/Public/JavaScript/main.js" />
+<f:layout name="Layout"/>
 <f:section name="Main">
     ...
 </f:section>
Copied!

Save the extracted layout to a file called Resources/Private/Templates/Layouts/Layout.html. This file now contains the following:

EXT:site_package/Resources/Private/Templates/Layouts/Layout.html
<f:asset.css identifier="bootstrap" href="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/css/bootstrap.min.css" />
<f:asset.css identifier="main" href="EXT:site_package/Resources/Public/Css/main.css" />
<main>
    <f:render partial="Header" arguments="{_all}"/>
    <f:render section="Main"/>
    <f:render partial="Footer" arguments="{_all}"/>
</main>
<f:asset.script identifier="bootstrap" src="EXT:site_package/Resources/Public/Libaries/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js" />
<f:asset.script identifier="main" src="EXT:site_package/Resources/Public/JavaScript/main.js" />
Copied!

Repeat the same for file Resources/Private/Templates/Pages/Default.html.

  • EXT:my_sitepackage/Resources/Private/Templates

    • Layouts

      • Layout.html
    • Pages

      • Default.html
      • Subpage.html
    • Partials

      • Navigation

        • Breadcrumb.html
        • FooterMenu.html
        • Menu.html
      • Footer.html
      • Header.html
      • Jumbotron.html

You can finde the complete site package extension at this step at branch main-step/fluid.

Next steps: Fetch the content and configure the menus