Developers

Compound components

Compound components let you assemble the same flow the drop-in dialogs render, but in your layout. The FSM, the gateway calls, the settlement watcher, the design tokens — all reused. The only thing you supply is the composition.

import { AssetPicker } from "@stridge/kit/deposit/widgets"

<AssetPicker />  // zero-prop. Reads driver state, dispatches FSM actions internally.

That's the default. Mount any widget inside <StridgeProvider> and it renders against the active driver — no snapshot reads, no action dispatches in your code.

Two layers: widgets and parts

The compound tier ships two layers of API, both public, both stable:

LayerSubpathWhat it isWhen to reach for it
Widgets (default)@stridge/kit/{flow}/widgetsZero-prop orchestrated wrappers. Read the driver snapshot, dispatch FSM actions, render the canonical sub-part composition. Sub-parts aliased on the widget's namespace for custom layouts.Almost always — this is the layer the shadcn registry scaffolds against.
Parts@stridge/kit/{flow}/compoundBare compound parts plus the FSM scaffolding (Deposit.Boundary / .Guards / .Steps / .Step). The parts don't read driver state on their own.The FSM scaffolding (always) and the rare case where you want to wire driver data by hand.

The widget and the bare part of the same name (e.g. AssetPicker from /widgets and AssetPicker from /compound) share identity-equal sub-part references<AssetPicker.Header /> from either subpath is the same component. The difference is what the root function does: the widget wires snapshot + actions; the bare part doesn't.

Note

The FSM scaffolding lives only on /{flow}/compoundDeposit.Boundary, Deposit.Guards, Deposit.Steps, Deposit.Step, and the withdraw mirrors. The widgets sit inside a <Step>; they don't replace it. The canonical composition below shows both layers cooperating.

The canonical composition

This is what the shadcn registry drops into your repo. Production deposit flows compose like this — FSM scaffolding from /compound for the routing, widgets from /widgets as the leaf children:

components/stridge/deposit/deposit.tsx
"use client"

import { useDeposit, useDepositState } from "@stridge/kit"
import { Deposit } from "@stridge/kit/deposit/compound" // FSM scaffolding
import { Dialog } from "@stridge/kit/ui"

import { StridgeDepositMethods } from "./methods"
import { StridgeDepositAssetPicker } from "./asset-picker"
import { StridgeDepositAmountEntry } from "./amount-entry"
import { StridgeDepositConfirm } from "./confirm"
import { StridgeDepositTransferCrypto } from "./transfer-crypto"
import { StridgeDepositProcessing } from "./processing"
import { StridgeDepositSuccess } from "./success"
import { StridgeDepositError } from "./error"
import { StridgeDepositActivityList } from "./activity-list"
import { StridgeDepositActivityDetail } from "./activity-detail"
import { StridgeDepositStatusBanner } from "./status-banner"

export function StridgeDeposit() {
  const state = useDepositState()
  const { close } = useDeposit()
  return (
    <Dialog open={state.name !== "closed"} onOpenChange={(open) => { if (!open) close() }}>
      <Dialog.Content>
        <Deposit.Boundary>
          <Deposit.Guards>
            <Deposit.Steps>
              <Deposit.Step name="deposit"><StridgeDepositMethods /></Deposit.Step>
              <Deposit.Step name="assetPicker"><StridgeDepositAssetPicker /></Deposit.Step>
              <Deposit.Step name="amountEntry"><StridgeDepositAmountEntry /></Deposit.Step>
              <Deposit.Step name="confirmDeposit"><StridgeDepositConfirm /></Deposit.Step>
              <Deposit.Step name="transferCrypto"><StridgeDepositTransferCrypto /></Deposit.Step>
              <Deposit.Step name="processing"><StridgeDepositProcessing /></Deposit.Step>
              <Deposit.Step name="success"><StridgeDepositSuccess /></Deposit.Step>
              <Deposit.Step name="error"><StridgeDepositError /></Deposit.Step>
              <Deposit.Step name="activityList"><StridgeDepositActivityList /></Deposit.Step>
              <Deposit.Step name="activityDetail"><StridgeDepositActivityDetail /></Deposit.Step>
            </Deposit.Steps>
          </Deposit.Guards>
          <StridgeDepositStatusBanner />
        </Deposit.Boundary>
      </Dialog.Content>
    </Dialog>
  )
}

Each leaf wrapper (StridgeDepositAssetPicker, StridgeDepositAmountEntry, …) is a thin file that mounts the matching widget with whatever composition you want:

components/stridge/deposit/asset-picker.tsx
"use client"

import { AssetPicker } from "@stridge/kit/deposit/widgets"

