Framework support

The SDK ships a <BdForm> binding for eight targets. Each is a thin view over the same framework-agnostic core, so the behaviour you rely on — values, validation, conditional visibility, multi-step navigation, uploads, submit lifecycle — is identical everywhere and doesn't change when your framework does.

Forms are where the bindings are equal, and where the equality stops. The rest of the SDK — chat, customer auth, storefront, cart, checkout, blog, the customer portal, marketing pages, followers, social links and the verification flows — currently exists only for React. The table below is the honest version; read it before you pick a stack.

What each adapter can do

A capability is not a yes/no, and reporting it that way would flatter us. Two things are being distinguished:

  • native — real components and hooks rendering into your DOM, styled by your own CSS.
  • iframe — a BusinessDash-hosted page in a sandboxed iframe. It works, and it is often the fastest way to ship, but it renders our markup inside your app and your design system does not reach it.
  • both — the surface exists in both forms. React's chat is the clearest example: an iframe widget and a headless hook for building your own UI.
Capabilityreactsolidvuesvelteqwikangularvanillaelement
Formsnativenativenativenativenativenativenativenative
Shared clientnativenativenativenative—native—native
AI chatbothbothnativenativenativenativenativenative
Storefrontbothbothnativenativenativenativenativenative
Cartbothbothnativenativenativenativenativenative
Blogbothbothnativenativenativenativenativenative
Checkoutbothbothnativenativenativenativenativenative
Customer portalbothbothnativenativenativenativenativenative
Customer authnativenativenativenativenativenativenativenative
Followersnativenativenativenativenativenativenativenative
Marketing pagesnativenativenativenativenativenativenativenative
Social linksnativenativenativenativenativenativenativenative
Email verificationnativenativenativenativenativenativenativenative
Phone verificationnativenativenativenativenativenativenativenative
Surfaces1414141413141314

Two things in that table are worth reading carefully, because they cut against the obvious summary.

Every capability is available on every adapter. The only cells that read — are the shared-client primitive on qwik and vanilla, and neither needs one: both take the client per form instead, which is the right shape for a resumability-first framework and for an imperative mount.

Ten shared cores carry the behaviour — forms, store, chat, blog, checkout, account, portal, marketing, verify and social — and each binding is a thin view over them. React and Solid additionally offer the hosted iframe widgets, which is why several rows read both.

Nothing is iframe-only. Where an iframe still exists it is an alternative, never the only option.

The customer portal

The largest surface here: 34 methods covering work, quotes, contracts, invoices, orders, downloads, shipping, reviews, referrals, profile and notification preferences. Three things about it are worth knowing before you build against it, and the SDK does not paper over any of them.

sessionToken is required. Every portal route is scoped to the signed-in customer, and a portal built without one renders empty rather than signed-out — which looks like a bug in your code, not a missing token. The controller takes it up front and binds it for you.

There are no list endpoints for invoices, quotes, contracts or shipments. work carries the counts and you open each by id. The SDK exposes exactly what exists rather than faking a list from repeated detail fetches.

A submitted review is queued for staff approval, so it will not appear in reviews. Watch reviewAwaitingModeration so you can tell the customer it is pending rather than implying it is live.

React remains ahead on <ChatbotInline> — a rendered chat UI, roughly 1,200 lines, on top of the same headless controller everyone now shares. The behaviour is common; only that component is React-only.

qwik and vanilla have no shared-client entry because neither needs one: both take the client per form — a client prop and a mountBdForm option respectively — which is the right shape for a resumability-first framework and for an imperative mount.

If you need more than forms on a non-React framework, you have two practical routes today:

  1. Talk to the API directly. Every capability above is a thin view over the package API, and BdClient (from the root export) is framework-agnostic — client.storefront(), client.cart(), client.customerPortal() and the rest all work anywhere JavaScript runs. You write the markup; the data layer is already there.
  2. Embed the web component. @businessdash/sdk/element registers <bd-form>, which renders inside any framework.

This table is generated from capabilities in @businessdash/sdk/framework-support, and a release gate asserts the code matches it — so it cannot drift into promising something an adapter doesn't export.

React-compatible targets

Next, Astro, Remix, React Router, TanStack Start and Preact are not separate adapters and don't need to be. They render React, or a drop-in replacement for it, so they import @businessdash/sdk/react and get all fourteen capabilities. Choosing Next, Astro or Preact is not choosing a smaller SDK.

FrameworkImportWhat it needsStarter
React@businessdash/sdk/reactNothing — import and render.React-Bun
Next.js (App Router)@businessdash/sdk/reactNothing, as of 0.9.61.T3-App
Astro@businessdash/sdk/react@astrojs/react, plus a client directive on the island.Astro
Remix / React Router 7@businessdash/sdk/reactNothing.Remix
TanStack Start@businessdash/sdk/reactNothing.Tanstack-Start
Preact@businessdash/sdk/reactThe standard preact/compat alias.—
React Native / Expo@businessdash/sdk/nativeThe native entry — DOM components don't render there.React-Native

