Dots

Transaction Row

The single row for a transaction, order or request, with a labelled group, an optional total and loading skeletons.

Today

Total -$27.90

Yesterday

Total +$3,175.00
"use client"

import {

The TransactionRow component shows one movement of money: who or what it was, the time and context with an inline status, and a signed amount. It has no leading icon. Group rows by day or state with TransactionGroup. Use it for activity feeds and history lists; for a row with media and actions, use Item, and for many columns, use a table.

Installation

pnpm dlx shadcn@latest add @dots/transaction-row

Usage

import {
  TransactionGroup,
  TransactionRow,
} from "@/components/ui/transaction-row"
<TransactionGroup label="Today" total={-27.9}>
  <TransactionRow
    title="Corner market"
    description="14:32 · Groceries"
    amount={-23.4}
  />
  <TransactionRow
    title="Coffee Point"
    description="08:14 · Eating out"
    amount={-4.5}
  />
</TransactionGroup>

Composition

Use the following composition to build a list of transactions:

TransactionGroup
├── TransactionRow
└── TransactionRow

While the list loads, render the skeletons in its place:

TransactionGroupSkeleton
└── TransactionRowSkeleton

Features

  • Signed amount through MoneyAmount, with the sign taken from amount or set by direction
  • Five statuses: completed shows no marker, pending and refunded add a quiet one, failed and cancelled also strike the amount
  • A plain row, a link with href, or a button with onPress, each as one pressable element
  • masked hides the digits when the user hides their balance
  • A group heading with an optional total, or a hidden heading, or an outside heading
  • Skeletons on the row's own metrics for loading states

Statuses

Set status to show where the transaction stands. The marker follows the description.

Corner market14:32 · Groceries-$23.40
City transit13:05 · Transport, Pending-$2.75
Online bookstore11:48 · Shopping, Failed-$41.90
Concert tickets10:20 · Entertainment, Cancelled-$120.00
Running shoes09:12 · Shopping, Refunded+$89.99
"use client"

import { TransactionRow } from "@/components/ui/transaction-row"

Direction

A negative amount is money out and a positive one is money in. Set direction when your data stores amounts without a sign.

Salary09:00 · Income+$3,200.00
Rent08:30 · Housing-$1,450.00
Maria LopezYesterday · Transfer+$60.00
Daniel KimYesterday · Transfer-$25.00
"use client"

import { TransactionRow } from "@/components/ui/transaction-row"

Pressable rows

Pass href to make the row a link, or onPress to make it a button that opens a details sheet. isDisabled works on both.

Coffee Point08:14 · Eating out-$4.50

Press a row to open its details.

"use client"

import * as React from "react"

Group labels

Inside a titled section, pass the title's id as aria-labelledby instead of label. Use hideLabel when the screen title already says what the list is.

Recent card payments

  • Corner market14:32 · Groceries-$23.40
  • Coffee Point08:14 · Eating out-$4.50

Upcoming payments

  • Rent1 Dec · Scheduled, Pending-$1,450.00
"use client"

import * as React from "react"

Masked

Set masked on the rows and the group to replace the digits with dots.

Today

Total Amount hidden
  • Corner market14:32 · GroceriesAmount hidden
  • Coffee Point08:14 · Eating outAmount hidden
"use client"

import {

Loading

Use TransactionGroupSkeleton and TransactionRowSkeleton while the data loads. Set media when the real rows lead with an avatar.

Loading activity
"use client"

import {

Accessibility

A row with href renders a React Aria Link and a row with onPress a React Aria Button, so the whole row is one focusable element with press, keyboard and disabled handling. Without either, it is plain content.

Row name

A pressable row is named by its text: title, description, status and amount, in that order. A comma is read between the description and the status. A static row has no role or name of its own; its text is read as plain content in the same order. Keep the title short and specific, such as a merchant or a person's name.

Groups

TransactionGroup renders a list named by its heading, and wraps each child in a list item. The heading is an h3 by default; set headingLevel to fit the page outline. The total is read with totalLabel before it, "Total" by default.

Loading

The skeletons are hidden from assistive tech. Mark the loading container with aria-busy="true" and announce loading once, not per row, with a role="status" element outside it.

Meaning beyond color

failed and cancelled strike the amount, and every status other than completed is also written as text.

API Reference

TransactionRow

Without href or onPress it accepts all props of <div>. With href it accepts React Aria Link props, and with onPress React Aria Button props.

PropTypeDefaultDescription
titleReactNode-Who or what the money went to or came from. Required.
descriptionReactNode-Time and context. The status marker follows it.
amountnumber-Amount in major units. Negative is money out. Required.
currencystring"USD"ISO 4217 code.
localestring"en-US"Locale for the amount.
status"completed" | "pending" | "failed" | "cancelled" | "refunded""completed"Where the transaction stands.
direction"in" | "out"-Overrides the sign of amount.
maskedbooleanfalseReplace the digits with dots.
hrefstring-Makes the whole row one link.
onPress(e: PressEvent) => void-Makes the whole row one button. Not with href.
classNamestring-Additional classes to apply to the row.

TransactionGroup

A labelled list of rows. Accepts all props of <div> except title. Pass either label or aria-labelledby.

PropTypeDefaultDescription
labelReactNode-What the rows share, such as "Today". Rendered as a heading.
hideLabelbooleanfalseKeeps the heading for screen readers only.
aria-labelledbystring-Id of an outside heading that names the list. Not with label.
totalnumber-Sum for the group, shown next to the label.
currencystring"USD"ISO 4217 code for the total.
localestring"en-US"Locale for the total.
totalLabelstring"Total"Read before the total by screen readers.
maskedbooleanfalseReplace the total's digits with dots.
headingLevel2 | 3 | 4 | 5 | 63Heading level of the label.

TransactionRowSkeleton

A placeholder with the row's metrics. Accepts all props of <div> except children.

PropTypeDefaultDescription
mediaboolean | "circle" | "square"falseA leading placeholder for rows with an avatar. true is a circle.

TransactionGroupSkeleton

A placeholder for a whole group. Accepts all props of <div> except children.

PropTypeDefaultDescription
rowsnumber3How many row placeholders to show.
totalbooleanfalseAdds a placeholder for the total.
mediaboolean | "circle" | "square"falsePassed to each row placeholder.