Theme Guide
This guide explains how themes work in Zova within the Cabloy monorepo.
Why Zova themes matter
Zova provides a theme system that is independent of any one UI library and supports theme switching out of the box.
This matters because Cabloy needs a frontend architecture that can survive across different edition-specific UI stacks.
Two theme dimensions
Two major dimensions of theme switching are:
- light/dark mode with
light,dark, andauto - brand style changes, often centered on brand colors but not limited to them
This is an important design point: theming is not only dark-mode toggling. It is also a broader token and branding system.
$theme
Zova injects $theme into BeanBase, so any bean instance can access theme state through this.$theme.
Important properties include:
namedarkModedarktoken
Representative method:
toggleDark
This is also why $theme and $token should be read together rather than as unrelated APIs.
Tokens as the contract between style and theme
A token is the design-value layer that sits between CSS-in-JS styles and the active theme.
A practical split is:
- styles decide where values are consumed
- tokens define the reusable design vocabulary
- themes provide the concrete active token values
That is why token-driven styling scales better than scattering hardcoded values across many pages and components.
Why token shape is shared in architecture but not fixed across UI libraries
The token architecture is shared across Cabloy Basic and Cabloy Start, but the exact token shape can still vary.
A practical distinction is:
- the idea of token-driven styling is shared
- the concrete token fields can still reflect the active UI library, component conventions, or project theme design
This matters because edition-sensitive UI differences should not be mistaken for a different styling architecture.
Theme beans
Each UI library provides a default theme bean, and theme beans are responsible for returning token values and deeper theme customizations.
A practical lifecycle is:
- theme bean code defines the concrete token payload
- the active theme exposes that payload through
$theme.token - pages and components consume those values through
$token - runtime theme switching swaps the active token set without changing the broader styling architecture
Representative pattern:
@Theme()
export class ThemeDefault implements IThemeBase {
async apply({ dark }: IThemeApplyParams) {
const token: ThemeToken = {
color: {
primary: '#1976d2',
},
var: {
borderColor: '#297acc',
},
component: {
page: {
background: dark ? '#121212' : '#fff',
color: dark ? '#fff' : '#000',
},
},
};
return { token };
}
}Custom themes
Custom theme beans can also be created by following the same pattern.
That makes the theme system programmable rather than locked to a small fixed set of predefined skins.
Consume tokens in pages and components
In practice, pages and components usually should not hardcode design values when those values belong to the theme vocabulary.
A practical rule is:
- use
$tokenwhen a page or component is consuming theme-defined design values - use
$themewhen code needs to inspect or switch the current theme state itself
This keeps token consumption separate from theme-state control while still letting both surfaces work together.
Runtime theme switching
Representative usage pattern:
this.$theme.name =
this.$theme.name === 'home-theme:default' ? 'home-theme:orange' : 'home-theme:default';This illustrates that theme switching is an ordinary part of the application model and can be driven directly from code.
A useful distinction is:
- dark-mode switching changes the light/dark state of the active theme flow
- brand-theme switching changes which named theme provides the token set
- both still work through the same
$themeand token architecture
Edition and UI-library decision gate
Before applying SSR theme rules, identify the active edition and UI library first.
In the current Cabloy monorepo context:
- Cabloy Basic currently means DaisyUI + Tailwind CSS assumptions
- Cabloy Start currently means Vuetify assumptions
The shared Zova theme architecture stays the same, but token shape, SSR output strategy, and hydration integration can vary by adapter.
That means a rule that is safe for one edition or UI library is not automatically portable to another.
What stays shared across editions
Across Cabloy Basic and Cabloy Start, the core theme architecture remains shared:
- theme beans provide token values
- pages and components consume those values through
$token - runtime code can inspect or switch theme state through
$theme - dark mode and brand-theme switching stay part of the same model
What may still vary by edition or UI library is:
- the exact token shape
- concrete default token values
- SSR server output strategy
- client hydration and theme-finalization behavior
- integration details for a specific component library or visual system
SSR flavor capability gate
After identifying the edition and UI library, identify the SSR flavor capability level.
A practical split is:
- Web SSR is usually the lower-authority path for final browser theme when cookie-backed SSR resolution is unavailable
- Admin SSR is the stronger path for SSR-stable theme-sensitive rendering when cookie-backed SSR resolution is available
In practice, always check SSR_COOKIE and the active adapter behavior before assuming that server-rendered theme-sensitive output can exactly match the hydrated client state.
With SSR_COOKIE=false, server reads of $theme.dark, $theme.darkMode, and $token should be treated as non-authoritative for the browser's final theme unless the active adapter explicitly documents a stronger guarantee.
With SSR_COOKIE=true, SSR theme-sensitive branching can rely on a stronger server/client match guarantee, but should still stay inside the established theme handler and hydration pipeline.
For the env-side explanation of SSR_COOKIE, see SSR Environment Variables. For the flavor/runtime selection model, see Environment and Config Guide.
Shared development rules
Apply these rules before writing adapter-specific logic:
- keep concrete theme values in theme beans instead of scattering them across pages or components
- use
$tokenwhen code consumes theme-defined design values - use
$themewhen code needs to inspect or switch theme state itself - keep adapter-specific DOM/theme application inside the active theme handler or client boot path rather than duplicating it in feature code
- do not assume token fields are portable across UI libraries without checking the active adapter contract
Cabloy Basic checklist: DaisyUI + Tailwind CSS
In the current __CABLOY_BASIC__ frontend setup:
- DaisyUI + Tailwind CSS is the active UI layer
- theme beans and
$tokenremain the shared architectural contract - Web SSR emits dual dark/light SSR markers and the browser selects the final theme during bootstrap
- the active theme handler owns
data-themeand CSS variable application
Apply these rules:
- In Web SSR, treat server-rendered reads of
$theme.dark,$theme.darkMode, and theme-derived$tokenvalues as non-authoritative for the browser's final theme. - Keep theme-sensitive SSR output fallback-safe or hydration-tolerant when exact browser theme matching matters.
- Defer final theme-sensitive decisions to the client when an exact browser theme match is required.
- Let the theme handler own
data-themeand CSS variable application instead of duplicating that logic in pages or components. - In Admin SSR, cookie-backed theme resolution is the stronger path for SSR-stable theme-sensitive branching.
Cabloy Start comparison checklist: Vuetify
In __CABLOY_START__, the theme architecture is still shared, but the adapter behavior is deeper:
- Vuetify-oriented token payloads are part of the active theme contract
- the SSR adapter writes theme name, dark-variant theme data, and token payloads for hydration
- client boot reconstructs the active Vuetify theme from SSR state
Apply these comparison rules:
- Do not collapse Cabloy Start behavior into the simpler Cabloy Basic
data-thememental model. - Treat Vuetify adapter state handoff and client boot hydration as part of the theme contract.
- When documenting or changing SSR theme rules, verify both the server handoff payload and the client reconstruction path.
- Web SSR still needs lower-authority assumptions when cookie-backed SSR resolution is unavailable, even though the adapter handoff is richer than in Cabloy Basic.
Quick comparison table
| Edition | UI library | SSR server handoff | Client hydration/finalization | Safe Web SSR rule |
|---|---|---|---|---|
| Cabloy Basic | DaisyUI + Tailwind CSS | Dual dark/light SSR markers plus handler-owned DOM theme output | Browser bootstrap resolves the final theme and applies data-theme | Do not treat server theme-sensitive reads as final browser truth |
| Cabloy Start | Vuetify | Adapter-driven SSR state including theme name, dark variant data, and token payloads | Client boot reconstructs the active Vuetify theme from SSR state | Do not reduce Start to a Basic-style data-theme-only model; still treat Web SSR as lower-authority without cookie-backed resolution |
SSR theme review checklist
Use this short review checklist when editing SSR theme behavior or reviewing AI-generated changes.
Do:
- identify the active edition marker before applying SSR theme rules
- identify the active UI library before assuming token shape or hydration behavior
- keep concrete theme values in theme beans and consume them through
$token - use
$themefor theme-state control and$tokenfor theme-value consumption - verify whether the active flavor provides cookie-backed SSR theme resolution before trusting server theme reads
- keep adapter-specific theme finalization inside the existing theme handler or client boot path
- verify both server handoff and client hydration behavior when documenting or changing adapter-specific SSR theme logic
- treat Web SSR as the stricter path unless the active adapter and cookie capability clearly provide a stronger guarantee
Don't:
- do not assume Cabloy Basic and Cabloy Start use the same adapter-level SSR theme handoff
- do not assume a Basic
data-themepattern fully describes Vuetify-based Start behavior - do not treat server reads of
$theme.dark,$theme.darkMode, or$tokenas final browser truth in cookie-disabled Web SSR - do not duplicate theme-finalization logic in pages or components when the active adapter already owns that responsibility
Reviewer template
Use this short template in PR review, code review, or AI review when a change touches SSR theme behavior.
- [ ] I identified the active edition marker before reviewing SSR theme behavior.
- [ ] I identified the active UI library before assuming token shape or hydration behavior.
- [ ] I verified whether the active flavor provides cookie-backed SSR theme resolution.
- [ ] I checked whether the change treats server reads of
$theme.dark,$theme.darkMode, or$tokenas lower-authority in cookie-disabled Web SSR. - [ ] I verified that adapter-specific theme finalization stays inside the existing theme handler or client boot path.
- [ ] I checked whether the rule or behavior is shared across editions or adapter-specific.
- [ ] For Cabloy Basic, I verified the change does not over-assume a final browser theme from server-side theme-sensitive reads.
- [ ] For Cabloy Start, I verified the change respects Vuetify SSR state handoff and client reconstruction rather than reducing it to a Basic-style
data-theme-only model. - [ ] I verified both server handoff and client hydration behavior for the active adapter.
Prompt-ready reviewer snippet
Use this block directly in a reviewer-agent or code-review prompt when a change touches SSR theme behavior:
Review this change with the Cabloy SSR theme rules in mind.
1. Detect the active edition marker and UI library before assuming SSR theme behavior.
2. Do not assume Cabloy Basic and Cabloy Start use the same adapter-level SSR theme handoff.
3. In cookie-disabled Web SSR, do not treat server reads of $theme.dark, $theme.darkMode, or $token as final browser truth.
4. Verify that adapter-specific theme finalization stays inside the existing theme handler or client boot path.
5. Verify both server handoff and client hydration behavior for the active adapter.
6. Flag any change that collapses Vuetify-based Start behavior into a Basic-style data-theme-only mental model.Verification checklist
When changing theme behavior or writing theme-sensitive SSR code, ask:
- which edition marker is active, and which UI library contract does that imply?
- is the change about token design, theme state control, SSR output, or client hydration?
- does the active flavor provide cookie-backed SSR theme resolution?
- is this rule shared across editions, or adapter-specific?
- does the implementation follow the existing handler and hydration path for the active UI library?
That keeps theme work scalable, edition-aware, and aligned with the real SSR capability boundary.