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.
- History and trends
- Advanced alerts
- Full cancel guides
- Export
.plan-optthe molecule: adivin a flex column, with the container hairline and the container corner. 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: 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
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 |
| 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 |
Best value$69a yearThat is $5.75 a month, and it saves about $27 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. 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 $7.99 a month can be read against each other, and D4 fixed both numbers. 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.
Its copy does the arithmetic for the person, from voice/docs/microcopy.md: "That is $5.75 a month, and it saves about $27 a year versus paying monthly." 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 four ticks are muted, so the eye lands on the thing to press rather than on the ornaments beside it.
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
- History and trends
- Advanced alerts
- Full cancel guides
- Export
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, --line-divider under the actionRetake, 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 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. The grid around it (.plans, one column and then three) is the Grid organism.
<div class="plan-opt">
<span class="chip tone-accent">Best value</span>
<span class="amt">$69<span class="per">a year</span></span>
<span class="muted">That is $5.75 a month, and it saves about $27 a year versus paying monthly.</span>
<a class="btn primary" href="...">Start Tendd Pro - $69 a year</a>
<div class="incl">
<span class="k">Everything in Tendd Pro</span>
<ul><li>History and trends</li><li>Advanced alerts</li><li>Full cancel guides</li><li>Export</li></ul>
</div>
</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>