> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sprig.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Enabling Dark Mode

**Sprig In-Product Studies can follow the respondent's system light or dark appearance automatically. This page covers how dark mode works, how it interacts with your custom CSS, and the patterns we recommend for keeping a study readable in both modes.**

<Note>
  **Availability:** Default CSS dark mode shipped in Web SDK **v2.49.0**, and studies rendered in a mobile SDK need a version built against it: see [Minimum SDK versions](#minimum-sdk-versions) below.

  Dark mode is **enabled per Sprig Product** and is not on by default. Reach out to your Customer Success Manager or [support@sprig.com](mailto:support@sprig.com) to have it turned on. **A self-serve setting in the dashboard is coming soon!**
</Note>

### How it works

Sprig loads a second stylesheet that responds to the respondent's operating system or browser appearance setting via the standard CSS media query:

```css theme={null}
@media (prefers-color-scheme: dark) {
  /* Sprig's dark palette */
}
```

There is no separate "dark theme" for you to configure and no toggle inside the survey. If a respondent's device or browser is set to dark, the study renders dark; if it's set to light, the study renders light. The switch is instant and happens on the respondent's device/browser.

Sprig's styles are built on **CSS custom properties** (variables). The light values are declared once, and the dark stylesheet re-declares the same variable names with dark values. Every Sprig selector reads from those variables, so changing one variable updates every place that color is used.

```css theme={null}
/* Light (always loaded) */
html {
  --background-color: #fff;
  --text-color: #2f2e2c;
}

/* Dark */
@media (prefers-color-scheme: dark) {
  :root {
    --background-color: #1c1c1e;
    --text-color: #fff;
  }
}
```

### Load order and specificity

How the CSS is applied:

1. **Your custom CSS is injected after Sprig's stylesheets.** At equal specificity, your rules win. You do not need `!important` for ordinary overrides.
2. **Sprig's dark values are declared on `:root`, not `html`.** `:root` has higher specificity than `html`, so a variable you set on `html` will apply in light mode but be overridden by Sprig in dark mode.

<Note>
  **The commented reference block in the CSS editor is light-mode only.** The default CSS shown in Settings > Surveys > Look & Feel > CSS is a snapshot of Sprig's selectors with hardcoded light colors. It does not list the CSS variables or the dark palette. Use the [variable reference](#css-variable-reference) below instead when you're working on dark mode.
</Note>

### Choosing an approach

| Approach                        | Best when                                                                                | Effort |
| :------------------------------ | :--------------------------------------------------------------------------------------- | :----- |
| **Do nothing**                  | Your customizations are structural (spacing, radius, font, layout) and don't set colors. | None   |
| **Override selectors directly** | You want plain CSS with no variables, or you're only restyling a few elements.           | Low    |
| **Override Sprig's variables**  | You want Sprig's dark behavior, just in your brand colors.                               | Low    |
| **Define your own tokens**      | You have a real design system with light/dark pairs to mirror.                           | Medium |

<Note>
  **You don't have to use CSS variables.** Variables are a convenience: because every Sprig selector reads from them, changing one value updates everywhere that color is used. But a plain `@media (prefers-color-scheme: dark)` block with ordinary selector overrides works exactly as well, and is often clearer if you're only touching a handful of elements. Use whichever fits your stylesheet.
</Note>

<br />

#### Approach 1: Do nothing (color-free customizations)

If your custom CSS only changes layout, spacing, typography, or border radius, it already works in both modes. Sprig's colors keep flowing through the variables.

```css theme={null}
/* Safe in both light and dark: no color declarations */
.ul-card__container--desktop {
  border-radius: 12px;
  box-shadow: none;
  padding: 24px;
}

.ul-question {
  font-family: 'Inter', sans-serif;
  font-size: 20px;
  font-weight: 700;
}
```

<Warning>
  **Any hardcoded color you add is a dark mode bug waiting to happen.** A rule like `.ul-question { color: #1f2937; }` applies in both modes, which means near-black text on Sprig's near-black dark card. If you set a foreground color, set the matching background color too, or move both into variables.
</Warning>

<br />

#### Approach 2: Override selectors directly

No variables involved. Write your light-mode rules as usual, then re-declare whatever needs to change inside a `@media (prefers-color-scheme: dark)` block. Your custom CSS is injected after Sprig's, so at equal specificity your rules win in both modes.

```css theme={null}
/* Applies in both modes */
.ul-card__container {
  border-radius: 12px;
}

.ul-question {
  color: #1f2937;
}

.ul-card-text__button {
  background-color: #0075de;
  color: #ffffff;
}

/* Dark only */
@media (prefers-color-scheme: dark) {
  .ul-card__container {
    background: #252525;
    border-width: 0;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  }

  .ul-question {
    color: #ffffff;
  }

  .ul-caption {
    color: #9b9a97;
  }

  .ul-card-text__button {
    background-color: #0075de;
    color: #ffffff;
  }
}
```

<Warning>
  **Direct overrides don't propagate.** A variable you change is picked up everywhere Sprig uses it; a selector you override only affects that selector. If you restyle `.ul-question` for dark mode, you still need to handle `.ul-caption`, `.choice`, the number rows, and anything else you've given a color to. Step through every question type in the preview before saving.
</Warning>

<br />

#### Approach 3: Override Sprig's variables

The lowest-effort way to brand both modes. Set Sprig's own variable names on `:root` for light, then re-declare the ones that need to change inside a dark media query. Every Sprig selector picks the new values up automatically.

```css theme={null}
/* Light */
:root {
  --text-color: #1f2937;
  --text-color-light: #4b5563;
  --background-color: #ffffff;
  --selection-background-color: #f3f0ff;
  --selection-border-color: #7c5cff;
}

/* Dark */
@media (prefers-color-scheme: dark) {
  :root {
    --text-color: #f5f5f7;
    --text-color-light: #a1a1aa;
    --background-color: #16161a;
    --selection-background-color: #2a2440;
    --selection-border-color: #a78bfa;
  }
}
```

<br />

#### Approach 4: Define your own tokens

If your company already maintains light/dark color pairs, declare them all up front, point a set of "active" tokens at the light values, and remap only the active tokens in the dark block. Your selectors then reference the active tokens and never need duplicating.

```css theme={null}
:root {
  /* Full palette: both modes declared once */
  --brand-text-light: #000000;
  --brand-text-dark: #ffffff;
  --brand-text-inverse-light: #ffffff;
  --brand-text-inverse-dark: #000000;
  --brand-button-light: #820076;
  --brand-button-dark: #f0a0ff;

  /* Active tokens default to light */
  --brand-text: var(--brand-text-light);
  --brand-text-inverse: var(--brand-text-inverse-light);
  --brand-button: var(--brand-button-light);
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Remap only the active tokens */
    --brand-text: var(--brand-text-dark);
    --brand-text-inverse: var(--brand-text-inverse-dark);
    --brand-button: var(--brand-button-dark);
  }
}

/* Selectors are written once and work in both modes */
.ul-question,
.likert-number {
  color: var(--brand-text);
}

.ul-card-text__button {
  background: var(--brand-button);
  color: var(--brand-text-inverse);
  border-radius: 20px;
  padding: 12px 40px;
  font-weight: 600;
}
```

### CSS variable reference

These are the variables Sprig's stylesheets read from, with the default value in each mode. Override any of them on `:root`.

<Note>
  This table reflects Web SDK v2.49.0. Sprig may add variables in later versions; anything not listed here should be overridden by selector instead.
</Note>

**Core surfaces and text**

| Variable                    | Light     | Dark      |
| :-------------------------- | :-------- | :-------- |
| `--background-color`        | `#fff`    | `#1c1c1e` |
| `--background-color-darker` | `#faf9f8` | `#2c2c2e` |
| `--text-color`              | `#2f2e2c` | `#fff`    |
| `--text-color-light`        | `#575653` | `#8f8f8f` |
| `--text-color-lighter`      | `#858481` | `#a8a8a8` |
| `--border-color`            | `#ececec` | `#3a3a3c` |
| `--border-color-heavy`      | `#ebebeb` | `#2c2c2e` |

**Buttons and progress**

| Variable                            | Light                 | Dark                        |
| :---------------------------------- | :-------------------- | :-------------------------- |
| `--button-disabled-background`      | `#f2f2f2`             | `#2c2c2e`                   |
| `--button-disabled-text-color`      | `#a8a8a8`             | `#8f8f8f`                   |
| `--progress-bar-background`         | `rgba(0, 0, 0, 0.1)`  | `rgba(255, 255, 255, 0.1)`  |
| `--prototype-button-background`     | `rgba(0, 0, 0, 0.01)` | `rgba(255, 255, 255, 0.01)` |
| `--thank-you-link-background`       | `rgba(0, 0, 0, 0.01)` | `rgba(255, 255, 255, 0.01)` |
| `--thank-you-link-background-hover` | `rgba(0, 0, 0, 0.03)` | `rgba(255, 255, 255, 0.03)` |

**Choice and selection elements**

| Variable                               | Light     | Dark      |
| :------------------------------------- | :-------- | :-------- |
| `--selection-border-color`             | `#c2c2c2` | `#3a3a3c` |
| `--selection-background-color`         | `#f2f2f2` | `#2c2c2e` |
| `--selection-background-color-hover`   | `#e6e6e6` | `#3a3a3c` |
| `--selection-indicator-color`          | `#a8a8a8` | `#8f8f8f` |
| `--selection-indicator-color-selected` | `#000`    | `#fff`    |
| `--select-border`                      | `#e6e6e6` | `#3a3a3c` |
| `--select-border-hover`                | `#dddcd9` | `#4a4a4c` |
| `--select-background`                  | `#fff`    | `#1c1c1e` |
| `--select-background-hover`            | `#f2f2f2` | `#2c2c2e` |

**Question-type specific**

| Variable                          | Light         | Dark      |
| :-------------------------------- | :------------ | :-------- |
| `--matrix-header-background`      | `#f7f7f7`     | `#121212` |
| `--nps-unselected`                | `#fcfcfc`     | `#2c2c2e` |
| `--nps-unselected-border`         | `#e6e6e6`     | `#3a3a3c` |
| `--nps-selected-color-low`        | `#b06d6d`     | `#f44336` |
| `--nps-selected-color-high`       | `#95a779`     | `#4caf50` |
| `--likert-symbol-color`           | `transparent` | `#8f8f8f` |
| `--rank-order-background`         | `#f2f2f2`     | `#2c2c2e` |
| `--rank-order-border`             | `#e6e6e6`     | `#3a3a3c` |
| `--rank-order-border-hover`       | `#d9d9d9`     | `#4a4a4c` |
| `--rank-order-number-background`  | `#fff`        | `#1c1c1e` |
| `--record-task-background`        | `#f2f2f2`     | `#2c2c2e` |
| `--record-task-border`            | `#e6e6e6`     | `#3a3a3c` |
| `--record-task-border-hover`      | `#d9d9d9`     | `#4a4a4c` |
| `--record-task-number-background` | `#fff`        | `#1c1c1e` |
| `--voice-video-background`        | `#f2f2f2`     | `#2c2c2e` |
| `--voice-video-border`            | `#e6e6e6`     | `#3a3a3c` |
| `--voice-video-border-hover`      | `#d9d9d9`     | `#4a4a4c` |
| `--voice-video-icon-color`        | `#a8a8a8`     | `#8f8f8f` |
| `--sprig-brand-color`             | `#000000`     | `#828183` |

### Conversational UI

Conversational studies load an additional dark stylesheet with its own variables for the chat transcript and inputs. The most commonly overridden:

| Variable                   | Controls                             |
| :------------------------- | :----------------------------------- |
| `--chat-right-bg`          | Respondent message bubble background |
| `--chat-left-bg`           | Sprig message bubble background      |
| `--chat-msg-no-response`   | Skipped/unanswered message text      |
| `--chat-end-text`          | End-of-conversation text             |
| `--chat-footer`            | Footer text                          |
| `--chat-choice-bg`         | Choice option background             |
| `--text-card-input-bg`     | Open text input background           |
| `--text-card-border`       | Open text input border               |
| `--select-checkbox-bg`     | Checkbox fill                        |
| `--select-checkbox-border` | Checkbox border                      |
| `--select-radio-bg`        | Radio button fill                    |
| `--consent-input-bg`       | Consent card name input background   |

Override these the same way, inside a `@media (prefers-color-scheme: dark)` block on `:root`.

### Common issues

<AccordionGroup>
  <Accordion title="My colors work in light mode but not dark mode">
    You most likely declared your variables on `html` instead of `:root`. Sprig's dark values are on `:root`, which has higher specificity, so it wins regardless of source order. Change your selector to `:root`.
  </Accordion>

  <Accordion title="Text is unreadable (e.g. dark text on a dark card)">
    You have a hardcoded foreground color with no matching background, or vice versa. Search your CSS for any literal hex value on a `color`, `background`, or `border-color` property and either move it into a variable pair or wrap the dark variant in a `@media (prefers-color-scheme: dark)` block.
  </Accordion>

  <Accordion title="A white seam or flash appears around the card in dark mode">
    Something in the card's ancestry still has a light background. Reset the wrappers to transparent so the host page shows through:

    ```css theme={null}
    html,
    body,
    #ul-app,
    .ul-app--overlay,
    .ul-app--visible,
    .ul-app__container {
      background: transparent !important;
    }
    ```
  </Accordion>

  <Accordion title="An icon or chevron is invisible in dark mode">
    Some Sprig icons are inline SVG data URIs with a fixed stroke color. Sprig's dark stylesheet replaces the affected ones, but if you've overridden a background shorthand you may have wiped the replacement. Set `background-color` rather than `background`, or re-declare the `background-image` in your dark block.
  </Accordion>

  <Accordion title="Dark mode isn't activating at all">
    Confirm three things: dark mode has been enabled for your Sprig Product (it is off by default; contact your Customer Success Manager or [support@sprig.com](mailto:support@sprig.com)), you're on Web SDK v2.49.0 or later or a mobile SDK version built against it (see [Minimum SDK versions](#minimum-sdk-versions)), and the test device's OS appearance is actually set to Dark.

    Enablement is per **Product**, not per environment: a Product's development and production environments share one setting. If dark mode works in one place and not another, check whether you're comparing two different Products rather than two environments of the same one. The in-editor preview reflects your browser's current appearance setting.
  </Accordion>
</AccordionGroup>

### Testing your CSS

Because custom CSS applies to **all studies in a product, including active ones**, test both modes before saving to production.

1. Work in a development environment first, then copy the finished CSS into production.
2. Toggle your OS appearance (macOS: System Settings > Appearance; Windows: Settings > Personalization > Colors) and re-check the preview panel.
3. In Chrome DevTools you can force the media query without changing your OS: open the Command Menu (`Cmd/Ctrl + Shift + P`), run **Show Rendering**, and set **Emulate CSS media feature prefers-color-scheme**.
4. Step through every question type in the preview, not just the first card. Matrix, NPS, Rank Order, and Open Text have the most mode-specific styling.
5. Check contrast. Brand colors tuned for a white background are frequently too dark on `#1c1c1e`; most design systems ship a lightened variant for dark surfaces for exactly this reason.

### Mobile SDKs

Studies rendered in the iOS, Android, React Native, and Flutter SDKs use the same web stylesheets inside a web view, so everything on this page applies there as well.

Native light/dark support (the SDK letting the web view render dark at all) landed earlier than the default CSS work, which is why some customers were already shipping their own dark mode CSS before v2.49.0. The mobile SDKs also expose APIs to force a study into light or dark regardless of the device setting — see the [SDK changelogs](/docs/developer-center/sdk-changelog/index).

#### Minimum SDK versions

These are the first mobile SDK releases built against Web SDK v2.49.0, and therefore the first to include the default CSS dark mode styles.

| SDK                  | Minimum version |
| :------------------- | :-------------- |
| Web                  | v2.49.0         |
| iOS                  | v4.33.1         |
| Android              | v2.27.2         |
| React Native         | v4.1.1          |
| Flutter              | v0.9.1          |
| Segment iOS          | v1.10.1         |
| Segment Android      | v1.10.1         |
| Segment React Native | v0.9.1          |

<br />

For the full list of selectors and non-color customization options, see [Styling Studies with Custom CSS](/docs/developer-center/styling/styling-your-studies-with-custom-css).
