Technical page · Structure & adjustment points
For developers: how the theme is built
This page explains the technical foundation: the design variables
(“tokens”), how they work together, the @layer cascade and
the order in which you customise the theme – from “only when forking the
framework” to “on a single element”. Finally: why this structure is well
suited to AI-assisted design automation.
One value, in one place
All --av-* defaults are defined once in
bundle/public/theme/tokens.css, in the
av.tokens layer on :root. Rules in the theme
never use raw values, always a token. That is why a colour change
propagates consistently everywhere – and the reason for the name
Advanced Variable Environment.
ave:css:build) only
concerns concatenating the framework source files into one.
Values that derive from each other
Many tokens are not set but calculated:
/* Secondary colour: from the primary colour, via two dials */
--av-color-secondary:
oklch(from var(--av-color-primary)
l calc(c * var(--av-sec-chroma))
calc(h + var(--av-sec-hue-shift)));
/* Muted text: a mix of text colour and surface */
--av-color-text-muted:
color-mix(in oklch, var(--av-color-text) var(--av-muted-fraction),
var(--av-color-surface));
/* Light/dark: color-scheme + light-dark(), switched via html.light / html.dark */
--av-color-primary-light: light-dark(var(--blue-3), var(--blue-9));Change the primary colour and you automatically get the secondary colour, hover variants, muted text and the dark-mode values along with it.
Scale factor or absolute step
Sizes come as a ramp plus one factor. The factor moves
the whole ramp; at 1, calc() collapses exactly
to the original value (pixel-identical – which is why “saving with
defaults” has no effect).
--av-radius-scale: 1;
--av-radius-sm: calc(var(--radius-2) * var(--av-radius-scale));
--av-radius-md: calc(var(--radius-3) * var(--av-radius-scale));
/* … */
/* move the whole ramp: */ :root { --av-radius-scale: 1.4; }
/* hard-set one step: */ .promo { --av-radius-lg: 8px; }
Other factors: --av-text-scale,
--av-heading-scale, --av-space-scale (density),
--av-layout-gap-scale, --av-shadow-scale,
--av-glow-scale, --av-duration-scale,
--av-bg-overlay-scale.
--av-radius-scale), never the four steps. A hard-coded
--av-radius-md: 16px in custom.css
disables exactly this slider.
The @layer cascade
The order is declared once in tokens.css. Early layers have the lowest priority. Client CSS in custom.css is unlayered and therefore wins against all layered CSS – regardless of selector specificity.
▲ higher priority
- av.animations
- av.layout
- av.theme
- av.forms
- av.components
- av.typography
- av.base
- av.tokens ← all --av-* defaults
- av.normalize
theme-vars.css (the deltas from the Design Studio) is
unlayered as well, but included before custom.css
– which is why a manual change in custom.css wins against
the saved Studio state.
The adjustment points – from global to local
The Design Studio (ave-editor) is one of several equally
valid tools, not the only one. It is an optional add-on
bundle; the theme framework (bundle) works without
it. On save it only writes
files/theme/CSS/theme-vars.css (a
:root { … } block). A project without the Studio sets the
same --av-* values by hand – in
theme-vars.css, in
custom.css or as changed defaults in
tokens.css (fork + build). If
theme-vars.css is missing, everything
comes from tokens.css.
| Where | What for | Reach |
|---|---|---|
| bundle/public/theme/tokens.css | Change the defaults themselves. Only when customising the framework; then run ave:css:build. |
all projects using the bundle |
| Design Studio | The intended site-wide sliders. Writes files/theme/CSS/theme-vars.css, versioned. | whole website |
| files/theme/CSS/custom.css | Manual overrides: any --av-* on :root site-wide – or limited to a selector / a section / a page. Plus project CSS. Wins over everything. |
freely chosen |
| files/theme/CSS/theme.css | Starter theme building blocks: header/footer/navigation, hero background, accordion variants, .display-hero, .section-index, .pull-quote. Comes from the bundle (bundles/avandoave/starter/); a file of your own at this path replaces it. |
whole website |
| Field Inline styles / CSS ID/class | Per section or element: any declarations including --av-* (--av-gutter: 2rem); utility classes; bridge tokens --av-el-* / --av-section-color inherit from the wrapper into the children. |
one section / one element |
Preparing a logo SVG for logo-tint
With the logo-tint class, the theme inserts an SVG into the
page as code instead of as an image and colours its named
parts. Naming is done with a CSS class on a group:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1017 170"> <g class="logo-mark" fill="#1c7ed6"> … symbol … </g> <g class="logo-word" fill="#808080"> … lettering … </g> </svg>
- Two names, two colours:
logo-markgets the accent colour of its surroundings,logo-wordtheir text colour. A single-colour logo only needslogo-word. Everything without a name keeps its colour – so an SVG with no names at all stays unchanged. - The
fillvalues are the original colours. They apply wherever the class is missing (email, PDF, back end). Colours on individual paths inside the group don't get in the way; they are overridden too. - Convert text to paths. Lettering as
<text>would appear in a fallback font as soon as the website doesn't load its font itself – and as an ordinary image (without the class) it would anyway. - Exporting from graphics apps: Affinity and Illustrator write
layer names as
id, not asclass. So add the two classes to the groups in a text editor after exporting. - Security: the file is sanitised before embedding (the same library as Contao's upload). Scripts, event attributes and references to external servers are removed; only files inside the project are read.
Arranging the header
The header spreads its parts across the full width: the first sits at the far left, the last at the far right, everything else evenly spaced in between. A part is every entry in the page layout (Themes → Page layouts → Included elements) in the header column.
Entries in header | Result | Demo |
|---|---|---|
| Logo · navigation · button | Navigation in the middle | Vion Consulting |
| Logo · element group (navigation + button) | Navigation on the right, next to the button | Wasser für Morgen, Studio Halcyon, Praxis Lindenhof |
| Logo · navigation | Navigation on the right | – |
An element group with the layout-row layout keeps its
children together in one row and counts as one part of the
header. With three parts, the navigation sits midway between logo and
button – exactly in the centre of the page only if logo and button are
the same width.
desktop-only – such as
a contact or donate button, which can then live inside the menu.
Build & operation
- CSS build:
bin/console ave:css:buildconcatenates the source files from bundle/public/theme/ into bundle/public/dist/theme.min.css, and together with Open Props, the reset and the starter files into dist/ave.min.css. The layout includesave.min.cssas its only stylesheet as long as there is no owntheme.cssorview-transitions.cssin files/theme/CSS/ – otherwisetheme.min.cssplus the individual files.--checkverifies that it is up to date (CI). - Fonts: on save in the Studio, the chosen Google variable font is downloaded and self-hosted under files/theme/fonts/<slug>/variable.woff2 (no external font service).
- Studio state: the source of truth is
var/ave-editor/theme-settings.json,
the version history var/ave-editor/history.json
(max. 20). After a formula change in the theme:
bin/console ave:theme:rebuild.
Pitfall: composite tokens
A custom property is resolved where it is declared. Tokens such
as --av-card-border, --av-card-bg or the glow
chains are therefore also set on the classes themselves. If you build a
section that changes --av-color-border or
--av-color-surface-2, you have to update the respective
card token in the same block – otherwise the card inside keeps the root
value. Likewise, relative URLs in --icon-src resolve at the
place of use; custom icons need an absolute path.
Well suited to agentic workflows
The same structure that makes the theme maintainable also makes it predictable for AI agents. If you automate design changes, you benefit from:
-
A small, named action space. The whole look depends
on a finite, documented set of
--av-*tokens (bundle/cheat-sheet.md lists each one with its default) plus around 150 utility classes with a clearly defined effect. An agent chooses from a vocabulary instead of inventing arbitrary CSS – that reduces hallucinations and makes every change verifiable. -
Deltas instead of full state. A complete brand look is
around twenty numbers in a
:root { … }block. The model only has to output a few values; the derivation formulas (secondary colour, muted text, light/dark, scale ramps) turn them into a consistent whole. Small output, big effect, little room for error. -
Deterministic and diffable. The same tokens produce
pixel-identical output (at factor
1, identical to the defaults). Results are predictable and can be compared against a baseline. - No build in the loop. Writing custom.css or theme-vars.css is enough, the browser resolves at runtime – no compiling, no cache dance to orchestrate, a short edit-and-check cycle.
-
Built-in check signals. The
Contrast scan (WCAG AA), the
automatic contrast adjustment of brand-coloured text,
prefers-reduced-motionand light/dark are part of the system. Contrast can be re-measured quickly after every change. The scan only covers the visible text of the open page in the active colour scheme, no hover or focus states; it does not replace a full accessibility audit. -
Guard rails in the repo.
bundle/ai-instructions.md records the
rules (Open Props only, native HTML5, no CDNs, the utility schema); the
consistency tests (
CheatSheetTest,ave:css:build --check, token rule guards) catch drift automatically. -
Ready-made tools for self-verification.
bin/theme-shotstakes baseline/comparison screenshots via CDP without dependencies – a harness an agent can drive. Andave-editoralready includes “Prompt → persona”: the architecture is designed so that a language model only has to emit--av-*values.
--av-* token with its default and every utility
class. Build and style rules are in bundle/ai-instructions.md.