# Theming & Styling

:::note
This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation [here](/sdk-reference/web-sdk-reference).  Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade. <br /><br />Full rollout to all clients still TBD.
:::

The Incode Web SDK uses CSS custom properties (variables) for theming, making it easy to match your brand.

> To replace a built-in branded illustration or animation (tutorial graphics, success/fail screens) instead of recoloring it, see [Asset Overrides](/sdk-reference/web-sdk-2-asset-overrides/).

## Quick Start

Import the base tokens and override the variables you want to customize:

```css
/* Import the SDK theme (defines all CSS variables you can override) */
@import '@incodetech/web/themes/light.css';

/* Override with your brand color — easiest path */
:root {
  --primitive-color-brand-500: #0066cc;
  --primitive-color-brand-400: #1f7fdf; /* slightly lighter, used on hover */
  --primitive-color-brand-600: #0052a3; /* slightly darker, used on press */
}
```

> **Note:** The SDK ships two themes — `@incodetech/web/themes/light.css` and `@incodetech/web/themes/dark.css`. Pick one as your base, then override variables to match your brand. Component layout/structure CSS lives separately in `@incodetech/web/base.css` and per-module CSS such as `@incodetech/web/flow/styles.css`. The full token catalog is documented below.

## Token Hierarchy

The SDK uses a layered token system:

```
Primitives → Semantic → Component-specific
```

Each layer references the layer above. Most overrides target the primitive or component layer; touching the semantic layer cascades broadly so use it intentionally.

### 1. Primitive Tokens

Concrete values that nothing else customizes:

```css
:root {
  /* Color scales (50, 100, 200, ..., 900, 1000) for: gray, brand, brand-secondary, positive, warning, negative */
  --primitive-color-gray-0: #ffffff;
  --primitive-color-gray-500: #6b7280;
  --primitive-color-gray-900: #1f2937;
  --primitive-color-brand-500: #0066cc;

  /* Spacing scale (--primitive-scale-*) and the friendly --spacing-* aliases */
  --primitive-scale-4: 4px;
  --spacing-4: var(--primitive-scale-4);
  --spacing-16: var(--primitive-scale-16);

  /* Typography */
  --primitive-typography-family-rethink-sans: 'Rethink Sans';
  --primitive-typography-size-16: 16px;
  --primitive-typography-weight-regular: 400;
  --primitive-typography-weight-medium: 500;

  /* Border radius scale (--primitive-border-radius-0/4/8/16/24/40/64/9999) and aliases */
  --border-radius-medium: var(--primitive-border-radius-16);
  --border-radius-full: var(--primitive-border-radius-9999);
}
```

### 2. Semantic Tokens

Purpose-based names that reference primitives. Different values resolve in `@incodetech/web/themes/light.css` vs. `dark.css`, so overriding a semantic token affects whichever theme you're on:

```css
:root {
  /* Surfaces — backgrounds */
  --surface-neutral-0: var(--primitive-color-gray-0); /* base background */
  --surface-neutral-50: var(
    --primitive-color-gray-50
  ); /* subtle elevated surface */
  --surface-neutral-900: var(--primitive-color-gray-900); /* highest contrast */
  --surface-brand-500: var(--primitive-color-brand-500); /* brand fill */
  --surface-status-negative-500: var(--primitive-color-negative-500);
  --surface-status-warning-500: var(--primitive-color-warning-500);
  --surface-status-positive-500: var(--primitive-color-positive-500);

  /* Text — foreground colors */
  --text-body-800-primary: var(
    --primitive-color-gray-800
  ); /* primary body text */
  --text-body-500-secondary: var(
    --primitive-color-gray-500
  ); /* secondary / muted */
  --text-body-400: var(--primitive-color-gray-400); /* placeholders, disabled */
  --text-link-default: var(--primitive-color-brand-500);
  --text-link-hover: var(--primitive-color-brand-300);
  --text-accent-brand: var(--primitive-color-brand-500);

  /* Borders */
  --border-neutral-100: var(--primitive-color-gray-100);
  --border-neutral-400: var(--primitive-color-gray-400);
  --border-status-negative: var(--primitive-color-negative-500);
  --border-status-focus: var(--primitive-color-brand-500);
}
```

