Dark mode displays content with a dark background and lighter text. It offers numerous benefits, such as:

  • Reducing eye fatigue in low-light conditions
  • Improving readability and focus
  • Enhancing visual comfort for sensitive eyes
  • Providing a sleek, modern aesthetic

Apps that don't support dark mode risk appearing visually inconsistent with the main Zendesk user interface or uncomfortable, which can negatively impact the overall user experience.

This article guides you through enabling dark mode support in your Zendesk apps. It can be done by leveraging Zendesk Garden’s built-in theme helpers or by implementing your own solution.

Accessing the current color scheme

Two API features are used with dark mode:

  • colorScheme property: Returns the current theme's color scheme, which can be either "light" or "dark"
  • colorScheme event: Fires whenever the theme's color scheme changes, allowing your app to dynamically update its styles

When your app iframe first loads, the current colorScheme is included as a query parameter in the iframe’s src URL.

<iframe src="https://...&colorScheme=light" />

You can read this parameter from the iframe URL and store it in JavaScript for use before the Zendesk App Framework (ZAF) initializes:

// get colorScheme even before ZAF is initializedconst queryParams = new URLSearchParams(location.search)const colorScheme = queryParams.get("colorScheme")

Alternatively, once ZAF is initialized, you can access the colorScheme property through the Zendesk App Framework client.

Listening for color scheme changes

Agents can switch themes dynamically within Zendesk Support. To keep your app in sync with these changes, listen for the colorScheme.changed event.

