Getting started

Get credentials, add a widget token route, wrap your app in a provider and render your first embedded component.

This package is the public npm surface for Tesouro embedded React widgets.

@tesouro/embedded-components-react is an ESM package for bundler-based React apps (Vite, webpack, esbuild, Next.js). react and react-dom (v19) are peer dependencies.

These steps put one widget on screen: a token route on your server, a provider in the browser, and the widget itself. The examples use the Next.js App Router.

Using a coding assistant? Point it at llms-full.txt, which has every page of the docs site as Markdown in one file. llms.txt is the index.

Before you start

You need three credentials from Tesouro: a client ID, a client secret and a widget secret. Authentication › User token in the Embedded Banking or FinOps docs explains how to request them. All three stay on your server.

1. Install

npm install @tesouro/embedded-components-react
npm install @tesouro/embedded-components-widget-token

The second package mints widget tokens and handles your secrets, so only your server imports it.

2. Add a token route

A widget acts as one of your users, authenticated by a short-lived widget token that your server mints. This route handler, at app/api/widget-token/route.ts, returns the { widgetToken, exp } shape the provider in step 3 expects:

import { NextRequest, NextResponse } from 'next/server';
import { configureCreateWidgetToken } from '@tesouro/embedded-components-widget-token';

const mintWidgetToken = configureCreateWidgetToken({
  clientId: process.env.TESOURO_CLIENT_ID!,
  clientSecret: process.env.TESOURO_CLIENT_SECRET!,
  widgetSecret: process.env.TESOURO_WIDGET_SECRET!,
});

export async function POST(req: NextRequest) {
  const session = await getSession(req);
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const { widgetToken, exp } = await mintWidgetToken({
    userId: session.userId,
    userEmail: session.userEmail,
    organizationReference: session.organizationReference,
  });

  return NextResponse.json(
    { widgetToken, exp },
    { headers: { 'Cache-Control': 'no-store' } },
  );
}

getSession stands in for your own server-side session check. Take the user and their organizationReference (your stable ID for the business they belong to) from that session, never from the request: the route mints a valid token for whatever identity it's given. Never expose the three credentials to the browser, for example through a NEXT_PUBLIC_ variable.

@tesouro/embedded-components-widget-token documents the other options, including the token lifetime.

Backend not on Node? Build the token yourself: Authentication › User token in the Embedded Banking or FinOps docs specifies its claims and encryption. Your route returns the same { widgetToken, exp }.

3. Add the provider

RefreshingRootWidgetProvider calls your route on mount, fetches a new token shortly before exp, and shares the signed-in scope with every widget inside it. It runs in the browser, so in the App Router it goes in a Client Component that your root layout renders, such as app/providers.tsx. Import the stylesheet there too: widgets render unstyled without it.

'use client';

import '@tesouro/embedded-components-react/styles.css';
import type { ReactNode } from 'react';
import {
  RefreshingRootWidgetProvider,
  type WidgetTokenFetcher,
} from '@tesouro/embedded-components-react';

const fetcher: WidgetTokenFetcher = async () => {
  const res = await fetch('/api/widget-token', { method: 'POST' });
  if (!res.ok) throw new Error('Widget token request failed');
  return res.json();
};

export function Providers({ children }: { children: ReactNode }) {
  return (
    <RefreshingRootWidgetProvider
      baseUrl="https://api.sandbox.tesouro.com"
      fetcher={fetcher}
    >
      {children}
    </RefreshingRootWidgetProvider>
  );
}

Use https://api.sandbox.tesouro.com while you build and https://api.tesouro.com in production.

4. Render a widget

Any widget inside the provider is ready to use:

import { BankAccountsWidget } from '@tesouro/embedded-components-react';

export default function AccountsPage() {
  return <BankAccountsWidget />;
}

Browse the widgets for Embedded Banking or FinOps. Every provider and widget prop is typed in the shipped declarations, so your editor documents them inline.

Entry points

Import pathWhat you get
@tesouro/embedded-components-reactAll widgets, WidgetSuite, and the providers, including RefreshingRootWidgetProvider.
@tesouro/embedded-components-react/styles.cssThe widget stylesheet. See About the stylesheet.
@tesouro/embedded-components-react/coreFramework-agnostic token utilities (createWidgetTokenManager).
@tesouro/embedded-components-react/experimentalWork in progress, with no compatibility promise. See below.

Experimental entry point

@tesouro/embedded-components-react/experimental (and .../experimental/<Module>) holds work in progress. It is outside semver: exports can change or disappear in any release, including a patch, and aren't documented here. Use it only if we point you at a specific module, and pin an exact version.

About the stylesheet

