Lists & feeds
Three distinct list patterns serve different data densities. The khata ledger row is dense and monospaced — built for financial data. The activity feed is conversational and prose-rich. The expense card list groups by date and leads with context. Each has its own empty state.
Transaction ledger (khata rows)
The ledger row uses a four-column grid: 48px (time) · 72px (amount) · 1fr (label) · auto (tag). All font-family is JetBrains Mono — the one exception is the label column, which uses Onest body to keep long names legible.
Amounts are right-aligned in their column for vertical scanning. Positive amounts (money received / settled) are teal with a + prefix. Pending amounts are ink-primary — no coral, no red. Coral is reserved for the owe card in the settle flow.
Row dividers are dashed — border-bottom: 1px dashed var(--ink-line). This is the khata ledger language, not a generic separator. It differentiates the ledger from any other list in the product.
Smooth rolling queue
Live-updating ledgers (real-time activity during a group spend) use a single translateY animation on the track container rather than individual row animations. The whole stack slides up one row-height, then snaps back while swapping rows.
The top and bottom edges are masked with 36px paper→transparent gradient overlays. Rows fade in and out at the mask boundary, not at random per-row timings.
// Rolling queue logic function addRow(track, rowEl) { track.appendChild(rowEl); const rowH = track.firstElementChild.offsetHeight; track.style.transform = `translateY(-${rowH}px)`; track.addEventListener('transitionend', () => { track.classList.add('snap'); track.firstElementChild.remove(); track.style.transform = 'translateY(0)'; requestAnimationFrame(() => { requestAnimationFrame(() => track.classList.remove('snap')); }); }, { once: true }); }
Activity feed
Activity feed rows are conversational: avatar left, prose description center, amount right. The prose uses bold for person names, plain text for actions. Amounts use coral for what you owe (−₹) and teal for what you receive (+₹). Non-financial events (someone joined, a group was renamed) show a muted dash in the amount column.
Timestamp: JetBrains Mono 11px, muted. Relative time ("2m ago", "Yesterday") is preferable to absolute timestamps on the list — switch to absolute on tap to view the detail.
Expense list (grouped by date)
Expense cards group by date, with date headers in JetBrains Mono uppercase. Each card shows: category emoji (in a warm-surface rounded square), expense name (Onest 600), payer + avatar group + count (muted), total amount (Mono 700), and your share with status.
"You owe ₹X" is coral. "You get ₹X" is teal. "Settled ✓" is teal muted — it's resolved, not requiring action. Never show "you owe" and "settled" in the same color family.
The avatar group in the meta line uses 20px avatars with 6px negative margin overlap and a paper border. Show max 4 avatars; if more, show "+2" in the same avatar style.
Empty states
Empty states use a large muted glyph (35% opacity), a short title, one line of brand-voice copy, and an optional action button. The title and copy should be specific to the context — not generic "Nothing here yet."
The "all settled up" empty state has no action button. That's the point — there's nothing to do. Don't fill the silence with a prompt.