webdevpuneet.com

Transaction List — Free HTML CSS JS Snippet

Transaction List · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

<div class="txl">
  <div class="txl-head">
    <h3>Transactions</h3>
    <div class="txl-tabs" id="txlTabs">
      <button class="active" data-f="all" type="button">All</button>
      <button data-f="in" type="button">Income</button>
      <button data-f="out" type="button">Spending</button>
    </div>
  </div>
  <div class="txl-list" id="txlList"></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Day grouping with net totals
A single-pass fold buckets rows by day and accumulates each day's net, recomputed per filter so headers match visible rows.
Sticky date headers
position: sticky pins the current day label inside the scroll area with proper hand-off to the next header.
Intl currency formatting
Intl.NumberFormat handles signs, separators, and decimals; two arguments switch the whole list to another currency.
Income / spending filter tabs
Pill tabs re-render through a sign predicate on amount, with an explicit empty state when nothing matches.
Category icon system
Inline SVGs with per-transaction tint and stroke colours — green income, per-category hues, no icon font.
Staggered row entrance
Rows fade and rise with 40ms animation-delay steps on every render for a polished filter switch.
Tabular numerals
font-variant-numeric: tabular-nums vertically aligns amount digits down the column.
Detail-ready rows
Rows are hover-highlighted pointer targets ready for a click handler that opens a transaction detail view.

About this UI Snippet

Transaction List — Banking-App Transaction History with Day Grouping and Filters

Screenshot of the Transaction List snippet rendered live

The grouped transaction list is the core screen of every banking and fintech app — Revolut, Monzo, Cash App, and every neobank render payments the same way: rows with a category icon, merchant name, and signed amount, bucketed under sticky date headers with a per-day total. This component builds that exact pattern in HTML, CSS, and vanilla JavaScript, with income/spending filter tabs, correct currency formatting via Intl.NumberFormat, and staggered entry animations.

Grouping rows into day buckets

The data is a flat TXNS array; render() folds it into day buckets in a single pass. It walks the filtered rows in order and either appends to the current bucket or starts a new one when the day changes, accumulating a running net total per bucket as it goes. Because the grouping happens at render time rather than in the data, the same array can be re-filtered (all / income / spending) and regrouped instantly — the day headers and their net totals always reflect exactly the rows on screen, so switching to "Spending" shows each day's total outflow rather than the mixed net.

Sticky date headers

Each day header uses position: sticky; top: 0 inside the scrollable list, so as you scroll, the current day's label pins to the top and is pushed away by the next one — the signature interaction of mobile banking feeds. The header carries the day's net total right-aligned, formatted with a leading + when positive. Sticky positioning needs an opaque background (#fff here) or rows would show through while pinned.

Real currency formatting

Amounts never go through manual string concatenation. new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }) handles the dollar sign, thousands separators, two-decimal padding, and — crucially — negative formatting, so -84.12 renders as -$84.12 without sign-juggling code. Change two constructor arguments to localise the whole list to EUR, GBP, or INR. The amounts also set font-variant-numeric: tabular-nums so digits align vertically down the column, which matters as soon as two rows sit next to each other.

Category icons without an icon font

Each transaction names an icon key, a pastel tint for the 40px rounded square, and a stroke colour, and the ICONS map holds six inline SVGs (bank, coffee, cart, refund, car, subscription). Inline SVG keeps the snippet dependency-free and lets the stroke colour be injected per transaction, so income rows read green and each spending category gets its own hue — the visual scanning aid that makes these lists parseable at a glance.

Filter tabs and the empty state

The pill tabs write a filter key (all, in, out) and re-render; filtering is a one-line predicate on the sign of amount. If a filter produces no rows the list renders an explicit empty-state message instead of a blank area — a small detail that separates production UI from demos. Rows animate in with a 40ms staggered translateY fade (the same cascade technique as the animated list), so every filter switch feels responsive rather than jarring.

Customisation

Feed TXNS from your API (map day from a date formatter like Intl.DateTimeFormat with relative labels for today/yesterday), extend ICONS with your categories, and adjust the max-height to fit your layout. Clicking a row is already wired as a pointer target — attach a handler to open a detail sheet or a bottom sheet with the full transaction.

Step by step

How to Use

  1. 1
    Paste the HTML, CSS, and JSA card renders with eight transactions grouped under Today, Yesterday, and Jun 30, each day header showing its net total on the right.
  2. 2
    Scroll the listDate headers stick to the top of the scroll area and hand off to the next day's header — the classic banking-feed interaction.
  3. 3
    Switch filter tabsClick Income or Spending — the list regroups instantly, day totals recompute for the visible rows, and rows cascade back in with a stagger.
  4. 4
    Check the empty stateFilters that match nothing show a "No transactions" message instead of a blank panel.
  5. 5
    Swap in your dataReplace the TXNS array with API data — name, category, time, signed amount, and an icon key per row; add categories to the ICONS map.
  6. 6
    Localise the currencyChange the Intl.NumberFormat locale and currency arguments to render EUR, GBP, INR, or any other currency correctly.

Real-world uses

Common Use Cases

Banking and neobank apps
The account activity screen — pair it with a wallet card header and a budget tracker card.
Expense and budgeting dashboards
Show categorised spending with day totals next to a donut chart category breakdown.
Freelance and invoicing tools
List payments in and out alongside an invoice preview — income rows read green automatically.
E-commerce order history
Reuse the grouped-by-day pattern for orders, refunds, and store credit events.
Crypto and investment trackers
Render buys, sells, and dividends with per-day net flow in the sticky headers.
Learning the grouping pattern
A reference for render-time bucketing, sticky section headers, and Intl.NumberFormat done right.

Got questions?

Frequently Asked Questions

render() walks the filtered array in order and starts a new bucket whenever the day field changes, pushing rows into the current bucket and adding each amount to that bucket's net. Grouping at render time (instead of storing pre-grouped data) means the same flat array serves every filter — switch to Spending and the buckets and totals are rebuilt for just the negative rows in one pass.

A sticky element stays in the document flow, so rows scroll underneath it while it is pinned. Without an opaque background the pinned header would visually overlap the row text sliding beneath it. Setting background: #fff (matching the card) plus a z-index makes the hand-off clean — the next day's header pushes the previous one out of view, which is the behaviour users know from banking apps.

Map your API timestamps through a labeller before rendering: compare each date to the current day and emit "Today"/"Yesterday" for the last 48 hours, otherwise format with Intl.DateTimeFormat (for example { month: 'short', day: 'numeric' } gives "Jun 30"). Because grouping only compares consecutive day strings, any labelling scheme works as long as rows arrive sorted newest-first.

Edit the one Intl.NumberFormat line: new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }) renders 1.234,56 € with German separators; 'en-IN' with INR gives ₹ and lakh/crore grouping. Every amount in rows and day headers flows through this single formatter, so there is exactly one place to localise — never concatenate currency symbols manually.

Keep the transactions array and active filter in state, compute the day buckets with the same fold inside a useMemo / computed / getter, and render nested maps (days → rows) instead of innerHTML strings. The filter tabs become buttons that set state. Sticky headers, tabular-nums, and the stagger animation are pure CSS and port unchanged; set each row's animationDelay inline from its index.