Configuration
Everything a site says on every page — the mark, the sections in the bar, the footer — is configuration and not markup. A theme that read those from the documents would have every page free to disagree with the others by the third one, and a theme that hard-coded them would fit exactly one project.
It is written in two places in the same file: the attributes on <guides>,
which are the renderer's own, and an <extension> element, which is how
Guides hands a block of configuration to the extension that claims it.
What the renderer needs#
soul. The attribute selects a theme; the <extension> element below
is what makes one called soul exist, and a project that sets the
attribute without loading the extension stops on Theme "soul" is not
registered. Selecting it is what puts the theme's templates in front of
the packaged ones.
A theme is not a list of template paths. Paths are searched after the renderer's own templates, so a file replacing one of theirs is never reached — which is the difference, and the reason a theme exists as a concept at all.
-
themerequired # -
- type
- string
soul. The attribute selects a theme; the<extension>element below is what makes one calledsoulexist, and a project that sets the attribute without loading the extension stops on Theme "soul" is not registered. Selecting it is what puts the theme's templates in front of the packaged ones.A theme is not a list of template paths. Paths are searched after the renderer's own templates, so a file replacing one of theirs is never reached — which is the difference, and the reason a theme exists as a concept at all.
Leave it false and every asset URL is absolute, which is a site that only works served from a domain root. Anything published under a repository path — GitHub Pages, most of the time — needs this on.
-
links_are_relative# -
- type
- bool
- default
- false
Leave it false and every asset URL is absolute, which is a site that only works served from a domain root. Anything published under a repository path — GitHub Pages, most of the time — needs this on.
The language a fenced block is highlighted as when it does not say. Set it,
and know that the Markdown parser does not consult it: a bare fence arrives
with the language null, and this theme's code template is what stops
that being fatal.
-
default_code_language# -
- type
- string
- default
- none
The language a fenced block is highlighted as when it does not say. Set it, and know that the Markdown parser does not consult it: a bare fence arrives with the language
null, and this theme's code template is what stops that being fatal.
rst or md. Both parsers arrive with the theme, so this is the whole
of choosing between them, and it names a file extension: a project is one
format, and files in the other are not documents. See the note in
Installation on what the Markdown parser cannot spell.
-
input-format# -
- type
- string
- default
- "rst"
rstormd. Both parsers arrive with the theme, so this is the whole of choosing between them, and it names a file extension: a project is one format, and files in the other are not documents. See the note in Installation on what the Markdown parser cannot spell.
The project element#
<project title="Your project" version="1.0" copyright="© 2026 Acme"/><project title="Your project" version="1.0" copyright="© 2026 Acme"/>
The title is the name in the bar when nothing else is configured, and the
name after the em dash in every <title> tag. The version is a badge at
the end of the bar rather than part of the mark: it is a fact about the
documentation the reader is in, not about the product, so it stands with the
search and the mode switch and it is the first thing the bar drops when it
narrows. The copyright, where there is one, prints in the footer.
The theme element#
<extension class="TYPO3\Soul\GuidesTheme\DependencyInjection\SoulExtension">
<signet>_images/signet.svg</signet>
<favicon href="_images/signet-s.svg" sizes="16x16"/>
<favicon href="_images/signet-l.svg" sizes="32x32"/>
<brand>Acme</brand>
<product>Your product</product>
<navigation>
<link href="/overview" label="Overview"/>
<link href="https://example.org" label="Elsewhere" external="true"/>
</navigation>
<pager>true</pager>
<footer>
<group title="Elsewhere">
<link href="https://example.org" label="Product site" external="true"/>
</group>
<social href="https://github.com/…" label="GitHub"/>
<note>A tool for TYPO3 community projects.</note>
</footer>
</extension><extension class="TYPO3\Soul\GuidesTheme\DependencyInjection\SoulExtension">
<signet>_images/signet.svg</signet>
<favicon href="_images/signet-s.svg" sizes="16x16"/>
<favicon href="_images/signet-l.svg" sizes="32x32"/>
<brand>Acme</brand>
<product>Your product</product>
<navigation>
<link href="/overview" label="Overview"/>
<link href="https://example.org" label="Elsewhere" external="true"/>
</navigation>
<pager>true</pager>
<footer>
<group title="Elsewhere">
<link href="https://example.org" label="Product site" external="true"/>
</group>
<social href="https://github.com/…" label="GitHub"/>
<note>A tool for TYPO3 community projects.</note>
</footer>
</extension>
The element itself is not optional — it is what registers the theme — but everything inside it is. Written empty, the bar carries the project title and the site's own sections, and the footer carries those sections again with the pages under them, beside that title and the copyright — which is the least a page can say and still be honest about what it is.
The mark#
A path relative to the documentation root, and it must be a file the
renderer can see: put it beside the documents, so it is copied into the
output with them rather than pointing at something that only exists on the
machine that built the site. _images/ is the conventional place.
The mark is linked like every other picture, so any format works and what is
drawn is the file's own colours — the same mark in light and in dark. Write
an SVG's as var(--token, #hex) anyway: Artwork says
what that buys and what has to ship in Firefox and Safari before a mark can
take the page's ink.
A signet is not an icon. It ships at three optical sizes with different construction, and bar height is the small one's job — see Brand for which file to hand over.
-
signet# -
- type
- string
- default
- none
A path relative to the documentation root, and it must be a file the renderer can see: put it beside the documents, so it is copied into the output with them rather than pointing at something that only exists on the machine that built the site.
_images/is the conventional place.The mark is linked like every other picture, so any format works and what is drawn is the file's own colours — the same mark in light and in dark. Write an SVG's as
var(--token, #hex)anyway: Artwork says what that buys and what has to ship in Firefox and Safari before a mark can take the page's ink.A signet is not an icon. It ships at three optical sizes with different construction, and bar height is the small one's job — see Brand for which file to hand over.
The mark in the tab. Written once per file rather than as one path, because
a browser picks between them at the link: sizes is the slot a file is
drawn for, spelled the way the attribute is, and one entry may leave it out
to be the file for everything else. Paths follow the same rule as the
signet — inside the documentation tree, where the renderer can see them.
Set nothing and the signet is the tab icon, which is the right answer for a project with one drawing: a bar with a mark above a tab without one is a site saying two things. Set the sizes where there is more than one drawing. This system ships three, at three optical sizes with different construction, precisely because scaling one of them is what a favicon slot cannot do — see Brand. A media query inside the file cannot pick either: linked, an SVG only ever sees its own viewport.
-
favicon# -
- type
- href, sizes
- default
- the signet
The mark in the tab. Written once per file rather than as one path, because a browser picks between them at the link:
sizesis the slot a file is drawn for, spelled the way the attribute is, and one entry may leave it out to be the file for everything else. Paths follow the same rule as the signet — inside the documentation tree, where the renderer can see them.Set nothing and the signet is the tab icon, which is the right answer for a project with one drawing: a bar with a mark above a tab without one is a site saying two things. Set the sizes where there is more than one drawing. This system ships three, at three optical sizes with different construction, precisely because scaling one of them is what a favicon slot cannot do — see Brand. A media query inside the file cannot pick either: linked, an SVG only ever sees its own viewport.
The name in the bar, when it is not the project's own title: a manual that documents one product inside a larger project says the product.
-
product# -
- type
- string
- default
- the project title
The name in the bar, when it is not the project's own title: a manual that documents one product inside a larger project says the product.
Whose product it is, where that is a second name. It becomes the first half of a lockup, with the accent rule between the two halves — one of exactly three places that colour appears. With no brand the mark is one name in the mark's own weight, because a single name is not the quiet half of a lockup with nothing beside it.
-
brand# -
- type
- string
- default
- none
Whose product it is, where that is a second name. It becomes the first half of a lockup, with the accent rule between the two halves — one of exactly three places that colour appears. With no brand the mark is one name in the mark's own weight, because a single name is not the quiet half of a lockup with nothing beside it.
Where the mark leads. Resolved as an asset path, for the case where the
documentation sits under a marketing page that is not part of the rendered
project. Left out, the mark leads to /index, which is where it should
lead.
-
home# -
- type
- string
- default
- the project's index
Where the mark leads. Resolved as an asset path, for the case where the documentation sits under a marketing page that is not part of the rendered project. Left out, the mark leads to
/index, which is where it should lead.
The bar's sections#
<navigation>
<link href="/guide/index" label="Guide"/>
<link href="/reference/index" label="Reference"/>
<link href="https://github.com/…" label="Source" external="true"/>
</navigation><navigation>
<link href="/guide/index" label="Guide"/>
<link href="/reference/index" label="Reference"/>
<link href="https://github.com/…" label="Source" external="true"/>
</navigation>
The handful of places a site has. Left out, the bar carries the top level of the toctree — the same source the footer's columns come from, so a project that configures nothing still has a bar to move around in. What is written here wins over that, because which of a site's sections are its front doors is the one thing the tree cannot know: this manual leaves a page out of its bar that the tree has, and adds nothing the tree has not.
Not the toctree entire: that is the rail's job, and a manual's every page in the bar is not navigation. A site whose top level is every page is a site that should write the handful it wants.
The whole tree is handed over all the same, on every page: the bar is given the site as one entry, with the page the reader is on marked wherever it sits, and draws as much of it as the width allows — the front doors in the row, a section's pages under it, and one level at a time in the drawer where the row has none of it. That is not configured and cannot be left out: which sections are front doors is a choice, and being able to reach the rest of the site from a phone is not.
href is a document — /guide/index, written the way a :doc:
reference is — unless external="true", in which case it is a URL and
opens in a new tab with the external marker on it.
label names the second kind only. A document is named by its own
:navigation-title:, everywhere it is named.
A section is marked current on its own page and on every page under it, which is the toctree walked upwards: a page three levels inside the guide still marks Guide. Marking only the exact page leaves the bar saying nothing on all but a handful of pages.
-
link# -
- type
- href, label, external
hrefis a document —/guide/index, written the way a:doc:reference is — unlessexternal="true", in which case it is a URL and opens in a new tab with the external marker on it.labelnames the second kind only. A document is named by its own:navigation-title:, everywhere it is named.A section is marked current on its own page and on every page under it, which is the toctree walked upwards: a page three levels inside the guide still marks Guide. Marking only the exact page leaves the bar saying nothing on all but a handful of pages.
The rail#
The rail is derived from the toctree rather than configured separately. The theme finds the current page's top-level section by walking its rootline upwards, so the answer does not depend on how deeply the page is nested. The bar and rail therefore mark the same section by construction.
On a section with children, the heading above the list is the way to the section's own page. A page with descendants is a row with the marker that opens them beside it, and what it holds is set in by one step. A section that is itself a single page has no rail at all — the bar naming it is the whole of what there is to say — and neither has the root, which is in no section: what the bar carries there is the whole site, as on every other page.
packages/guides-theme/src/Navigation/Menu.php turns the tree into that one
entry and Rail.php takes the section of it a page's column carries. The transformation is recursive application logic rather than template
markup, so Twig receives a finished heading, item tree and active position.
The document template renders that result before the bar and body: an empty
rail then removes both its column and its opening control from the same answer.
The way on#
The pages either side of this one, as two links at the end of the column. The order is the toctree flattened — the order the rail lists, and the order somebody reading front to back would take — with the root first, because a toctree lists what is under a page and never the page it is written on.
Off unless a project says otherwise, and that is a decision rather than
caution: the renderer computes no such thing, so this is the theme offering
a path, and a reference nobody reads front to back is a reference where
that path is a row of noise under every page. A manual that is read in
order says true.
A page the tree does not hold gets no row at all. An orphan is reached from somewhere else, and a way onward from a page that is not on the way is a path this theme invented.
-
pager# -
- type
- boolean
- default
- false
The pages either side of this one, as two links at the end of the column. The order is the toctree flattened — the order the rail lists, and the order somebody reading front to back would take — with the root first, because a toctree lists what is under a page and never the page it is written on.
Off unless a project says otherwise, and that is a decision rather than caution: the renderer computes no such thing, so this is the theme offering a path, and a reference nobody reads front to back is a reference where that path is a row of noise under every page. A manual that is read in order says
true.A page the tree does not hold gets no row at all. An orphan is reached from somewhere else, and a way onward from a page that is not on the way is a path this theme invented.
Per-page settings#
Two fields at the top of a document, before the title. Both are read by the theme; anything the parser does not claim renders as a definition list in the body, which is what a stray field looks like when it is misspelled.
:navigation-title: Overview
:layout: marketing
============
The long one
============:navigation-title: Overview
:layout: marketing
============
The long one
============
What the rail, the trail and the browser tab call this page. The renderer's own field, and the reason it exists is that a page title written for the page is often too long for a list of thirty of them.
-
- type
- string
- default
- the page title
What the rail, the trail and the browser tab call this page. The renderer's own field, and the reason it exists is that a page title written for the page is often too long for a list of thirty of them.
:layout: marketing builds the page as a run of full-bleed bands with no
rail; anything else, and any page that writes no such field, is the manual
shape — a column beside the rail, held to the measure. It is written down in
Directives, beside the directives that fill a marketing page, because
on its own it is a page shape with nothing in it.