Component kit

Every component of the product in one place, in Petrol and Paper. A screen is assembled from these classes, inside an .app or .landing host, and carries no style of its own: if a detail is missing it goes into the kit first and onto the screen second. A rule written on a screen looks flawless in the browser and becomes drift at the next stage, where the system derives its tokens from exactly these files.

The level is not a label. Stage 08 splits the CSS in this order and imports it in this order, so a container that sorts above its own parts lands higher in the cascade than the things it contains, and every contextual fix after that reaches for !important.

18 atoms23 molecules14 organisms read from 57 screens179 classes45 control forms to 16 components

Atoms

Contains nothing else from the kit. One visual element, one content slot.

Button

.btnfilled authored

One component that takes an element: it renders as both a link and a button, 46 times across the set, and whether it navigates or acts is invisible in the class. The filled form is authored, not extracted: in grey .primary is only a heavier border, because greyscale has no fill to give. One filled button per zone.

Button, inverse

.btn.inversedeleted 2026-08-12

The plate is gone with the rule, and leaving it would have been the worse of the two mistakes. Authored at stage 07 and worn on none of the 55 grey pages and none of the 28 coloured ones: two rules and two hovers describing a control the product does not have. With the rule deleted the plate drew the ordinary light pair on a petrol ground, and measured at 1280 the filled one computed rgb(28, 106, 118) on a plate of rgb(28, 106, 118), an invisible button demonstrating an absence. A stand that keeps a specimen for a class nobody defines is showing a reader something to copy. The petrol ground it was written for exists in one place, the landing, and the landing wears .lp-btn five times and has never worn this. It comes back, authored again, the day a petrol band ships with a control on it. See Button.

Chip

.chip

One base where the inventory found four look-alikes with four owners and no base. Sentence case, not the wireframe's uppercase: a shouted status is not a calm one. The tones are by state, not by owner. Three tones left this plate at step 6, and the count that removed them named two corpora when there are three: the 28 coloured screens and the 57 grey pages both returned zero for tone-attention, tone-error and cancelled, and this stand was not swept, so it was the last place in the repository writing them. A price change and a failed payment are a full wash block with a sentence, because neither state fits in two words.

Active Trial Best value

Quiet inline link

.quiet a

Thirteen rules for one job collapsed into this, and the five sizes into two. Six of the thirteen had no target rule at all, so the 44px floor on this base is what closes 21 under-target controls across 10 pages. Petrol at rest only inside the trust line, which the concept names; everywhere else the underline carries the affordance.

Do this later

Available for US banks today. More regions soon.

Field

.field input .field selectinvalid authored

One rule for the input and the select, which the grey CSS had already settled. The invalid state is authored: a sweep of all 57 pages returns no aria-invalid, no required and no error class, so the kit would otherwise inherit an input that cannot be invalid. Clay, never red, and the hook is the native attribute, so no page needs a new class.

The address your list is kept under.

Enter an amount, like 17.99.

The total, and its slots

.total .amt .muted

Seven slot names had no base rule at all and were sized only by their host. Base first, host sizes as modifiers. Exactly one object per screen is set at display size, and it is the number the person came for.

$192.90

a month, for what you have signed up for

Skeleton

.skel

Pulses at 1.4s and stops entirely under prefers-reduced-motion, which is already wired. A placeholder in a box that shrinks to its content has no percentage to resolve against, so those widths are stated by the host.

Merchant mark, all three states

.logo .logo.is-skelunmatched added at step 5

One atom, three states: the mark we have, the mark still arriving, and the merchant we could not name. The third has no class of its own and is selected structurally, :not(.is-skel):not(:has(img)), because the grey has no hook for it and the wireframes are frozen; the product writes [ ? ] inside it on the unrecognized charge. Dashed on purpose, in colour as in grey: "we have nothing to show here" is already what a dashed outline says to a reader.

Destination icon

.ic-home .ic-alerts .ic-save .ic-youadded at step 5

The four GC2 destinations, drawn as masks so the kit owns the mark and the screen carries nothing but the class. The mask is painted with currentColor, which is what turns the current tab petrol without a second rule. It sits on a pseudo-element rather than on .ic itself, because a mask clips its own children and .ic hosts the unread dot. The grey wireframe draws these as an empty bordered box on purpose, so this is the one atom the extract could not take from _wf.css.

Step-forward link, and the caption over a number

.next .lbl .lbl.strong .mutedadded at step 6

Three atoms the Guided Reveal brought in, and all three are reconciliations rather than new inventions. .next is the only link in the product that carries a direction, because the step it points at is further down the same page; it is a standalone control, so the 44px floor applies to it (the exemption is for links inside a sentence). .lbl.strong is the caption over the 46px Display, declared as a modifier because the grey wrote it as a host restyling an atom. .tone shared every value with .context and differed only in the space above it: one set of values, two names, because the jobs differ. Step 6 folded both into .muted, which is where that reasoning landed.