export function StridgeDepositAssetPicker() {
  return (
    <AssetPicker>
      <AssetPicker.Header />
      <AssetPicker.Body>
        <AssetPicker.List />
      </AssetPicker.Body>
      <AssetPicker.Footer />
    </AssetPicker>
  )
}

The widget reads the source-asset list from the driver and feeds it into <AssetPicker.List /> — your wrapper file just states the layout.

Custom compositions inside a widget

Every widget accepts children to swap the default composition. Pass them to interleave your own JSX between sub-parts, drop sub-parts you don't want, or wrap them.

Interleaving your own JSX

A worked example — adding a small explainer between the recipient and the amount fields on the withdraw form:

import { WithdrawForm } from "@stridge/kit/withdraw/widgets"

<WithdrawForm>
  <WithdrawForm.Header />
  <WithdrawForm.Body>
    <WithdrawForm.RecipientField />

    {/* Your custom component, slotted into the form. */}
    <YourRecipientHint />

    <WithdrawForm.AmountField />
    <WithdrawForm.ReceiveSelectors />
    <WithdrawForm.BreakdownCard />
  </WithdrawForm.Body>
  <WithdrawForm.Footer />
</WithdrawForm>

You can do the same anywhere a widget exposes named sub-parts — between asset rows on the picker, between details rows on the success screen, between method tiles on the picker. The Kit doesn't restrict what you mount alongside its parts; layout flows naturally because the parts use logical CSS.

Tip

When the interleaved JSX is a small UI atom — a button, a callout, an inline link, a tile — reach for @stridge/kit/ui rather than designing one from scratch. Button, Card, Alert, IconButton, SelectableTile, Text, Badge are the exact primitives the Kit's own widgets use, so your inserted element matches the surrounding flow's spacing, type scale, focus rings, and theming for free.

Default composition

Mount any widget bare — <AssetPicker /> — and you get the canonical sub-part composition. Equivalent to:

<AssetPicker>
  <AssetPicker.Header />
  <AssetPicker.Body>
    <AssetPicker.List />
  </AssetPicker.Body>
  <AssetPicker.Footer />
</AssetPicker>

When you want the default, the bare mount is enough — the widget renders the same tree internally.

When restyling is enough

If all you need is a different look, you don't need compound — reach for the skin tier instead and override the Kit's CSS variables. Drop into compound when restyling isn't enough and you need different structure — swap a screen, embed a step inline, drop a sub-part, interleave your own JSX.

Bootstrap with the shadcn registry

The fastest way to start a compound integration is to scaffold the canonical composition into your repo with the shadcn registry:

pnpm dlx shadcn@latest add @stridge/kit --registry https://registry.ui.stridge.com/r

This drops a components/stridge/ directory containing one file per FSM screen, each composed against the widgets on this page. You own the dropped files — restyle them, wrap them, delete the ones you don't need. The Kit still owns the wiring (FSM, gateway calls, settlement watcher); the dropped wrappers are just composition you'd otherwise hand-write.

See shadcn registry for the per-item file lists and how the dropped files map to the widgets below.

FSM scaffolding — Deposit and Withdraw

These ship only from /compound — they're the routing skeleton you mount inside a dialog (or inline) before any widget renders.

import { Deposit } from "@stridge/kit/deposit/compound"

<Deposit.Boundary>
  <Deposit.Guards>
    <Deposit.Steps>
      <Deposit.Step name="…">{/* widget or your component */}</Deposit.Step>
    </Deposit.Steps>
  </Deposit.Guards>
</Deposit.Boundary>
PartRole
Deposit.BoundaryError-boundary wrapper. onReset is auto-wired to useDeposit().close().
Deposit.GuardsGates the rest of the tree behind wallet-required / bootstrap-fatal / bootstrap-loading states. Render order: wallet → bootstrap-fatal → bootstrap-loading → children.
Deposit.StepsFSM router. Reads useDepositState().state.name and renders only the matching child step.
Deposit.Step name="…"One leaf, keyed by FSM state name. Children render only when the step is active.

Step names: "deposit", "assetPicker", "amountEntry", "confirmDeposit", "transferCrypto", "processing", "success", "error", "activityList", "activityDetail".

Note

The "deposit" step name is the method picker (wallet vs. transfer). Counterintuitive but stable — the FSM tag predates a clarifying rename.

The withdraw mirror — Withdraw.Boundary / .Guards / .Steps / .Step from @stridge/kit/withdraw/compound — works identically. Step names: "form", "inProgress", "success", "error", "activityList", "activityDetail".

Deposit widget catalog

Every widget below ships from @stridge/kit/deposit/widgets. Each is zero-prop by default and accepts children to override the default sub-part composition.

