Files
timebank-cc-public/references/THEME_IMPLEMENTATION.md
T

5.4 KiB

Theme System

How the look of an installation is chosen and applied. Setting up a platform's theme and branding step by step: MULTI_PLATFORM_INSTANCES.md, sections 3, 5 and 6.

Last checked against the code: 2026-10-19 (branch laravel-11).

Where the active theme comes from

  1. platform/theme.php in the installation's platform folder: one theme definition with its own id. When it exists, App\Providers\PlatformServiceProvider registers it in themes.themes.<id> and makes it the active theme.
  2. Otherwise config('themes.active'), which is TIMEBANK_THEME in .env (default timebank_cc), one of the themes in config/themes.php: timebank_cc, uuro, vegetable, yellow (template config/themes.php.example, from which deploy.sh creates the file and config:merge adds new keys).

A theme definition has name, description, logos, colors and typography. colors needs every key the templates have (primary, secondary, button-secondary, button-secondary-text, accent, success, warning, danger-dark, danger, danger-light, info, brand, background, border, surface, reaction, reaction-hover, text, logo): a missing key leaves its CSS variable empty. Copy a whole block from config/themes.php.example when you make a theme.

How it is applied: at runtime, one build for all themes

  • theme_css_vars() turns the active theme's colours and typography into CSS custom properties (--color-primary-500: 37 99 235;, --font_family_body: ...). The layouts (layouts/app, layouts/guest) put them in a <style> block on every page.
  • tailwind.config.js defines the theme colours as rgb(var(--color-...) / <alpha-value>), so classes such as bg-theme-brand, text-theme-primary, border-theme-border and bg-primary-500 follow the active theme.
  • So switching themes needs no npm run build: one asset build serves every theme. Only VITE_* values in .env are compiled into the build.
  • <html data-theme="@themeId"> carries the theme id; no CSS depends on it.

Logos, favicons and the preview image

Each asset is looked up in the platform folder first, then through the theme's logos settings:

Asset Platform folder Theme setting Helper
Website logo (inline SVG) platform/views/logos/<theme>.blade.php logos.svg_inline, a view name such as logos.uuro (resources/views/logos/) theme_logo('svg_inline')
Email and PDF logo platform/public/mail-logo.png logos.email_logo, relative to storage/app/public/ theme_email_logo_url() (emails), theme_email_logo_file() (PDFs, inline images)
Email logo for dark headers platform/public/mail-logo-inverted.png logos.email_logo_inverted the same with true
Favicons platform/public/ logos.favicon_dir, relative to public/ theme_favicon('favicon.ico'); theme_favicon_optional() for favicon.svg, favicon-96x96.png, site.webmanifest
Preview image for shared links platform/public/og-image.png logos.og_image, relative to public/ theme_og_image()

Without any of these, an email gets a generated PNG with the platform name (App\Services\GeneratedLogo; the timebank_cc theme falls back to the tracked app_mail_logo.png), favicons come from public/, and there is no preview image. The title, the icons and the Open Graph tags are in one partial that both layouts include: resources/views/layouts/partials/brand-head.blade.php.

Helpers and Blade directives

PHP (app/Helpers/ThemeHelper.php) Blade (app/Providers/ThemeServiceProvider.php) Returns
theme($key = null, $default = null) @theme('key') the active theme's definition, or one key of it
theme_id() @themeId the active theme's id
theme_name() @themeName its display name
theme_color('primary.500') @themeColor('primary.500') a colour
theme_font('font_family_body') @themeFont('font_family_body') a typography value
is_theme('uuro') @isTheme('uuro') ... @endIsTheme whether that theme is active
theme_css_vars() @themeCssVars the CSS custom properties
theme_logo($type), theme_email_logo($inverted) and the helpers in the table above logos, favicons, preview image

Prefer theme classes and helpers over theme checks: code that asks is_theme('uuro') breaks for a theme of a new platform. Only the generated email logo checks the id (timebank_cc keeps its own PNG).

In views always use theme classes, never fixed colours. UI patterns: STYLE_GUIDE.md, THEME_CLASS_CONVERSION_GUIDE.md.

Adding a theme

  • For one platform: in its platform/theme.php, with its logo in platform/views/logos/<id>.blade.php. Nothing in the code changes. php artisan platform:init --from-current writes the active theme there as a start.
  • For every installation: a block in config/themes.php.example and a logo view in resources/views/logos/, committed to git. config:merge themes then adds it to each installation's config/themes.php.

Testing

  • tests/Feature/ThemeButtonColorsTest.php, PlatformBrandingTest.php, PlatformFolderTest.php (theme from the platform folder) and PlatformHeadTest.php (icons and Open Graph).
  • By hand: switch TIMEBANK_THEME in .env and reload; with a platform/theme.php present, that one wins.