Stripe
Billing
Invoices, subscriptions, customers and balance transactions
4 read-only object families
Inspect sampleDesign system
Every part Marquee ships, rendered once with real content: 18 sections, 16 primitives, 67 tokens per mode. Four of the primitives are tt-ui parts — the library owns Escape, the focus return and the submit flow; this theme owns every class string, glyph and word in them.
src/components · src/components/ui · src/components/ui/_tt · src/theme.css · src/fonts.ts
66 variables per mode in src/theme.css: 37 colour, 25 type, four shape, weight and motion. These chips read the active mode tokens.
--background
--foreground
--card
--card-foreground
--primary
--primary-foreground
--secondary
--secondary-foreground
--muted
--muted-foreground
--accent
--accent-foreground
--primary
--primary-hover
--primary-active
--surface-tint
--font-display · Bricolage Grotesque
Aa Bb Cc 0123
--font-text · Schibsted Grotesk
Aa Bb Cc 0123
--font-mono · Spline Sans Mono
Aa Bb Cc 0123
--radius 0.875rem · --radius-inner 0.625rem
--border · hairlines never carry structure alone
--duration-ticker 90s · the one moving part
The marquee drift. Everything else is placed.
Each section rendered once, at the width and with the content it ships with.
Sticky nav: wordmark, four links, the mode toggle and the primary action. Below md it collapses into a JS-free details menu.
The centered manuscript and the reconciliation surface: MRR at display size, twelve months of bars, the wire, and the match verdict.
Subscription analytics
Recur reads your billing feeds, matches every invoice to the ledger, and publishes metrics your accountant will sign.
No card · free to $2,500 MRR · export everything, any time
recur · the close · March
Δ $0.00Monthly recurring revenue
$128,410
+6.1% vs February
The wire
2,412 of 2,412 normalized records matched
Δ $0.00THE SIGNATURE — a full-bleed strip of timestamped events drifting past. Three feeds: wire, match, wins. A Pause switch in the feed cell; pauses on hover and focus too; static under reduced motion. Shown here on the wire feed.
The wire
Eight available sources on a hairline grid, each derived from the canonical fixture.
Sources
Review the eight sample adapters, then inspect objects, permissions and mapping evidence on the integrations page.
Stripe
Billing
Paddle
Merchant of record
Chargebee
Billing
Lemon Squeezy
Merchant of record
App Store
Mobile
Play Billing
Mobile
Braintree
Billing
Manual invoices
Manual
Local search and category filtering with available, sample-authorized, partial, planned and recoverable no-result states. No request is simulated.
Eight adapters are available in this sample; planned sources stay visibly unavailable.
9 sources
Stripe
Billing
Invoices, subscriptions, customers and balance transactions
4 read-only object families
Inspect samplePaddle
Merchant of record
Checkout invoices, adjustments and subscription changes
Merchant-of-record normalization
Connect providerChargebee
Billing
Subscriptions, invoices, credit notes and customer records
Recurring billing objects
Connect providerLemon Squeezy
Merchant of record
Orders, subscriptions, refunds and tax-inclusive totals
Merchant-of-record normalization
Connect providerApp Store
Mobile
In-app renewals, proceeds and subscription status changes
Mobile subscription receipts
Connect providerPlay Billing
Mobile
Google Play subscription purchases, renewals and reversals
Mobile subscription purchases
Connect providerBraintree
Billing
Card and PayPal transactions, disputes and settlements
Payment and settlement objects
Connect providerManual invoices
Manual
CSV or email-in invoices with explicit ledger mapping
77 sample records, 1 variance
Start setupRevenueCat
Mobile
No adapter or sample connection is included in this release
Planned, not available
No adapter or action included
Stripe
Billing
Invoices, subscriptions, customers and balance transactions
4 read-only object families
Inspect sampleManual invoices
Manual
CSV or email-in invoices with explicit ledger mapping
77 sample records, 1 variance
Start setupRevenueCat
Mobile
No adapter or sample connection is included in this release
Planned, not available
No adapter or action included
No source matches “warehouse ledger mirror”
Query and category stay visible; Clear filters returns focus to search.
The Stripe sample expanded through read-only objects, sync health, run stages, exact mappings and a frontend-only setup checklist.
Sample connection
Inspect what the sample reads, when it ran, and what blocks publication.
Healthy sample
Stripe is sample-authorized and 5 minutes old at evaluation.
1,904
Retained sample records
31 Mar 2026, 14:24 UTC
Last sample sync
As of 31 Mar 2026, 14:29 UTC · 30-minute rule · 1,904 new records accepted
0 identity gaps; the March statement published after variance resolution
Automated identity match 3m 41s · 14 currencies · ECB daily rates
Identity match
2,412 of 2,412
Every source record has an invoice identity; this does not clear value differences.
Variance review
Invoice 8843 · $84.00
Manual invoices is linked, but its ledger value needs review.
Resolution
31 Mar 2026, 14:23 UTC
The ledger mapping is corrected before publication.
Published
Δ $0.00
March 2026 closes after the exception is resolved.
Long source paths remain exact; ignored fields never become zero values.
| Source field | Recur concept | Status | Rule |
|---|---|---|---|
| Source fieldinvoice.id | Recur conceptInvoice identity | StatusMapped | RuleExact source identifier |
| Source fieldcustomer.id | Recur conceptSubscriber identity | StatusMapped | RuleStable customer join |
| Source fieldsubscription.current_period_end | Recur conceptService period end | StatusMapped | RuleUTC timestamp |
| Source fieldbalance_transaction.exchange_rate | Recur conceptExchange rate | StatusMapped | RuleCompared with ECB daily rate |
| Source fieldinvoice.metadata.ledger_account_code | Recur conceptLedger account | StatusMapped | RuleMapped to SaaS subscription revenue |
| Source fieldcustomer.description | Recur conceptNone | StatusIgnored | RuleNot used in reconciliation |
Delayed sample
The sample is 55 minutes old, beyond the 30-minute rule.
1,904
Retained sample records
31 Mar 2026, 13:34 UTC
Last sample sync
As of 31 Mar 2026, 14:29 UTC · 30-minute rule · 0 new records accepted
Keep the prior sample visible; your backend should retry the sync
Partial sample
Manual invoices are identity-matched, but one value needs review.
77
Retained sample records
31 Mar 2026, 14:19 UTC
Last sample sync
As of 31 Mar 2026, 14:29 UTC · 30-minute rule · 77 new records accepted
Invoice 8843 carries an $84 variance until the mapping is resolved
Failed sample
The sample request failed before new records were accepted.
1,904
Retained sample records
31 Mar 2026, 13:34 UTC
Last sample sync
As of 31 Mar 2026, 14:29 UTC · 30-minute rule · 0 new records accepted
Retain the prior sample and wire retry behavior in your backend
Read-only boundary
Illustrative Recur permission labels are visible; authorization and retries belong to your backend.
Manual invoice 8843 stays blocked until its ledger account is mapped.
| Source field | Recur concept | Status | Rule |
|---|---|---|---|
| Source fieldmanual_invoice.invoice_number | Recur conceptInvoice identity | StatusMapped | Rule77 sample records are identity-linked |
| Source fieldmanual_invoice.ledger_account_code | Recur conceptLedger account | StatusNeeds mapping | RuleMap invoice 8843 before publication |
| Source fieldmanual_invoice.internal_review_note | Recur conceptNone | StatusIgnored | RuleNot used in reconciliation |
The recovery instruction and frontend boundary remain visible together.
Provider objects reviewed
CompleteManual invoice columns are documented
Ledger account missing
BlockedMap manual_invoice.ledger_account_code before publication
Backend connection added
Frontend onlyUpload, retries and persistence belong to your backend
Six metric cards, each opening with a real sample figure; the delta card carries the chip.
The close
Every number below is computed from matched invoices, so the board pack and the books agree.
MRR + movements
$128,410
New, expansion, contraction and churn split out, month by month.
Net revenue retention
100.6%
Opening MRR carried through expansion, contraction and churn, with the customers driving it named.
Revenue churn
1.4%
Churned MRR against the opening balance; early-warning flags on usage cliffs.
Records matched
2,412
Three sources, one invoice identity per line before any value is reviewed.
Forecast
$1.54M ARR
A twelve-month run-rate from matched invoices, not from optimism.
The delta
Δ $0.00Every metric ties to the ledger; anything that will not match gets flagged.
Wire, match, close as a ruled 7/5: the three steps beside a match run with one flagged row.
How it works
Three steps between a charge landing and a statement you can sign.
Every charge, refund and credit note arrives from your billing feeds within minutes.
Each event is paired with its invoice and its ledger entry. Anything that will not pair gets flagged, not smoothed over.
When the delta reads zero, the month's statement publishes itself. Your accountant gets a trail, not a spreadsheet.
Match run 442
Identity match · 3m 41s
| Invoice | Amount | Delta |
|---|---|---|
| 8839 | $1,240.00 | Δ $0.00 |
| 8840 | $88.00 | Δ $0.00 |
| 8841 | $429.00 | Δ $0.00 |
| 8842 | $1,188.00 | Δ $0.00 |
| 8843 | $536.00 | Δ $84.00 |
1 value variance · identity matched · publication waits
Named outcome quotes: the lead quote at display size, two supporting, each beside the figure it claims.
Customers
Three teams, three closes, outcomes they put their names on.
“Our close was nine days of spreadsheet archaeology. With Recur it is one afternoon, and the auditors take the statement as-is.”
9 days → 1 afternoon
monthly close
Maren Ilves
Finance lead, Standfirst
“Recur flagged a cohort going quiet 41 days before the cancellations landed. We called them; most stayed.”
Dele Akande
Founder, Loam
41 days
earlier churn warning
“The board pack is now an export of the close. Nobody re-derives MRR in a slide the night before.”
Sofia Brandt
COO, Tidemark
0
hand-built board slides
Three surfaces in alternating 7/5 splits, wire to match to close, ending in the Δ $0.00 band. Shown without its page lead; /product hands one in.
01 · The wire
Charges, refunds, upgrades and credit notes land within minutes of the processor, normalized across eight sources and fourteen currencies.
The wire · today
02 · The match
Every event pairs with its invoice and ledger entry. A row that will not pair is flagged and assigned, so exceptions get owners instead of burial.
Match run 442
Δ $84.002,412 identity-matched · 1 value variance under review
03 · The close
When the delta reads zero the statement publishes: MRR movements, retention, cohorts and forecast, each figure tied to matched invoices.
The close · March 2026 statement
Δ $0.00
The state your month ends in. Anything else is a flag with an owner.
A client island: the billing switch does the monthly/annual arithmetic. One meter, three plans, risk reversal at the price.
For a product with its first revenue.
$0forever
For a team that closes every month.
$49/month
For finance teams with auditors.
$199/month
30 days of Growth free, no card · cancel any month · export everything, any time
MRR tracked is the meter. Seats never are.
Six pricing objections as native details disclosures on a 5/7 split. The +/− swap is instant and shape-based.
Questions
The six questions every finance lead asks; answers a salesperson would give you anyway.
One close told in full at prose measure, figures in a narrow rail, the pull quote signed. Shown without its page lead.
Case study
Standfirst sells newsroom analytics to regional publishers. Sixty-one people, London and Tallinn, and revenue arriving three ways: Stripe for self-serve, Chargebee for the annual contracts, and invoices the sales team still sends by hand.
The close took nine days. Finance exported each source to its own spreadsheet, matched rows by eye, and every board pack carried an MRR figure somebody had re-derived the night before. Two of those figures disagreed in the same quarter. The auditors noticed.
Recur read all three feeds on a Tuesday. The first match run finished in 54 minutes and surfaced 17 rows that would not pair; twelve were credit notes booked against the wrong month, five were duplicate manual invoices nobody had caught. Finance cleared the queue by Thursday.
Since then the statement publishes when the delta reads zero. The board pack is an export of the close, the auditors take the trail as-is, and the March figure everyone quotes is the March figure the ledger holds.
“Our close was nine days of spreadsheet archaeology. With Recur it is one afternoon, and the auditors take the statement as-is.”
Maren Ilves
Finance lead, Standfirst
The go-live wall: the five companies the wins rail announces, plus the action cell.
Go-lives
What each team runs, and what changed after the first clean match.
Standfirst
newsroom analytics
close now one afternoon
Loam
sensor platform for growers
churn flagged 41 days early
Tidemark
marina bookings
board pack from the close
Bellwether Labs
clinical trial software
3 sources, one ledger
Quartermaster
fleet maintenance
first clean close in 2 years
Your close next
30 days of Growth free, no card
Start the matchA client island: validation, focus recovery and a designed success state. Specimen mode renders h2; /signup keeps the default h1.
Start free
30 days of Growth free, no card. Free to $2,500 MRR after that.
The capture surface, put where the reader needs it: the integrations no-result state links here. Three controls, one of them optional, on ui/form — so a refusal can reach the summary rather than always landing on one control.
Sources
Name the source that bills you; we answer with where it sits in the queue.
The closing band: one primary action, cost, risk and effort answered beneath it.
Connect a billing source and watch the first match run before the coffee is done.
The ui/ parts every section is built from, in one frame.
ui/button · variant
ui/button · size + icon trim
Three heights, 36 / 44 / 48px. The icon side trims 2px of padding because a glyph carries its own optical margin; the last control shows the trim against the one above it.
ui/button · disabled + pending
Disabled carries the DOM attribute, so it leaves the tab order and the press ramp is gone rather than dimmed. Pending takes the independent info family, reports aria-busy and says what it is doing; it never spins, because the ticker rail holds this theme’s whole ambient budget. Keyboard: neither is reachable by Tab, so a second submit is impossible from the keyboard as well as from the pointer.
ui/badge · ui/delta · ui/mode-toggle
Keyboard: the mode control is a real toggle button — Enter or Space flips it and aria-pressed reports the setting, not the next state. Badge and the delta chip carry their words as well as their tone, so a flagged figure survives a greyscale capture.
ui/logo · ui/section-heading
Recur
The close
Every number computed from matched invoices, so the board pack and the books agree.
ui/container · three measures
3xl · 768px — the case study and long-form answers
6xl · 1152px — every marketing section
7xl · 1280px — the rails and the widest tables
ui/eyebrow · ui/type
Subscription analytics
Sizes come from ui/type.ts only: caption 12 · small 14 · body 16 · lead 18 · d1 24 · d2 32 · d3 42.7 · d4 56.9. There is no size between the rungs.
ui/integration-card · availability states
Stripe
Billing
Invoices, subscriptions, customers and balance transactions
4 read-only object families
Inspect samplePaddle
Merchant of record
Checkout invoices, adjustments and subscription changes
Merchant-of-record normalization
Connect providerManual invoices
Manual
CSV or email-in invoices with explicit ledger mapping
77 sample records, 1 variance
Start setupRevenueCat
Mobile
No adapter or sample connection is included in this release
Planned, not available
No adapter or action included
ui/sync-health · health states
Healthy sample
Stripe is sample-authorized and 5 minutes old at evaluation.
1,904
Retained sample records
31 Mar 2026, 14:24 UTC
Last sample sync
As of 31 Mar 2026, 14:29 UTC · 30-minute rule · 1,904 new records accepted
0 identity gaps; the March statement published after variance resolution
Delayed sample
The sample is 55 minutes old, beyond the 30-minute rule.
1,904
Retained sample records
31 Mar 2026, 13:34 UTC
Last sample sync
As of 31 Mar 2026, 14:29 UTC · 30-minute rule · 0 new records accepted
Keep the prior sample visible; your backend should retry the sync
Partial sample
Manual invoices are identity-matched, but one value needs review.
77
Retained sample records
31 Mar 2026, 14:19 UTC
Last sample sync
As of 31 Mar 2026, 14:29 UTC · 30-minute rule · 77 new records accepted
Invoice 8843 carries an $84 variance until the mapping is resolved
Failed sample
The sample request failed before new records were accepted.
1,904
Retained sample records
31 Mar 2026, 13:34 UTC
Last sample sync
As of 31 Mar 2026, 14:29 UTC · 30-minute rule · 0 new records accepted
Retain the prior sample and wire retry behavior in your backend
ui/field-mapping-table · complete, unmapped, ignored
| Source field | Recur concept | Status | Rule |
|---|---|---|---|
| Source fieldinvoice.id | Recur conceptInvoice identity | StatusMapped | RuleExact source identifier |
| Source fieldcustomer.id | Recur conceptSubscriber identity | StatusMapped | RuleStable customer join |
| Source fieldsubscription.current_period_end | Recur conceptService period end | StatusMapped | RuleUTC timestamp |
| Source fieldbalance_transaction.exchange_rate | Recur conceptExchange rate | StatusMapped | RuleCompared with ECB daily rate |
| Source fieldinvoice.metadata.ledger_account_code | Recur conceptLedger account | StatusMapped | RuleMapped to SaaS subscription revenue |
| Source fieldcustomer.description | Recur conceptNone | StatusIgnored | RuleNot used in reconciliation |
| Source fieldmanual_invoice.ledger_account_code | Recur conceptLedger account | StatusNeeds mapping | RuleMap invoice 8843 before publication |
ui/connection-checklist · complete, blocked, frontend-only
Provider objects reviewed
CompleteManual invoice columns are documented
Ledger account missing
BlockedMap manual_invoice.ledger_account_code before publication
Backend connection added
Frontend onlyUpload, retries and persistence belong to your backend
Four parts whose behaviour and structure come from tt-ui and whose every class string, glyph and word are decided here.
One graph, two recipes: the FAQ's ruled row and the header's plate. Both are native details elements, so the answers are in the page with no script; both close on Escape and hand focus back to the summary, which the element alone never did.
closed · the FAQ row at rest, two rows for the rhythm
open · the second row open, and the header plate beside it
Keyboard: Enter and Space open a row, and Escape from inside the answer closes it and puts focus back on the question — stopped there, so a row inside a row closes only the inner one. Under prefers-reduced-motion the 240ms height tween is off and the answer is simply there.
The shell around a native control: a mono label, the optional marker in words, the read-only sentence, and one message line whose id never changes as it carries the description, the error or the receipt. Each state is drawn for the input, the select and the textarea, because the three used to disagree.
rest · the description, and the optional marker in words
The invite goes here
More sources connect after the first match
Published with the statement, plain text
invalid · the error replaces the description on the same id
That address is missing an @ or a domain. Check it and retry.
Pick the source that bills most of your revenue. More can follow.
Say what changed. One sentence is enough for the audit trail.
valid · a checked value, and a durable receipt under it
Domain answers, inbox accepts.
Stripe is authorized for read-only sample access.
Long enough to publish with the statement.
read-only · the value still matters, so it stays legible and selectable
Taken from the March run. Change it at the source.
A native select has no read-only, so it is out of play instead.
The statement is published; the note travels with it.
disabled · out of play — the neutral register, never the resting one faded
Connect a source before inviting anyone
Nothing to choose until a source authorizes
The March close has not run
One line at a time, error before receipt before description, on one id — so a reader who tabs in hears whichever of the three the field is carrying and never two of them. The critical edge and the focus ring are separate roles, so invalid and focused hold both meanings at once.
The panel a refused submit lands on: one row per failed field, each a real link to the control it names. It is inserted on the refusal rather than revealed, so it is announced once and on the submit that matters — and only when more than one answer failed, because a summary of one is a detour.
error-summary · three refused answers, and the same panel with a message that wraps
3 answers need fixing. Nothing was sent.
Keyboard: focus lands on the panel one render after the refusal, when it exists; each row is a link, so Enter jumps to the control it names. The four states of ui/form around it — idle, invalid, submitting, success — run for real on /signup and on /integrations.
Brand block, three link columns and the legal row. Every link resolves to a page or an anchor the theme ships. Rendered in place as the page shell.