Skip to content

Dark mode theming

Dark mode theming

Bilberry widgets support a first-class dark mode. When enabled, the widgets adapt their surfaces, text, dividers, overlays, and disabled states for dark backgrounds — all derived from a small set of theme properties you provide.

Dark mode is opt-in via an explicit flag. Tenants that do not set the flag get exactly the same behavior they did before — nothing changes for existing light-mode integrations.

Enabling dark mode

Set darkMode: true on window.BilberryCustomTheme and provide the two surface tokens that dark mode is built on:

window.BilberryCustomTheme = {
darkMode: true,
widgetBackgroundColor: '#2a2a3e', // the widget surface
widgetTextColor: '#e0e0e0', // primary text color on that surface
// …your other theme properties
};

That is the minimum configuration. With these three values set, all widgets will render with a dark surface and light text.

darkMode

  • Type: boolean
  • Default: false (or unset)

When true, the widgets switch to dark-mode rendering. Leave unset or set to false for normal light mode.

widgetBackgroundColor

  • Type: hex color string
  • Default: #ffffff

The widget’s primary surface color. In dark mode this is the background color used for most widget panels (date pickers, dialogs, overlays, inputs).

widgetTextColor

  • Type: hex color string
  • Default: unset (falls back to primaryColor for primary text)

