# How to Enable Dark Mode in Retool

- Tool: Retool
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Retool Cloud and Self-hosted
- Last updated: March 2026

## TL;DR

Retool has a built-in dark mode you can enable in the Theme Editor (App Settings → Appearance → Theme → select Dark preset). Users can also control their editor display mode in Personal Settings. For a user-togglable in-app dark mode, use a Temporary State variable with a Toggle component and CSS class switching. Custom CSS overrides need !important to work in dark mode.

## Three Approaches to Dark Mode in Retool

Retool offers dark mode through its built-in Theme Editor, which provides a Dark preset that switches the entire app's color palette. This is the simplest approach: one click in App Settings changes all components to use dark backgrounds, light text, and appropriately adjusted borders.

Beyond the static theme, you can build a dynamic user-togglable dark mode using a Temporary State variable and CSS class switching. When the user clicks a Toggle, the app body class changes, and CSS rules handle the visual transformation.

For quick prototyping or edge cases, the CSS filter trick (`body { filter: invert(1) hue-rotate(180deg); }`) inverts all colors instantly — useful for testing but not production-ready.

This tutorial covers all three approaches with specific Retool UI paths and CSS syntax.

## Before you start

- A Retool app with components you want to display in dark mode
- Editor or Admin access to the Retool app
- Basic understanding of Retool's App Settings panel
- Optional: familiarity with CSS variables for advanced customization

## Step-by-step guide

### 1. Enable the built-in Dark theme via Theme Editor

In the Retool editor, click the gear icon in the top toolbar to open App Settings. Go to Appearance → Theme. Click the Theme Editor button. In the Theme Editor modal, look for the Theme preset selector at the top — it shows Default (light). Click it and select Dark. Click Save to apply. The entire app canvas instantly switches to a dark color scheme including backgrounds, text, borders, and component surfaces.

**Expected result:** The app canvas, all components, and the background switch to dark colors immediately after saving the theme.

### 2. Customize dark mode color tokens

With the Dark preset active in the Theme Editor, you can override individual color tokens. The Theme Editor exposes CSS variable tokens like --retool-color-background-primary, --retool-color-text-primary, and --retool-color-accent. Click any color swatch in the Theme Editor to open a color picker and set a custom value.

For more precise token control, add CSS variables in App Settings → Custom CSS:

```
/* Override specific dark mode color tokens */
:root {
  /* Custom dark background — slightly lighter than Retool default */
  --retool-color-background-primary: #1a1b2e !important;
  
  /* Custom accent color in dark mode */
  --retool-color-accent: #7C3AED !important;
  
  /* Text color */
  --retool-color-text-primary: #E2E8F0 !important;
}
```

**Expected result:** The dark theme uses your custom color values for backgrounds and accents throughout the app.

### 3. Create a user-togglable dark mode switch

For apps where users can toggle dark/light mode at runtime, you need a Temporary State variable and a Toggle component. First, create a Temporary State: in the editor, open the State panel (or press S key in the editor) and add a new Temporary State named darkMode with a default value of false.

Next, add a Toggle component to the app. In the Toggle's Inspector, set the Default value to `{{ darkMode.value }}`. Add an event handler: On Change → Run Script → enter: `await darkMode.setValue(!darkMode.value);`.

Note: setValue() is async — do not read darkMode.value immediately after setting it in the same script execution context.

```
// Toggle component event handler (On Change → Run Script)
// Note: setValue is async — the new value is not immediately available
await darkMode.setValue(!darkMode.value);

// Do NOT do this — darkMode.value will still be the old value here:
// await darkMode.setValue(!darkMode.value);
// console.log(darkMode.value); // still old value!
```

**Expected result:** The Toggle component changes the darkMode temporary state value when clicked.

### 4. Apply dark mode CSS based on the state variable

With the darkMode temporary state variable controlling a class on the body, add CSS rules to App Settings → Custom CSS that activate dark styles when the class is present. Use the conditional CSS class approach: when darkMode is true, add a class to the app container, then write CSS targeting that class.

Alternatively, combine with a Text component that dynamically sets a CSS class:

In a Text component's CSS Class Name (Inspector → Advanced), enter: `{{ darkMode.value ? 'dark-mode-active' : '' }}`

Then write CSS for .dark-mode-active descendants:

```
/* Dark mode styles activated by .dark-mode-active class */

/* When dark mode is on, override app background */
.dark-mode-active ~ ._retool-app-body,
body._dark-mode ._retool-app-body {
  background-color: #1a1b2e !important;
  color: #E2E8F0 !important;
}

/* Table in dark mode */
.dark-mode-active ._retool-table,
.dark-mode-active ._retool-table thead tr {
  background-color: #2d2f3e !important;
  color: #E2E8F0 !important;
  border-color: #4a4d6b !important;
}

.dark-mode-active ._retool-table tbody tr:hover {
  background-color: #3a3d52 !important;
}
```

**Expected result:** When darkMode.value is true, the app body and table components display dark colors.

### 5. Use the CSS filter quick hack for prototyping

For rapid prototyping or testing what a dark mode would look like, you can use a CSS filter inversion trick. In App Settings → Custom CSS, add:

```css
body {
  filter: invert(1) hue-rotate(180deg) !important;
}

/* Re-invert images so they look correct */
img, video, ._retool-image {
  filter: invert(1) hue-rotate(180deg) !important;
}
```

This inverts all colors and then rotates hues by 180 degrees to restore natural-looking colors while darkening backgrounds. White becomes black, light gray becomes dark gray. It's not perfect — some colors look off — but it's useful for a 30-second dark mode preview.

