Design Lounge
Install

Nº 506 of 520

Manage subscription, honest cancel

An iOS subscription screen: navy plan card, monthly or yearly with real arithmetic, billing rows, Restore purchases, and a cancel sheet that states the end date and the losses.

Preview

Open full size
390 × 844 · 19.0 KB · 282 lines

Use it

Free to use · credit appreciated

1 Once, in your project

npx skills add SusanAcharya/Design-Lounge

2 Then ask

Using the Design Lounge skill, build "Manage subscription, honest cancel" (piece phone-subscription-manage) in my stack. Match its brief; restyle it onto my product's theme.

No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.

Brief

234 lines

Build brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.

What it is

The subscription screen of Larkmoor, an invented podcast app, in an iOS 26-ish language: a glass nav bar, a deep navy plan card with a faint waveform, two plan tiles, grouped billing rows, and a bottom sheet for cancelling. The price is set in Gloock, a high-contrast display serif, so the number reads like a receipt headline. Everything else is Onest. What’s worth copying is that it refuses dark patterns. All the arithmetic is computed and shown. Cancel is a normal row in the accent colour, not a grey whisper. The cancel sheet names the exact date access ends and the four concrete things that stop. “Keep Plus” and “Cancel plan” are the same size, side by side.

Reference behaviour

  1. Initial state, active monthly. The plan card shows “LARKMOOR PLUS”, a green “Active” chip, “$7.99” at 52px with “per month”, “Renews 14 Nov”, and “Billed to card ending 4417”.
  2. Plan tiles. Two radio tiles in a 2-column grid, 10px gap, min 116px tall:
    • Monthly: “$7.99 /mo”, “$95.88 a year”, grey “Current” tag, selected (2px ink ring, filled radio).
    • Yearly: “$69.99 /yr”, “$5.83 a month”, persimmon “Save 27%” tag.
    • All derived values are computed from integer cents: 799 × 12 = 9588; 9588 − 6999 = 2589; round(2589 / 9588 × 100) = 27; round(6999 / 12) = 583.
  3. Selecting a different plan opens a panel under the tiles (max-height 0 → 260px, 340ms):
    • Monthly to yearly: “You pay $69.99 on 14 Nov, then once a year. That is $25.89 less than 12 months at $7.99.” plus an ink “Switch to yearly” button.
    • Yearly to monthly: “You pay $7.99 on 14 Nov, then every month. Over a year that is $95.88, $25.89 more than yearly.” plus “Switch to monthly”.
    • Selecting the current plan again closes the panel.
    • A permanent hint under the tiles: “Plan changes start at your next renewal. Nothing is charged today.”
  4. Switch. Tap “Switch to yearly”. The card keeps “$7.99 per month” (that is still what you pay now) and the line becomes “Renews 14 Nov as Yearly, $69.99”. The “Current” tag moves to Yearly. The card does a 340ms scale settle from 0.98. Toast: “Switches to yearly on 14 Nov.” Switching back toasts “Change undone. You stay on monthly.”
  5. Billing rows. “Card ending 4417 / Expires 08/28” with a chevron (the demo toasts “Payment methods open in your device settings.”). “Restore purchases / Use after reinstalling”. Tap Restore: the chevron becomes an 18px spinner, the line reads “Checking your purchases…” for 1.3s, then “Checked just now”, and a toast says “Up to date. Plus is already active here.” (when cancelled: “No active plan found. Plus ends on 14 Nov 2026.”).
  6. Cancel row. Its own group, label “Cancel subscription” in --accent-ink, 56px tall. Under it: “Cancel any time. You keep Plus until the end of the period you paid for.”
  7. Cancel sheet. Rises from the bottom (340ms, iOS sheet curve) over a 38% navy scrim. Grabber 36×5. Title “Cancel Larkmoor Plus?” in Gloock 26px. Then “You keep everything until 14 Nov 2026 and won’t be charged again. After that date:”, followed by four losses, each with an icon:
    • “38 downloaded episodes are removed from this phone”
    • “Ads play again on free shows”
    • “Transcripts and chapter search are turned off”
    • “Morning Queue stops building each day”
    • Then “Your follows, listening history and playlists stay.”
    • Two equal 50px buttons: “Keep Plus” (outlined) and “Cancel plan” (ink fill). Focus starts on Keep Plus. Escape or a scrim tap keeps the plan.
  8. Cancelled state. The chip turns persimmon “Cancelled”. The line reads “Ends on 14 Nov 2026” and “You won’t be charged again”. A full-width “Resubscribe” button appears inside the card. The Change plan block and the Cancel row are removed. The footer reads “Resubscribing keeps your downloads. Billing restarts on 14 Nov.” Toast: “Cancelled. Plus stays on until 14 Nov 2026.” Focus moves to Resubscribe.
  9. Resubscribe restores the active state with whatever plan was scheduled, toasts “Welcome back. Plus renews 14 Nov.”, and moves focus to the Cancel row.