The primary text color used on top of widgetBackgroundColor. Set this to a light tone (e.g. #e0e0e0 or #ffffff) when using a dark widgetBackgroundColor.

Essential tenant adjustments for a polished dark theme

The darkMode: true flag handles most things for you. But a handful of tenant-level properties still matter for a coherent dark design. The most common pitfalls are below.

1. Keep lightestGrey lighter than widgetBackgroundColor

In dark themes, elevation reads as lighter — cards that sit “on top of” a surface should be lighter than that surface, not darker. Many nested surfaces in widgets (the checkout summary card, related-products cards) derive their background from lightestGrey.

// ❌ Collapses into the outer widget surface — no visual elevation:
widgetBackgroundColor: '#2a2a3e',
lightestGrey: '#2a2a3e',
// ✅ Elevated cards clearly raise from the surface:
widgetBackgroundColor: '#2a2a3e',
lightestGrey: '#4a4a5e',

A value roughly 10–30% lighter than widgetBackgroundColor works well.

2. Keep primaryColorContrast distinct from your page background

primaryColorContrast is used as a background color for several button variants (e.g. the outlined “Book now” button in nested checkout cards). If it matches your page background, those buttons will look like they sink into the page.

// ❌ button bg (primaryColorContrast) matches page bg — button disappears:
primaryColorContrast: '#1a1a2e', // and body bg is also #1a1a2e
// ✅ button bg lifts from the page:
primaryColorContrast: '#1a1a2e', // and body bg is a different dark tone

If you have control over the surrounding page, ensure the page color and primaryColorContrast are visibly different. Otherwise choose a primaryColorContrast that contrasts with your page.

3. Provide a dark-tuned bookingSearchFormInputColor

By default the booking-search form has a white-background input. On a dark booking-search-form surface, white inputs are jarring. Explicitly set:

bookingSearchFormInputColor: '#2a2a3e',
bookingSearchFormInputTextColor: '#e0e0e0',

4. Adjust linkColor for dark backgrounds

The default light-mode link tone (usually a deep blue) may be hard to read on a dark surface. Pick a brighter link color:

linkColor: '#6ba3ff', // brighter blue, readable on dark surfaces

Also consider setting productCardLinkColor — it controls links rendered inside product cards and does not inherit from linkColor if unset:

productCardLinkColor: '#6ba3ff',

5. Use brighter heading colors

The default h1Colorh6Color values assume text on white. For dark mode, set them explicitly:

h1Color: '#ffffff',
h2Color: '#e0e0e0',
h3Color: '#d0d0d0',
h4Color: '#d0d0d0',
h5Color: '#d0d0d0',
h6Color: '#c0c0c0',
bodyColor: '#e0e0e0',

6. Nets / Nexi payment iframe — not yet supported

If you process payments through Nets (Nexi) on the inline embedded checkout, the payment form runs inside an iframe served by Nets. Theming that iframe to match a dark surface is not currently supported — the widget does not forward any styling values to the Nets payment session, so the iframe always renders in its light default regardless of darkMode. Expect a light Nets iframe inside an otherwise-dark widget for now.

What dark mode adjusts automatically

When darkMode: true is set, you do not need to configure the following — the widgets handle them for you:

  • Menu, dialog, tooltip, popover backgrounds — derived from widgetBackgroundColor.
  • Divider, hover, focus, and disabled-state colors — switched to dark-tuned defaults.
  • Secondary text (labels, helper text, captions) — set to a readable bright on-surface tone so it stays legible without you having to override it.
  • Loading skeletons and shimmer overlays — use a dark-mode default overlay.
  • Date picker day, weekday label, and arrow colors — routed through bodyColor and the dark-mode palette.

Common pitfalls

  • Forgetting darkMode: true when setting dark surface colors. Without the flag, the widgets stay in light mode and many components (menus, tooltips, disabled states) will render with light defaults on top of your dark surface.
  • Using the same value for widgetBackgroundColor and lightestGrey — collapses the elevation hierarchy, making nested cards invisible.
  • Matching primaryColorContrast to your page background — makes outlined CTAs (like “Book now”) blend into the page.
  • Leaving bodyColor, h1Colorh6Color at light-mode values — produces near-invisible dark-on-dark text.
  • Not setting widgetTextColor — primary text falls back to primaryColor, which is usually your brand accent color, not a readable text color.
  • Expecting the Nets / Nexi iframe to follow darkMode — it doesn’t. Theming the inline Nets checkout is not yet supported; the iframe always renders in its light default.

Full working example

The example below uses the Bilberry 2026 brand palette (deep navy primary, warm-grey neutrals, Inter Tight typography) as a starting point. Replace the brand colors with your own and adjust surface tones to taste.

<script>
window.BilberryCustomTheme = {
// Dark mode opt-in + surface tokens
darkMode: true,
widgetBackgroundColor: '#1d2435', // dark navy widget surface
widgetTextColor: '#ffffff',
// Brand — Bilberry Blue
primaryColor: '#3d6da4', // mid-blue accent
primaryColorContrast: '#ffffff',
secondaryColor: '#788274', // warm grey
secondaryColorContrast: '#ffffff',
// Elevated surface — clearly lighter than widgetBackgroundColor
lightestGrey: '#3c423c',
// Booking widget
bookingWidgetColor: '#162c4c', // deeper navy for visual weight
bookingWidgetColorContrast: '#ffffff',
bookingWidgetPrimaryColor: '#3d6da4',
// Booking search form — darkest navy, dark inputs
bookingSearchFormColor: '#0e2645',
bookingSearchFormInputColor: '#1d2435',
bookingSearchFormInputTextColor: '#ffffff',
// Accommodation search form
accommodationSearchFormColor: '#0e2645',
accommodationSearchFormColorContrast: '#ffffff',
accommodationSearchFormInputColor: '#1d2435',
accommodationSearchFormInputTextColor: '#ffffff',
// Checkout header — distinct from page bg
checkoutHeaderColor: '#081830',
checkoutHeaderColorContrast: '#ffffff',
checkoutHeaderPrimaryColor: '#3d6da4',
// Basket icon
basketIconColor: '#3d6da4',
basketIconTextColor: '#ffffff',
basketIconBorderColor: '#3d6da4',
basketIconCountColor: '#ffffff',
// Product cards — elevated surface
productCardColor: '#162c4c',
productCardTextColor: '#ffffff',
productCardPrimaryColor: '#0e2645', // text on inline-summary CTA
productCardPrimaryColorContrast: '#3d6da4', // bg on inline-summary CTA
productCardAccentColor: '#3d6da4',
productCardAccentColorContrast: '#ffffff',
productCardIconColor: '#5e8bbc',
productCardLinkColor: '#8aaad0',
// Input fields
inputFieldTextColor: '#ffffff',
inputFieldLabelColor: '#bfc7b9',
inputFieldBorderColor: 'rgba(255, 255, 255, 0.2)',
// Typography
fontFamily: '"Inter Tight", "Helvetica", "Arial", sans-serif',
bodyColor: '#ffffff',
h1Color: '#ffffff',
h2Color: '#ffffff',
h3Color: '#edf1e9',
h4Color: '#edf1e9',
h5Color: '#bfc7b9',
h6Color: '#bfc7b9',
// Links — readable on dark
linkColor: '#8aaad0',
// Status
errorColor: '#f43f5e',
warningColor: '#f59e0b',
informationColor: '#382f50',
};
</script>
<script type="module" src="https://bilberry-widgets.b-cdn.net/v4/main.js"></script>

Switching between light and dark at runtime

BilberryOverrideTheme can flip darkMode on the fly, which is useful if your site has a theme toggle:

// Switch to dark mode
window.BilberryOverrideTheme({
darkMode: true,
widgetBackgroundColor: '#2a2a3e',
widgetTextColor: '#e0e0e0',
// …other dark overrides
});
// Switch back to light
window.BilberryOverrideTheme({
darkMode: false,
widgetBackgroundColor: '#ffffff',
// …your usual light theme
});

Only the specified properties are applied; other theme properties retain their current values. If you maintain two separate theme objects (light and dark), pass the entire object you want to switch to.