# Framework Integration

<WebSDK2 />

Every Incode UI module ships as a [standard Web Component](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) — registered via a side-effect import (`import '@incodetech/web/<module>'`) and consumed like any HTML element. Because the elements are framework-agnostic, the same SDK code runs from vanilla HTML, React, Angular, Vue, or any other web framework. The only difference is how each framework lets you mount custom elements and bind non-string properties.

This page documents the four mainstream paths. All of them share one rule:

> **Set `config`, `onFinish`, and `onError` as JavaScript properties on the element**, not as HTML attributes. They are object/function values, not strings, so they can't go through `setAttribute`. Each framework section below shows how to do that idiomatically.

## Vanilla HTML / TypeScript

```html
<incode-flow id="flow"></incode-flow>

<script type="module">
  import { setup } from '@incodetech/core';
  import '@incodetech/web/flow';
  import '@incodetech/web/flow/styles.css';

  await setup({ apiURL: 'https://demo-api.incodesmile.com/0', token: session.token }); // Use /0 if called from UI.

  const flow = document.getElementById('flow');
  flow.config = { token: session.token };
  flow.onFinish = (result) => {
    /* ... */
  };
</script>
```

The same shape applies to every other module — replace `flow` with `phone`, `selfie`, `id`, etc., and set the corresponding config / handlers on the element.

## React

### Universal pattern (works on React 16.8+)

Use the custom element directly with a ref. React renders custom elements as-is, and you assign non-string properties imperatively in `useEffect`. This works on every React version since hooks landed:

```typescript
import { useEffect, useRef } from 'react';
import { setup } from '@incodetech/core';
import type { FlowConfig } from '@incodetech/web/flow';
import type { FinishStatus } from '@incodetech/core/flow';
import '@incodetech/web/flow';
import '@incodetech/web/flow/styles.css';

type FlowElement = HTMLElement & {
  config: FlowConfig;
  onFinish: (result?: FinishStatus) => void;
};

export function MyFlow({ token }: { token: string }) {
  const ref = useRef<FlowElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    el.config = { token };
    el.onFinish = (result) => console.log('Done:', result);
  }, [token]);

  return <incode-flow ref={ref} style={{ display: 'block', height: '100vh' }} />;
}
```

### React 19+ shortcut

React 19 ships native custom-element support: it sets non-string JSX props as DOM properties and bridges `on*` props through `addEventListener`. If you're on React 19 you can drop the ref + `useEffect` ceremony entirely:

```typescript
import { setup } from '@incodetech/core';
import '@incodetech/web/flow';
import '@incodetech/web/flow/styles.css';

export function MyFlow({ token }: { token: string }) {
  return (
    <incode-flow
      config={{ token }}
      onFinish={(result) => console.log('Done:', result)}
      onError={(error, code) => console.error(error, code)}
      style={{ display: 'block', height: '100vh' }}
    />
  );
}
```

This shortcut **does not work on React 18 or earlier** — those versions serialize JSX props through `setAttribute`, which coerces `config={{token}}` to the string `"[object Object]"`. Use the universal pattern above unless you're certain every consumer of your code is on React 19+.

### TypeScript: JSX support for `incode-*` tags

**React 19+** treats unknown lowercase tags as HTML elements automatically — no extra TypeScript setup is needed.

**React 18 and earlier** strict-checks `JSX.IntrinsicElements`, so referencing `<incode-flow>` (or any other Incode tag) directly in JSX errors out. Add a one-time augmentation somewhere in your project (e.g. `src/types/incode.d.ts`) that covers every `incode-*` tag your app uses:

