Card

A white rectangle with a hairline edge and a 14px corner, holding a title, a sentence and whatever else the screen puts in it. It is the plainest object in the system and the one every other raised block is measured against: the Flat Paper Rule says a new surface earns a hairline and a background change, never a shadow, and this is that rule drawn once.

Organism3 grey pagesno axisno states, not interactivehosts 4 components

Anatomy

Two names, one object. Everything below is drawn by design/system/components/card.css and by the files of the components inside it, through the same entry point a product screen loads, so this page cannot drift from the screens: there is no second copy of the CSS here to drift from.

The Pro gate, .card.prose

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

Behaviour at width

A measure and no point at all, and the point came off at stage 10. .app > .screen > .card.prose takes a 560px measure so that a card which is read rather than scanned stops widening with the pane. That rule used to sit inside @container (min-width: 47.5rem), carried over from the grey, and the query was doing nothing: a max-width never widens anything, so below 560 the declaration is inert either way. What the query did buy was a band. The width sweep priced it: at a 750 screen, ten pixels under the old point, the card ran 684px and its paragraph read at 78.9 characters to a line, the worst measure in the product. The other forms of the card keep the width of their column at every width.

The selector is bound through > .screen > and that is load-bearing, not tidiness. Written as .app .card.prose it would tie at 0-2-0 with the shell's blanket max-width: none past the desktop point, lose on source order, and stop applying with no symptom anybody would look for. This product has now found that unbinding six times: the form column, the Pro gate, the plan row, the readout, the groups cap and the standalone muted line. See Width.

Its paragraph reads 60.7 characters, and that is the one measure in the product still over 52ch. 560px is a named decision with its own note in card.css, and 52ch is the system's reading measure; inside the padding they disagree by eight characters. A named value is not re-derived in a verification step, so the disagreement is written up in docs/backlog.md for the founder instead of being quietly resolved here.

A source, .card

Chase

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

No shadow, and that is a rule rather than a taste. There is exactly one elevation in this system and it belongs to the dialog sheet at a wide container. DESIGN.md states it as the Flat Paper Rule: a new surface earns a hairline and a background change, never a shadow, because the moment two elevations exist inside one screen the eye starts ranking them, and ranking is the work this product takes away from the person.

Variants and sizes

One form, so there is no matrix. A card is paper, a hairline, a corner and padding, and it is the same object on all 5 occurrences across the 3 pages. Drawing a table with one row would suggest an axis exists and is waiting to be filled in, and none does: the two names below are not two variants, they are the same rule found twice under different names, which is what step 2 merged.

The merge, and the measured evidence for it. The two names differed on exactly two properties and one of them was already drift.

Propertywas .lockedwas .sourceAfter the fold
fill, edge, cornerpaper, --line, 14pxpaper, --line, 14pxidentical before the fold as well as after
padding18px16pxboth --space-16. This was the drift the consolidation named, and the 8px grid closes it
title15px15pxboth --type-sub
bottom margin22px14px--space-24 and --space-16. It does not fold, so it stays two rules: the base carries 16 and .prose takes 24, which is the one thing the merged name still has to tell apart

One responsive rule, and it is the gate's reading measure. Past a 760px container .card.prose stops growing with the screen and holds 560px, because the card is three sentences and a list of three and the line length has to stay readable. It is written .app > .screen > .card.prose at 0-3-0 rather than .app .card.prose at 0-2-0, and the third level is load-bearing: at equal specificity the shell's blanket max-width: none at a 900px container wins on source order, and the gate rendered at 748px on a screen that had already been accepted at 360 and at 760.

No size axis and no tone axis, and both absences are checked rather than assumed. There is no small card, no wide card and no accent card anywhere in the 57 grey pages: a block that wants a tone is the Wash block, which is a fill with no edge and no corner of the card's size, and a block that wants a head band is the Panel. Adding a tone here would give the product two ways to say the same thing.

When to use it

A card is for one subject that a person may act on, standing on the page ground beside other things they are not acting on. Both of its occurrences are exactly that shape: the Pro gate is one offer with one button, and a source is one connection with its facts and its two controls. The hairline is doing the only job it has, which is to say where the subject starts and stops.

On the path, a person meets it in two places and neither of them is early. The Pro gate is node 5.12, History and trends, and it sits BELOW the free chart frame and never in front of it, which is D3 read literally: the paywall is at depth and never at basic visibility. Its copy comes from voice/docs/microcopy.md: "Trends are part of Tendd Pro", then what Pro adds, then "See what Pro adds". The source card is node 6.14, Your sources, which a person reaches from the trust line under any list, and its copy is "Bank connection through Plaid", with "Last checked" on every card because a silently stale connection makes the whole calm view quietly wrong and the person has no way to know.

The manual path gets a card of equal standing, and that is D2 drawn rather than stated. "Added by you" is a source with the same edge, the same corner and the same facts as the bank connection beside it, because a second-class card would say the typed path is the lesser one.

The rule, and the anti-rule

A card holds, and it does not paint

Trends are part of Tendd Pro

Your list, your total and your alerts stay free and uncapped.

Limits

U15, composition. One content measure per screen, and a block narrows itself only for reading. This component owns two measures and they are not the same kind: 560px on .card.prose is the width of a BLOCK on a screen, so it stays scoped to a direct child of .screen and inside a column the column decides; --container-text on the paragraphs and list items inside it is a READING measure, and on 2026-08-19 it lost its > .screen > guard, because a measure that belongs to the line cannot be conditional on where the box around the line is parked. The Pro gate found it by moving this card into a column: the sweep priced the paragraph at 77.3ch against 52ch everywhere else in the product. Full table with sources and how to check: Usage rules.

