Skip to content
V
For developers
Architecture, conventions, the core and security
Look and themes / Frontend themes

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, template1template9.

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

CMS_THEME_OVERRIDE=template2 php -S 127.0.0.1:8099 -t www
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#

  1. 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.
  2. The SCSS belongs in template1/desktop/assets/scss/; the build:
sass assets/scss/style.scss assets/css/style.css --style=compressed
  1. The JavaScript goes into assets/js/{components,behaviors,pages}/; the build:
npx webpack --config webpack.config.js
  1. Look at the result in the component catalogue (ui-demo/) — it is the only place showing most of the elements at once.
  2. 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#

  1. Copy the whole base of an existing theme; keep your differences in a layer of their own (custom/), not scattered across the tree.
  2. Verify it with diff -r against 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.
  3. Do not switch it on in the settings until the files are deployed everywhere (see the warning above). Use CMS_THEME_OVERRIDE for development.
  4. Go through the device variants — a missing mobile falls back to desktop, 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:

php bin/theme-previews.php              # all themes
php bin/theme-previews.php template5    # one theme only

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