Skip to main content

Theming

The widget is customized with two complementary mechanisms:

  1. --nrn-* CSS variables inside the Shadow DOM — ~30 tokens across the categories surface, text, border, primary, secondary, accent, radius, shadow, transition, z-index and layout.
  2. StyleOverrides from configuration (config.styles) — overrides any subset of the variables. Accepts hex (#06b6d4), rgb (rgb(6 182 212)) or RGB-triplet (6 182 212).

Because the widget lives inside a Shadow DOM, host styles don't leak in and widget styles don't leak out. This is deliberate: it guarantees visual parity across stores and removes conflicts with host CSS frameworks (Bootstrap, theme Tailwind, etc.).

RGB-triplet format

The widget stores colors in R G B format (no commas) to enable composition with rgb and runtime alpha:

:host, .neuroon-widget {
--nrn-primary: 6 182 212; /* cyan-500 */
--nrn-surface-base: 255 255 255; /* white */
}

.button {
background-color: rgb(var(--nrn-primary));
/* dynamic alpha without re-declaring the color */
box-shadow: 0 0 0 4px rgb(var(--nrn-primary) / 0.18);
}

If you pass a hex (#06b6d4) in StyleOverrides, the widget converts it to triplet automatically.

Per-theme override

NeuroonWidget.init({
/* … */
styles: {
light: {
primary: '#06b6d4',
surfaceBase: '#ffffff',
textPrimary: '#0f172a',
},
dark: {
primary: '#22d3ee',
surfaceBase: '#0f172a',
textPrimary: '#f8fafc',
},
},
})

The widget detects the shape with isThemedStyles and applies the block matching the active theme.

When the user switches theme (because the OS changes or the host emits neuroon:setTheme), the widget re-injects the overrides for the active theme via injectCustomStyles.

Variable categories

CategoryPrefixExamples
Surfaces--nrn-surface-*surface-base, surface-elevated, surface-overlay, surface-hover
Text--nrn-text-*text-primary, text-secondary, text-muted, text-disabled
Borders--nrn-border-*border-default, border-emphasis, border-subtle
Primary brand--nrn-primary*primary, primary-light, primary-dark
Secondary brand--nrn-secondary*secondary, secondary-light, secondary-dark
Accent--nrn-accent-*accent-purple, accent-emerald, accent-amber
Semantic--nrn-success, --nrn-error, --nrn-warning
Typography--nrn-font-*font-family, font-size-{xs..2xl}
Spacing--nrn-spacing-*xs, sm, md, lg, xl
Radius--nrn-radius-*sm, md, lg, xl, 2xl, full
Shadow--nrn-shadow-*sm, md, lg, xl
Transition--nrn-transition-*fast, base, slow
Z-index--nrn-z-*dropdown, sticky, modal-backdrop, modal, tooltip
Layout--nrn-container-max-width, --nrn-drawer-width, --nrn-dropdown-max-height

Exhaustive table with light/dark defaults at Reference → CSS variables. StyleOverrides → CSS var mapping defined.

End-to-end example

<div id="neuroon-search"></div>
<script>
window.NeuroonWidget = window.NeuroonWidget || {}
</script>
<script
src="https://cdn.neuroon.ai/widget@0.9.10/widget.js"
data-token="WIDGET_TOKEN"
data-container="#neuroon-search"
data-theme="auto"
defer
></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
if (!window.NeuroonWidget?.getInstance) return
const w = window.NeuroonWidget.getInstance('#neuroon-search')
w?.setStyles({
light: {
primary: '#0f766e',
primaryLight: '#14b8a6',
radiusLg: '0.5rem',
fontFamily: '"Inter", system-ui, sans-serif',
},
dark: {
primary: '#5eead4',
surfaceBase: '#031c1a',
textPrimary: '#ecfeff',
},
})
})
</script>

Automatic host detection (theme: 'auto')

With theme: 'auto' (default), the widget queries window.matchMedia('(prefers-color-scheme: dark)') on mount and subscribes to its changes. Each change internally fires a CustomEvent('neuroon-theme-change') that re-injects the overrides.

Force theme from the host

If the host wants to enforce the theme (e.g. WooCommerce admin with preview), it sends a postMessage:

const iframe = document.querySelector('iframe.neuroon-preview')
iframe.contentWindow.postMessage({
type: 'neuroon:setTheme',
theme: 'dark', // 'light' | 'dark' | 'auto'
}, '*')

Further reading