Logo
The square a merchant's mark sits in. It is the only saturated object in a subscription row, and it is the one thing on a screen that is data rather than chrome: it is the person's own Netflix, rendered as Netflix.
Anatomy
One zone and one content slot, which is what makes it an atom. Everything below is drawn by design/system/components/logo.css 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.
.logothe atom itself: a fixed 36px square, centred, with a control corner and no border. It is aflexbox withflex: none, so the row beside it takes the slack and the square never squeezes.logo imgthe mark, at 100% by 100% of the square. It never bleeds past it, so the row's geometry does not change per merchant.alt=""on every one, because the whole row is a single link with a single accessible nameoverflow: hiddenthe containment rule made mechanical. A mark that overshoots is clipped by the corner rather than allowed to break the row.logo.is-skelloading. The same square in the placeholder fill:not(.is-skel):not(:has(img))unmatched. Selected structurally, because the grey has no class to hook and the wireframes are frozen. This is the one form that adds a border- a drawn mark not a merchant, and named rather than blank. Two arrived on 2026-08-14 with the rollout, both on the Connections card: a bank, a pediment over three columns, and entered by hand, a pen on a rule. They are inline
svgin the markup exactly as the unmatched question mark is, they take the square's ink throughcurrentColor, and they sit in the same square the unmatched form draws, because a source that is not a merchant is not a merchant we failed to name. See Icons and marks font-size: 8pxnot a typography decision: it is the size at which the string standing in for a missing mark fits a 36px square
Variants and sizes
Two axes. Size is set by the container and never by importance: the same Netflix mark is 52px on a detail hero and 22px in the reveal's chip group because the host is bigger, not because that subscription matters more. Content is what we know about the merchant, and its three values are three different sentences.
| Size Set by the HOST. A logo never carries a size class of its own, and there is no |
Corner | Its host |
|---|---|---|
52 .hero .logo |
--radius, 14px | the subscription detail hero. Past 36px the square stops reading as a control and becomes a container, so it takes the container corner |
36 .logo |
--radius-sm, 10px | the base, and the list row: the object the whole product is made of. Also the source card, and since 2026-08-14 the landing window's tile. 97 of the 136 places, recounted that day |
32 .alert .logo |
--radius-sm | an alert item, where the mark yields a little to the sentence beside it |
30 .tile .logo |
--radius-sm | a preset tile on Add a subscription, where the mark is the whole point of the tile |
22 .rgroup li .logo |
--radius-xs, 6px | the reveal's chip group, where marks run in a line as evidence rather than as rows. At 22px a control corner reads as a pill, which is the same argument that named --radius-xs for the chip |
| 20 | - | The grey draws the reveal's group at 20px (wireframes/_wf.css:623) and the colour layer has shipped 22 since stage 07. The inventory's "20 / 22 / 30 / 32 / 36 / 52" counts both files; only five of those six sizes render anywhere today. Listed so the sixth is not re-derived from the grey at stage 12 |
| Content Three values, and each says something different about what we know. Do not use one to stand in for another: a loading square that never resolves is a lie, and an unmatched square that pulses says we are still trying |
Tokens | What it says |
|---|---|---|
A mark .logo > img |
--bg-callout | we know the merchant. 14 marks cover the whole canonical dataset; the fill behind them is the callout wash, so a mark with transparency still has a square |
Unmatched .logo, no image |
--bg-callout --line-container --text-muted |
we looked and could not name it. Node 2.7's unrecognized state is the whole reason that screen exists, and the grey writes "[ ? ]" where a mark would go. The one form with a border |
| [ ? ] | ||
Loading .logo.is-skel |
--bg-placeholder | we do not know yet. It takes the skeleton fill so it reads as part of the loading row, and it does not pulse: only .skel animates. See Skeleton bar |
Four of the five sizes are shown live above and the fifth is not, for a reason worth naming. The hosts of the 52, 32 and 30 forms are plain classes, so a bare <span class="hero"> is enough to reach them here. The 22px form is selected as .app .rgroup li .logo, and the li in the middle is an element rather than a class: it can only be reached by building a real list, which would drag a browser list marker onto this page and say nothing true about the component. It is live on Guided Reveal. This is the same finding the chip page records about .appbar .plan, and the same repair closes both: step 6's rename moves the size onto the square rather than onto the path to it.
Solid, not dashed, and this is the one place the colour layer departs from the wireframe convention on purpose. The grey draws every logo square with a dashed border (wireframes/_wf.css:311 for the row, :638 for the hero) because that is what "we have nothing to show" means in a wireframe. DESIGN.md forbids a dashed border outright: "There are no dashed borders, no double borders, and no border that exists to decorate a block that would read fine without one." The container hairline plus the callout fill says the same thing in this language.
One number here disagrees with DESIGN.md, and the code wins. Its prose calls this "a 40px logo tile"; the stylesheet has shipped 36 since stage 07, on 136 places across 21 pages (111 before the landing window put 28 marks on the public page). Step 3 decided the document moves to the code and not the other way round, and the row is on the correction checklist in docs/tokens-audit.md. Nothing in this component is 40.
When to use it
A person recognises their own subscriptions by their marks before they read a single word, and that is the whole job: on Home the fourteen squares are what turn a list of amounts into my list. DESIGN.md sets the rule that makes it work: a real name and a real mark wherever the merchant is known, never a cryptic bank code, never stock art, never an illustration standing in for a company. The founder's decision on 2026-08-10 was full colour real logos, and zero photography anywhere in the product.
The containment rules are binding, and they are what let a brand colour into a calm screen at all. The mark is the only saturated thing in its row: the amount, the name and the date stay ink and muted, and a row never tints its background, its border or its text to match the brand. Petrol never sits inside the square, so the one accent and the brand colour never touch, and no brand colour appears anywhere outside the square. The One Voice Rule survives fourteen logos because of exactly those two sentences.
Where a person meets it: on Home in every group, on a subscription detail as the 52px hero mark, on Add a subscription inside the preset tiles, on Alerts beside each sentence, and on the guided reveal as a line of small marks that is the evidence for the number above it.
The rule, and the anti-rule
A merchant, in a row that is about that merchant. The mark is data: it identifies the thing the row is about, so it belongs to the content and not to the interface.
A place inside the product is not a merchant, and it takes the destination icon, .ic-*: a mask painted in currentColor, with no brand colour and no fill behind it. The tell is ownership. A logo belongs to somebody else and is allowed to be saturated; a destination mark belongs to Tendd and therefore inherits the one voice. Putting a merchant mark in the tab bar, or a masked glyph in a subscription row, breaks the rule in both directions at once.
An unmatched charge gets the bordered empty square and the plain question mark. It is honest and it is quiet, and the screen beside it says what to do about it in a sentence.
An unmatched merchant is not an error, so it never takes clay and never takes an error tone. DESIGN.md: clay is scoped to a genuine failure, and "Don't use clay on an empty state. Nothing went wrong in an empty state." When something really did fail, the object is the wash block, .wash.error, carrying a sentence and a way to retry. The square stays neutral either way.
Limits
U13, context. LIFT. The merchant mark rises by one nudge and grows to 1.06 when the target it stands in is hovered: a row, an alert, a preset tile or a door. It is the identity of the thing you are about to open, which is why the mark answers and the amount beside it never does. The rule is scoped to the pressable form of every host and excludes .is-skel twice over, so a placeholder never lifts. Everything in this product that moves travels exactly one --nudge, which is 2px, and nothing anywhere travels two. Hover and release run at --dur-state (150ms) on --ease-arrive; a press runs at --dur-press (90ms), because it answers a finger that is already down. The full language and the live specimens are on Motion; the table with sources is Usage rules.
States
None, and that is a decision rather than an omission. The square is never the tap target: on every screen the whole subscription row is one link with one accessible name, so a screen reader reads the subscription and not five fragments, and a pointer that hovers the mark is hovering the row. logo.css therefore declares no hover, no focus and no disabled, and the row's own hover is the row's rule. The three forms above are content, not state: they say what we know about the merchant, and none of them is reachable by an interaction.
The technical half
| Reads | Which token | Growing from |
|---|---|---|
| the square behind a mark | --bg-callout | --canvas. The same role the unmatched square takes: both are a wash inside a card, and in the dark theme they lift off the surface together |
| the loading square | --bg-placeholder | --skeleton |
| the unmatched border | --line-container | --hairline. Decorative, not a control edge: nothing about this square has to be found by touch |
| the "[ ? ]" | --text-muted | --muted, the single secondary tier. 5.17:1 on the callout fill |
| the three corners | --radius, --radius-sm, --radius-xs | primitive, straight: geometry has nothing to override |
Lives in design/system/components/logo.css. The fourteen marks themselves are files in design/visuals/logos/ and are catalogued, with their fidelity debt, on Icons and marks. Stands on 22 grey pages and 22 coloured ones, in 182 grey places and 207 coloured ones, recounted 2026-08-20. It said 136 for both sides; the two corpora carry the same PAGES and not the same places, and the 25 that differ are all on the landing, the one page the colour may differ on by more than styling. Coloured screens carrying it: Home, Subscription Detail, the unrecognized state, Home, loading, Add a Subscription, Guided Reveal.
<span class="logo"><img src="visuals/logos/netflix.svg" alt=""></span>
<span class="logo">[ ? ]</span>
<span class="logo is-skel"></span>
the size comes from the host, never from a class on the square:
<div class="hero"><span class="logo">...</span></div> 52px
<li class="alert"><span class="logo">...</span></li> 32px