Preact

Preact gets the full surface for the cost of a bundler alias. The React binding imports nine runtime symbols — createContext, Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore — all of which preact/compat provides, and it never imports react-dom, so nothing drags in React's reconciler.

// vite.config.js
import preact from '@preact/preset-vite'

export default {
  plugins: [preact()],   // aliases react -> preact/compat for you
}

If you're wiring the alias by hand:

resolve: {
  alias: { react: 'preact/compat', 'react-dom': 'preact/compat' },
}

This isn't a claim we make by inspection. A release gate bundles the real SDK source twice — once against React, once with react aliased to preact/compat — renders the same component trees on both, and asserts the markup is identical. The only permitted difference is inline-style serialisation, where React writes margin:0 and Preact writes margin:0px for the same computed value.

Next.js and "use client"

The React adapter carries its own "use client" boundary, so you can import it straight into a server-rendered page:

// app/contact/page.tsx — a Server Component
import { BdForm } from '@businessdash/sdk/react'

export default function ContactPage() {
  return <BdForm slug="contact" />
}

Before 0.9.61 this threw "you're importing a component that needs useState", and the workaround was a hand-written "use client" wrapper for every component you used. Those wrappers still work — you don't have to remove them — but new code doesn't need them.

Astro and client directives

Astro renders islands to static HTML unless you ask for hydration, and a form with no JavaScript attached will look correct and do nothing:

---
import ContactForm from '../components/ContactForm.tsx'
---
<ContactForm client:load />

Use client:load for anything interactive above the fold, client:visible for forms further down the page.

Supported versions

ImportFrameworkVersionsShips asStatus
@businessdash/sdk/reactreact>=18 <20compiledSupported
@businessdash/sdk/solidsolid-js>=1.8 <2sourceSupported (1.x)
@businessdash/sdk/vuevue>=3.3 <4sourceSupported
@businessdash/sdk/sveltesvelte>=5 <6sourceSupported (runes)
@businessdash/sdk/qwik@builder.io/qwik>=1.5 <2sourceUntested
@businessdash/sdk/angular@angular/core>=17sourceUntested
@businessdash/sdk/vanilla—anycompiledSupported
@businessdash/sdk/element—anycompiledSupported

These ranges are declared as optional peerDependencies, so your package manager warns you on a mismatch instead of failing at build time with a confusing error.

The same data is importable if you want to assert on it in your own tooling:

import { FRAMEWORK_SUPPORT, supportFor } from '@businessdash/sdk/framework-support'

supportFor('solid-js') // every adapter dialect for Solid

When your framework ships a breaking major

Frameworks rework their own APIs, and an adapter written against the old dialect won't compile against the new one. When that happens we add a second adapter for the new major rather than replacing the old one, and this page lists both. Code on either major keeps working, and you upgrade when you choose to.

Two things limit the damage:

The core is framework-agnostic. Only the binding layer — how external state becomes reactive, and how a list renders — is version-sensitive. Nothing about how forms actually behave lives there.

@businessdash/sdk/element is always available. It's a web component with no framework dependency, it works inside any framework, and it's the fastest escape hatch if you're on a brand-new major before its adapter lands.

Solid 2.0

Solid 2.0 shipped in August 2026 with a substantial API rework. The current @businessdash/sdk/solid adapter is written against the 1.x dialect and is not yet verified on 2.x.

If you're on Solid 2.0 today, use @businessdash/sdk/element or @businessdash/sdk/vanilla — both are framework-independent and give you the same form behaviour — and watch this page for the v2 adapter.

Non-JavaScript starters

Swift, Kotlin, Dart, PHP and Elixir consumers do not import @businessdash/sdk — each starter carries a hand-written client library. As of 0.9.61 all six reach every product surface:

StarterCoverage
Swift (iOS/macOS)15/15
Kotlin (Android)15/15
Flutter / Dart15/15
Laravel (PHP)15/15
Phoenix (Elixir)15/15
Vapor (Swift server)15/15

They started between 8 and 11. The shared gap was social links and the two verification kinds — surfaces added to the JS side and never carried across, which is why sdk/test/non-js-parity.test.ts now measures this on every run. It reads each client's source and matches on request paths, the one thing six languages have in common.

Full coverage means every surface is reachable from every language. It does not mean the ergonomics match, and the gate cannot tell you that. Read a green run as "nothing is missing", not "the starters are finished".

The social-platform table is generated

The 31 platforms — key, label, icon slug, URL prefix — come from sdk/src/socials.ts and are emitted into all six languages by pnpm gen:socials. Six hand-maintained copies would drift the first time a platform was added, and the copy that drifted would be whichever language nobody was using that week. pnpm gen:socials:check fails CI when they are stale.

The resolver around that table is hand-written per language, because that is what a developer actually reads. Generate the data, hand-write the idiom.