The stylesheet won't restyle your host page. It ships no global CSS reset (the reset is scoped under the .tesouro-embedded wrapper every widget renders), and all utilities are ttw-prefixed, so it won't collide with your own Tailwind. Design tokens (--ttw-*) are declared on :root / .dark. Dark mode follows an ancestor .dark class, and you can set the widget font with --ttw-font-family.

Theming

Widgets read their look from --ttw-* CSS variables. Set them on :root, or on any element around the widgets, in plain CSS or with createTheme.

Basic theming is all most integrations need: your brand color, the corner radius and the font.

import type { CSSProperties } from 'react';
import { createTheme, themeToCss } from '@tesouro/embedded-components-react';

const theme = createTheme({
  primary: '#0f766e',
  primaryForeground: '#ffffff',
  radius: '0.5rem',
  fontFamily: 'Inter, sans-serif',
});

// On a wrapper around the widgets…
<div style={theme as CSSProperties}>{/* widgets */}</div>;
// …or once for the page: themeToCss(theme) prints a `:root { … }` rule.

A wrapper theme reaches the widgets' dialogs, sheets and menus too, although they render at the end of <body>: the widgets copy the wrapper's values onto their overlay root each time they render.

Nested widgets share that overlay root. When a themed widget sits inside another widget (or inside another WidgetProvider / RootWidgetProvider scope), its dialogs, sheets and menus render into the outer scope's overlay root and so take the outer theme. If two widgets need different themes, theme the outermost wrapper, or render them side by side rather than nested.

Advanced theming reaches every public variable:

GroupKeys (createTheme) and variables
Surfacesbackground, foreground, card, cardForeground, popover, popoverForeground, muted, mutedForeground (--ttw-background …)
Actionsprimary, primaryForeground, secondary, secondaryForeground, accent, accentForeground, destructive, destructiveForeground
Linesborder, input, ring
Tonesinfo, success, warning, and destructive: one color each for a status badge
Accentsviolet, cyan: for a status no tone carries
Overrides<tone>Subtle and <tone>Foreground (--ttw-success-subtle, --ttw-success-foreground; for destructive, destructiveSubtle and destructiveSubtleForeground), all optional
Overlayoverlay: the scrim behind dialogs and sheets, black at 50% by default
Shape/typeradius (0–1.25rem), fontFamily

A tone or accent is one color. The badge's tinted fill and its text are derived from it where the badge is painted, so they follow a dark card or a tinted page without extra variables; set the Subtle / Foreground override only to pick either half yourself.

Compatibility variables. surfaceSubtle (--ttw-surface-subtle) and the eight sidebar keys (sidebar, sidebarForeground, sidebarPrimary, sidebarPrimaryForeground, sidebarAccent, sidebarAccentForeground, sidebarBorder, sidebarRing) keep working for existing themes and will be removed in a future major version. None is declared: until you set one, surfaceSubtle is muted and each sidebar color is its base color (card, foreground, primary, accent, border, ring). A new theme can leave them out.

In development, createTheme warns once in the console about unknown keys, text under 4.5:1 contrast (3:1 for mutedForeground on muted) and a radius outside 0–1.25rem, and the widgets run the same contrast check once against the theme they actually render with. Nothing warns in production, and a warning never stops a widget rendering. themeIssues(tokens) returns the same findings as data, for a test in your own CI; a variable you leave out is judged at its default, so themeIssues(createTheme({ primary })) checks primary against the default primaryForeground.

A status colour you set (info, success, warning, destructive, violet, cyan) gets its pale fill and its text derived from it. Browsers without CSS relative colours derive them a different way, so themeIssues checks each one both ways: a contrast issue means it fails in current browsers, and a fallback-contrast issue means it only fails in those older ones. Setting its text colour explicitly (warningForeground, or destructiveSubtleForeground for destructive) fixes both. If your CI switches on issue.kind, handle fallback-contrast too.

Only the variables above are supported. Tailwind's own variables share the --ttw- prefix (--ttw-spacing, --ttw-text-sm, --ttw-color-* …) because the utilities are prefixed ttw; they are pinned inside the widgets, so setting one has no effect. --ttw-chart-* were removed.

UI frameworks (shadcn default, Tecton optional)

Widgets render with the default shadcn surface and need no configuration. Tecton (the Q2 / Stencil design system) ships as a separate, opt-in package:

npm i @tesouro/embedded-components-react-tecton-ui
import { registerTectonUI } from '@tesouro/embedded-components-react-tecton-ui';
registerTectonUI();

Call registerTectonUI() once at app startup, then set uiFramework="tecton" on a provider. Without the extension installed it falls back to shadcn. See the extension's README for setup.

TypeScript module resolution

Set "moduleResolution" to "bundler" (or "node16" / "nodenext"); every entry point then resolves, and the declarations type-check with skipLibCheck: false. Legacy "node" / node10 resolution is not supported (it ignores the exports map and reports TS2307). The package is ESM-only and can't be require()d from CommonJS.

On this page