> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ActivityElement

> An account's ledger activity: every movement of money in or out, newest first. The list pages as the viewer scrolls, and rows report which one was tapped instead of navigating, so you can open your own detail screen.

<Info>**Upcoming.** These docs cover unreleased development, ahead of any published release. Use the channel picker at the top of the sidebar for the docs of a published release.</Info>

*In development, not yet part of a stable release.*

<Tabs>
  <Tab title="Web">
    Belongs to the [`Wallet`](/elements/upcoming/wallet/overview) group. Render `<ActivityElement />` inside it (React), or call `wallet.create('activity', { … })` on the handle (vanilla). Consumer props and `on<Event>` callbacks both go in the create options / JSX props.

    ## Preview

    A live, interactive demo of this element with sample data:

    <div data-whop-demo-shell style={{ position: "relative", minHeight: "320px", transition: "min-height 200ms ease" }}>
      <div data-whop-demo-skeleton style={{ position: "absolute", inset: "0", borderRadius: "12px", background: "rgba(140, 140, 140, 0.12)", pointerEvents: "none", transition: "opacity 200ms ease" }} />

      <div data-whop-demo-native="element:wallet/activity" data-whop-elements-version="" style={{ position: "relative" }} />
    </div>

    ## Usage

    <div data-whop-usage="wallet/activity">
      <CodeGroup>
        ```tsx React theme={null}
        import { WhopElements, Wallet, ActivityElement } from "@whop/elements-react";
        import { loadWhop } from "@whop/elements";

        function Example() {
          return (
            <WhopElements elements={loadWhop()}>
              <Wallet /* options */>
                <ActivityElement onActivitySelected={(payload) => console.log("activitySelected", payload)} onDateRangeChanged={(payload) => console.log("dateRangeChanged", payload)} />
              </Wallet>
            </WhopElements>
          );
        }
        ```

        ```html Vanilla theme={null}
        <script src="https://js.whop.cloud/elements/amber/elements.js" data-whop-elements></script>
        <script type="module">
          const wallet = window.WhopElements().wallet.create({ /* options */ });
          wallet.create('activity', {
            onActivitySelected: (payload) => console.log("activitySelected", payload),
            onDateRangeChanged: (payload) => console.log("dateRangeChanged", payload)
          }).mount('#wallet-activity');
        </script>
        ```
      </CodeGroup>
    </div>

    ## Props

    <ResponseField name="currency" type="string">
      Only show activity for this currency. Without it, activity from every currency is shown.
    </ResponseField>

    <ResponseField name="accessToken" type="string">
      A scoped token for the read. An account needs `company:balance:read`, and a user needs `user:balance:read`. Mint it on your server with `POST /v1/access_tokens`. Without it the read uses the viewer's own session, which only works same-origin.
    </ResponseField>

    <ResponseField name="canOpenCardTransactionDetails" type="boolean">
      When off, card transaction rows render inert (no click, no hover) instead of firing `activitySelected`. Use it when you know the viewer isn't allowed to see those details. Defaults to `true`.
    </ResponseField>

    ## Events

    Pass a callback in the create options (or React prop) to receive these.

    ### `onActivitySelected`

    A row was clicked. Open your own detail view or route for it; the element never navigates.

    **Signature:** `((payload: { activity: LedgerActivity; }) => void)`

    ### `onDateRangeChanged`

    The viewer changed the date filter through the filter row rather than through `setDateRange`. Clear any range selection of your own, like a chart brush, that no longer matches.

    **Signature:** `((payload: { dateRange: { start: string; end: string; } | null; }) => void)`

    ### `onLoaderStart`

    Fired the moment the element's own loading skeleton has painted inside its frame. This is the earliest point a consumer-managed loading state can hand off without ever exposing a blank. Always precedes `onReady`; most consumers only need `onReady`.

    **Signature:** `(() => void)`

    ### `onReady`

    Fired once the element has booted and painted its first complete frame.

    **Signature:** `(() => void)`

    ### `onError`

    Fired when the element fails to load or crashes; the element shows its own error fallback. `message` is human-readable; framework refusals also carry `code` (e.g. `HOST_SOURCE_FAILED`, with `sourceKey` naming the failed hostState key) so hosts can switch on codes, never message text.

    **Signature:** `((e: { message: string; code?: string | undefined; sourceKey?: string | undefined; }) => void)`

    ## Methods

    Call these on the element handle, which is the return of `create` (vanilla) or the component `ref` (React).

    ### `refresh`

    Re-fetch the account's activity from the first page. Call it after a mutation made elsewhere (a deposit, a send) so the list reflects it without waiting for the cache to expire.

    **Signature:** `() => Promise<void>`

    ### `setDateRange`

    Set the date filter from outside the element, for example wiring a chart brush selection into the list. Pass `null` to clear it.

    **Signature:** `(input: ActivityDateRangeOverride | null) => Promise<void>`

    ### `mount`

    Place the element on the page: appends its container to `target` (a CSS selector or an element) and starts loading. Nothing renders until this is called. React consumers never call it; the component mounts itself.

    **Signature:** `(target: string | HTMLElement) => void`

    ### `destroy`

    Remove the element from the page and release its frame and subscriptions. Safe to call more than once. React consumers never call it; unmounting the component does it.

    **Signature:** `() => void`

    ### `update`

    Change this element's consumer props after mount, and it re-renders with the merged props. React consumers never call it; updating the JSX props does the same.

    **Signature:** `(options: Partial<ActivityElementProps>) => void`

    ## Styling

    Each part below is a stable class name, safe to depend on. Restyle a part by mapping its class to a **style declaration object** under `appearance.classes` (properties camelCase or kebab-case, values as strings with units, the same shape as React's `style` prop). The element renders in its own frame, so page stylesheets can't reach it: these declarations are sanitized against a safe-property allowlist and injected inside the frame for you.

    | Class                   | Targets                                                          |
    | ----------------------- | ---------------------------------------------------------------- |
    | `.whop-ActivityRow`     | One ledger activity row — its icon, title, timestamp, and amount |
    | `.whop-ActivitySurface` | The activity feed — title, filters, and up to every fetched row  |

    ```ts theme={null}
    const wallet = whop.wallet.create({
      appearance: {
        classes: {
          'whop-ActivityRow': { borderRadius: '8px', fontWeight: '600' },
          'whop-ActivitySurface': { borderRadius: '8px', fontWeight: '600' }
        }
      }
    });

    // restyle live at any point, the same shape through update()
    wallet.update({
      appearance: { classes: { 'whop-ActivityRow': { fontWeight: '700' } } }
    });
    ```

    In React, pass the same object as the `appearance` prop on `<Wallet>`; `appearance` also applies globally at `WhopElements({ appearance })`.
  </Tab>

  <Tab title="Swift">
    <div style={{ display: "flex", gap: "2.5rem", alignItems: "flex-start", flexWrap: "wrap" }}>
      <div style={{ flex: "1 1 24rem", minWidth: 0 }}>
        `ActivityElement` shows an account's ledger activity: payments, payouts, transfers, swaps and disputes, newest first. It loads its own data and pages as the viewer scrolls, so the only thing you have to pass in is the account id. Pass a closure as well and you get the row the viewer tapped. It never navigates, so routing stays yours.

        ### Usage

        ```swift theme={null}
        import SwiftUI
        import WhopElements

        // WhopSDK.configure(tokenProvider:) runs once at app launch. See Getting started.
        struct ActivityScreen: View {
            var body: some View {
                ScrollView {
                    ActivityElement(
                        accountId: "biz_xxxx"
                    ) { activity in
                        print(activity.title, activity.amount)
                    }
                    .padding()
                }
            }
        }
        ```

        ### Parameters

        <ResponseField name="accountId" type="String" required>
          Whose ledger to list, given as a company's `biz_…` tag or a user's `user_…` tag. Both work directly; the feed reads the tag without resolving a ledger first.
        </ResponseField>

        <ResponseField name="showsTitle" type="Bool">
          Shows the built-in "Activity" heading. Defaults to `true`. Turn it off when your screen already has one.
        </ResponseField>

        <ResponseField name="onActivitySelected" type="((WalletActivity) -> Void)?">
          Called with the row the viewer tapped. The view never navigates, so route to your own detail screen.
        </ResponseField>

        ### `WalletActivity`

        What a selection hands back:

        * `id: String`: stable per row
        * `title: String` / `subtitle: String`: the row's two lines
        * `amount: Decimal`: signed, negative for money out
        * `currencyCode: String`
        * `postedAt: Date`
        * `isIncoming: Bool`

        ### States

        Shows a skeleton list while the first page loads, `No activity found` when the ledger has no movements, and the failure message with a `Try again` button when the read fails. If a later page fails, the rows already loaded stay on screen and a `Try again` appears at the bottom.

        ### Good to know

        * **Give it a scroll container.** The feed is a `LazyVStack` and pages as its last row appears, so outside a `ScrollView` it renders page one and never loads another.
        * Takes `biz_…` and `user_…` tags directly, so unlike the other wallet views it never has to resolve a ledger first.

        <Note>
          Authentication is set once for every element, not per element, so `WhopSDK.configure(tokenProvider:)` at app launch is enough. A view mounted before it lands renders a spinner and picks the token up when it arrives. See [Getting started](/elements/upcoming/getting-started) for the token provider. Theme with the `.whopTheme(_:)` modifier.
        </Note>
      </div>

      <div style={{ flex: "0 1 22rem", width: "22rem", maxWidth: "100%" }}>
        <div style={{ position: "sticky", top: "5rem" }}>
          <iframe src="https://app.revyl.ai/embed/7289cae9-8eaf-4d14-a771-fb05c879c6c5?accentColor=%23111" title="ActivityElement running on an iPhone simulator" loading="lazy" className="whop-ios-simulator" style={{ width: "100%", aspectRatio: "390 / 800", border: 0, display: "block", borderRadius: "1.25rem" }} allow="fullscreen; clipboard-read; clipboard-write" />
        </div>
      </div>
    </div>
  </Tab>
</Tabs>
