Templates & Frontend
jdoc Placeholders and Module Chrome in Joomla 6
A template's entire connection to Joomla runs through a handful of jdoc:include tags. This is what each one renders, and how the wrapper around a module — its chrome — is defined and replaced.
The types
metas— the document head:<title>, meta tags, base href, schema output. Once per document.styles— stylesheets and inline CSS collected by the web asset manager. Once.scripts— scripts and inline JavaScript. Once. Keeping this separate fromstylesis what lets you decide where scripts land.component— the output of whatever component the current route resolves to. Exactly once: this is the page's main content.message— queued system messages. Once, somewhere the visitor will see it.modules— every published module in a position. Any number of times.module— one specific module, by name. Useful when a single module belongs in a fixed spot rather than a position anyone can fill.
A position exists as soon as you write the tag, but list it in templateDetails.xml as well — otherwise it never appears in the module manager's position dropdown and nobody can assign anything to it.
Chrome: the wrapper around a module
The style attribute names the chrome:
<jdoc:include type="modules" name="sidebar" style="card" />
Chrome decides the element that wraps the module, whether the title renders and at what heading level, and where moduleclass_sfx ends up. Omit style and the module is emitted bare, with no wrapper and no title — which is what style="none" also does, and what you want for a menu or a search box.
Joomla ships none and html5; templates add their own. Cassiopeia, for instance, provides card and noCard.
Writing your own
Chrome is a layout file, one per style, under your template:
templates/yourtemplate/html/layouts/chromes/panel.php
The name of the file is the name of the style. Inside, the data arrives in $displayData:
<?php
defined('_JEXEC') or die;
$module = $displayData['module'];
$params = $displayData['params'];
$attribs = $displayData['attribs'];
if ($module->content === null || $module->content === '') {
return;
}
$sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_QUOTES, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_QUOTES, 'UTF-8');
?>
<div class="panel <?php echo $module->position . ' ' . $sfx; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="panel__title">
<?php echo $module->title; ?>
</<?php echo $headerTag; ?>>
<?php endif; ?>
<div class="panel__body"><?php echo $module->content; ?></div>
</div>
Then use it:
<jdoc:include type="modules" name="sidebar" style="panel" />
Details that matter
Return early on empty content. That guard is the difference between an unpublished module disappearing cleanly and leaving an empty box with its border and padding intact.
Respect header_tag. Hard-coding <h3> takes the choice away from whoever configures the module, and heading level is a structural decision that belongs with the page, not the wrapper.
Escape parameters. moduleclass_sfx is typed into the module manager and lands in a class attribute. Escape it.
Do not escape $module->content. It is rendered HTML from the module itself; escaping it prints markup on the page.
Passing options from the template
Extra attributes on the tag arrive in $attribs, which lets one chrome serve several positions:
<jdoc:include type="modules" name="footer" style="panel" class="panel--muted" />
Read $attribs['class'] in the layout and append it. Useful when the difference between two positions is one modifier class rather than a whole new chrome.
If you meet modChrome functions
Older templates carry an html/modules.php file full of modChrome_xhtml, modChrome_rounded and modChrome_table functions. That mechanism was replaced by the layout files described here. When porting such a template, each function becomes one file under html/layouts/chromes/ — the markup usually transfers directly, and the table-based ones are worth rewriting rather than transferring.