Skip to content

Theming

The widget's theme can be fully recolored from the host page without rebuilding the bundle. All theme parameters flow through the same config pipeline (see Configuration): set them via window.MZBOT_CONFIG, a data-mz-* attribute, or live via MZBOT.setTheme({...}).

Color scheme

Four modes: light · white · dark · auto.

The widget live-observes data-mz-theme on <html> and reacts immediately:

js
document.documentElement.setAttribute('data-mz-theme', 'dark');   // dark
document.documentElement.setAttribute('data-mz-theme', 'light');  // light
document.documentElement.setAttribute('data-mz-theme', 'white');  // much lighter than light
document.documentElement.setAttribute('data-mz-theme', 'auto');   // follow the OS

You can also set it statically in markup — it applies on load:

html
<html data-mz-theme="dark">

Invalid values are ignored; removing the attribute leaves the current theme unchanged. There's no feedback loop: the widget writes its own state onto the <mz-bot> host, never back onto <html>.

Via the JS API

js
MZBOT.setTheme('dark');                  // string shorthand
MZBOT.setTheme({ theme: 'dark' });       // object form
MZBOT.getTheme();                         // → { theme, resolvedTheme, accent, ... }
MZBOT.subscribe((t) => console.log(t));   // listen for changes; returns an unsubscribe fn

Initial config (before the bundle loads)

html
<script src="bundle.min.js" data-mz-theme="dark" data-mz-accent="#0dbb52" defer></script>
<!-- or -->
<script>window.MZBOT_CONFIG = { theme: 'dark' };</script>

Accent & pattern

js
MZBOT.setTheme({
  accent: '#0dbb52',     // any CSS color — colors everything themed
  pattern: 'mesh',       // none | whisper | mesh | scales | bloom | cipher | prism
  patternOpacity: 0.08,  // 0..1
  patternScale: 1.5,     // 0.25..4
});

Available patterns: MZBOT.patterns. Animations: MZBOT.animations. Themes: MZBOT.themes.

Design language (design)

A skin that sits on top of the theme rather than replacing it: theme, accent, pattern, msgAnimation and glow all keep working in either one.

js
// widget.config.js
theme: {
  design: 'glass',   // 'default' (or omitted) | 'glass'
}
  • default — solid tiered surfaces. The widget's own look.

  • glass — glassmorphism. The card becomes a translucent, accent-tinted pane that blurs the host page behind it; composer, message bubbles, suggestion pills and the closed launcher are sheets of the same glass stacked above it. The glass takes its ground from the theme: light glass in light/white, dark glass in dark — the accent tints both.

    There is no header bar: its chrome sits directly on the glass with no fill, no edge and no shadow, and ink drops to the pane's own text colours (8–15:1 in light, 6–13:1 in dark, across any host-page background). The accent doesn't leave the header — it stops being a wash and becomes an object: a hairline presence ring held off the avatar by a gap, lit when someone is there and muted when not. It breathes slowly while online, the design's only ambient motion; prefers-reduced-motion: reduce stops it. The ring replaces the status dot rather than joining it.

    The colour budget. Accent is spent, not spread: a whisper of tint in the pane (low enough to read as warm grey), the visitor's own bubbles, and the things that signal — send button, presence ring, hover/active states, links, read receipts, the closed launcher. Every other surface is neutral: white-to-grey in light, grey-to-graphite in dark; --mz-glass-tint-a is the knob for the pane. Panels that float over the conversation — the options menu and the pre-send attachment card — are deliberately opaque; you must not be able to read messages through them. They stay glass by blur, a specular band and a lit rim instead.

Same resolution model as every other theme key:

html
<script src="bundle.min.js" data-mz-design="glass" defer></script>
js
MZBOT_CONFIG = { design: 'glass' };
MZBOT.setTheme({ design: 'glass' });   // live; MZBOT.designs lists the values

What glass changes about the rest of the config

KeyBehaviour in glass
accenttints every surface, not just the buttons
bgColors / bgAngleignored — an opaque chat-area fill would sit on top of the pane and hide it
pattern, patternImagestill work; the texture reads as suspended inside the glass
mode, msgAnimation, glowunchanged

Degradation

Engines without backdrop-filter (and visitors who turn on Reduce transparency) get the same skin at near-opaque alphas: it loses the frost, never the legibility. The tint itself is built from rgba() over a --mz-accent-rgb triplet that theme.js writes next to --mz-accent, so it needs no color-mix() and renders on older Safari/iPadOS and Android WebViews.

Accent glow

A glow (box-shadow halo) around the open widget, toggleable per resolved theme.

js
// window.MZBOT_CONFIG
theme: {
  glow: {
    dark:      true,   // glow when the dark theme is active
    light:     false,  // no glow on light
    intensity: 1,      // strength multiplier (1 = default; ~0.1..5)
  },
}

Runtime:

js
MZBOT.setGlow({ dark: true, light: true, intensity: 2 });
MZBOT_CONFIG = { glow: { dark: true, intensity: 1.5 } };