Structure

390 x 844
+--------------------------------------------+  glass nav, padding-top 54
| < Account        Subscription              |  44px
+--------------------------------------------+
| +----------------------------------------+ |  plan card, navy, radius 24
| | LARKMOOR PLUS              (o Active)  | |  padding 18 20 20
| | $7.99  per month           ||| ||| ||  | |  52px Gloock, waveform 180x70
| | Renews 14 Nov                          | |
| | Billed to card ending 4417             | |
| | [        Resubscribe        ] (off)    | |  only when cancelled
| +----------------------------------------+ |
| CHANGE PLAN                                |
| +------------------+ +------------------+  |  radio tiles, radius 16
| | Monthly [Current]| | Yearly [Save 27%]|  |  min-height 116
| | $7.99 /mo        | | $69.99 /yr       |  |
| | $95.88 a year (o)| | $5.83 a month ( )|  |
| +------------------+ +------------------+  |
| [ change panel + Switch button ]           |  only when selection differs
| Plan changes start at your next renewal... |
| BILLING                                    |
| | [card] Card ending 4417  Expires 08/28 > |  rows 56px
| | [ccw]  Restore purchases               > |
| | Cancel subscription                      |  own group
| Cancel any time. You keep Plus until ...   |
+--------------------------------------------+
  • header.nav with the back button and an h1 “Subscription”.
  • The plan card is a section labelled by the brand line. The waveform is a decorative svg with aria-hidden.
  • The tiles are button role="radio" inside role="radiogroup" labelled by the “Change plan” heading, with roving tabindex.
  • The change panel is aria-live="polite", so the new arithmetic is read out when it opens.
  • The billing rows are buttons in a role="group". The cancel sheet is role="dialog" with aria-modal, aria-labelledby, and aria-describedby pointing at the date sentence.

Tokens

:root {
  --bg: #e6ecee;            /* cool mist page */
  --surface: #f7fafa;       /* tiles, rows, sheet */
  --fill: #dbe3e6;          /* row icon wells, Current tag */
  --ink: #0f2130;           /* text, selected ring, ink buttons */
  --ink-2: #3a4c59;
  --ink-3: #5a6a76;
  --line: #d0d9dd;
  --navy: #12324a;          /* plan card */
  --navy-2: #1b4462;        /* waveform */
  --on-navy: #f3eee6;
  --on-navy-2: #b9c6cf;
  --accent: #e8552d;        /* persimmon, decorative only */
  --accent-btn: #c8461f;    /* Resubscribe fill, white text */
  --accent-ink: #b23f1c;    /* back link, cancel row, Save tag text, focus */
  --accent-soft: #fbe2d8;
  --ok: #2d7a52;
  --glass: rgba(230,236,238,.8);
  --serif: "Gloock", Georgia, serif;
  --sans: "Onest", -apple-system, system-ui, sans-serif;
  --r-s: 12px; --r-m: 16px; --r-l: 24px; --r-sheet: 28px; --pill: 999px;
  --ease: cubic-bezier(.2,.7,.2,1);
  --sheet: cubic-bezier(.32,.72,0,1);
  --micro: 160ms; --layout: 340ms;
  --top: max(54px, env(safe-area-inset-top));
  --bottom: max(34px, env(safe-area-inset-bottom));
}

