The interruption

Something stopped: a load failed, a link expired, a list is empty, a payment did not go through. The container says what happened, in one sentence a person can act on, and gives the way on directly under it. It is the most common composition in the product and the one most likely to be built badly, because every screen meets it at its worst moment.

Pattern12 screens16 grey pages7 in colour4 hostsno styles of its own

Anatomy

Two required parts and one optional one, in this order and no other. Everything below is drawn by design/system/patterns/interruption.css plus the component files it arranges, through the same ../system/index.css a product screen loads, so this page cannot drift from the screens.

That payment did not go through

Your bank did not approve it, so nothing was charged. This happens most often with a card that has expired or a bank that wants to confirm a new payment.

You can come back to this from any Pro feature. Nothing about your subscriptions or your data changed.

Variants

No visual variants, and that is the point of a pattern. What varies is the host it stands in and what stands between the announcement and the exit, and neither changes a value: the four hosts below render identically because the pattern reads the host's width and nothing else. The fourth arrived on 2026-08-13, from the first screen built after this page existed, and it cost no CSS at all: an axis read off the corpus that stands is a description of that corpus, and the next screen is allowed to extend it.

Host

The container the class goes on. All three are containers the product already had; the pattern adds none

Selector Where it stands
The screen, and the screen is now for this main.screen.interruption Eleven pages. The whole screen has stopped: the trends failed to load, the reveal has nothing to reveal, the sign-in link expired

We could not load your trends

Something on our side did not answer. Your subscriptions, your total and your alerts are not affected: this is only the history view.

One column of a detail .col1.interruption One page, subscription detail, could not load. The identity of the thing survives in the hero above and only the rest of the record failed, so the interruption takes the column and not the screen
A dialog sheet .sheet.interruption Three pages, all still grey: the cancel guide with no guide, the add-a-source sheet, the delete confirmation. No coloured page carries this host yet, so it is the one part of this pattern that the pixel comparison could not check. It reaches colour at stage 12
The list column, added 2026-08-13 .rows-col.interruption One page, alerts, could not reach your alerts, and it is the first screen built after this pattern was written. The three hosts above all hold the announcement as a direct child of the screen or of a detail column; this screen puts its list column in between, because its grey original does. The class moves to whichever element actually holds the pair, which is what a child selector means. Measured on the page at both viewports: 24px with the class and 24px without it
A tone: error red, warning yellow - Forbidden with a reason rather than missing. A status block IS the whole container: there is nothing beside it to be told apart from, and a fill around a full-screen message is decoration rather than information. The product has one desaturated clay for a genuine error and it is spent on the sentence, never on a panel behind it. This is the calm rule of Concept at the composition level

When to use it

Every flow in ia/docs/flows.md meets it. Flow A hits it when the bank connection is cancelled or fails, Flow B when the service list will not load, Flow C in the cancel guide's dead end, Flow D at an alert that leads to a screen with nothing on it, Flow E when the sign-in link has expired. That is the reason it earned a name before anything else did: it is not one screen's problem, it is the shape the product takes whenever it has to say no.

The rule that makes it calm is in the copy, and the composition protects it. The announcement says what happened and what is still fine, in that order, and the exit is a real one. Both come from voice/docs/voice.md: no fault, no exclamation, and a way out always present. A composition that puts the button above the sentence would invite the opposite text, which is why the order is part of the pattern rather than a habit.

It is not the same thing as an empty block. The empty block is a component for a zone inside a working screen, the way Home shows an empty list under a head that still works. The interruption is the container itself having stopped.

The rule, and the anti-rule

The container has stopped

Nothing to reveal yet

Add at least one subscription and your list appears here. Even a partial list is saved, so you can come back any time.

Do this later

Nothing else is on the screen, because nothing else can be. The announcement, the way on, and a real way past it. This is the pattern.

The container still works

We could not refresh just now. Your figures are from this morning. Try again

You're paying for 14 subscriptions

Home when the refresh failed. Every figure is still there and still true, so the screen has not stopped and this is not an interruption: it is a wash block annotating a screen that works, with the recovery inside the sentence that explains it. Turning this into an interruption would hide fourteen subscriptions to report that one request was slow. Two more pages are this case and not the pattern: add-subscription-empty, where the manual form under the notice is the point of the screen, and cancel-guide-no-guide, where the guide beside it is still readable.

An exit that is a control

That link has expired

Links last one hour, so we can be sure it is you. Ask for a new one and it will be there in a moment.

A person taps and something happens. Every exit in this pattern is an edge a person takes.

A wait is not an interruption

Confirming with your bank

This usually takes a few seconds.

upgrade-processing and connect-bank-loading carry a status block and no way out at all, and neither takes this class. An edge the system takes is not a control: a button on a wait screen offers an action the product does not have, and on a charge in flight it offers the one thing that screen must not offer. That is rule U7 in the usage rules, and these two pages are its whole evidence.

Limits

U4, composition. One .textblock.status per screen, so one interruption per screen. Two announcements are two states. U6, composition. The way out is required and lives in the same container, except on cancel-guide-no-guide, where it is in the column beside it. U1, composition. One filled action in the row, whatever else the exit offers. Full table with sources: Usage rules.

Where it stands

Sixteen pages across twelve screens, counted on the grey product, which is the whole of it. Seven are in colour today and carry the class; the other nine are still grey and take it at the rollout. This list is the proof the pattern exists, so it is named page by page rather than summarised.

ScreenPagesHostIn colour
Add subscriptionadd-subscription-errorscreenyes
Guided revealguided-reveal-emptyscreenyes
History and trendshistory-trends-empty, history-trends-errorscreenyes
Subscription detailsubscription-detail-error.col1yes
Upgradeupgrade-payment-failedscreenyes
Connect bankconnect-bank-cancelled, connect-bank-empty, connect-bank-errorscreengrey, stage 12
Sign insign-in-expired, sign-in-sentscreengrey, stage 12
Alertsalerts-error.rows-colyes
Share snapshotshare-snapshot-errorscreengrey, stage 12
Cancel guidecancel-guide-blocked.sheetgrey, stage 12
Connectionsconnections-add-source.sheet, exit is a grid of doorsgrey, stage 12
Data and privacydata-privacy-delete-confirm.sheetgrey, stage 12

The dialog sheet host is the one thing on this page that pixels have not checked, because no sheet exists in colour yet. It is said here rather than left to be noticed: three grey pages, none proved, and the rollout is where it gets proved.

The technical half

DeclaresValueWhere it came from
the gap under the announcementvar(--space-24)the text block's own bottom margin, which stays in text-block.css for the other 27 text blocks in the product. Inside the pattern it is zeroed there and declared here, so the composition has one home
colour, type, radius, elevationnonea pattern arranges components and reads tokens. A hex here would be a component nobody declared

Lives in design/system/patterns/interruption.css, imported at the foot of design/system/index.css, after every component. Measured before and after the extraction at 360 and 1280 on all six coloured pages: the gap is 24px on both sides of the change, and no element moved. Under 900 the screen is normal flow and adjacent margins collapse, so 24 and 0 collapse the same way 0 and 24 do; at 900 and past it the screen is a flex column and both readings are a plain sum. The account is on the proof.

<main class="screen interruption act-foot"> <div class="textblock status" role="status" aria-live="polite"> <h1>That payment did not go through</h1> <p>Your bank did not approve it, so nothing was charged.</p> </div> <div class="actions"> <a class="btn primary" href="upgrade.html">Try another payment method</a> <a class="btn" href="history-trends-locked.html">Maybe later</a> </div> <p class="muted">You can come back to this from any Pro feature.</p> </main>