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.
Via the data-mz-theme attribute on <html> (recommended)
The widget live-observes data-mz-theme on <html> and reacts immediately:
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 OSYou can also set it statically in markup — it applies on load:
<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
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 fnInitial config (before the bundle loads)
<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
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.
// 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 inlight/white, dark glass indark— 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: reducestops 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-ais 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:
<script src="bundle.min.js" data-mz-design="glass" defer></script>MZBOT_CONFIG = { design: 'glass' };
MZBOT.setTheme({ design: 'glass' }); // live; MZBOT.designs lists the valuesWhat glass changes about the rest of the config
| Key | Behaviour in glass |
|---|---|
accent | tints every surface, not just the buttons |
bgColors / bgAngle | ignored — an opaque chat-area fill would sit on top of the pane and hide it |
pattern, patternImage | still work; the texture reads as suspended inside the glass |
mode, msgAnimation, glow | unchanged |
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.
// 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:
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:
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 surfacebgColors— an array of 1..n CSS colors (1 = solid; 2+ =linear-gradient), a comma-separated string (handy fordata-mz-bg-colors), or a full CSS background string (any*-gradient(...)) used verbatim.'none'clears it.bgAngle— gradient direction in degrees (default160). 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:
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. whiteinherits thelightbranch unless it has one of its own.- A theme with no branch keeps its own default surface —
bgColors: { 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.
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 overlaypatternImage— a URL. Acceptshttp(s)://, protocol-relative (//), root/relative (/…,./…) anddata: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 CSSbackground-size.
Via loading-script attributes
<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):
<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
MZBOT.setTheme({ msgAnimation: 'unfurl' }); // bubble | unfurl | cascade | pop | breatheAll theme methods are on the JavaScript API page.