Template Inheritance and Composition
Layouts, blocks, and includes let you build pages from reusable pieces. Use s:extends for layouts, s:block for named regions, and s:include for partials.
TIP
Keep layout files under a layouts/ folder and partials under partials/ to make intent obvious.
Folder Layout and Paths
This suggested structure keeps layout inheritance, includes, and components easy to discover.
templates/
├── pages/
│ ├── home.sugar.php
│ └── profile.sugar.php
├── layouts/
│ └── base.sugar.php
├── partials/
│ ├── header.sugar.php
│ └── footer.sugar.php
└── components/
├── s-button.sugar.php
├── s-card.sugar.php
└── s-alert.sugar.phpBy default, inheritance and include paths resolve relative to the current template. To enforce root-style paths, enable the loader option documented in Engine Configuration.
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<s-template s:include="partials/header"></s-template><s-template s:extends="layouts/base.sugar.php"></s-template>
<s-template s:include="partials/header"></s-template>Dependency Safety
Sugar tracks template dependencies during inheritance and includes. Circular dependencies are detected and rejected so a template cannot extend or include itself indirectly.
TIP
Keep layouts and partials layered in one direction (base -> page -> partials) to avoid accidental loops.
Details
What is prevented
- Circular inheritance (A extends B, B extends A)
- Circular includes (A includes B, B includes A)
- Diamond-shaped chains that would re-enter a template already on the stack
Layout Inheritance
Use s:extends to inherit a layout and replace its s:block regions. The s:extends element can be empty; it just declares the parent template.
When a parent block is an element, that wrapper is preserved and the child's wrapper is discarded (only the child's children replace the parent's children). If the parent block is a fragment, the child's wrapper is preserved.
<!-- layouts/base.sugar.php -->
<!DOCTYPE html>
<html>
<head>
<title s:block="title">Default Title</title>
</head>
<body>
<main s:block="content">Default content</main>
</body>
</html><!-- pages/home.sugar.php -->
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<title s:block="title">Home Page</title>
<div s:block="content">
<h2>Welcome!</h2>
</div><!DOCTYPE html>
<html>
<head>
<title>Home Page</title>
</head>
<body>
<main>
<h2>Welcome!</h2>
</main>
</body>
</html>TIP
Notice how the parent's <main> wrapper is preserved and the child's <div> wrapper is dropped. If you want the child wrapper preserved on replace, define the parent block as a fragment (<s-template s:block="...">).
Limitations and Rules
- Only one
s:extendsdirective is allowed per template. - When a template uses
s:extends, onlys:block,s:append, ands:prependcontent is kept. Any top-level markup or raw PHP outside those blocks is discarded, so variable assignments must live inside a block (or be passed vias:with) unless you render specific blocks viaEngine::render()with the blocks option (see Render Only Specific Blocks). s:block,s:append, ands:prependare mutually exclusive on the same element.s:withonly scopes values to the immediates:includeand does not leak to parent scope.s:includeands:extendspaths resolve relative to the current template unlessabsolutePathsOnlyis enabled.
The example below focuses on the s:extends rule about top-level content being discarded.
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<s-template s:block="content">
<?php $var = 'I AM A VARIABLE'; ?>
<h1>Home</h1>
<s-template s:include="partials/card" s:with="['var' => $var]" />
</s-template><?php $var = 'I AM A VARIABLE'; ?>
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<s-template s:block="content">
<h1>Home</h1>
</s-template>The example below shows the mutual exclusivity of block directives on a single element.
<section s:block="content">
<p>Block content</p>
</section><section s:block="content" s:append="content">
<p>Not allowed</p>
</section>The example below shows that s:with only scopes variables inside the included template, not the parent template.
<s-template s:include="partials/card" s:with="['title' => 'Card']" /><!-- $title is not available outside the include -->
<h2><?= $title ?></h2>The example below shows relative vs absolute-only paths.
<s-template s:extends="../layouts/base.sugar.php"></s-template><s-template s:extends="layouts/base.sugar.php"></s-template>Template Inheritance Directives
s:extends- Declare the parent layout this template inherits from.s:block- Replace a named block in the parent template.s:append- Add content after a parent block without replacing it.s:prepend- Add content before a parent block without replacing it.s:include- Insert another template at this location.s:with- Pass scoped variables to ans:include.
Blocks
Use s:block to replace a parent block. Use s:append or s:prepend to extend it. Only one of s:block, s:append, or s:prepend is allowed on the same element.
<!-- Invalid: multiple block directives on one element -->
<section s:block="content" s:append="content">
<p>Not allowed</p>
</section><!-- Valid: multiple append elements targeting the same block -->
<section s:append="content"><p>First</p></section>
<section s:append="content"><p>Second</p></section>Append and Prepend Blocks
Use s:append or s:prepend in a child template to add content to a parent block instead of replacing it. When the parent block is an element, the appended/prepended element wrapper is stripped and its children are inserted into the parent block. When the parent block is a fragment, the wrapper is preserved.
<!-- layout: layouts/base.sugar.php -->
<main s:block="content">
<p>Base content</p>
</main>
<!-- child: pages/home.sugar.php -->
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<s-template s:append="content">
<p>Appended content</p>
</s-template><main>
<p>Base content</p>
<p>Appended content</p>
</main><!-- layout: layouts/base.sugar.php -->
<main s:block="content">
<p>Base content</p>
</main>
<!-- child: pages/home.sugar.php -->
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<s-template s:prepend="content">
<p>Prepended content</p>
</s-template><main>
<p>Prepended content</p>
<p>Base content</p>
</main><!-- layout: layouts/base.sugar.php -->
<main s:block="content">
<p>Base content</p>
</main>
<!-- child: pages/home.sugar.php -->
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<s-template s:append="content">
<section class="alert">
<p>Wrapped content</p>
</section>
</s-template><main>
<p>Base content</p>
<section class="alert">
<p>Wrapped content</p>
</section>
</main><!-- layout: layouts/base.sugar.php -->
<s-template s:block="content">
<p>Base content</p>
</s-template>
<!-- child: pages/home.sugar.php -->
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<section class="alert" s:append="content">
<p>Wrapped content</p>
</section><p>Base content</p>
<section class="alert">
<p>Wrapped content</p>
</section>Block Wrappers
Blocks keep their wrapper element by default. The wrapper that survives depends on the parent block type:
<!-- Parent layout -->
<main s:block="content">
<p>Base content</p>
</main>
<!-- Child replaces -->
<div s:block="content">
<p>New content</p>
</div>
<!-- Output: parent wrapper preserved -->
<main>
<p>New content</p>
</main><!-- Parent layout -->
<s-template s:block="content">
<p>Base content</p>
</s-template>
<!-- Child replaces -->
<div s:block="content">
<p>New content</p>
</div>
<!-- Output: child wrapper preserved -->
<div>
<p>New content</p>
</div>Render Only Specific Blocks
You can render one or more blocks directly by passing a list of block names as the third argument to Engine::render(). This skips layout inheritance and outputs the matching blocks in template order, preserving their wrapper elements. Includes still run before block extraction. It is especially handy when you need to return partials for AJAX responses or other incremental updates.
echo $engine->render(
template: 'pages/home',
data: ['user' => $user],
blocks: ['sidebar', 'content'],
);<!-- pages/home.sugar.php -->
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<aside s:block="sidebar">...</aside>
<section s:block="content">...</section><!-- Output -->
...sidebar contents...content contents...Include
Includes are great for shared fragments like headers, footers, or cards.
<s-template s:include="partials/header"></s-template><div class="card" s:include="partials/alert"></div><s-template s:include="partials/header"></s-template>
<section>
<s-template s:include="partials/hero"></s-template>
</section><s-template s:include="partials/user-card" s:with="['user' => $user]"></s-template>Include Scope and s:with
Use s:with only in combination with s:include on the same element. It does not create a standalone scoped block and does not apply to child includes.
<s-template s:include="partials/user-card" s:with="['user' => $user]"></s-template>Example
Combine layouts and partials for full pages:
<s-template s:extends="../layouts/base.sugar.php"></s-template>
<title s:block="title">Dashboard</title>
<div s:block="content">
<s-template s:include="partials/header"></s-template>
<s-template s:include="partials/stats"></s-template>
</div><s-template s:extends="../layouts/base.sugar.php"></s-template>
<title s:block="title">Settings</title>
<div s:block="content">
<aside>
<s-template s:include="partials/sidebar"></s-template>
</aside>
<section>
<s-template s:include="partials/settings"></s-template>
</section>
</div><s-template s:extends="../layouts/base.sugar.php"></s-template>
<title s:block="title">Profile</title>
<div s:block="content">
<s-template s:include="partials/user-card" s:with="['user' => $user]"></s-template>
</div><s-template s:extends="../layouts/base.sugar.php"></s-template>
<title s:block="title">Reports</title>
<div s:block="content">
<section>
<s-template s:include="partials/report-summary"></s-template>
<s-template s:include="partials/report-table"></s-template>
</section>
</div>