@get-asset/sdk

Snapshot Card

Headless compound for a quick revenue vs. expense snapshot with net profit and period-over-period change. Includes proportional bar parts so you can render comparisons without a chart library. The reporting range follows the AssetProvider's DatePicker-driven range — a month, quarter, or year compares against the one before it, and a custom range against the same number of days immediately before it. Pass dateFrom / dateTo to pin a fixed range instead.

Anatomy

Import the primitive and compose the parts you need.

<SnapshotCard.Root
dateFrom="2026-01-01"
dateTo="2026-12-31"
onViewDetails={({ period, basis }) => router.push(`/reports?year=${period?.year}&basis=${basis}`)}
>
<SnapshotCard.ViewDetails>View details</SnapshotCard.ViewDetails>
<SnapshotCard.Success>
<SnapshotCard.Period />
<SnapshotCard.NetProfit />
<SnapshotCard.Tone>
<SnapshotCard.Change />
</SnapshotCard.Tone>
<SnapshotCard.Revenue />
<SnapshotCard.RevenueBar />
<SnapshotCard.Expense />
<SnapshotCard.ExpenseBar />
</SnapshotCard.Success>
</SnapshotCard.Root>

Required scopes

The access token used by AssetProvider must include these scopes for this primitive to fetch data successfully:

  • report:read
  • business:read

API reference

Root props

PropTypeDefaultDescription
dateFromCalendarDateStringInclusive start (YYYY-MM-DD) of a fixed reporting range. Setting it detaches the card from the provider's shared range entirely; must be paired with dateTo (one without the other throws). Use periodDateRange to derive the pair from a month, quarter, or year.
dateToCalendarDateStringInclusive end (YYYY-MM-DD) of a fixed reporting range. Must be paired with dateFrom. Omit both to follow the provider's DatePicker-driven range.
revenuenumberOverride fetched revenue.
expensenumberOverride fetched expense. Provide as a positive number.
previousNetProfitnumberOverride prior-period net profit (drives the % change badge).
scalenumberOverride the +/- bound for the bar chart scale.
state"loading" | "error" | "empty"Force a specific state for testing.
lockedbooleanGate the report for an unentitled viewer. Activates the Locked state and skips the API fetch. See /docs/sdk/locked-upsell.
upsell{ title?, description?, ctaLabel?, onUpgrade? }Locked-state copy + upgrade handler; merged field-by-field over the card's default copy, then the generic fallback.
onViewDetails(ctx: { range, period, basis }) => voidCallback fired when the viewer asks to see more details (e.g. to navigate to the full report). Makes SnapshotCard.ViewDetails render. Receives the card's resolved range, the exact period it represents (or null for a custom range) and the accounting basis, so the destination can open on the same window the card shows.
basis"accrual" | "modified_cash"Report on a specific accounting basis. Omit to inherit the AssetProvider's basis, which itself falls back to the Business's country — Canadian Businesses report on Accrual, everyone else on Cash (see Accounting basis).
basisTogglebooleanLet the end user flip between Cash and Accrual. Off by default — BasisSelect renders nothing until it's set. Flipping the control moves every component that inherits the provider's basis, so it can't leave a screen half cash and half accrual (see Accounting basis).
childrenrequiredReactNodeCompound parts.

Parts

Container

Always rendered. Provides data and context to its children.

PartDescription
SnapshotCard.RootFetches revenue/expense totals and provides context.
SnapshotCard.Basis
asChild?
Renders the active basis as text ("Accrual" / "Cash") and sets data-basis. Rendered whether or not the toggle is enabled.
SnapshotCard.BasisSelectWires basis state to a Select-Root-shaped child (injects value + onValueChange). Renders nothing unless basisToggle is enabled.
SnapshotCard.BasisItemsIterates the Accrual / Cash options, providing per-option context.
SnapshotCard.BasisItem
asChild?
Renders one basis option. Injects value and the option label.
SnapshotCard.ViewDetails
asChild?
Button that fires Root's onViewDetails callback with the card's resolved { range, period, basis } — the viewer's way to ask for more details (e.g. the full report). Renders nothing unless onViewDetails is set. Use asChild to slot a router Link or anchor instead; the handler still fires.