Typography

RoleFamilySizeWeightLine-heightTrackingCase
Nav titleOnest17px6001.450Sentence
Card brandOnest12px60010.12emUpper
Card priceGloock52px4001-0.02emlining nums
Card period, lineOnest16px / 15px4001.450Sentence
Section headingOnest13px6001.450.06emUpper
Tile nameOnest14px6001.450Sentence
Tile priceGloock26px4001.10n/a
Tile sub, hintsOnest13px4001.450tabular nums
TagOnest11px70010.04emSentence
Row titleOnest16px5001.450Sentence
Sheet titleGloock26px4001.15-0.01emSentence
ButtonsOnest16px, Resubscribe 17px60010Sentence

Gloock is only for prices and the sheet title. Never set body copy in it.

Motion

ThingTriggerPropertyFrom → toDurationEasingReduced motion
Tile selectclick / arrowsring, radio fill1px line → 2px ink; radio inset 1.5 → 6px160ms--easeinstant
Tile press:activescale1 → 0.98160ms--easeinstant
Change panelselection differsmax-height, opacity0, 0 → 260px, 1340ms / 160ms--easeinstant
Plan cardswitch, cancel, resubscribescale0.98 → 1340ms--sheetnone
Scrimsheet openopacity0 → 1340ms--easeinstant
Cancel sheetopen / closetranslateY100% → 0340ms--sheetinstant
Restore spinnertaprotateloop800mslinearstatic ring
ToastmessagetranslateY, opacity24px, 0 → 0, 1340ms / 160ms--sheetinstant, 2.6s

States

  • Active, no change pending: green chip, “Renews 14 Nov”.
  • Active, change pending: “Renews 14 Nov as Yearly, $69.99”. The Current tag sits on the pending plan. The other tile shows “Save 27%” (yearly) or no tag (monthly).
  • Cancelled: persimmon chip, “Ends on 14 Nov 2026”, Resubscribe inside the card, plan tiles and cancel row removed.
  • Restoring: row aria-busy="true", spinner, “Checking your purchases…”. Repeat taps are ignored.
  • Tile hover: ring darkens to #aebcc3. Selected: 2px --ink ring, filled radio.
  • Focus-visible: 2px --accent-ink outline, offset 2px, radius 8px.
  • Error (not simulated): a failed restore shows “Couldn’t reach the store. Try again.” in the row line. Keep the chevron and don’t change plan state.
  • Empty (never subscribed): replace the card with a plain “Larkmoor Free” card and show the tiles with a “Start Plus” button. There is no cancel row and no Restore spinner unless tapped.

Accessibility

  • Plan tiles are a radiogroup. Arrow keys move selection and focus. Only the selected tile is in the tab order.
  • The change panel is aria-live="polite". The arithmetic sentence is real text, not an image.
  • The cancel sheet traps Tab between its two buttons, starts focus on “Keep Plus”, closes on Escape, and returns focus to the cancel row. After a confirmed cancel, focus goes to Resubscribe, because the cancel row no longer exists.
  • Toasts are role="status". The waveform and icons are aria-hidden.
  • Targets: back 44px, tiles 116px tall, rows 56px, buttons 50px, Resubscribe 50px.
  • Contrast: #0f2130 on #f7fafa is 15.6:1. #5a6a76 on #f7fafa is 5.3:1 (4.7:1 on the #e6ecee page). #b23f1c on #f7fafa is 5.5:1 and 4.7:1 on #fbe2d8 (Save tag). #f3eee6 on #12324a is 11.5:1. #b9c6cf on #12324a is 7.6:1. White on #c8461f is 4.8:1. Do not use #e8552d behind white text.
  • The status chip has a text label (“Active”, “Cancelled”), not just a coloured dot.

