Plan option
One plan on the screen that asks for money. Three of them stand side by side and the decision is a comparison, which is why the price is the card's one big thing and everything else is deliberately quiet. It is also the component that carries the two open questions of this round.
Anatomy
Five slots and four atoms, which makes it the densest molecule in the round. Everything below is drawn by design/system/components/plan-option.css and the atom files it hosts, through the same entry point a product screen loads. Three of the sizes you can see here are not in this file, and that is the point: the badge is chip.css, the price is amount.css, the "Everything in Tendd Pro" label is eyebrow.css, and this file only says where they go.
Synced from the product, 2026-09-30 (P26, D4 amended). The founder, on Upgrade from inside the product: "очень плохо выглядит выбор планов". Measured then at 1280: the same list printed three times, so 60% of each card was a repeat and the three prices were the smallest thing in a wall of ticks, and the buttons stood in a row under the cards, so a price was read in one place and pressed for in another. Since 2026-09-29 each card carries its own button at its foot, the yearly card is marked with the selection edge (.plan-opt.best), the cadence sits beside the figure on one baseline, and what Pro includes is said once, in one .incl under the row, in two columns past 760. The row is three columns from container 900 and stacked below (Grid). Prices are D4 as amended on 2026-09-30: $9.99 a month, $69 a year. No string on the card changed except the saving, which the new monthly price moved from $27 to $50.
- Every name in your bank scan
- A bank connection that keeps the list up to date
- History and trends
- Advanced alerts
- Full cancel guides
- Export
The stage is past 900 at a 1280 window, so the row stands in three and the list runs in two columns; narrow the window and the cards stack, one full-width card each, which is the phone's arrangement. In the product the row is a form wearing .plans, because each card's button posts its interval (P5); here it is a div and the buttons are links, which the grid does not tell apart.
.plan-optthe molecule: adivin a flex column, with the container hairline and the container corner..best, since 2026-09-29 (P26), gives the yearly card--line-selectedas its edge: petrol's "current selection" job, and the landing's same card. NOT a control, which is why it keeps the hairline while the door keeps the control edge.chip.tone-accentthe chip atom in its accent tone, on one of the three cards. It was.bestuntil step 6. It leaves the flow on both hosts since 2026-08-18: this file takes it out of the column and drops its margin, and each host says where it goes - the top edge on the landing, the top right corner here. Theposition: relativethis file has carried on the card since stage 07 is the anchor both of them use.amtthe amount atom at its 32px plan size, declared inamount.css. The card's one big thing.perthe cadence, "a year" or "a month", and it is this card's own slot,display: inlinebeside the figure since 2026-09-29, on one baseline: it lives INSIDE the amount and the amount atom does not declare it. It resets the weight, the tracking and the ink so "$69 a year" does not read as one 32px phrase.pthe sentence about what the price means.flex: 1 1 auto, so it absorbs the height difference between three cards of unequal copy. It is what keeps the three actions and the three includes lists on one line, measured at 360 and at 1100.btnthe button atom, stretched to the card. One filled, one outline, and the third card has none. It is the one button in the product allowed to wrap, since 2026-08-13:button.cssgives.plan-opt .btnawhite-space: normalthat no other button gets, because this label is a sentence carrying a price and a period. Measured at 390px: "Start Tendd Pro - $7.99 a month" is 249px at the browser default and 356px at a 24px root, in a 358px track, and the page scrolled sideways by 16px before the exception. The rule lives inbutton.cssand not here, which is this file's own header holding: white-space is the button's property and a host reaching in to change it is the defect.qwhat stands where the button would be on the card that has none. It reads--tapas a geometric FLOOR rather than as a target: it stops the slot collapsing below a button's height when its sentence is short. It does not do the aligning,.pdoes.inclwhat the plan includes, under a divider, below the action and never above it: the price and the button are the decision, the list is the reassurance a person reads after making it. Since 2026-09-29 (P26) it is outside the cards, one block after.plans, said once instead of three times; its selectors lost their.plan-optscope and its label's placement moved with it in eyebrow.css. Six rows now: "Every name in your bank scan" and "A bank connection that keeps the list up to date" joined when the bank became Pro (D-Gate)
Variants and sizes
One axis, and it produces no visual difference at all: the card is declared once for two hosts. What looks like a second axis, "with a button" against "with a question line", is not a variant of the card but a fact about the plan, and it is in the second table because a person reading this page will look for it.
| Host ONE rule, two hosts, and this is the pattern that retires the seven |
Difference | Where it stands |
|---|---|---|
In the app .app .plan-opt |
none | 3 cards on the Upgrade screen, which is the one place in the product that asks for money |
On the landing .landing .plan-opt |
none | 3 cards on the welcome page, which has no .app shell at all. The markup is identical apart from where the buttons go, and the grid around it is narrower. Synced from the product, 2026-09-30 (P16): since Light 2 the landing's pricing is the landing plan's own .lp-plan cards, and no landing markup wears .plan-opt. The .landing scope stays in the file by the two-host rule of docs/architecture.md, and this specimen still renders from it; it describes a host the product no longer uses |
| The action slot A card offers a button when the plan can be bought, and a sentence when it cannot. It never offers a disabled button: a control that looks live and does nothing is worse than one that says so in words |
Values | Where it stands |
|---|---|---|
A button .btn |
--tap floor align-self: stretch |
4 of the 6 cards. One filled per screen and one outline, which is the zone rule: the yearly plan is the recommendation and says so once. Since 2026-09-29 (P26) the button stands in its own card at the foot, and the .muted sentence's --space-24 bottom margin plus flex: 1 line the three actions up across the row |
Best value$69a yearThat is $5.75 a month, and it saves about $50 a year versus paying monthly.Start Tendd Pro - $69 a year | ||
A question line .q |
--tap floor --type-meta --text-muted |
2 of the 6, the lifetime card on both hosts. Measured on the coloured Upgrade screen its sentence wraps to two lines at every width the product has, so it renders 4px taller than a button rather than exactly its height. That costs nothing, because .p absorbs the difference |
Lifetimeone paymentTendd Pro stays open, for people who would rather never think about a renewal again.Not on sale yet. We are still working out the price. | ||
| A disabled button | - | Forbidden with a reason rather than missing. The lifetime plan has no price yet and the honest form of that is a sentence, not a grey control: "Not on sale yet. We are still working out the price." That line replaced a version of this screen that printed the project's own open-question notation as product copy, on two screens rather than one |
The price is 32px and not 46px, and the gap is the One Number Rule. 46px belongs to the monthly total and to nothing else in the product; a plan price at that size on a screen that is not about the person's own money would be the loudest thing they have ever seen in Tendd, on the screen where they trust it least.
When to use it
A person meets three of these on Upgrade and on the welcome page, and nowhere else. (Upgrade only, in the product since P16: the landing's pricing is its own organism.) The card exists because a price is a comparison: three prices in three identical frames is the only shape in which $69 a year and $9.99 a month can be read against each other, and D4 fixes both numbers (the monthly one moved from $7.99 on 2026-09-30, the yearly one did not). The screen sits at depth and never in front of basic visibility, which is D3: the list, the total and the alerts are free and uncapped, and this card sells history, trends, advanced alerts, cancel guides and the analytical export, and since D-Gate (2026-09-02) every name in a bank scan and a bank connection that keeps reading.
Its copy does the arithmetic for the person, from voice/docs/microcopy.md: "That is $5.75 a month, and it saves about $50 a year versus paying monthly." (It said $27 until 2026-09-30, when D4 was amended to $9.99 a month; microcopy's rewrite log carries the arithmetic.) The button carries the amount as well as the verb ("Start Tendd Pro - $69 a year"), because nobody should learn what they are being charged after the tap.
The rule, and the anti-rule
- History and trends
- Advanced alerts
- Full cancel guides
- Export
One petrol object per card, and it is the button. The ticks are muted, so the eye lands on the thing to press rather than on the ornaments beside it. Since 2026-09-29 the list stands once under the row rather than in every card, and the rule holds for it unchanged; the selection edge on the yearly card is the second petrol job on the screen and not a second action.
And since 2026-09-29, never the same list three times, which was P26's own finding: 60% of each card's height was a repeat. That is what this screen shipped at stage 07. Twelve ticks across three cards carried the accent, so on the one screen that asks for money the colour meaning "this is the safe thing to press" was smeared over twelve ornaments and the actual button stopped being the loudest thing on the page. A tick in a feature list is not the primary action, not the current selection and not the trust line, which is the whole of the One Voice Rule.
Two or more offers with a number to compare, standing side by side, each with its own action. The card is what makes the numbers comparable, and the numbers are what make the card necessary.
A choice between two ways to reach the same outcome, with no price and nothing to compare arithmetically, is the door: same corner, control edge instead of a hairline, and no action inside it because the whole card is the action. The two components look alike on purpose and differ in one thing that matters. A door is the target; a plan option contains one.
Limits
U12, context. A state change is colour over --dur-state, and a component declares a transition only where its host is not a native interactive element. base.css puts the one transition on :where(a, button, input, select, textarea, summary, label) and moves four properties: background-color, border-color, color, text-decoration-color. Nothing on that list moves an object, and the focus ring is deliberately off it. This component is one of the three the rule names, and it declares the transition itself because its host is a div: border-color alone, reading the same two tokens so the two can never drift. Full table with sources and how to check: Usage rules, and the register is on Motion.
2026-08-18: three prices that did not start on one line
The founder opened the coloured Upgrade screen and said “выглядит не очень”. The loudest thing in that screenshot is the comparison failing at the first glance: only the yearly card carries a badge, the badge was a block in the flow above the price, so $69 stood 32px below $7.99 and Lifetime - measured at every width from 768 to 1920, on the one screen in the product whose whole job is a comparison between three numbers.
The answer had been written three days earlier, inside one host. The landing plan hit the same defect on 2026-08-15 and recorded all three arrangements it tried: “Stacked above the price it pushed the yearly figure 31px below the other two, so the numbers a person compares did not start on one line. Beside the price, in a second column, it fixed that and cost the card its axis... On the top EDGE it costs no flow height at all, so the price is the first thing in every card.” It even quoted this file's position: relative and called it “the day” the anchor was needed. That fix went into .landing .lp-plan, so the app's card kept the defect - the same shape as the story's release the day before: a correction written in one arrangement instead of in the component.
So the flow height is the component's rule and the place is the host's. The chip is out of the column here with margin: 0, which is what makes the amount the first box on every card and the three prices share a line by construction; the landing keeps its straddle and no longer has to undo a margin this file set. Top right and not the top edge on this host, on the axis argument the landing named: a centred card has no corner to belong to, and a left-aligned card standing shoulder to shoulder with two others would read a straddling badge as a label for the row. Measured so the corner is not a guess: the badge is 66 wide, $69 is 60.41, and the narrowest card content this screen has ever had is 201 - 74.6px of clear air at the tightest width in the product, more at every other. Verified: the price rows are 0px apart at every three-column width from 760 to 1920.
And the card finally got the width its own button was measured for. The button label wrapped to two lines at every width from 768 up, with “year” alone on the second line - and the cap it wrapped inside was widened at stage 07 because the button label wrapped. The arithmetic behind that widening had answered the eyebrow (181.23 needed, 201 given) and not the button (248.78 needed, 201 given). The grid carries the recomputation and the band below a 913px window where the label still wraps; here the consequence is that the card is 293px instead of 235 past that window, so the supporting sentence reads in three lines rather than laddering into four of three words each. The band that is still wrapping is a row in docs/backlog.md, open and addressed to the founder with Voice for the string.
States
One state, and it is the open question of this round. The card is not a control: it is a div holding a button, it cannot be focused, it cannot be pressed and it has no accessible role, which is exactly why the consolidation refused to treat it as one and why it keeps the container hairline. The four states a person actually uses on this screen belong to the button inside it and are on button.html.
And since 2026-08-16 that one state FADES. The card is a div in all six of its placements, three on the landing and three on upgrade.html, so it falls outside the element list base.css carries the product's one transition on - by exactly the same argument the paragraph above makes: it is not a control. It declares the transition itself, on border-color alone, because that is the only thing its one state moves. The rule and its tokens are in Limits above.
IT HAD A POINTER RESPONSE UNTIL 2026-08-20 AND THE FOUNDER DROPPED IT. kit.css strengthened the card's edge on hover, from the hairline to a darker hairline; the value had no role, so stage 08 declared one, and the question underneath stayed open for eight days: should a card that cannot be clicked answer a pointer at all? It is answered no. The card is a div holding a button - no focus, no press, no accessible role - and an edge that strengthens under the pointer is this product promising a click it does not deliver. The button inside keeps its own state and is the thing that was always meant to be pressed.
THREE DELETIONS AND ONE DECISION, which is the part worth keeping. The hover went, so --line-container-hover had no reader anywhere in the system, so --hairline-strong and its computed dark pair had no role. Each step follows from the one above it and only the first was a choice. Measured before removal, on Upgrade at 1280: the card's edge went rgb(228, 233, 234) to rgb(205, 215, 217) under the pointer and nothing else on the card moved. That is the whole pixel cost, on three cards on one screen. The two answers, kept for the record, and the second one won:
| The option | What it costs | What it buys |
|---|---|---|
Declare --line-container-hover at --hairline-strong, with a dark pair | taken 2026-08-12, reversed 2026-08-20 | the hover came back exactly as it was, zero pixels moved, and the system gained a container-hover role. It had one consumer for its whole eight-day life, which is the shape tokens.css warns about in its own words |
| Drop the hover | taken 2026-08-20, founder | an honest simplification: the card is not a target, so a person who hovers it and then clicks it gets nothing. It also let --hairline-strong leave the palette, which is the list it had been on since stage 09 |
Read --line-control-hover | rejected | It exists, it is a hover, and it is a CONTROL edge. DESIGN.md forbids it on a container by name, and at 5.78:1 it would draw a hard dark line around a card whose whole job is to be quiet |
Rest, live
Hover the card and then hover the button inside it. The button responds and the card does not, and since 2026-08-20 that is the answer rather than the question: the thing that can be pressed says so, and the thing that cannot stays still.
Rest, in both themes
The states are described, not photographed. Founder's decision, 2026-08-11: the snapshot strip went from every page in this kit, 120 images across 18 pages. They were framed as measurements rather than illustrations, and that framing was honest, but the bill came due all session: a value moves, and every picture of it is quietly stale until somebody re-shoots it. A stale picture is worse than no picture, because it looks checked. What is left is what a picture never carried anyway: the state, and the token it reads. The resting component is live and interactive above, so hover it, tab to it, and read the value off the real thing rather than off a photograph of it.
--bg-surface, --line-container; the yearly card --line-selected since P26; --line-divider over the includes list, which stands under the row nowRetake, and this is the whole recipe, because there is no node Playwright in this repo:
serve the repo, then for theme in (none, data-theme="dark" on <html>):
goto /design/kit/plan-option.html
move the pointer away -> screenshot #live-rest -> screens/plan-option-rest-<theme>.png
TWO PICTURES AND NOT EIGHT, on purpose. Hover renders identically to rest until the role
above is decided, and a picture of it would be a picture proving there is no difference.
The decision landed on 2026-08-20 and it went the other way: there is no hover pair to gain.
The technical half
| Reads | Which token | Growing from |
|---|---|---|
| the card fill | --bg-surface | --paper |
| the selected card's edge (P26) | --line-selected | petrol's current-selection job, on .plan-opt.best only |
| the card edge | --line-container | --hairline. NOT the control edge: a plan card is a container, and DESIGN.md names it on the hairline side of that split |
| the divider over the includes list | --line-divider | --hairline-soft |
| the cadence, the question line, the tick | --text-muted | --muted |
| the includes list | --text-body | --slate |
| the sentence about the price | --text-body | --slate, and this one is carried rather than settled: .p is the muted line atom, which is --text-muted everywhere else in the product. See the note below |
| the hover edge | --line-container-hover | --hairline-strong, with --hairline-strong-dark #303c3f computed for the pair. Declared for this. See States |
| the padding and the gaps | --space-2, --space-8, --space-16, --space-24 | primitive: a spacing has nothing to override |
| the corner, and the action slot's floor | --radius, --tap | primitive, straight. --tap is a floor here rather than a target: nothing in the question line can be pressed |
The second carried question, said plainly. .plan-opt .p paints the muted line atom in body ink. That is a host restyling an atom, which is the defect this round exists to remove, and it is carried rather than fixed for two reasons: dropping it changes three paragraphs on a live coloured screen with no decision behind the change, and the fix is not this file's to pick. The two answers are an emphasis modifier declared on the muted line, or this card accepting muted ink the way every other card in the product does. Recorded here, in the component's own file, and in the report of this round.
Lives in design/system/components/plan-option.css. Stands on 2 grey pages and 1 coloured one, Upgrade. The landing's three cards are still grey, so that host's render is read from the grey. In the product on 2026-09-30 it stands on Upgrade only: the landing sells with .lp-plan since P16. The grid around it (.plans, one column and then three) is the Grid organism.
<div class="plans">
<div class="plan-opt best">
<span class="chip tone-accent">Best value</span>
<span class="amt figure">$69<span class="per">a year</span></span>
<span class="muted">That is $5.75 a month, and it saves about $50 a year versus paying monthly.</span>
<a class="btn primary" href="...">Start Tendd Pro - $69 a year</a>
</div>
...
</div>
<!-- what Pro includes, said once, under the row (P26) -->
<div class="incl">
<span class="k">Everything in Tendd Pro</span>
<ul><li>Every name in your bank scan</li>...<li>Export</li></ul>
</div>
<!-- the card with no price yet: a sentence where the button would be -->
<span class="q">Not on sale yet. We are still working out the price.</span>