shadcn.io is not affiliated with official shadcn/ui
Billing History Timeline Block for Ark UI
Four invoices on a rail, each opening in place to its payment attempts, reminder emails and the day it was paid, with totals for invoiced, collected and still owed and a Retry payment button on the overdue one.
Installation
Overview
See what happened to every invoice with this billing-history timeline for React with Ark UI. It is written for Remitwell, a fictional invoice-collection service. A hairline frame with a plus mark on each corner holds a band of three figures (invoiced, collected and still owed), then four invoices on a rail, newest first. Each row gives the invoice number in monospace, the amount, Paid or Overdue in words and a derived summary: "Paid 6 days after the due date", or "Overdue 10 days, due 8 Oct" counted from 18 Oct. Pressing a row opens a small rail of the attempts made against it: a failed attempt with the card's reason, a reminder email, the next attempt and the day it was paid. On the overdue invoice, "Retry payment" queues a retry in this demo and says that no card was charged. It is built from Guides and Button with a native button per row and CSS for the rail. It is not the invoice table: it shows the collection process, not the list of documents. The invoices are example data.
FAQ
How do I change the invoices?
Edit INVOICES at the top of the file: an id, the amount in integer cents, the ISO due date and the steps, oldest first, each a kind ("paid", "failed" or "email"), a day and a line of text. The totals, the "days after the due date" and the overdue days are derived from them and from AS_OF.
How does it decide an invoice is paid?
An invoice is paid when one of its steps has the kind "paid". The days late are that step's date minus the due date; an open invoice counts its overdue days from the fixed AS_OF date, never from the clock, so the page reads the same on every load.
What stays in the page, and how do I make Retry real?
The open rows and the queued retry stay in the page; nothing is saved or sent and no card is charged. To wire it, call your billing API from queue(), add the new attempt to the invoice's steps when it answers, and keep the status line as the place where the result is spoken.
Is it accessible?
Each row is a real button with aria-expanded and aria-controls, the closed attempts are removed from the tab order, and Paid or Overdue is a word beside the node, never only its fill. Failed, email and paid steps each carry text as well as an icon. The foot is a polite status line.