Deposit

The method picker — wallet vs. transfer. Reads connected-wallet balance, brand name, and per-chain deposit addresses from the driver.

import { Deposit } from "@stridge/kit/deposit/widgets"

<Deposit>
  <Deposit.Header />
  <Deposit.Body>
    <Deposit.Methods />
  </Deposit.Body>
</Deposit>

Sub-parts: Header, Body, Methods, Method, Steps, Step, Dialog.

AssetPicker

The chain + token picker. Reads the source-asset list from the driver's balances entity.

import { AssetPicker } from "@stridge/kit/deposit/widgets"

<AssetPicker>
  <AssetPicker.Header />
  <AssetPicker.Body>
    <AssetPicker.List />
  </AssetPicker.Body>
  <AssetPicker.Footer />
</AssetPicker>

Sub-parts: Header, Body, List, Asset, Footer, Dialog.

AmountEntry

The numeric amount entry hero with optional percentage pills. Reads the live USD/token conversion off the driver's quote stream.

import { AmountEntry } from "@stridge/kit/deposit/widgets"

<AmountEntry>
  <AmountEntry.Header />
  <AmountEntry.Hero />
  <AmountEntry.Pills />
  <AmountEntry.Footer />
</AmountEntry>

ConfirmDeposit

The pre-broadcast confirmation step. Shows the source → destination route, the fee breakdown, and the confirm CTA.

import { ConfirmDeposit } from "@stridge/kit/deposit/widgets"

<ConfirmDeposit>
  <ConfirmDeposit.Header />
  <ConfirmDeposit.Body>
    <ConfirmDeposit.SourceAsset />
    <ConfirmDeposit.Arrow />
    <ConfirmDeposit.DestAsset />
    <ConfirmDeposit.Details />
  </ConfirmDeposit.Body>
</ConfirmDeposit>

TransferCrypto

The QR + address copy step rendered when the user picks the transfer method on the deposit-method picker.

ProcessingState

The watcher screen. Reads the live settlement status from the snapshot.

import { ProcessingState } from "@stridge/kit/deposit/widgets"

<ProcessingState>
  <ProcessingState.Header />
  <ProcessingState.StatusPill />
  <ProcessingState.Body>
    <ProcessingState.Details>
      <ProcessingState.Detail label="Tx hash" value="0x…" />
    </ProcessingState.Details>
  </ProcessingState.Body>
</ProcessingState>

SuccessState

The terminal success screen.

import { SuccessState } from "@stridge/kit/deposit/widgets"

<SuccessState>
  <SuccessState.Header />
  <SuccessState.Body>
    <SuccessState.Headline />
    <SuccessState.AssetValue />
    <SuccessState.Details>
      <SuccessState.Detail label="To" value="0x…ABCD" />
    </SuccessState.Details>
    <SuccessState.MoreDetails label="Receipt">
      {/* Collapsible block — explorer link, settlement id, … */}
    </SuccessState.MoreDetails>
  </SuccessState.Body>
  <SuccessState.Actions>
    {/* Action buttons — close, view receipt, copy hash */}
  </SuccessState.Actions>
</SuccessState>

ErrorState

The terminal error screen.

import { ErrorState } from "@stridge/kit/deposit/widgets"

<ErrorState>
  <ErrorState.Header />
  <ErrorState.Hero />
  <ErrorState.Body>
    <ErrorState.StatusValue />
    <ErrorState.Details />
    <ErrorState.HelpInfo />
  </ErrorState.Body>
  <ErrorState.Actions />
</ErrorState>

DepositStatusBanner

The inline status banner the deposit dialog renders alongside terminal states. Mount it anywhere inside <Deposit.Boundary> to surface in-progress settlements without taking over the screen.

DepositActivityList and DepositActivityDetail

The deposit-side activity surface. Mount inside the "activityList" / "activityDetail" FSM steps. The widgets read the rolling settlement history off the standalone activity driver — see Activity below.

DialogShell

Convenience export — pre-composed Dialog.Root + Dialog.Trigger + Dialog.Content with the kit's design tokens. Use it when you want to mount a single widget in its own dialog frame without wiring useDeposit() open-state manually.

Withdraw widget catalog

Every widget below ships from @stridge/kit/withdraw/widgets.

WithdrawForm

The form step — recipient, amount, receive-asset + receive-chain selectors, fee breakdown. The withdraw flow's centerpiece.

import { WithdrawForm } from "@stridge/kit/withdraw/widgets"