Many tokens have a `-static` variant that stays fixed across light/dark themes (used for elements that should look identical regardless of theme, like a brand-coloured button). For example `--surface-brand-500-static` does not switch in dark mode the way `--surface-brand-500` might.

### 3. Component Tokens

Specific to individual components. These reference semantic tokens, so overriding a component token gives you targeted control without affecting other components:

```css
:root {
  /* Primary button */
  --button-primary-surface-default: var(--surface-brand-500-static);
  --button-primary-surface-hover: var(
    --surface-brand-400-static
  ); /* lighter on hover */
  --button-primary-surface-pressed: var(
    --surface-brand-600-static
  ); /* darker on press */
  --button-primary-surface-disabled: var(--surface-neutral-100);
  --button-primary-text-default: var(--text-body-0-static);
  --button-primary-border-radius: var(--border-radius-medium);

  /* Inputs */
  --input-surface-default: var(--surface-neutral-50);
  --input-surface-disabled: var(--surface-neutral-100);
  --input-border-default: var(--border-neutral-400);
  --input-border-focused: var(--border-status-focus);
  --input-border-negative: var(--border-status-negative);
  --input-text-field-default: var(--text-body-800-primary);
  --input-text-field-placeholder: var(--text-body-400);

  /* Spinner */
  --spinner-surface-primary: var(--surface-brand-500-static);
  --spinner-surface-secondary: var(--surface-brand-50);
  --spinner-text-title: var(--text-body-800-primary);
  --spinner-text-subtitle: var(--text-body-500-secondary);
}
```

`button-*` tokens come in `primary`, `secondary`, and `tertiary` variants, each with `surface`, `text`, and `border` properties across `default` / `hover` / `pressed` / `disabled` states.

## Common Customizations

### Brand colors

The simplest path is to override the primitive brand scale. Everything semantic (`--surface-brand-*`, `--text-accent-brand`, button colors, focus rings, etc.) inherits automatically:

```css
:root {
  --primitive-color-brand-50: #e6f0ff;
  --primitive-color-brand-100: #cce0ff;
  --primitive-color-brand-300: #4d9aff;
  --primitive-color-brand-400: #1f7fdf; /* button hover */
  --primitive-color-brand-500: #0066cc; /* main brand */
  --primitive-color-brand-600: #0052a3; /* button pressed */
}
```

If you need finer control, override the component tokens directly:

```css
:root {
  --button-primary-surface-default: #0066cc;
  --button-primary-surface-hover: #1f7fdf;
  --button-primary-surface-pressed: #0052a3;
}
```

### Dark mode

The SDK already ships a dark theme — import it instead of rolling your own:

```css
@import '@incodetech/web/themes/dark.css';
```

If you want to switch themes per user preference, gate the import or apply it conditionally:

```css
/* Default to light, switch to dark when the user prefers dark */
@import '@incodetech/web/themes/light.css';

@media (prefers-color-scheme: dark) {
  @import '@incodetech/web/themes/dark.css';
}
```

### Typography

The SDK uses Rethink Sans by default. Override the primitive family if you want a different font everywhere:

```css
:root {
  --primitive-typography-family-rethink-sans:
    'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
```

Sizes (`--primitive-typography-size-12` through `--primitive-typography-size-80`) and weights (`--primitive-typography-weight-regular`, `--primitive-typography-weight-medium`, `--primitive-typography-weight-semibold`, `--primitive-typography-weight-bold`) are also override-able primitive tokens.

### Border radius

The radius scale is `--border-radius-{none,x-small,small,medium,large,x-large,xx-large,full}`. Components reference these via component tokens:

```css
:root {
  /* Round all primary buttons more aggressively */
  --button-primary-border-radius: var(--border-radius-large);

  /* Or change the underlying scale */
  --primitive-border-radius-16: 12px; /* shrinks --border-radius-medium */
}
```

## Container Styling