client.on("colorScheme.changed", newScheme => {  // Update your app’s styles based on newScheme ('light' or 'dark')})

Integrating dark mode

If your app uses Zendesk Garden v9, enabling dark mode support is straightforward. Garden v9 includes integrated theme support that allows your app to switch between light and dark modes with minimal code adjustments. By wrapping your components in the ThemeProvider and specifying the correct base theme, your app will automatically apply the proper styles for each mode.

Apps using older versions will either need to migrate to Garden V9 or manage their color usage manually.

Example:

import React, { useState, useEffect } from "react";import { Button } from "@zendeskgarden/react-buttons";import { ThemeProvider, DEFAULT_THEME } from "@zendeskgarden/react-theming";
// get colorScheme even before ZAF is initializedconst queryParams = new URLSearchParams(location.search);const colorScheme = queryParams.get("colorScheme");
const App = () => {  const [base, setBase] = useState(colorScheme || "light");
  useEffect(() => {    const client = window.ZAFClient.init();    // client.get('colorScheme') is optional, if you have already used query param, you may not need this    client.get("colorScheme").then((data) => setBase(data.colorScheme));    client.on("colorScheme.changed", (colorScheme) => setBase(colorScheme));  }, []);
  const themeObject = {    ...DEFAULT_THEME,    colors: { ...DEFAULT_THEME.colors, base },  };
  return (    <ThemeProvider theme={themeObject}>      <Button>Default</Button>    </ThemeProvider>  );};

Option 2: Using custom react apps without full Garden UI

Apps using a different UI framework or no framework at all will need to manage their color usage manually by reading the semantic color values from the Zendesk Garden DEFAULT_THEME. This gives you direct access to the theme variables without requiring the full Garden component system, making it suitable for apps that have custom UI components but still want to maintain theme consistency.

Example:

import React, { useState, useEffect } from "react";import { DEFAULT_THEME } from "@zendeskgarden/react-theming";
// get colorScheme even before ZAF is initializedconst queryParams = new URLSearchParams(location.search);const colorScheme = queryParams.get("colorScheme");
const App = () => {  const [base, setBase] = useState(colorScheme || "light");
  useEffect(() => {    const client = window.ZAFClient.init();
    // client.get('colorScheme') is optional, if you have already used query param, you may not need this    client.get("colorScheme").then((data) => setBase(data.colorScheme));    client.on("colorScheme.changed", (colorScheme) => setBase(colorScheme));  }, []);
  const themeVariables = DEFAULT_THEME.colors.variables[base];  const background = themeVariables.background.default;  const color = themeVariables.foreground.default;
  return (    <body>      <button style={{ background, color }}>Default</button>    </body>  );};

Option 3: Using vanilla JS with Garden CSS (no framework)

Apps that don’t use a JavaScript framework can still support dark mode by toggling CSS classes based on the user’s theme preference. By listening to the theme event in ZAF and switching between Garden’s predefined CSS classes, your app can stay visually consistent with the Zendesk interface. This approach works well for simple HTML-based apps that use Garden’s CSS packages without requiring a full component library.

Example:

Stylesheet 

@import "@zendeskgarden/css-buttons";

JavaScript

const client = ZAFClient.init();const queryParams = new URLSearchParams(location.search);const colorScheme = queryParams.get("colorScheme");
const setBase = (base) => {    const buttons = document.querySelectorAll('button.c-btn');
    buttons.forEach(button => button.classList.toggle('c-btn--dark', base === "dark"));};setBase(colorScheme);
// client.get('colorScheme') is optional, if you have already used query param, you may not need thisclient.get("colorScheme").then((data) => setBase(data.colorScheme));client.on("colorScheme.changed", (colorScheme) => setBase(colorScheme));

HTML

<html>  <body>    <button class="c-btn">Button</button>  </body></html>

Option 4: Using fully manual theming with CSS variables

Apps using a different UI framework or no framework at all will need to manage their color usage manually. Hardcoded colors should follow Zendesk Garden’s semantic color system and palette. This approach uses CSS variables and data-theme attributes to toggle between light and dark themes, making it compatible with any JavaScript framework or vanilla JavaScript apps.

Example:

Stylesheet 

[data-theme="light"] {  --background-default: rgb(255, 255, 255);  --foreground-default: rgb(41, 50, 57);}
[data-theme="dark"] {  --background-default: rgb(21, 26, 30);  --foreground-default: rgb(216, 220, 222);}
button {  background: var(--background-default);  color: var(--foreground-default);}

JavaScript

function setBase(base) {  document.documentElement.setAttribute("data-theme", base);}
// get colorScheme even before ZAF is initializedconst queryParams = new URLSearchParams(location.search);const colorScheme = queryParams.get("colorScheme");setBase(colorScheme);
const client = ZAFClient.init();
// client.get("colorScheme") is optional, if you have already used query param, you may not need thisclient.get("colorScheme").then((data) => setBase(data.colorScheme));client.on("colorScheme.changed", (colorScheme) => setBase(colorScheme));

HTML

<html data-theme="light">  <body>    <button>Button</button>  </body></html>

Development best practices for dark mode support

Here are some recommendations to ensure your Support app looks and functions well in both light and dark themes.

Avoid hardcoded colors

Use semantic CSS or variables instead of fixed values like #ffffff or #000000. Hardcoded colors can cause poor readability or visual issues in one of the modes, so avoid inline styles or fixed color codes and rely on theme-aware styling instead.

Avoid UI libraries or widgets that don't support theme switching

If your app uses third-party UI frameworks or custom widgets that don’t support dynamic theming, they will likely stay locked to a single style (usually light mode), creating mismatches with the Zendesk UI.

What to do:

  • Choose libraries that support theming, such as CSS variables or theme properties
  • If using custom components, build them with dynamic theming in mind
  • For third-party components that can’t be themed, consider overriding their styles or isolating them inside containers to minimize visual conflicts

Tips for building an optimal dark mode experience

  • Choose neutral color palettes and minimize harsh contrasts to enhance visual comfort
  • Regularly test your app in both light and dark modes to ensure consistent appearance and usability
  • Verify that any custom components or third-party libraries support dynamic theming or apply manual styling as needed
  • Avoid hardcoding colors; instead, leverage CSS variables or utility classes to maintain theme flexibility
  • Use the Zendesk profile menu toggle to switch between themes during testing
  • Use developer tools to inspect and troubleshoot CSS inheritance and theme-specific styling issues