Theming & Styling

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

📘

This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation here. Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade.

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.

Quick Start

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

/* 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:

: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:

: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:

: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:

: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:

: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:

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

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

/* 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:

: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:

: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:

<!-- 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:

.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:

/* 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.

ComponentRoot classNotable child classes
Button.IncodeButton.IncodeButtonPrimary / Secondary / Link, .IncodeButtonL / M / S, .IncodeButtonLoading
Input.IncodeInputState reflected via [aria-invalid], :disabled, [type='date']
Typography.IncodeTypographyH1H5, .IncodeTypographyPNested under .IncodeComponent
Flow start screen.IncodeFlowStart.IncodeFlowStartSubtitle, .IncodeFlowStartLogo
Flow completed screen.IncodeFlowCompleted.IncodeFlowCompletedTitle, .IncodeFlowCompletedSubtitle
Spinner.IncodeSpinnerSee 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 }):

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,
});
OptionTypeDescription
logoSrcstringReplaces the default Incode logo.
logoHeightstringSets the rendered height of logoSrc (a CSS length, e.g. '32px').
subtitlestringReplaces the default subtitle text under the logo.
hideHeaderbooleanHides the default header entirely.
hideFooterBrandingbooleanHides 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) 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 above. The loading spinner displayed during flow initialization, module loading, and transitions exposes four tokens:

: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:

SizeIcon Dimension
small24px
medium48px (default)
large64px

CSS Class Targeting

You can also target spinner classes directly:

/* 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.

CategoryExamples
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}

Did this page help you?