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

# Theming

> Match the Insito modal to your brand with one of four presets or any subset of design tokens.

The SDK ships four themes out of the box — `light`, `dark`,
`minimal`, `rounded` — and a deep token system so you can override
anything down to the corner radius of a button.

<Note>
  You can also style surveys without touching code using the
  [Brand Kit](/dashboard/brand-kit) in the dashboard. The SDK applies the
  Brand Kit on top of the base preset, then applies any `init()` overrides on
  top of that — so **code always wins over the dashboard**:

  `base preset` → `Brand Kit (dashboard)` → `init() overrides`
</Note>

<Note>
  Theming controls colours, type, and shape. To change the modal's **text**
  (buttons, placeholders, NPS anchors, progress counter), see
  [Labels](/sdk/react-native/labels).
</Note>

## Pick a preset

```tsx theme={null}
MicroSurvey.init({
  apiKey: "proj_xxx",
  theme: { preset: "rounded" },
});
```

| Preset            | When to use                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------------------ |
| `light` (default) | Clean white surfaces, blue primary. Works against most app palettes.                                   |
| `dark`            | Black surfaces, off-white text. Pair with your app's dark mode.                                        |
| `minimal`         | Hairline borders, neutral grey primary. Great for content-first apps that want the modal to disappear. |
| `rounded`         | Pill buttons and large radii (a distinct primary colour). Consumer / lifestyle apps.                   |

## Override tokens

Pass `overrides` to deep-merge on top of a preset. Only the keys you
provide are changed.

```tsx theme={null}
MicroSurvey.init({
  apiKey: "proj_xxx",
  theme: {
    preset: "light",
    overrides: {
      colors: {
        primary: "#FF6B35",
        primaryText: "#FFFFFF",
      },
      typography: {
        fontFamily: "Inter",
      },
      radii: {
        md: 12,
        pill: 999,
      },
    },
  },
});
```

## Full token map

```ts theme={null}
interface InsitoTheme {
  colors: {
    background: string;       // Modal canvas
    surface: string;          // Card / button surfaces
    overlay: string;          // Backdrop behind the modal (incl. alpha)
    text: string;
    textMuted: string;
    border: string;
    primary: string;          // "Next" / "Submit" buttons, accents
    primaryText: string;      // Text on top of primary
    npsDetractor: string;     // 0–6 bucket color
    npsPassive: string;       // 7–8 bucket color
    npsPromoter: string;      // 9–10 bucket color
  };
  typography: {
    fontFamily?: string;      // System default if unset (see note below)
    questionSize: number;     // Question text
    bodySize: number;         // Body / option text
    labelSize: number;        // Helper text
    fontWeightRegular: "400" | "500";
    fontWeightBold: "500" | "600" | "700";
  };
  spacing: { xs: number; sm: number; md: number; lg: number; xl: number };
  radii: { sm: number; md: number; lg: number; pill: number };
  components: {
    showPoweredBy: boolean;        // "Powered by Insito" badge at bottom
    modalMaxHeightFraction: number; // 0..1, default 0.85
  };
}
```

## Custom fonts

