A lightweight, framework-agnostic SDK for Tailbits experiences. Components are implemented as Web Components using Lit, with real-time updates over a resilient reconnecting WebSocket (via `reconnecting-websocket`) and background data fetching powered by T
npm install tailbits-jsA lightweight, framework-agnostic SDK for Tailbits experiences. Components are implemented as Web Components using Lit, with real-time updates over a resilient reconnecting WebSocket (via reconnecting-websocket) and background data fetching powered by TanStack Query. React wrappers are generated for convenient use in React and a CDN build is available for drop‑in usage.
Features
- Web Components for framework agnosticism
- Background refetch + interval polling via @tanstack/query-core
- Real-time invalidation via reconnecting-websocket
- Typed API client generated from OpenAPI via orval, using ky as fetcher
- React wrappers generated from the Custom Elements Manifest
- CDN and ESM builds
Use the CDN as the primary integration path. Import the loader, and call init(...) once.
``html`
.
- Use preload: ['tb-journey'] in init(...) if you want to eagerly load known tags. The default behavior is to load features lazily, as they're detected on the page.$3
Call
init(options) once per page load. Subsequent calls throw.| Option | Type | Default | Required | Description |
|-----------------|------------------------------|--------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------|
|
projectId | string | — | Yes | Your Tailbits project ID. |
| environmentId | string | — | Yes | The environment identifier for the page. |
| preload | string[] | [] | No | Feature tags to eagerly load (e.g., ['tb-journey']). Otherwise features load lazily when first seen on the page. |
| onError | (error: unknown) => void | undefined | No | Global error handler for SDK-level errors. Components also emit tb-error events. |$3
Handle errors globally via
onError, and at the component level via the tb-error event. In most cases, it's enough to use one of these hooks.`html
`Components
$3
Render
tb-journey anywhere on your page, and the loader will dynamically fetch the required assets to wire it up.`html
Install the CLI
`The above is all it takes to wire up a Tailbits Journey. Add more elements with different steps to wire up the complete journey. Note that every
tb-journey element must have a journey-id and step assigned.#### Events
It’s possible to attach event listeners to
tb-journey elements using the native addEventListener API.`javascript
const el = document.querySelector('tb-journey');
el.addEventListener('tb-step-update', (event) => {
console.log('step status:', event.detail.status);
});el.addEventListener('tb-error', (event) => {
console.error('component error:', event.detail);
})
`React (npm)
If using the CDN route is not an option for you, install the package and the peer dependencies, initialize via the loader export, and render the generated React wrappers.
`bash
npm i tailbits-js react react-dom
``tsx
import { init } from 'tailbits-js/loader';
import Journey from 'tailbits-js/react/tb-journey';init({
projectId: '...',
environmentId: '...',
});
export function App() {
return (
journeyId="..."
step="install_cli"
onStepUpdate={(e) => console.log('step status:', e.detail.status)}
>
Install the CLI
);
}
`Architecture
Components (Lit)
- Web Components live in
src/features/* and extend a small base in src/internal/core/base-element.ts.
- Elements emit typed custom events (e.g., tb-step-update, tb-error) declared in src/events/*.Loader + Registry
- Entry point
src/loader.ts sets global config and registers a lazy element loader using import.meta.glob.
- The registry in src/internal/core/registry.ts observes the DOM and dynamically imports elements on first sighting; preload lets you eagerly load known tags.Data Layer (TanStack Query Core)
- Central client in
src/internal/api/query.ts configures sensible defaults:
- staleTime: 60_000, gcTime: 5 * 60_000, retry: 3
- refetchOnWindowFocus: true for background freshness
- Per-query refetchInterval (e.g., 30s for journey progress)
- Components call this.query(...) to subscribe to query results and lifecycle; errors are surfaced via a tb-error event.Real-time (WebSocket)
-
src/internal/core/socket.ts uses reconnecting-websocket to create a resilient connection.
- Incoming messages trigger targeted invalidations (e.g., step_update invalidates ['journey', id]), seamlessly syncing server pushes with cached data.API SDK (HTTP)
-
orval.config.ts generates src/internal/api/client.ts from the Tailbits OpenAPI spec.
- HTTP is performed by ky via a small wrapper in src/internal/api/fetcher.ts (with centralized error forwarding via src/internal/core/errors.ts).React Wrappers
- Thin React adapters for each element are generated with
@lit/react and live under src/react/ and dist/react/.
- Codegen is driven by the Custom Elements Manifest using a Vite plugin: scripts/vite-plugin-cem-react.ts.
- Event props map to element events (e.g., onStepUpdate → tb-step-update).Agnostic by Design
- The primary deliverable is a set of standard Web Components (Lit) that run without any framework.
- React wrappers are optional and add zero business logic—just ergonomic bindings.
Background Fetching & Intervals
- Queries are configured for background freshness (refetchOnWindowFocus: true).
- Interval polling is used where appropriate (e.g., getJourneyQueryOptions sets refetchInterval: 30_000).
- WebSocket messages act as server‑driven invalidation signals for immediate consistency.Builds
Library (ESM)
-
npm run build:lib produces ESM modules in dist with type declarations.
- Exports map:
- tailbits-js → dist/cdn/loader.js (CDN/loader)
- tailbits-js/loader → dist/loader.js
- tailbits-js/react/ → dist/react/.js
- tailbits-js/features/ → dist/features/.js
- tailbits-js/events/ → dist/events/.jsCDN
-
npm run build:cdn creates a CDN‑friendly bundle in dist/cdn for