intensity scales the halo blur and the accent opacity (clamped to ~0.1–5). The glow follows the active theme automatically (including auto and live data-mz-theme switches): theme.js sets data-mz-glow on the host, which swaps --mz-widget-shadow. Only visible while the widget is open.

Chat background

Two independent knobs customize the message area.

Gradient / fill (bgColors / bgAngle)

Overrides the theme's default surface with your own color or gradient:

js
MZBOT.setTheme({ bgColors: ['#1b2735', '#283e51', '#0a1622'], bgAngle: 160 }); // 3-color gradient
MZBOT.setTheme({ bgColors: ['#0d4f2b'] });                                      // single color = solid fill
MZBOT.setTheme({ bgColors: 'radial-gradient(circle at 30% 0%, #2a5298, #0a1622)' }); // raw CSS background
MZBOT.setTheme({ bgColors: 'none' });                                           // back to the theme surface
  • bgColors — an array of 1..n CSS colors (1 = solid; 2+ = linear-gradient), a comma-separated string (handy for data-mz-bg-colors), or a full CSS background string (any *-gradient(...)) used verbatim. 'none' clears it.
  • bgAngle — gradient direction in degrees (default 160). Ignored for a single color or a raw string.

A single value replaces the surface across all themes (it's an explicit override) — or give one background per theme (below).

One background per theme

Every background key — bgColors, bgAngle, pattern, patternOpacity, patternScale, patternImage, patternImageOpacity, patternImageSize — accepts either one value for both themes, or a { light, dark } map:

js
theme: {
  mode: 'auto',
  bgColors: {
    light: ['#e9eeea', '#dfe8e0', '#d6e0d4'],   // while the light theme is active
    dark:  ['#1b2735', '#283e51', '#0a1622'],   // …and this one under the dark theme
  },
  bgAngle: 160,                                  // plain value = both themes
  pattern: { light: 'whisper', dark: 'scales' },
}
  • The branch is resolved against the active theme and re-applied on every switch: MZBOT.setTheme('dark'), mode: 'auto' (follows the OS), <html data-mz-theme>. No rebuild, no reload.
  • white inherits the light branch unless it has one of its own.
  • A theme with no branch keeps its own default surfacebgColors: { dark: [...] } customizes dark only and leaves light stock.
  • Works from every source: widget.config.js, window.MZBOT_CONFIG, MZBOT.setTheme({...}), and script attributes (data-mz-bg-colors-light / data-mz-bg-colors-dark).

Labels on the background fit themselves

The user's message timestamp and the date divider ("today") are painted straight on the background, so their colour isn't fixed — it's fitted to it. The widget takes every bgColors stop (or the theme surface), picks the direction — black or white, whichever holds up better against the worst stop — and walks back from that extreme toward the background as far as a ~4:1 contrast target allows (accounting for the opacity: .85 these labels render at). The result is the quietest colour that still reads confidently: visible on any background, never competing with the messages.

Nothing to configure — it's recomputed on every theme and background change. When a background can't be served by one colour at all (a gradient running near-white to near-black), the labels also get a soft halo (text-shadow) in the opposite ink.

Seamless image overlay (patternImage)

Tiles a transparent-background pattern image on top of the background. Either/or with the built-in pattern: when patternImage is set it replaces the SVG pattern (the image wins); patternImage: 'none' falls back to the SVG.

js
MZBOT.setTheme({
  patternImage:        'https://cdn.morze.tech/patterns/topography.png',
  patternImageOpacity: 0.5,    // 0..1 (the PNG's own alpha still applies)
  patternImageSize:    280,    // px tile, or any CSS background-size
});

MZBOT.setTheme({ patternImage: 'none' });   // remove the overlay
  • patternImage — a URL. Accepts http(s)://, protocol-relative (//), root/relative (/…, ./…) and data:image/…; anything else (or a URL with unsafe characters) is rejected. 'none'/'' turns it off.
  • patternImageSize — a number (px tile width, height auto) or any CSS background-size.

Via loading-script attributes

html
<script src="bundle.min.js"
        data-mz-bg-colors="#1b2735,#283e51,#0a1622"
        data-mz-bg-angle="160"
        data-mz-pattern-image="https://cdn.morze.tech/patterns/topography.png"
        data-mz-pattern-image-opacity="0.5"
        data-mz-pattern-image-size="280"
        defer></script>

A background per theme — add a -light / -dark suffix to any of those attributes (the plain attribute stays the value for whichever side you don't spell out):

html
<script src="bundle.min.js"
        data-mz-theme="auto"
        data-mz-bg-colors-light="#e9eeea,#dfe8e0,#d6e0d4"
        data-mz-bg-colors-dark="#1b2735,#283e51,#0a1622"
        data-mz-bg-angle="160"
        defer></script>

Message animation

js
MZBOT.setTheme({ msgAnimation: 'unfurl' });  // bubble | unfurl | cascade | pop | breathe

All theme methods are on the JavaScript API page.