<Warning>
  `typography.fontFamily` (set in code or via the dashboard Brand Kit) only
  applies if the font is **already loaded in your app** — for example through
  [`expo-font`](https://docs.expo.dev/versions/latest/sdk/font/) or a native
  font link. The SDK does not bundle or load fonts. If the named font is not
  loaded, React Native silently falls back to the system font.
</Warning>

```tsx theme={null}
import { useFonts, Inter_400Regular, Inter_600SemiBold } from "@expo-google-fonts/inter";

function Root() {
  const [loaded] = useFonts({ Inter_400Regular, Inter_600SemiBold });
  if (!loaded) return null;
  // "Inter_600SemiBold" / "Inter_400Regular" are now valid fontFamily values.
  return <App />;
}
```

## Dark mode & auto appearance

The dashboard [Brand Kit](/dashboard/brand-kit) has an **Appearance** setting —
`light`, `dark`, or `auto` — delivered to the SDK as `brandConfig.themeMode`.
No code is required: the SDK resolves the right scheme per survey.

* **`auto`** follows the device appearance via React Native's `Appearance`
  API. The provider subscribes to `Appearance.addChangeListener`, so if the
  user flips system dark mode **while a survey is open**, the modal re-themes
  live without closing.

  <Note>
    `Appearance.getColorScheme()` returns your **app's** effective interface
    style, not the raw device toggle. If your app pins itself with Expo's
    [`userInterfaceStyle`](https://docs.expo.dev/versions/latest/config/app/#userinterfacestyle)
    (or a native `overrideUserInterfaceStyle`), `auto` follows that pinned
    style. Use `userInterfaceStyle: "automatic"` to follow the device, or drive
    the survey from your own in-app theme with the
    [`appearance` prop](#follow-your-apps-theme) below.
  </Note>
* **`dark`** forces the SDK `dark` preset as the base; **`light`** keeps your
  configured `init()` preset.
* The Brand Kit ships **two colour palettes** (light and dark). The matching
  one is mapped on top of the resolved base preset; any dark colour left unset
  in the dashboard inherits the built-in `dark` preset token.

Precedence is unchanged — `init()` overrides still win over the Brand Kit:

`resolved scheme preset` → `Brand Kit palette` → `init() overrides`

So a project with `themeMode: "auto"` and no custom dark colours renders a
clean dark survey on dark devices automatically. Surveys with no Brand Kit (or
a kit that predates dark mode) default to **light** — no behaviour change.

## Follow your app's theme

When your app manages light/dark itself (a theme context, a user preference,
etc.) rather than only the OS toggle, `auto`'s device detection can disagree
with what the user sees. Pass your app's current appearance to
`<InsitoProvider appearance>` and `themeMode: "auto"` surveys follow it instead
of the device.

```tsx theme={null}
import { InsitoProvider } from "@insito/react-native";
import { useTheme } from "./theme"; // your app's theme context

function Root() {
  const { isDark } = useTheme();
  return (
    <InsitoProvider appearance={isDark ? "dark" : "light"}>
      <App />
    </InsitoProvider>
  );
}
```

| `appearance`         | Behaviour                                                           |
| -------------------- | ------------------------------------------------------------------- |
| `"system"` (default) | Follow the device via `Appearance.getColorScheme()`, updating live. |
| `"light"` / `"dark"` | Force that scheme — use it to mirror your in-app theme.             |

The prop only affects surveys whose Brand Kit `themeMode` is `auto`; `light` /
`dark` Brand Kits always render their forced scheme. Updating the prop
re-themes an open survey live, and switching back to `"system"` resumes device
following.

<Tip>
  If you instead pin your whole app's native appearance with Expo's
  `userInterfaceStyle`, set it to `"automatic"` so `auto` surveys follow the
  device. A fixed `"dark"` / `"light"` there makes `Appearance.getColorScheme()`
  always report that value.
</Tip>

## Reading the resolved theme

If you want to extend Insito's color system into the rest of your
app (matching button colors, e.g.), read `MicroSurvey.theme` after
`init`:

```tsx theme={null}
const theme = MicroSurvey.theme;
console.log(theme.colors.primary);
// "#FF6B35"
```

Or use the standalone helper for preview / Storybook scenarios:

```tsx theme={null}
import { resolveTheme } from "@insito/react-native";

const previewTheme = resolveTheme("rounded", {
  colors: { primary: "#FF6B35" },
});
```

## Branding rules

* `components.showPoweredBy` toggles the "Powered by Insito" badge. Resolution
  is: a per-survey `showBranding` value from the server (when present) wins,
  otherwise the theme token applies (the `minimal` preset defaults it to
  `false`). Plan-based branding enforcement happens server-side / in the
  dashboard, not as a client hard rule.
* Keep colors WCAG AA-compliant — primary on background should hit
  3:1 contrast minimum for usability. The default presets already
  pass.
* `modalMaxHeightFraction` (default `0.85`) sets the sheet's max height as a
  fraction of the screen. Keep it roughly within `0.5`–`0.95` for a usable
  sheet; the value is applied as-is (not clamped by the SDK).

## Custom rendering (advanced)

If the built-in bottom sheet isn't enough — say you want an inline survey
embedded inside a screen — you can render your own UI:

1. Subscribe to `useInsito()` to read the `activeSurvey` reactively.
2. When `activeSurvey != null`, render your custom UI from
   `activeSurvey.questions`.
3. Call `MicroSurvey.submitResponse(activeSurvey.surveyId, answers)`
   when the user submits.

Note: `<InsitoProvider>` is still required for the legacy modal
fallback. We're tracking \[INS-…] for a "headless mode" that drops the
provider entirely.
