Panel
A white card with a corner, a quiet band across the top, and a list or a gate under it. It is the one container in the product that clips its content, and that is what lets a full-bleed list reach its own edges without square-cornering the card. Seven of them stand in the product and only one can be pressed.
Anatomy
One surface, one head slot, and whatever the screen puts under it. Everything below is drawn by design/system/components/panel.css and by the hosted components' own files, 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.
Recent charges
- Jul 7$11.99
- Jun 7$11.99
- May 7$11.99
Three months are free. Longer history and trends are part of Tendd Pro.
.panelthe organism itself: paper, a hairline edge, a 14px corner, and 24px under it. Rendered as asectionon six of the seven and as adetailson the seventhoverflow: hiddenload-bearing rather than tidy. It is what clips the list's own corners to the card's, and therefore what lets the list be full-bleed at all> h2the band. The panel's own slot, not a hosted component: it carries no class on any page. 12px uppercase, muted, with a divider under it.chargesa molecule. The Charge list, which draws no margin and no padding of its own so its rows reach these edges. This file gives it nothing.amtan atom. The Amount, sized for the charge row byamount.cssand never from here.gatea slot of this organism and not a component: it occurs nowhere outside a panel, five times on five pages. A recessed strip that says what Pro adds.gate > pthe gate's own sentence. No size and no ink of its own: both are inherited from the gate, which is what makes it one declaration instead of four.gate .actionsa molecule. The Action row, and the one rule this file writes onto a hosted component:margin: 0, so the row does not push the gate's bottom padding open to 32
The gate sits below what is free and never in front of it. Three months of charges are on the screen, uncapped, and the offer comes after them. That is D3 read literally, and it is the reason the gate is a recess at the foot of a panel rather than a curtain over it.
Variants and sizes
One axis, and it has three values rather than the two docs/inventory.md records. Counted by opening all seven: five panels open with a banded h2, one opens with a summary, and one opens with nothing at all. The third is not a defect and not an omission, and the matrix below says what it is for. Size is deliberately not an axis: the panel is 100% of the column it stands in at every width, measured at 430px on design/subscription-detail.html at a 1440 viewport and 313px at 360.
| Head Chosen by whether the content needs a NAME and whether it needs to be closed. A band names, a summary names and closes, and nothing at all is right only where the panel holds one thing that speaks for itself |
Element | Where it stands |
|---|---|---|
Banded .panel > h2 |
section + h2 --type-meta --text-muted |
5 panels on 5 pages, every state of a subscription. The band names a list that is not obvious from its rows: three dates and three amounts need to be told they are charges |
Recent charges
| ||
Disclosure .panel > summary |
details + summary --type-body --text-body |
1 panel on 1 page, the "Older" group on Alerts. The only disclosure in the whole app shell, and the only interactive thing this component has |
None .panel > .gate:first-child |
section no head rule fires |
1 panel on 1 page, the Pro callout on cancel-guide. The panel holds one gate and nothing else, so a band would name a paragraph. This is also the case :not(:first-child) exists for: with no head above it, the gate draws no top rule |
The steps above are free and always will be. Tendd Pro adds screenshots for each one and a direct link that skips the retention screens. | ||
The stand shows this component at 440px and not at 620px, and that is a measurement rather than a preference. The panel renders 430px wide on design/subscription-detail.html at a 1440 viewport, because it stands in .col2 of the detail screen's two-track grid, and 313px at 360. The stand's default measure is 440px, so the specimens above are within 10px of the widest the product ever draws one. .wide would have shown it at 620, half again as wide as any panel in the product, and a charge list stretched that far reads as a sparse table.
When to use it
A panel is for a named block of evidence that a person meets after the thing it explains. Every one of the seven is on a screen a person reached on purpose: five on a subscription they opened, one on the Alerts screen under the alerts that matter now, one on the cancel guide under the steps. Nothing on Home is a panel, and that is not an accident: Home is a list of rows under category heads and a panel around any of them would put a second frame inside a screen whose job is to look calm.
Its recess is where the product asks for money, and the recess is the whole of the ask. The gate reads --bg-recessed, a step down inside a surface, and its buttons are two outline buttons: no filled accent, no badge, no colour that says urgency. D3 puts the paywall at depth and never at basic visibility or at the cancel moment, and this component is what "at depth" looks like: three months of charges are above it, free and uncapped, and the offer is the quietest thing on the card. The lines come from voice/docs/microcopy.md and say what is free before they say what is more: "Three months are free. Longer history and trends are part of Tendd Pro."
The disclosure form is for the recent past against the distant past, and it is the only place in the app shell where content is hidden behind a press. Node 3.8 says the recent past is the job of the Alerts screen and the distant past is node 5.12, so "Older" is closed by default and its one line is a summary a person can leave alone.
The rule, and the anti-rule
Recent charges
- Jul 12$14.00
- Jun 12$14.00
The list draws no margin and no padding, the row carries the side inset, and the panel clips the corners. That is why a marked row's amber wash can reach the card's own edge instead of floating inside it as a highlighted rectangle. Take overflow: hidden off and the bottom row squares off both corners of the card.
Trends are part of Tendd Pro
Your list, your total and your alerts stay free and uncapped. What Pro adds here is the past:
When the content is prose that never reaches the edges, the component is the Card. A card is padded on all four sides and holds sentences; a panel is clipped and holds a band that is flush. The two look alike from a distance and the test is one question: does anything inside have to touch the edge. If nothing does, a panel is a card with an unused clip and one more name to learn.
Three months are free. Longer history and trends are part of Tendd Pro.
The offer comes after the free thing, in a recess, in two outline buttons. Nobody is stopped and nothing is hidden. Five gates in the product and every one of them plays it this way.
Netflix went up by $2.50, now $17.99 a month.
See what changedSomething that is true right now and needs saying is the Wash block, not a panel. A wash is a tinted paragraph inside the flow with no frame and no head; a panel is a framed container with content under a name. The difference is not decoration: a wash carries a tone (attention, error) and a panel never does. A panel with an amber fill would be a card claiming to be an alarm, on the product that refuses alarms.
States
This component is interactive on one of its seven pages and nowhere else, which is unusual enough to be the first thing said. The section form has no href, no button and no target of its own; the details form has a summary, which is a control by element rather than by class, and it is the one thing here a person can press. So there are two states and they belong to a single element on a single screen.
Each is a token override rather than a style of its own: no hex and no number appears inside :hover. The hover reads --bg-hover and not --bg-recessed, which are one value today and two roles: one is a resting surface and the other is a transient response. Reading the recess here would tie a summary's hover to the gate's ground, and the day a hover wants to be warmer than a rail they would move together and one of the two would be wrong.
Rest, live
A real details with a real summary, which is the only way the states below can be reached at all: hover it, tab to it, press Enter and it opens. Faking a state with .is-hover is the same defect as inventing a role, because it documents something the product cannot do.
The two, 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. A value moves, and every picture of it is quietly stale until somebody re-shoots it, which 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 read the value off the real thing.
--text-body on the label, --bg-surface under it, no fill of its own--line-container at 1.23:1 light and 1.33:1 dark, decorative in both--bg-hover and --text-primary. No border, no shift, no size change: a border arriving here would push the alerts below it down by a pixel--color-focus, 6.23:1 as a line, drawn 2px wide at outline-offset: -2px. The offset is negative and this is the only ring in the system that is, because it is the only control standing inside a box that clips. Measured after the change: 0px lost on all four edges--color-focus, 6.82:1, same width, same negative offset, same 0px lost. At the corners the panel's 13px inner radius trims the ring's square corner, so the ring takes the panel's own curve. Checked at 8x in both themesThe ring used to be invisible, and "used to be" is the honest wording: it was 100 percent clipped, not partly. The summary's border box sits exactly 1px inside the panel, and the ring was drawn at outline-offset: 2px, so its outer edge stood 4px beyond the panel's clip box on every side. overflow: hidden clips to the padding box, so the whole 2px width was cut on the left, the top and the right, every time. And the disclosure ships closed, which is how "Older" stands on the Alerts screen, so the summary is then the panel's only child and its bottom edge is the panel's bottom edge too: the fourth edge went with the other three. Tab onto "Older" and nothing at all appeared. Measured at 360 and at 1280, in both themes, identical in all four, and screenshotted at 4x to be sure it was the clip and not the token.
Fixed at the founder's review of the components, 2026-08-12, by turning the ring inward rather than by removing the clip. overflow: hidden is load bearing on this component in a way it is not on a card: it is the only thing holding the gate's --bg-recessed fill to the two bottom corners, the summary's own hover fill to the two top corners, and a marked charge row's amber wash to whichever corner it reaches. Dropping it would mean a matching radius on the first and last child of every panel, which is three new rules keyed by position, a 13px inner corner no geometry primitive carries, and a real risk of a paper sliver at four corners on five coloured screens. The inset ring costs one sign.
No active and no disabled. kit.css declared neither, and writing one here would be a new decision about how the product answers a press, taken by the person moving a file. An "Older" group with nothing older in it is not rendered dim; it is not rendered.
And the summary now declares its 44px floor. Same review, same date. 16px of padding on a 14px line at line-height 1.5 gives 53px, measured here at 360 and 1280 in both themes, so min-height: var(--tap) never applies and the panel renders byte-identically with the line and without it. That is the point of adding it rather than an argument against: a target big enough by arithmetic shrinks the day its padding or its type step changes and nothing reports it, and a target big enough by declaration cannot. The floor is the primitive, so 44 lives in one place.
The technical half
| Reads | Which token | Growing from |
|---|---|---|
| the card surface | --bg-surface | --paper, and --paper-dark in the dark theme |
| its own edge | --line-container | --hairline. Decorative at 1.23:1 light, deliberately: nothing about a panel has to be found by touch |
| under the band, and over a gate | --line-divider | --hairline-soft, the separator inside one surface |
| the band's label | --text-muted | --muted, the single secondary tier |
| the summary and the gate's sentence | --text-body | --slate |
| the gate's ground | --bg-recessed | --panel. A step down inside a surface, the same role the desktop rail reads |
| the summary's hover | --bg-hover, --text-primary | --panel, --ink. Same value as the recess and a different role |
| its focus ring | --color-focus | --petrol. 2px wide at outline-offset: -2px, the one inset ring in the system, because the panel clips |
| the summary's tap floor | --tap | 44px, primitive. Declared 2026-08-12 and never reached: the summary measures 53 |
| its corner | --radius | primitive, straight: a corner has nothing to override |
| type and spacing | --type-meta, --type-body, --space-0 to --space-24 | primitive, straight |
The seam was real, it was measured, and it is closed. On design/subscription-detail.html, at 360 and at 1280, the last li of the charge list drew border-bottom: 1px solid rgb(238, 242, 243), the .gate below it draws border-top of the same value, and the two rectangles touched at exactly 0.00px. One seam carried two pixels of --line-divider where every other divider inside that panel carries one. It survived unseen since stage 07 because the gate's recessed fill (rgb(244, 247, 248)) sits one step from the divider's own value, so the doubling read as a slightly firmer line rather than as a fault.
Closed on the list's side, not on this one, and that choice is the whole argument for a principle over a patch. Founder's review of the components, 2026-08-12: the last row of a divided list drops its bottom rule, because a divider divides two things and after the last row there is nothing to divide. The rule that moved is .charges li:last-child in charge-list.css; the gate keeps its top rule exactly as kit.css wrote it. Taking the rule off the gate instead would have closed this one seam and left the identical one on the loading state, where the list is the panel's last child and its final rule sat flush at 0.00px on the panel's own hairline: 1px of --line-divider stacked on 1px of --line-container, and nobody had named it. Measured: the panel is 1px shorter on all five subscription-detail screens, at both viewports, and nothing else moves.
Two rules that could have been written here are not. .app .panel .divlist { padding } lives in divided-list.css, because it is a variant of the divided list keyed by this host, written .divlist.inset since step 6 renamed it, and the subject of a rule owns it. And no rule anywhere in this file touches a charge row, an amount, a button or an alert item: the only thing it writes onto a hosted component is margin: 0 on the action row, which action-row.css names and hands here by name.
Lives in design/system/components/panel.css. Stands on 7 grey pages, 6 of them coloured: Subscription detail, a price change, a payment that failed, an unrecognised charge, loading. The disclosure form landed in colour on 2026-08-13, on Alerts, which is the only page in the product that carries it. The headless form is still grey, on wireframes/cancel-guide.html.
<section class="panel" aria-label="Recent charges">
<h2>Recent charges</h2>
<ul class="charges">
<li><span>Jul 7</span><span class="amt">$11.99</span></li>
</ul>
<div class="gate">
<p>Three months are free. Longer history and trends are part of Tendd Pro.</p>
<div class="actions">
<a class="btn" href="upgrade.html">See what Pro adds</a>
<button type="button" class="btn">Maybe later</button>
</div>
</div>
</section>
<details class="panel">
<summary>Older</summary>
<ul class="divlist">...</ul>
</details>
The gate's sentence took a measure on 2026-08-17. It read 77.3 characters to a line on the coloured cancel guide at a 750 window, 684px, and the reason is a chain rather than a rule anybody broke: the panel takes the screen's width, the gate takes the panel's, and nothing between the words and the shell had ever been asked how long a line should be. Ink and size are still inherited from the gate above; the measure is the one thing this slot declares for itself, because it is the element that holds the words. Five gates on five screens.