@ptlm-azulejo/themes
@ptlm-azulejo/themes
Multi-brand theme tokens for the @ptlm-azulejo components, aligned with the
Mozaic Design System. It provides the color,
typography, spacing, radius and shadow values that the components consume at
runtime via CSS variables — the equivalent of @mozaic-ds/tokens.
Concept
The theme has two parts:
| File | Contents | Brand-specific? |
|---|---|---|
base.css |
Structural + status tokens + imports preflight.css |
No |
preflight.css |
UA wipe for form controls (button, input, …), scoped to .preset-* |
No |
presets/<brand>.css |
Brand tokens — typeface (--font-family) and functional colors (--color-brand, --color-text-*, --color-background-*, --color-border-*) |
Yes |
The brand is selected with a class on the root element (.preset-lm,
.preset-adeo) and light/dark mode with the data-theme attribute.
Font sizes and weights live in
base.cssand are the same for every brand (400 / 500 / 600 / 700). The typeface itself is the brand's voice, so--font-familylives in the preset.Heads-up for anyone doing a strict DE→PARA: upstream Mozaic defines the weights per brand —
build/<brand>/css/root.cssin@mozaic-ds/tokenssets--font-weight-semi-boldto 500 for Adeo (aliased tofont.weight.medium) and 600 for Leroy Merlin. This library uses 600 for both, and exposes 500 separately as--font-weight-medium. The two also differ on which cuts physically exist: LeroyMerlinSans ships no Medium (500) or Bold (700) file, so 500 falls back to Regular and 700 is browser-synthesized from SemiBold.
Installation
yarn add @ptlm-azulejo/themes @ptlm-azulejo/button
Usage
Install the component and the theme packages, then in the app's entry point
import a brand preset (it already bundles base.css) and the component
styles:
Leroy Merlin projects
import '@ptlm-azulejo/themes/presets/leroy-merlin.css' // includes base.css
import '@ptlm-azulejo/button/style.css'
Adeo projects
import '@ptlm-azulejo/themes/presets/adeo.css' // includes base.css
import '@ptlm-azulejo/button/style.css'
Each preset
@importsbase.css(the structural + status tokens and a scoped UA preflight), so a single preset import gives you everything. You can still importbase.csson its own (@ptlm-azulejo/themes/base.css) if you ever need it without a brand — but note it carries no--font-family, since the typeface belongs to the brand. Without a preset, components render uncolored and fall back to a generic sans-serif (the font weights still apply). Define--font-familyyourself if you go this route.The preflight only runs under
.preset-lm/.preset-adeo(via:where(…)so component utilities still win). It clears browser defaults onbutton,input,select, andtextareaso Azulejo controls look the same whether or not the host app runs Tailwind Preflight.
Then apply the brand class on <html> (or any container):
<!-- Leroy Merlin, light (default) -->
<html class="preset-lm">
<!-- Adeo, dark -->
<html class="preset-adeo" data-theme="dark"></html>
</html>
Important: without importing a preset (which bundles
base.css) and without the.preset-*class on the root, the components render uncolored — they only reference the CSS variables; the values come from here.
Light / dark mode
data-theme="dark"forces dark;data-theme="light"forces light.- With no
data-theme, the preset follows the operating system'sprefers-color-scheme.
Fonts
Each preset names its brand's typeface but ships no font files and no
@font-face — exactly like upstream Mozaic, which sets
font-family: LeroyMerlin and assumes the app already loaded it. Loading the
files is the app's job.
There is a package per brand that does exactly that job:
| Brand | --font-family |
Font package |
|---|---|---|
.preset-lm |
"LeroyMerlin", Arial, sans-serif |
@ptlm-azulejo/fonts-leroy-merlin |
.preset-adeo |
"Roboto", Arial, sans-serif |
@ptlm-azulejo/fonts-adeo |
yarn add @ptlm-azulejo/themes @ptlm-azulejo/fonts-leroy-merlin
import '@ptlm-azulejo/themes/presets/leroy-merlin.css'
import '@ptlm-azulejo/fonts-leroy-merlin'
Until you load a typeface the fallback stack renders, so nothing breaks — it just
isn't on-brand. You can also wire the @font-face yourself if you'd rather
control hosting, subsetting and preload; the packages are a convenience, not a
requirement.
Declare the cuts the tokens reference — 400, 500, 600 and 700. One upstream quirk
worth knowing: LeroyMerlinSans ships only Light, Regular and SemiBold
(@mozaic-ds/web-fonts), so it has no Medium (500) or Bold (700) file — 500 falls
back to Regular and 700 is synthesized from SemiBold, same as upstream Mozaic.
Roboto on the Google Fonts CDN serves all four cuts.
How each brand loads its font
The two font packages differ, for licensing reasons:
@ptlm-azulejo/fonts-leroy-merlinships the files and serves them from your own domain — LeroyMerlinSans is proprietary and not on a public CDN.@ptlm-azulejo/fonts-adeoloads Roboto from the Google Fonts CDN — Roboto is OFL-1.1 and Google serves every cut the tokens reference, so the package is a single@importwith no binaries to vendor.
Only the active brand's font is downloaded
You can declare both brands' faces and switch freely — the other brand's file
is never fetched. @font-face is a declaration, not a fetch: a browser only
requests a font file when text it is actually rendering computes a font-family
naming it. With .preset-lm on the root, nothing ever computes Roboto.
The one thing that breaks this is <link rel="preload" as="font">, which is
eager and bypasses that matching. If you ship a single fixed brand, preload it —
just never preload both.
Opting out
Components apply the brand typeface themselves, so they follow the brand rather than inheriting your page's font. To hand a component back to the page font:
.preset-lm .btn {
font-family: inherit;
}
Switching brand at runtime
Just swap the root class (both presets can be imported at the same time):
document.documentElement.classList.remove('preset-lm', 'preset-adeo')
document.documentElement.classList.add('preset-adeo')
Creating a custom brand
Define the same brand tokens of the contract under your brand class:
.preset-my-brand {
/* Typeface. The 400/500/600/700 weights come from base.css; override them here
only if your typeface lacks those cuts. */
--font-family: 'Inter', Arial, sans-serif;
--color-brand: #6c2bd9;
--color-brand-dark: #531fb0;
--color-brand-light: #efe7fb;
--color-text-primary: #111111;
--color-text-accent: var(--color-brand);
--color-text-on-accent-inverse: #ffffff;
--color-background-primary: #ffffff;
--color-background-accent: var(--color-brand-light);
--color-background-accent-inverse: var(--color-brand);
/* …remaining functional tokens (see presets/leroy-merlin.css for reference) */
}
Customizing a component without changing the brand
The components expose theme utilities mapped in tailwind.css (@theme inline).
You can override the underlying functional tokens for a scoped element:
.preset-lm .btn--accent {
--color-background-accent: #d6f5e6;
}
Relationship with Mozaic
The functional token names (--color-text-primary, --color-background-accent,
…) and the theming mechanism (brand class + data-theme) follow the Mozaic
Design System, to ease DE→PARA migration of components between projects.