Guides
Make your own theme
Start a theme from the default one, override a template, and ship your own CSS.
At the end of this page your site runs a theme of your own: a folder under themes/, copied
from the default theme, with one template and one stylesheet changed. Nothing under vendor/ is
touched, so an upgrade leaves your work alone.
You need a shell in the project and a text editor. If you cannot deploy files, skip to edit templates from the admin, which changes the same templates from a browser. What a theme is and which template renders which page is themes; this page is the work.
Duplicate the default theme
The default theme ships inside the glueful/thallo-render pack. You never edit it; you copy it.
-
Copy it into your project:
bash php glueful render:theme:clone my-themeThe name must start with a lower-case letter or digit and hold only lower-case letters, digits, dashes and underscores.
defaultis refused, and so is a name whose folder already exists.--from=<theme>copies one of your own themes instead of the default. -
Look at what you got:
text themes/my-theme/ theme.json screenshot.jpg templates/ assets/It is a full copy — templates, assets and
theme.json— and it inherits nothing implicitly. Intheme.json,nameis nowmy-theme, the source theme'stitle,authorandtagsare gone, anddescriptionsays which theme it came from. The screenshot is kept, which is true until you change the look.
The themes/ directory has to be writable by whoever runs the command.
Make the theme live
- Open Site › Appearance in the admin. The Theme card shows a card per theme; the one being served is badged Live.
- Press your theme's card. It says Chosen — Save to make it live, and the preview beside the cards switches to it.
- Press Save. The next page view uses it.
A folder is only offered as a card when it holds templates/ and a theme.json whose vocabulary
is complete and whose stylesheets all exist, so a half-finished theme cannot reach the site by
accident.
RENDER_THEME in .env names the theme to use when nothing has been chosen in the admin. The
saved choice wins over it, and a change to it takes effect when the app restarts.
Change one template
Templates live in themes/my-theme/templates/, named as the
template hierarchy names them.
-
Edit a file —
templates/entry.twig, say, ortemplates/blocks/hero.twig. -
Reload the site. Twig recompiles a template whose file changed, into
storage/cache/twig/{theme}, so there is nothing to rebuild. -
If the page still shows the old HTML, the rendered page cache is serving it. Clear it:
bash php glueful render:cache:clearWhile you are working on a theme, set
RENDER_CACHE_ENABLED=falsein.envinstead and the cache stays out of the way.
Fallback is per template file. Delete a template you have not changed and the default theme's
copy renders in its place, so a theme can be a handful of files. Stylesheets do not fall back:
the CSS a page loads is exactly the files your theme.json lists, and nothing of the default
theme's.
Ship your own CSS
The theme's stylesheets are concatenated in manifest order into one artifact, served by content
hash. Add a file by putting it in assets/ and adding it to stylesheets in theme.json; never
link it from a template.
The build refuses two things, fails the page, and names the file and line:
@import,@charsetand@namespace. The artifact is delivered inside@layer themeand cannot carry them; declare the stylesheet intheme.jsoninstead.!importanton a property the Style tab writes — padding, margin, width, colours, radius, border, shadow,font-size,displayand their kin — in a rule whose selector holds.thallo-block. A block's settings sit in the layer above the theme and must always win.
One more rule has no error message: var(--x) with no fallback, where nothing defines --x,
makes the whole declaration invalid and the property silently takes its initial value. Define
every custom property your CSS reads.
What theme.json declares
Three keys are required. name is the theme's own name. vocabulary maps every name of the
style vocabulary to a CSS value, and stylesheets lists the theme's CSS files in load order.
Miss a vocabulary name, or list a stylesheet that is not there, and the theme cannot load, cannot
be switched to, and is reported by php glueful thallo:doctor — which checks the theme chosen on
the Appearance page, or the one RENDER_THEME names when none is chosen.
These are the names, written domain.name — spacing.lg, typography.size.2xl:
| Domain | Names |
|---|---|
spacing |
none xs sm md lg xl 2xl 3xl |
width |
narrow content container full |
radius |
none sm md lg full |
color |
background surface surface-2 text muted line accent accent-contrast transparent white |
shadow |
none xs sm md lg xl |
typography.size |
xs sm md lg xl 2xl 3xl |
A value is any CSS value, including a reference to a variable of your own: the default theme maps
spacing.xs to var(--space-1), so changing one variable re-scales everything the Design view
offers. color.white fills itself in as #ffffff when a theme omits it; every other name has to
be there. A name the vocabulary does not have cannot be declared: there are no extra tokens.
The rest is optional and cannot break the theme — a wrong value is left off the card rather than refused:
| Key | What it is |
|---|---|
title |
The name on the card. Without it, the folder's name. |
version |
Shown beside the title. |
description |
Up to 300 characters. |
author |
Who made it. |
tags |
Up to six short words. |
screenshot |
An image inside the theme's folder: png, jpg or webp, 2 MB at most. A screenshot.jpg, .png or .webp at the theme's root is found without being named. The card is 4:3; the shipped screenshot is 1200×900. |
colors |
background, text and accent as hex. When there is no screenshot, the admin draws a small page in these colours, so a card is never blank. |
settings |
The theme's presentation defaults: show_title (true or false) and layout (full or centered), and a types object keyed by content type slug carrying the same two keys for that type. An unknown key here is an error, not an omission. |
The site serves the screenshot at /_thallo/theme-screenshot/<name> — that one file of a theme,
and nothing else.
The hooks a template must keep
The copy carries all of this already. Keep it as you rewrite the markup.
- Style targets. Each block type names the parts of its template that take settings. Put
{{ style_classes('root') }}inside that element's class attribute and{{ style_attrs('root') }}on its tag, for every target the type declares — the button'scontrol, the feature block'smarker, the tabs block'sbarandtab. - Slots. Put
{{ slot_attrs('field') }}on the element that wraps ablocks()call, once perblocksfield, so the builder knows where a dragged block may land. Render that wrapper even when the list is empty;is_canvas()tells you when you are on the stage. - Editable text.
{{ data.title|editable_text('title') }}is what makes text editable in place. Plain{{ data.title }}renders and cannot be edited. - No inline style. A template writes no
style=attribute and no<style>element. - Leave the block wrapper alone. The engine marks blocks with
data-thallo-blockon the canvas; the editor finds them by that, never by your classes, which is why the classes are yours to change. - Behaviour is the pack's. Keep
{{ runtime_script() }}in a copiedlayout.twig: the carousel, tabs, navigation, colour mode and forms all run from it. Themes ship CSS, not JavaScript. - Ken Burns. Where a block sets a picture drifting, the picture stays the direct child of the
element that carries the frame's target, and you set no
transformof your own on it. - The container. If you restyle
.thallo-block-container, keepdisplay: flexandflex-direction: columnon its inner element, keep itsgap, and keep the rule that clears the vertical margin of its children. Without them a container's children touch.
Edit templates from the admin
Site › Theme editor edits the same templates without a deploy. It needs the Manage templates permission.
The switcher at the top left chooses which theme you are editing, and the button beside it
duplicates that theme into a new themes/{name} folder — the same copy the command makes. The
list below groups the theme's files by folder, each badged with where it comes from: db,
theme, package or default.
Open a file and press Save: the edit is live at once, as a database override that shadows the
file and never changes it. The badge turns db, History lists every version and who saved
it, and Delete override drops back to the file on disk. custom.css is pinned at the top
under Site; it loads after everything else on every page, which makes it the last word in the
cascade. theme.json, the theme's assets/, blocks/html.twig and blocks/shortcode.twig are
read-only.
A saved template is checked against the template policy, and a refusal is reported line by line — which is the one difference from editing on disk, where nothing checks your work.
Check it worked
- Open the site and reload. Your changed template renders, wearing your CSS.
- Open Site › Appearance and confirm your theme's card is badged Live.
- Run
php glueful thallo:doctor. It reports whether the theme maps the vocabulary and whether the compiled style artifact is published.
Next: make your own block type, which adds a template of its own to the theme you just made.