Front-end themes#
A theme is the site's whole visual package — layouts, templates, SCSS and JavaScript
together. www/themes/, with the selection logic in app/Core/Theme/.
Two follow-up chapters expand on its parts: SCSS structure and Design system and tokens.
Structure#
www/themes/
admin/default/desktop/ the administration
frontend/template1/desktop/ one of nine front-end themes (template1–template9)
layouts/ the layouts and their parts
assets/{scss,css,js,dist,imgs}
ui-demo/ the component catalogue
webpack.config.js
frontend/template2/desktop/ another theme, switched from the administration
A theme has device variants: DefaultThemeResolver picks mobile, tablet or
desktop from DeviceDetector — and when the variant does not exist in the theme, it
falls back to desktop.
Which theme is active#
The theme is not chosen in code but by a setting in the database: theme.frontend
(the name) and theme.frontend.path (the relative path, computed). It is no longer
edited by hand today — an administrator picks it together with the skin in
Nastavení → Vzhled webu (ThemeSettingPresenter), see
Site appearance. There are nine themes today,
template1–template9.
Switching the theme in the settings switches it in production too
Dev and production share one database and the settings table has no column for the
environment. Changing theme.frontend.path would not merely repaint the live site —
production does not have the new theme's files, so every front-end page would break
on missing layouts. This risk applies to the "Vzhled webu" screen too — it is just a
different interface over the same settings keys.
Locally the theme is switched by an environment variable, not by the setting
Without the variable it is a no-op — production never sets it. A non-existent directory is ignored so that a typo cannot take the site down.Paths into the theme from templates#
A template must not be tied to the theme's name. Two functions are available (they work as filters too) plus one test:
| Notation | What it does |
|---|---|
{=themeAsset('assets/dist/bundle.js')} |
a path to a file inside the active theme |
{=themeUrl('…')} |
an absolute URL into the active theme |
{if $x is isTheme('template2')} |
branching by theme |
They are registered by app/Core/Latte/Extensions/ThemeLatteExtension.php.
A hardcoded path stops working when the theme is switched
And nobody finds out until the theme is switched — typically at a customer with their
own template. In template1 themeAsset() is used in 30 places.
An asset belongs with a ?v= timestamp
Otherwise the browser keeps the old file in its cache and the change "does not appear".
Procedure: changing the running front end's appearance#
- Check which branch the build actually uses.
themes/holds plenty of dead branches —desktop_bck,scss-backup,scss_2,js2,ui-demo_old. Editing a dead branch is the most common waste of time in this part of the system. - The SCSS belongs in
template1/desktop/assets/scss/; the build:
- The JavaScript goes into
assets/js/{components,behaviors,pages}/; the build:
- Look at the result in the component catalogue (
ui-demo/) — it is the only place showing most of the elements at once. - Click through the mobile variant too, if the theme has one.
npx sass is NOT the same as sass
It is a different compiler version and produces a different output. Use sass.
Without a build an SCSS change does not appear — not even after a hard reload
The application reads css/style.css, not scss/. On top of that, the browser holds
the old version in its cache.
Procedure: creating a new theme#
- Copy the whole base of an existing theme; keep your differences in a layer of their
own (
custom/), not scattered across the tree. - Verify it with
diff -ragainst the source theme — it should show only your layer, the component catalogue and the build outputs. Anything else is an unintended divergence that will drift the next time the source theme is fixed. - Do not switch it on in the settings until the files are deployed everywhere (see the
warning above). Use
CMS_THEME_OVERRIDEfor development. - Go through the device variants — a missing
mobilefalls back todesktop, which is fine only when the desktop layout is responsive.
How to add a theme#
A new theme is a directory www/themes/frontend/<name>/ with the same structure as
the others (desktop/, optionally mobile//tablet/). Next to it belongs a
theme.json manifest:
{
"label": "Přírodní kosmetika",
"description": "Jemný e-shop s přírodní kosmetikou",
"color": "#c9785a",
"order": 80,
"group": "eshop"
}
| Key | Meaning |
|---|---|
label |
the name shown in the theme list |
description |
a short description under the name |
color |
the swatch colour / fallback tile shown when there is no screenshot |
order |
position within the group |
group |
the group slug, see below |
group is a slug from the dictionary App\Core\Theme\ThemeCatalog::GROUPS
(eshop, classifieds, company, magazine, other), not free text. The group
name shown in the administration is a CMS text and must be translated into
cs/sk/en — free text from the manifest could never make it into the translations.
An unknown or missing slug falls back to other, so a typo in a manifest can never
break the screen.
Previews for the administration#
The Vzhled webu screen needs a home-page screenshot for every theme and its skin. It is generated with:
The script photographs the home page of the running dev server
(127.0.0.1:8000) with headless Chrome and saves:
<theme>/preview.webp— the base colour,<theme>/previews/<skin>.webp— every other colour variant.
The format is WebP, not PNG — the same set of previews was 9.2 MB in PNG and 1.4 MB in WebP.
Do not run the generator while someone is working on the site
It photographs the site exactly as it is at that moment — work in progress on files or a theme switched in the administration ends up straight in the preview. The failure is not caught automatically: the script only checks that Chrome finished and that the file was created, not that the snapshot did not capture an error page. This has happened, and was only noticed by eye.
Regenerate the previews after reworking a theme
They are not generated automatically by the build or by deployment. Without running the script by hand, the administration keeps showing the old appearance.
A missing preview is not an error — the theme's or skin's card renders fine in the
administration without an image, showing a colour tile from color in the
manifest instead.
Accessibility#
Build disclosure widgets with <details> wherever possible
It works without JavaScript, screen readers handle it, and it needs no extra ARIA attributes.
Skills for working with templates#
| Skill | For |
|---|---|
cs-template-layout |
layouts |
cs-template-subpage |
a subpage |
cs-template-from-image |
a template from an image |
cs-template-from-sample |
a template from a sample |
cs-ui-audit |
reviewing a finished design |
Where to look#
| I want | Where |
|---|---|
| the theme and variant selection | app/Core/Theme/DefaultThemeResolver.php |
the themeAsset / themeUrl functions |
app/Core/Latte/Extensions/ThemeLatteExtension.php |
| the active front-end theme | www/themes/frontend/template1/desktop/ |
| the component catalogue | www/themes/frontend/template1/desktop/ui-demo/ |
| the administration theme | www/themes/admin/default/desktop/ |
Follow-up chapters: SCSS structure · Design system and tokens · Latte templates · Components