**Expected result:** The entire app inverts its colors instantly. Backgrounds darken and text appears light.

### 6. Handle custom CSS rules in dark mode context

If you have existing custom CSS rules (from other styling work), review them for dark mode compatibility. Rules that hardcode light colors (e.g., background-color: #ffffff !important) will break the dark theme. Use CSS custom properties to make your color rules theme-aware:

Define them for both light and dark, then reference them in component rules:

```
/* Theme-aware CSS custom properties */
:root {
  --app-surface: #ffffff;
  --app-text: #1a1a1a;
  --app-border: #e2e8f0;
}

/* Dark mode overrides — triggered by .dark-mode-active class or dark theme */
@media (prefers-color-scheme: dark) {
  :root {
    --app-surface: #1a1b2e;
    --app-text: #e2e8f0;
    --app-border: #4a4d6b;
  }
}

/* Using the variables in component styles */
._retool-container {
  background-color: var(--app-surface) !important;
  color: var(--app-text) !important;
  border-color: var(--app-border) !important;
}
```

**Expected result:** Color values automatically adapt between light and dark modes without hardcoded hex values in component rules.

## Complete code example

File: `App Settings → Custom CSS (dark mode toggle)`

```css
/* ========================================
   Retool Dark Mode — Toggle Implementation
   Requires: darkMode temporary state variable
   CSS class: dark-mode-active on wrapper element
   ======================================== */

/* Light mode defaults (CSS custom properties) */
:root {
  --app-bg: #f9fafb;
  --app-surface: #ffffff;
  --app-text: #111827;
  --app-text-muted: #6b7280;
  --app-border: #e5e7eb;
  --app-accent: #7C3AED;
}

/* Dark mode overrides */
.dark-mode-active {
  --app-bg: #0f172a;
  --app-surface: #1e293b;
  --app-text: #f1f5f9;
  --app-text-muted: #94a3b8;
  --app-border: #334155;
  --app-accent: #a78bfa;
}

/* Apply to app body */
._retool-app-body {
  background-color: var(--app-bg) !important;
  color: var(--app-text) !important;
}

/* Tables */
._retool-table {
  background-color: var(--app-surface) !important;
  border-color: var(--app-border) !important;
}

._retool-table thead tr {
  background-color: var(--app-bg) !important;
}

._retool-table tbody tr:hover {
  background-color: var(--app-border) !important;
}

/* Text components */
._retool-text {
  color: var(--app-text) !important;
}

/* Inputs */
._retool-textInput input {
  background-color: var(--app-surface) !important;
  color: var(--app-text) !important;
  border-color: var(--app-border) !important;
}

/* Containers */
._retool-container {
  background-color: var(--app-surface) !important;
  border-color: var(--app-border) !important;
}
```

## Common mistakes

- **Using setValue() to toggle darkMode and immediately reading darkMode.value to update CSS, getting the old value** — undefined Fix: setValue() is async. Pass the new value directly to subsequent logic rather than re-reading: const newVal = !darkMode.value; await darkMode.setValue(newVal); — then use newVal locally.
- **Applying dark mode via a theme preset but still having hardcoded white backgrounds in custom CSS that override the dark theme** — undefined Fix: Audit your Custom CSS for any hardcoded light colors (background: #fff, color: #000) and replace them with CSS custom properties that respond to the dark mode class.
- **Forgetting that !important is needed even in dark mode CSS overrides** — undefined Fix: CSS rules targeting ._retool-* selectors still need !important in dark mode, just as in light mode. Retool's component styles have high specificity regardless of theme.

## Best practices

- Use Retool's built-in Dark theme preset for the simplest dark mode — it handles all component colors automatically
- When building a user-togglable dark mode, store the preference in localStorage so it persists across sessions
- Use CSS custom properties (variables) for color values so light/dark switching is a one-line change per token
- Remember that setValue() is async — don't read darkMode.value immediately after calling await darkMode.setValue()
- Test dark mode with images and charts — they often need separate treatment (images: filter: brightness(0.9); charts: may need custom theme colors)
- Add re-inversion rules for any image or video elements if using the CSS filter dark mode hack
- Don't hardcode hex colors in component CSS rules — always use CSS custom properties that you can override for dark mode

## Frequently asked questions

### Can users set their own dark mode preference in Retool without me building a toggle?

Yes — Retool users can set their personal display preference in their Personal Settings (click profile avatar → Personal Settings → Appearance). This controls the Retool editor and app display for their account only. However, this only works for logged-in Retool users; external users on public apps cannot change this setting.

### Does Retool's dark theme work with custom CSS I've already written?

Partially. Retool's built-in dark theme switches its own component colors, but your custom CSS rules with hardcoded hex values will still apply as written. Audit your Custom CSS for light-color hardcodes (e.g., background-color: white !important) and replace them with CSS custom properties that you can override for dark mode.

### Is there a way to automatically detect the user's OS dark mode preference in Retool?

Yes, using the CSS @media (prefers-color-scheme: dark) query in your Custom CSS. Add your dark mode overrides inside this media query and they'll activate automatically based on the user's OS setting — no toggle needed. Combine with JavaScript window.matchMedia('(prefers-color-scheme: dark)').matches to initialize the darkMode state variable.

---

Source: https://www.rapidevelopers.com/retool-tutorial/how-to-enable-dark-mode-in-retool
© RapidDev — https://www.rapidevelopers.com/retool-tutorial/how-to-enable-dark-mode-in-retool