```ts
// src/types/incode.d.ts
import 'react';

type IncodeElementAttrs = React.DetailedHTMLProps<
  React.HTMLAttributes<HTMLElement>,
  HTMLElement
>;

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'incode-flow': IncodeElementAttrs;
      'incode-flow-completed': IncodeElementAttrs;
      'incode-selfie': IncodeElementAttrs;
      'incode-id': IncodeElementAttrs;
      'incode-phone': IncodeElementAttrs;
      'incode-email': IncodeElementAttrs;
      'incode-document-capture': IncodeElementAttrs;
      'incode-face-match': IncodeElementAttrs;
      'incode-consent': IncodeElementAttrs;
      'incode-geolocation': IncodeElementAttrs;
      'incode-antifraud': IncodeElementAttrs;
      'incode-curp-validation': IncodeElementAttrs;
      'incode-identity-reuse': IncodeElementAttrs;
      'incode-redirect-to-mobile': IncodeElementAttrs;
      'incode-signature': IncodeElementAttrs;
      'incode-electronic-signature': IncodeElementAttrs;
      'incode-ae-signature': IncodeElementAttrs;
      'incode-qe-signature': IncodeElementAttrs;
      'incode-ekyc': IncodeElementAttrs;
      'incode-ekyb': IncodeElementAttrs;
      'incode-workflow': IncodeElementAttrs;
      'incode-cross-document-data-match': IncodeElementAttrs;
      // Add any other incode-* tags you mount; the shape is identical.
    }
  }
}
```

The element-specific properties (`config`, `onFinish`, `onError`) are set imperatively through the ref — that's why `IncodeElementAttrs` is just the standard HTML element shape. The full per-tag catalog lives in [Web Components](doc:web-sdk-2-web-components).

## Angular

Due to Angular's security mechanisms, properties starting with `on*` cannot be used in one-way binding. To circumvent this issue, create a directive that bypasses the mechanism:

```

@Directive({
  selector: '[libWebProps]',
})
export class WebPropsDirective {
  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
  readonly libWebProps = input.required<Record<string, unknown>>();

  constructor() {
    effect(() => {
      const target = this.el.nativeElement as unknown as Record<string, unknown>;
      for (const [key, value] of Object.entries(this.libWebProps())) {
        target[key] = value;
      }
    });
  }
}
```

Add `CUSTOM_ELEMENTS_SCHEMA` to the module that uses Incode tags:

```typescript
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  declarations: [MyFlowComponent, WebPropsDirective],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
```

This works for standalone components as well:

```
@Component({
  imports: [RouterModule, WebPropsDirective],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  selector: 'app-flow',
  templateUrl: './app-flow.html',
  styleUrl: './app-flow.css',
})
export class MyFlowComponent {
	readonly flowHandlers = {
    onFinish: (event: unknown) => {
			//Use on finish event as needed
      console.log('Flow finished:', event);
    },
    onError: (event: { message?: string } | undefined) => {
			//Use on error event as needed
      console.error('Flow error:', event);
    },
  };
}
```

Then bind to the element's properties from a template using property binding:

```html
<incode-flow
  [config]="flowConfig"
  [libWebProps]="flowHandlers"
></incode-flow>
```

## Vue 3

Tell the template compiler to treat `incode-*` tags as custom elements:

```typescript
// vite.config.ts
import vue from '@vitejs/plugin-vue';

vue({
  template: {
    compilerOptions: {
      isCustomElement: (tag) => tag.startsWith('incode-'),
    },
  },
});
```

Then bind properties with `.prop` modifiers:

```vue
<incode-flow :config.prop="flowConfig" :onFinish.prop="handleFinish" />
```

## See also

* [Getting Started](doc:web-sdk-2-getting-started): three integration paths walked through end to end
* [IncodeFlow Component](doc:web-sdk-2-incodeflow-component): the all-in-one component reference
* [Web Components](doc:web-sdk-2-web-components): full tag catalog
* [Individual Modules](doc:web-sdk-2-individual-modules): per-module reference pages
* [Troubleshooting → Build / TypeScript issues](doc:web-sdk-2-troubleshooting#build--typescript-issues): common JSX errors and fixes
