npm.io
1.3.2 • Published 1 week ago

@ptlm-azulejo/themes

Licence
Version
1.3.2
Deps
0
Size
22 kB
Vulns
0
Weekly
0

@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.css and are the same for every brand (400 / 500 / 600 / 700). The typeface itself is the brand's voice, so --font-family lives in the preset.

Heads-up for anyone doing a strict DE→PARA: upstream Mozaic defines the weights per brandbuild/<brand>/css/root.css in @mozaic-ds/tokens sets --font-weight-semi-bold to 500 for Adeo (aliased to font.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 @imports base.css (the structural + status tokens and a scoped UA preflight), so a single preset import gives you everything. You can still import base.css on 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-family yourself 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 on button, input, select, and textarea so 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's prefers-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-merlin ships the files and serves them from your own domain — LeroyMerlinSans is proprietary and not on a public CDN.
  • @ptlm-azulejo/fonts-adeo loads 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 @import with 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.