Freed up

All together

$192.90

a month, for what you have signed up for

This is what you have signed up for, not what you wasted.

Molecules

Hosts atoms. It may position an atom and never restyle one: a rule like .charges li .amt{font-size:15px} looks like a tidy adjustment and is an unnamed variant, which is the defect this stage exists to prevent.

Subscription row

.rowsignature

The object the whole product is made of. A line in a list, not a card in a stack. The whole row is one link with one accessible name, so a screen reader reads the subscription and not five fragments. The badge appears only when the state is not the plain default: fourteen "Active" tags in a column is noise on the one screen whose job is calm. The row draws no floor of its own, the list separates.

Trust line

.trustsignature

Not fine print and not a footer: it sits where the number is, because a trust claim that has to be scrolled to is not a trust claim. The petrol shield is one of the three jobs the accent is allowed to do, and it marks the block rather than one line, because all three lines are the claim.

From Chase, read-only. Tendd cannot move your money.

Last checked today, 9:14 AM.

What we read

Banners

.wash .wash.attention

A price change is amber, a genuine failure is desaturated clay, and there is no red in this system for any state. Neither carries an icon: the sentence is the alert. Kept as two components on purpose, because one of them is news and the other is a failure.

Netflix went up by $2.50, now $17.99 a month.

See what changed

We could not reach Chase. Your list is the one from yesterday, and nothing about your money has changed.

Notice, both tones

.notice .notice.is-errortone authored

The tone is opt in, and that is a correction rather than a preference. Clay is reserved for a genuine failure, and three of the five pages that carry .notice are not failures: "we could not match this charge" and "your bank wants you to sign in again" are ordinary news. With clay as the default they told the person something had broken when nothing had. Greyscale could not express tone at all, so the modifier is the colour layer's to add, exactly like the destination icons.

It repeats like a subscription, but we could not match it to a service. Name it and pick a category so it reads clearly next time.

We could not refresh just now. Showing your last update from today, 9:14 AM.

Try again

Facts

.pairs .pairs.sentences

Label left, answer right. The .sentences variant stacks the pair, because a sentence set against its label on the right wraps into a ragged column and stops being readable at 360.

Next charge
in 6 days, Aug 7
Billing cycle
Monthly
Charged to
Chase checking
What arrives
A link you tap. There is no code to copy out
How long it works
Once, and then it expires. You can send another at any time

Doors

.grid.roomy .door

Two equal choices, so neither is recommended and neither gets petrol: two petrol objects on one screen is the One Voice Rule failing. The category hides one of these two doors; showing both is the decision.

Plan card

.plan-opt

One rule with two hosts, the app and the landing, rather than an lp- copy. Rebuilt into the shape the category has taught people to read: the badge qualifies the card from the top, the price is the card's one big thing, and every card carries its own action. Only the recommended card takes the accent, because three accent buttons in a row would break the one-per-zone rule.

Best value $69a year That is $5.75 a month, and it saves about $27 a year versus paying monthly. Start Tendd Pro - $69 a year
Everything in Tendd Pro
  • History and trends
  • Advanced alerts
  • Full cancel guides
  • Export
$7.99a month Month to month. Cancel any time, no lock-in. Start Tendd Pro - $7.99 a month
Everything in Tendd Pro
  • History and trends
  • Advanced alerts
  • Full cancel guides
  • Export
Lifetimeone payment Tendd Pro stays open, for people who would rather never think about a renewal again. Not on sale yet. [? D4, the lifetime price, $99 to $139, is still being decided]
Everything in Tendd Pro
  • History and trends
  • Advanced alerts
  • Full cancel guides
  • Export

Segmented control

.range

Not a button variant, and the difference is real: it is a selection rather than an action, its width is set by the group, it has a disabled state the button has no equivalent for, and its selected state is a petrol tint where a primary button is a petrol fill.

Switch row, nav row, preset tile

.switch .navrow .tile

The product's toggle is a real checkbox inside a label: there is no custom switch anywhere, and the 44px target is the label, not the box. A pressed tile is a current selection, so it takes petrol.

Merchant chip group

.rgroupadded at step 6

Step two of the reveal, and the place the merchant mark earns its colour: a bank statement line reading SPOTIFYAB STOCKHOLM becomes a face a person recognises. No amounts here, ever - the number comes third. The group heading is the same .group-head.plain the list screens carry, so the chip is the only new shape. Its radius is a named decision: the grey draws a square 1px box, and the chip takes the control radius its own 22px mark carries.

Streaming (4)

  • Netflix
  • Disney+
  • Hulu