<WithdrawForm>
  <WithdrawForm.Header />
  <WithdrawForm.Body>
    <WithdrawForm.RecipientField />
    <WithdrawForm.AmountField />
    <WithdrawForm.ReceiveSelectors />
    <WithdrawForm.BreakdownCard />
  </WithdrawForm.Body>
  <WithdrawForm.Footer />
</WithdrawForm>

WithdrawInProgress

The processing screen. Mounts inside the "inProgress" FSM step.

WithdrawSuccess / WithdrawError

Terminal screens. Mount inside "success" and "error" FSM steps respectively.

WithdrawActivityList / WithdrawActivityDetail

The withdraw-side activity surface. See Activity.

Activity

The Kit ships an activity surface — a unified, read-only view of recent settlements across deposit + withdraw — reachable in-flow through dedicated "activityList" / "activityDetail" FSM steps. The deposit / withdraw activity widgets read from the standalone activity driver that <StridgeProvider> derives automatically (or that you pass via <KitProvider activity={…}>) — the same driver that powers the standalone <ActivityDialog /> / useActivity() surface. See Headless → Activity for the headless hooks and driver shape.

Widget imports:

// Deposit side
import {
  DepositActivityList,
  DepositActivityDetail,
} from "@stridge/kit/deposit/widgets"

// Withdraw side
import {
  WithdrawActivityList,
  WithdrawActivityDetail,
} from "@stridge/kit/withdraw/widgets"

The orchestrated widgets read driver state and dispatch FSM actions internally. The bare compound parts they wrap ship from a sibling subpath if you want to wire data by hand:

import {
  Activity,
  ActivityDetail,
  ActivityHeader,
  ActivityListHeader,
  ACTIVITY_SLOTS,
  ACTIVITY_ROW_SLOTS,
  type ActivityPayload,
  type ActivityRowPayload,
} from "@stridge/kit/activity/compound"

Activity and ActivityDetail are the bare compound roots; the deposit / withdraw widgets above wire them to the standalone activity driver so you don't need the bare parts unless you're building a fully custom activity surface.

Putting it together — an embedded inline deposit

A trading-app sidebar that funds the margin balance without opening a modal. Widgets handle the per-step data; the FSM scaffolding from /compound routes between them.

TradeSidebar.tsx
import { useDeposit } from "@stridge/kit"
import { Deposit } from "@stridge/kit/deposit/compound"
import {
  AssetPicker,
  AmountEntry,
  ConfirmDeposit,
  ProcessingState,
  SuccessState,
  ErrorState,
} from "@stridge/kit/deposit/widgets"

export function TradeSidebar() {
  return (
    <aside>
      <h3>Fund margin</h3>
      <Deposit.Boundary>
        <Deposit.Guards>
          <Deposit.Steps>
            <Deposit.Step name="assetPicker"><AssetPicker /></Deposit.Step>
            <Deposit.Step name="amountEntry"><AmountEntry /></Deposit.Step>
            <Deposit.Step name="confirmDeposit"><ConfirmDeposit /></Deposit.Step>
            <Deposit.Step name="processing"><ProcessingState /></Deposit.Step>
            <Deposit.Step name="success"><SuccessState /></Deposit.Step>
            <Deposit.Step name="error"><ErrorState /></Deposit.Step>
          </Deposit.Steps>
        </Deposit.Guards>
      </Deposit.Boundary>
    </aside>
  )
}

Call useDeposit().open({ method: "wallet" }) from a "Fund" button and the sidebar renders inline — no modal, no portal, no extra wiring.

Going below widgets — bare parts

The same sub-part components ship from @stridge/kit/{flow}/compound without the data wiring. Reach for them when you're building a fully custom surface and want to drive the parts off your own snapshot reads — e.g. compose <AssetPicker.List /> against a balance list you transformed yourself, instead of the driver's balances entity.

import { AssetPicker } from "@stridge/kit/deposit/compound"
import { useDepositSnapshot } from "@stridge/kit"

function CustomAssetPicker() {
  const snapshot = useDepositSnapshot()
  const assets = transformToYourShape(snapshot.balances)
  return (
    <AssetPicker assets={assets} onAssetSelect={}>
      <AssetPicker.Header />
      <AssetPicker.Body>
        <AssetPicker.List />
      </AssetPicker.Body>
    </AssetPicker>
  )
}

Most integrations never need this — the widgets cover the production cases. Use bare parts when you have a specific reason: custom balance source, A/B testing a different data shape, embedding outside the orchestrator entirely.

Data attributes on every sub-part

Every widget and every bare part is stamped with data-stridge-slot="<name>". Hosts target sub-parts from CSS without forking — see Data attributes & slots for the complete catalogue, with click-to-copy selectors.

Next

Was this page helpful?