Responsive rules

  • Designed at 390×844. The active state fits in one frame. Opening the change panel makes the page scroll.
  • At 360 wide the two tiles stay side by side (each about 157px). The price in the card stays 52px.
  • Largest text size: rows grow vertically. The tile grid is repeat(auto-fit, minmax(150px, 1fr)), so it stacks to one column when two tiles no longer fit. In production, set the minimum in em so it reacts to text size. Row title and value (flex-wrap: wrap) stack. In the sheet, the paired buttons stack with Keep Plus first. The card price may drop to 44px, and never truncates.
  • Tablet: present as a form sheet, max 560px wide.
  • Do not draw a status bar or home indicator.

Acceptance checklist

Always

  • The plan card shows the price, the period, and the next renewal date in words.
  • Every derived number (yearly total, per-month equivalent, saving, percent) is computed from cents and shown.
  • Plan changes state when they take effect and that nothing is charged today.
  • Payment method and Restore purchases are visible without opening the cancel flow.
  • Cancel is a full-size row in the accent colour, not hidden or greyed.
  • The cancel sheet states the exact end date, says “won’t be charged again”, lists concrete losses, and lists what stays.
  • Keep and Cancel are the same size. No guilt copy, no countdown offers, no extra “are you sure” screens.
  • The cancelled state shows “Ends on ” and a Resubscribe button.
  • Tiles are a keyboard-operable radiogroup. The sheet traps and restores focus.
  • Every target is at least 44px. Reduced motion removes the sheet slide and card settle.

This demo

  • $7.99 monthly, $69.99 yearly, $95.88 a year, $5.83 a month, $25.89 saving, “Save 27%”.
  • “Renews 14 Nov”, “Ends on 14 Nov 2026”, card ending 4417 expiring 08/28.
  • Losses: 38 downloaded episodes, ads on free shows, transcripts and chapter search, Morning Queue.
  • Navy #12324a card on mist #e6ecee, persimmon accent, Gloock prices with Onest text.

Implementation notes

1. Do the money in integer cents. Floating point gives $25.890000000000001, and rounding per month before multiplying gives the wrong saving.

const M = 799, Y = 6999;
const usd = c => '$' + (c / 100).toFixed(2);
const yearOfMonthly = M * 12;              // 9588
const saving = yearOfMonthly - Y;          // 2589
const pct = Math.round(saving / yearOfMonthly * 100); // 27
const perMonth = Math.round(Y / 12);       // 583

2. Keep three plan variables apart. cur is what you pay now, next is what renews, and pick is what the radio shows. Mixing them is how screens end up claiming you pay yearly before you do.

let cur = 'm', next = 'm', pick = 'm', active = true;
function line() {
  if (!active) return `Ends on ${END}`;
  return next === cur ? `Renews ${RENEW}`
    : `Renews ${RENEW} as ${next === 'y' ? 'Yearly, ' + usd(Y) : 'Monthly, ' + usd(M)}`;
}
// change panel is open while pick !== next

3. The equal-weight pair. The two cancel-sheet buttons share one grid. Do not make Keep a filled hero and Cancel a text link.

.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.btn { min-height: 50px; border-radius: 999px; font-weight: 600; }
.btn.line { box-shadow: inset 0 0 0 1.5px var(--ink); color: var(--ink); }
.btn.ink { background: var(--ink); color: var(--surface); }

Common mistakes:

  • “Are you sure you want to lose all your benefits?” copy, or a sad illustration. State facts and a date.
  • Offering a discount inside the cancel flow before the user can confirm.
  • Hiding Cancel in a grey footnote, or behind a “Manage” link that opens a web page.
  • Charging or switching the plan immediately when the user picks yearly. It starts at renewal.
  • Showing “Save 27%” without the dollar amounts it comes from.
  • Rendering the cancelled state as “Inactive” with no date.
  • Drawing a status bar or home indicator.

Details

Palette
Type
Gloock · Onest
Motion
Subtle motion
Build
One session
Designed bySusan AcharyaKathmandu

Studied

Moremedia

A control. It studies this site for quiet, not for a show.

All sources