The SDK elements fill their container. Style the parent for layout control:

```html
<!-- Full viewport height -->
<div style="height: 100vh;">
  <incode-flow></incode-flow>
</div>

<!-- Fixed height modal -->
<div style="height: 600px; width: 400px;">
  <incode-flow></incode-flow>
</div>
```

## Direct CSS Overrides

CSS variables cover color, typography, spacing, and border radius. Some properties — text alignment, for example — have no dedicated token, because most integrations never need to change them. For those, override the component's CSS class directly.

The SDK's web components render without Shadow DOM: every custom element registers with `shadow: false`, so your page's CSS cascades into components exactly as it cascades into any other element on the page. There's no encapsulation boundary to work around.

Every component also renders plain, `Incode`-prefixed CSS classes on its root and key child elements — for example `.IncodeButton`, `.IncodeInput`, `.IncodeFlowCompletedTitle`. These class names are stable across releases, so your stylesheet can target them directly.

### Matching selector specificity

Some classes are flat and override with a single selector:

```css
.IncodeFlowStartSubtitle {
  text-align: right;
}
```

Others are nested under an ancestor, or qualified with a tag name, for extra specificity. Match or exceed that specificity in your override, or fall back to `!important`:

```css
/* Overrides .IncodeComponent .IncodeFlowCompleted h1.IncodeFlowCompletedTitle */
.IncodeComponent .IncodeFlowCompleted h1.IncodeFlowCompletedTitle,
.IncodeComponent .IncodeFlowCompleted p.IncodeFlowCompletedSubtitle {
  text-align: right;
}
```

> **Note:** Some styles switch at a breakpoint. `IncodeFlowCompletedTitle` left-aligns by default and centers above 992px width, for example. Override the base rule and any matching `@media` rule together, so your alignment holds at every screen size.

### Common classes to target

This is a starting point, not a full inventory — inspect the rendered element in your browser's devtools for anything not listed here.