The two buttons inside are drawn by button.css and by nothing else. The one rule this file writes about them is a margin: 0 on the row they stand in, which is layout and is the host's to give. Measured on this page: the button inside the card is 14px at weight 600 with a 44px floor, which is what it is on its own page and on all 104 buttons of the coloured product.

Never let the host set the child's type
.card.prose .btn { font-size: 15px }

A rule like that reads as a tidy adjustment inside one card and is an undeclared variant: the product now has two buttons and a class name for only one of them. The same shape is already in docs/backlog.md eight times over, and the closest one to this file is .plan-opt .p, where a card sets the Muted line to body ink and every other host leaves it muted. If a child genuinely looks wrong inside a card, the answer is a declared modifier on the child, in the child's file, with the class in the markup.

Use a card

Added by you

Private, entered by hand
Private

One subject, its own edge, and something a person can do about it. Four of these stand in a column on Your sources and each is a separate decision, so each needs a boundary.

Use a panel instead
A card around a list of rows draws a second edge inside the first, and the list stops reading as one object.

When the subject is a list rather than a statement, the container is the Panel: it has the same paper, the same hairline and the same corner, and it adds the two things a list needs and a card must not have, a banded head and overflow: hidden so the first and last rows inherit the corner. DESIGN.md puts it in one line: rows stack inside one bordered container so a list reads as one object rather than as a stack of cards, and nothing inside that container draws a second edge. A card wrapped around a list gives you both edges at once.

States

None, and the reason is one line: a card is not interactive. Neither form is an anchor, a button or a summary on any of the 57 grey pages or the 57 coloured ones, neither carries a tabindex or a role, and kit.css gave neither a hover, an active or a focus rule. What a person presses inside a card is a Button, and its four states are live and hoverable on its own page.

The absence is worth stating rather than leaving as an empty block, because the neighbouring organism does not share it and the difference is instructive. .plan-opt is also a div holding a button and it does strengthen its edge under the pointer, which is an open row in docs/backlog.md: a hover that promises a click the card does not deliver. This card promises nothing, which is why nothing here had to be decided.

The technical half

ReadsWhich tokenGrowing from
the surface--bg-surface--paper, and --paper-dark in the dark theme
its own edge--line-container--hairline. Decorative at 1.23:1 by decision, because a card is a surface and not a control
the line between two list items--line-divider--hairline-soft
the gate's sentence--text-body--slate
what kind of source it is--text-muted--muted, the single secondary tier
title, sentence, list line--type-sub, --type-bodyprimitive, straight: geometry has nothing to override
padding, margins, the top line's gap--space-0, --space-8, --space-16, --space-24primitive, straight
the corner--radiusprimitive, straight. 14px, the container corner, not the 10px control one
no shadow, no fill change, no tonenoneby decision. The Flat Paper Rule, and the one elevation belongs to the dialog sheet

Four components stand inside it and this file paints none of them. Action row gets a margin and nothing else; Button, Logo, Chip and Pair list get no rule at all. The margin is the third of the three container overrides action-row.css names and hands to the containers themselves: .card.prose .actions is here, .panel .gate .actions belongs to the Panel and is byte-identical to it, and .rstep .actions belongs to the Reveal step. The two identical ones are one line for step 9.

One selector has a known range, recorded rather than tightened. .app .card h2 is a descendant selector at 0-2-1, so it would also reach a Group head written as an h2 inside a source card and outrank group-head.css at 0-2-0. No page does that today, checked across all 57 grey pages and all 57 coloured ones. Re-measured on the 55-page corpus on 2026-08-17 by design/kit/screens/inert12.cjs. Narrowing it would be a change with no defect behind it; step 6 owns the markup and can close it with the rename.

Lives in design/system/components/card.css. Stands on 3 grey pages, 5 cards: history-trends-locked (1), connections (2), connections-reconnect (2). One is coloured: History and trends, the Pro gate. The source card has never been rendered in colour, which docs/census.md lists among the nine components with no occurrence in the sample.

<section class="card prose" aria-label="Trends are part of Tendd Pro"> <h2>Trends are part of Tendd Pro</h2> <p>Your list, your total and your alerts stay free and uncapped.</p> <ul><li>How your monthly total moved, over 3, 6 or 12 months</li></ul> <div class="actions"><a class="btn primary" href="upgrade.html">See what Pro adds</a></div> </section> <section class="card" aria-label="Chase, bank connection"> <div class="top"> <span class="logo"></span> <span><h2>Chase</h2><span class="kind">Bank connection through Plaid</span></span> <span class="chip">Connected</span> </div> <dl class="pairs"><div><dt>Last checked</dt><dd>today, 9:14 AM</dd></div></dl> </section>

The specimens above stand in .kit-stage app full wide, 620px, because that is what the product gives them: measured at a 1440 viewport, a source card renders 620px on Your sources and the Pro gate renders 560px on History and trends. The stand's default 440px is the row column and would show both narrower than any screen ever does.

The prose card's 560 and the system's 52ch stopped disagreeing on 2026-08-17, and the answer was not to change either number. Stage 10 measured the paragraph inside the card at 59.5 characters to a line, eight past the measure, and left it as a founder's question: 560 becomes the card's measure with the line reading 52ch inside it, or 560 stands as a declared exception. It is the first. 560 is the width of the CARD, a box with padding, a border, three sentences and a list; the paragraph and the list items inside take --container-text like every other prose leaf in the product. The subject stays declared, > p and > ul > li rather than *, so a chip or a button inside a prose card keeps its own width.