Success

Renders when data is available.

PartDescription
SnapshotCard.SuccessState wrapper. Compose the snapshot UI inside.
SnapshotCard.Period
asChild?
Renders the resolved period — defaults to MMM YYYY, Q<N> YYYY, YYYY, or the range label ("7 – 14 Jan 26") for a custom range. Pass format(range, period) to override.
SnapshotCard.NetProfit
asChild?
Renders revenue − expense.
SnapshotCard.Revenue
asChild?
Renders the revenue value.
SnapshotCard.Expense
asChild?
Renders the expense value.
SnapshotCard.Change
asChild?
Percent change vs. previousNetProfit.
SnapshotCard.Tone
asChild?
Adds data-tone to its child based on the sign of the change.
SnapshotCard.RevenueBar
style?, className?
Proportional bar sized by revenue / scale. Renders a filled track.
SnapshotCard.ExpenseBar
style?, className?
Proportional bar sized by expense / scale.

Loading

Renders while the fetch is in flight.

PartDescription
SnapshotCard.LoadingState wrapper. Render any skeleton or placeholder UI inside.

Empty

Renders when the period has zero revenue and expense.

PartDescription
SnapshotCard.EmptyState wrapper. Render an empty-state message inside.

Error

Renders on fetch error.

PartDescription
SnapshotCard.ErrorState wrapper. Compose the error UI inside.
SnapshotCard.ErrorMessage
asChild?
Renders the error message string.
SnapshotCard.Retry
asChild?
Button that re-runs the fetch.

Locked

Renders when `locked` is set — the viewer isn't entitled to the report. Mutually exclusive with the other states, and no data is fetched. See the Locked / upsell guide for composing the teaser + upgrade UI.

PartDescription
SnapshotCard.LockedState wrapper. Compose the upgrade / upsell UI inside — see LockedUpsell.

Unlocked

Renders whenever the card is NOT locked — the inverse of Locked.

PartDescription
SnapshotCard.UnlockedState wrapper. Renders children whenever the card isn't locked.

Hooks

PartDescription
useSnapshotCard()Hook returning the full context, including locked, the resolved basis (basis, setBasis) and onViewDetails.

Accounting basis

SnapshotCard reports on one of two accounting bases: Accrual (accrual on the wire), which counts invoices and bills when they are issued, or Cash (modified_cash), which counts them when the money moves. Cashflow components take no basis — cashflow is a cash concept.

The basis is resolved per component, first match winning:

  • the card's own basis prop;
  • the provider's basis — defaultBasis on AssetProvider, or whatever the toggle was last flipped to;
  • the Business's country — Canadian Businesses report on Accrual, everyone else on Cash.

Only the last step costs a request — a Business read, which is why every basis-aware component's scopes includes business:read — and it is serial: the card holds its own fetch until the Business answers. One request is shared across every basis-aware component on the screen, but pass defaultBasis on AssetProvider when you already know the basis and skip it entirely. If the lookup fails the card falls back to Cash and stays there — retry() re-runs the report query, not the basis lookup.

basisToggle is off by default: SnapshotCard.BasisSelect renders nothing and the card reports on its resolved basis. Turn it on and flipping the control moves every basis-aware component under the provider with it, so the toggle can never leave a screen half cash and half accrual. A component pinned with its own basis prop is the exception — it keeps the basis you gave it and renders no toggle of its own, which is how you show both bases side by side on purpose.

SnapshotCard.Basisrenders the active basis as text ("Accrual" / "Cash") and sets data-basis, whether or not the toggle is enabled — use it to label the figures even on a screen that offers no control.