| Component              | Root class             | Notable child classes                                                              |
| ----------------------- | ----------------------- | ----------------------------------------------------------------------------------- |
| Button                  | `.IncodeButton`         | `.IncodeButtonPrimary` / `Secondary` / `Link`, `.IncodeButtonL` / `M` / `S`, `.IncodeButtonLoading` |
| Input                   | `.IncodeInput`          | State reflected via `[aria-invalid]`, `:disabled`, `[type='date']`                   |
| Typography              | `.IncodeTypographyH1`–`H5`, `.IncodeTypographyP` | Nested under `.IncodeComponent`                                    |
| Flow start screen       | `.IncodeFlowStart`      | `.IncodeFlowStartSubtitle`, `.IncodeFlowStartLogo`                                   |
| Flow completed screen   | `.IncodeFlowCompleted`  | `.IncodeFlowCompletedTitle`, `.IncodeFlowCompletedSubtitle`                          |
| Spinner                 | `.IncodeSpinner`        | See [Spinner Customization](#spinner-customization) below                           |

## Global UI Config

CSS variables and class overrides change how components look. `setUiConfig` changes what they show, for branding elements that aren't worth a CSS override — the logo, the subtitle text, and whether the default header or footer renders at all.

Call it directly, or pass the same shape to `setup({ uiConfig })`:

```ts
import { setUiConfig } from '@incodetech/web';

setUiConfig({
  logoSrc: 'https://your-cdn.com/logo.svg',
  logoHeight: '32px',
  subtitle: 'Verify your identity to continue',
  hideHeader: false,
  hideFooterBranding: true,
});
```

| Option               | Type      | Description                                        |
| --------------------- | --------- | --------------------------------------------------- |
| `logoSrc`             | `string`  | Replaces the default Incode logo.                   |
| `logoHeight`          | `string`  | Sets the rendered height of `logoSrc` (a CSS length, e.g. `'32px'`). |
| `subtitle`            | `string`  | Replaces the default subtitle text under the logo.   |
| `hideHeader`          | `boolean` | Hides the default header entirely.                   |
| `hideFooterBranding`  | `boolean` | Hides the default "Powered by Incode" footer branding. |

All options are optional — pass only the ones you want to change.

> **Using `<incode-flow>` or `<incode-workflow>`?** By default, the orchestrator fetches your Incode dashboard's theme once it initializes and applies it with this same call, which replaces the entire config rather than merging into it. That fetch overwrites `logoSrc`, `subtitle`, and `hideFooterBranding` with the dashboard's values, and clears `hideHeader`/`logoHeight` back to unset, even if you set them first. Set `disableDashboardTheme: true` in the flow or workflow config (see [IncodeFlow Component](/sdk-reference/web-sdk-2-incodeflow-component/#properties)) to keep your own `setUiConfig` values, or set the logo and subtitle on the dashboard instead.

## Spinner Customization

This follows the same override pattern described in [Direct CSS Overrides](#direct-css-overrides) above. The loading spinner displayed during flow initialization, module loading, and transitions exposes four tokens:

```css
:root {
  /* Spinner icon colors */
  --spinner-surface-primary: #0066cc; /* foreground arc */
  --spinner-surface-secondary: #e6f0ff; /* track */

  /* Spinner text */
  --spinner-text-title: #1a1a1a;
  --spinner-text-subtitle: #666666;
}
```

The full-screen overlay background uses `--surface-neutral-0` from the active theme — override that primitive (or the corresponding `--primitive-color-gray-0`) if you want a different overlay color, rather than reaching for a dedicated token (there isn't one).

### Spinner Sizes

The spinner component supports three sizes that you can configure via the `spinnerConfig` prop:

| Size     | Icon Dimension |
| -------- | -------------- |
| `small`  | 24px           |
| `medium` | 48px (default) |
| `large`  | 64px           |

### CSS Class Targeting

You can also target spinner classes directly:

```css
/* Main spinner container */
.IncodeSpinner {
  /* your styles */
}

/* Full-screen spinner overlay */
.IncodeSpinnerFullScreen {
  /* your styles */
}

/* Spinner content wrapper */
.IncodeSpinnerContent {
  /* your styles */
}

/* Wrapper around title + subtitle */
.IncodeSpinnerText {
  /* your styles */
}

/* Title and subtitle text */
.IncodeSpinnerTitle {
  /* your styles */
}

.IncodeSpinnerSubtitle {
  /* your styles */
}
```

## Key CSS Variables

A high-level cheat sheet of the categories you'll most often override. The full inventory ships in `@incodetech/web/themes/light.css` and `@incodetech/web/themes/dark.css` — inspect them in your browser's devtools or your bundler's source view to see every variable.

| Category     | Examples                                                                                                                                                            |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Brand**    | `--primitive-color-brand-{50..600}` (override these to re-skin everything)                                                                                          |
| **Surfaces** | `--surface-neutral-{0..1000}`, `--surface-brand-{50,100,500,500-static,600-static}`, `--surface-status-{negative,warning,positive}-{50,500}`                        |
| **Text**     | `--text-body-{0,400,500-secondary,800-primary}`, `--text-link-{default,hover,visited,disabled}`, `--text-accent-brand`, `--text-status-{negative,warning,positive}` |
| **Borders**  | `--border-neutral-{100,400}`, `--border-status-{negative,warning,positive,focus}`                                                                                   |
| **Buttons**  | `--button-{primary,secondary,tertiary}-{surface,text,border}-{default,hover,pressed,disabled}`, `--button-*-border-radius`                                          |
| **Inputs**   | `--input-{surface,border,icon,text-field,text-helper,text-label}-{default,focused,disabled,negative}`                                                               |
| **Spinner**  | `--spinner-surface-{primary,secondary}`, `--spinner-text-{title,subtitle}`                                                                                          |
| **Layout**   | `--spacing-{2..96}`, `--border-radius-{none,x-small,small,medium,large,x-large,xx-large,full}`                                                                      |
| **Type**     | `--primitive-typography-family-*`, `--primitive-typography-size-{12..80}`, `--primitive-typography-weight-{regular,medium,semibold,bold}`                           |
