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.
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
.card, .card.prosethe organism itself: paper, a--line-containerhairline, the--radiuscorner and--space-16of padding. One rule carries both forms, which is the merge. They were.sourceand.lockeduntil step 6h2the card's own title slot, at--type-sub. It carries no class on any page, so it is not the Group head and not the Eyebrow: it is a heading the card ownspthe card's own sentence, at--type-bodyin--text-body. Declared on the gate only, and the reason is in the CSS: the source card's paragraph is the Muted line atom and this file may not repaint itul, liwhat the gate holds back, one sentence per line, divided by--line-dividerwith the last one bare.actionsa molecule. The Action row, and the one thing this file says about it is amargin: 0, so a row that ends a card does not push the card's own padding open.btnan atom. The Button, drawn entirely by its own file. Nothing here touches its fill, its ink, its edge or its size.topthe source's first line: a mark, a growing text column, a chip. The middle span carries no class, which is what:not([class])selects, and growing it is what pushes the chip to the end.logoan atom. The Logo at 36px, sized for this host inlogo.cssand never here. It renders in its placeholder form above, and honestly so:design/visuals/logos/holds fourteen merchant marks and no bank mark, because the source card has never been drawn in colour.chipan atom. The Chip, quiet tone, saying what state the source is in. It was.badgeuntil step 6.pairsa molecule. The Pair list. This file gives it no rule at all: it spaces itself and divides itself. It was.factsuntil step 6
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.
| Property | was .locked | was .source | After the fold |
|---|---|---|---|
| fill, edge, corner | paper, --line, 14px | paper, --line, 14px | identical before the fold as well as after |
| padding | 18px | 16px | both --space-16. This was the drift the consolidation named, and the 8px grid closes it |
| title | 15px | 15px | both --type-sub |
| bottom margin | 22px | 14px | --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
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.
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.
Added by you
Private, entered by hand PrivateOne 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.
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
| Reads | Which token | Growing 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-body | primitive, straight: geometry has nothing to override |
| padding, margins, the top line's gap | --space-0, --space-8, --space-16, --space-24 | primitive, straight |
| the corner | --radius | primitive, straight. 14px, the container corner, not the 10px control one |
| no shadow, no fill change, no tone | none | by 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.