Organisms

Hosts molecules, or is a screen shell. This is the ceiling; there is no level above it. The shell reflows on container queries, not media queries, so a screen behaves by the width it is given rather than by the width of the browser.

App shell

.app .app.flow .app.detail

Three forms. At container 760 the tab bar becomes a 220px left rail and the app bar folds into its top; at 900 Home becomes a dashboard and the detail takes as many columns as its pane holds. The groups run in as many 300px columns as fit, capped at three, and no third step decides it. The onboarding chain overrides the grid rather than inheriting it, because onboarding has no desktop delta. The review frame that wraps a screen on these pages is not in the kit: it hosts the navigation panel and does not travel to a real build.

Open any screen in the UI + Visual hub to see the shell at work. It is not specimened here, because a shell with nothing in it is a rectangle.

Panel, with the gate folded in

.panel .gate

Two forms from one class: a section with a band, and a disclosure whose summary replaces the band. The gate occurs nowhere outside a panel, so it is a slot of this organism and not a component of its own, and it sits below what is free and never in front of it.

Recent charges

  • Aug 3$17.99
  • Jul 3 was $15.49$15.49

Three months are free. Longer history and trends are part of Tendd Pro.

Pro gate card

.card.proseadded at step 5

A card organism, built like the share card: its own frame, a heading, a short reason, the three things Pro adds, and one action row. It sits under the chart frame rather than over it, and it is deliberately quiet: no blur and no cover, because a blurred chart is a lie about our own product and the honest empty frame persuades better. The filled button here is the one accent of its zone, so nothing above it may take petrol.

Trends are part of Tendd Pro

Your list, your total and your alerts stay free and uncapped. What Pro adds here is the past:

  • How your monthly total moved, over 3, 6 or 12 months
  • Which subscriptions went up, which are new, which held steady
  • Your history as a spreadsheet

Save-focus candidate list

.candidates .cand .btn.softadded at step 5

The cancel moment, which principle 5 names the product's most important emotional beat. The row keeps its whole-row target to the subscription and the cancel control is a second, separately named control beside it: confusing the two is what costs the cancellation, so the cut is a bordered target of its own rather than a word inside the row. Filed as a one-off at step 2, which is why the kit had nothing for it and the list first rendered with browser bullets, a 40px indent and the cut as a bare blue link. Below 460 it drops under the row and keeps to the right. It was .cut, its own family of six rules, until 2026-08-20: measured after the fold it was .btn minus a font-weight and a hover fill, so it became class="btn soft" and the family went.

Source card

.card

One column at every width: a settings sidebar does not survive 360px, which is also why this cluster is three screens and not one. The unclassed middle span grows, which is what pushes the status chip to the end, so the chip needs no margin written on it from the parent.

Chase

Bank connection through Plaid
Connected
Last checked
today, 9:14 AM
Tracking from here
11 subscriptions

Reveal step

.rstepadded at step 6

The only organism in this kit that exists to test a hypothesis rather than to hold content. H0, the assumption the whole product is built to prove, says an avoider who looks at the number feels calmer rather than worse - and this is where a person looks. Three steps live on one page and never on three routes, so back never means starting over. D1 read literally: the count, then the categories, then the total, and every step carries its own action. It joined the sample at step 6, when the strategy check found the five screens chosen for component coverage had left H0 with no coloured screen at all.

Step 1 of 3

You're paying for 14 subscriptions

Found in your Chase account. No total yet, and no verdict.

Step 3 of 3

All together

$192.90

a month, for what you have signed up for

This is what you have signed up for, not what you wasted.

What was authored, and what this kit retired

Extraction can only return what greyscale could express. Everything else is named here so the exception stays deliberate.

authored The filled button, the inverse button, the invalid field. The first exists in grey only as a heavier border, the second existed on no page at all and was deleted on 2026-08-12, and the third has no hook anywhere: a sweep of the 55 pages returns no aria-invalid, no required and no error class. Two smaller ones join them: a focus ring, because the grey draws exactly one and only on the field, and the chip tones, because four look-alikes render fourteen states through markup that carries a class for none of them.

Retired. Thirteen rules for one quiet link became one atom, and the five sizes became two. Five chip rules became one base and five tones. Seven slot names got the base they never had, and twenty-one host rules became seven bases plus modifiers. The landing's lp-btn twin is gone, and with it the one rule it had already lost: the 44px floor.

literal Ten places still carry a literal value, each marked LIT in kit.css before it was deleted. Fourteen variables are proposed in docs/inventory.md and none was added: a value enters this system by a named decision, never as a side effect of an extract. Four of the fourteen are pure naming of a literal the file already writes; one is a real 8px difference between the kit's rail and the grey's, and it needs a decision rather than a merge.