For a clear next step with a specific outcome: review, save, connect, or remove. Keep the primary treatment flat.
Curated components
Every piece earns its place in the decision.
This is a smaller working set, not a catalog. Each specimen is a current Web component or faithful Web reading pattern. Production Web is authoritative; iOS correspondence preserves the same intent in native form.01 / Action
Button
Make the next meaningful action easy to find, and let its consequence determine its weight.Choose a hierarchy and state, then try the action.
Giving every action the primary treatment or using vague labels such as “Continue.”
Label, hierarchy, and state are the whole component. Ready accepts one action; disabled explains why action is unavailable; loading retains the label and blocks repeat requests.
Keep the target generous and focus visible. A loading action exposes busy state without replacing its outcome-oriented label.
SpecificationsCurrent Web values
- Minimum height
2.25rem / 2.75rem / 3.25remSmall, medium, and large.- Padding
0.45rem 0.7rem → 0.85rem 1.1remScales with the selected size.- Corner radius
0.65rem / 0.8remSmall uses the tighter radius.- Primary treatment
Flat semantic fill · no elevationHover changes color without lifting the control.- Label
0.82rem–1rem · weight 850Medium inherits the surrounding interface size.- Content gap
0.45remBetween an icon and label.- Focus and motion
1px softened ring + 3px offset · 140msTransitions are removed for reduced motion.- States
ready / disabled / loadingLoading retains its label, blocks repeat action, and exposes aria-busy.
Web measurements are implementation references. Preserve the design intent when adapting to iOS rather than copying these values literally.
ImplementationWeb verified · Svelte
<script lang="ts">
import Button from '$lib/components/Button.svelte';
let isSaving = $state(false);
</script>
<Button variant="primary" size="lg" loading={isSaving}>
Save forecast
</Button>Web verifiedThe shared action preserves semantic button or link behavior, focus treatment, and the primary-to-danger hierarchy.
iOS mappedUse native button configurations and the same hierarchy. A destructive path needs context and an appropriate confirmation step.
02 / Input
Form field
Financial information is easier to correct when the label, context, and recovery path remain together.Use an address you monitor for account access and finance notifications.
2.5rem borderless control on a filled popup card.
For one answer that affects a person’s account, notification, or financial decision.
Using placeholder text as the only label or giving an error with no way to recover.
Persistent label, short help, control, and recovery message work as one unit. The bordered Form Field serves general forms; compact popup forms use their own borderless filled controls.
Keep the label connected to the control, state requiredness in words, and say what is wrong plus what a valid answer looks like.
SpecificationsCurrent Web values
- Control height
min 2.75remInput and select; textarea starts at 7rem.- Control radius
0.75remOne semantic boundary surrounds the editable control.- Input padding
0.55rem 0.7remTextarea uses 0.75rem.- Stack gap
0.45remLabel, help, control, and recovery remain grouped.- Type
0.86rem label · 0.8rem help/errorLabel weight 800; help line-height 1.45.- Focus
1px softened ring + 2px offsetError changes the boundary without replacing focus.- States
default / error / read-only / disabledNon-editable and unavailable are visually distinct.- Money entry
text + inputmode="decimal"Use locale-aware formatting and tabular, end-aligned figures.- Popup context
2.5rem · borderless fillCompact popup controls are a separate production pattern; do not remove the Form Field boundary globally.
Web measurements are implementation references. Preserve the design intent when adapting to iOS rather than copying these values literally.
ImplementationWeb verified · Svelte
<script lang="ts">
import FormField from '$lib/components/FormField.svelte';
import { formatMoneyInput } from '$lib/ledger/format';
</script>
<FormField
label="Starting balance"
forId="starting-balance"
description="Enter the balance shown by the account."
required
>
<input
id="starting-balance"
type="text"
inputmode="decimal"
value={formatMoneyInput(1842000)}
aria-describedby="starting-balance-description"
/>
</FormField>Web verifiedThe shared Form Field keeps a visible boundary, label, description, error, and focus treatment together. Compact popup controls are a separate production context.
iOS mappedUse a native labeled control with appropriate keyboard type, persistent help where needed, and recovery text near the field.
Related production pattern. Popup card grouping, selectors, validation, and actions are documented in Popup forms.
03 / Grouping
Surface
Surfaces make related information readable together; they should never turn every paragraph into a card.$18,420 available today
Supporting detail stays present without competing for attention.
Review the payroll transfer before Friday.
To group a balance, decision, or related controls into a legible unit.
Stacking decorative containers when hierarchy, spacing, and a heading would do the job. Repeated account rows belong in a flat list, not one Surface per group.
Boundary, interior rhythm, content hierarchy, and optional selection state are enough. Elevation should support separation, not status.
A surface does not automatically become interactive. Give actionable groups a clear control and a meaningful accessible name.
SpecificationsCurrent Web values
- Corner radius
1remShared across default, quiet, and selected variants.- Boundary
1px semantic dividerPart of the Surface contract; repeated financial lists use a flat list context instead.- Padding
0.8rem / 1.25rem / 1.5–3remSmall, medium, and responsive large.- Elevation
0 / 1 / 2 / 3Semantic elevation roles, plus the shared inset highlight.- Variants
default / quiet / selectedVariant communicates grouping or selection—not status.- Semantic element
div / article / aside / sectionChoose the element from the content’s role.
Web measurements are implementation references. Preserve the design intent when adapting to iOS rather than copying these values literally.
ImplementationWeb verified · Svelte
<script lang="ts">
import Surface from '$lib/components/Surface.svelte';
</script>
<Surface as="section" variant="default" elevation={1} padding="md">
<h2>Operating account</h2>
<p>Account context and the decision it supports.</p>
</Surface>Web verifiedCurrent Web surfaces communicate default, quiet, and selected grouping with restrained elevation and a visible boundary. Flat repeated lists remain a separate composition.
iOS mappedAdapt the same hierarchy to native grouped content and material conventions; preserve purpose, not the Web card shape.
04 / Feedback
Notice
A message earns a place when it changes how someone understands the moment or what they can do next.The projected balance includes only scheduled movement already visible in this example.
For a state that affects confidence, needs recovery, or explains a material change.
Using a banner as decoration, or relying on its color to carry urgency on its own.
Signal, concise title, and next-useful detail. Tone should be reinforced by plain language: what happened, what it means, and what to do now.
Use a status announcement for updates and an alert only when recovery needs immediate attention. Do not interrupt reading for low-stakes confirmation.
SpecificationsCurrent Web values
- Padding
0.8rem 0.9remKeeps the marker and message in one compact group.- Corner radius
0.85remSlightly tighter than a general surface.- Content gap
0.65remBetween the semantic marker and message.- Marker
0.65rem + 0.22rem ringColor follows the selected semantic tone.- Type
0.86rem / 1.5 · title 0.9remCompact but readable for status and recovery.- Announcement role
status · danger defaults to alertUse alert only for recovery needing immediate attention.
Web measurements are implementation references. Preserve the design intent when adapting to iOS rather than copying these values literally.
ImplementationWeb verified · Svelte
<script lang="ts">
import Notice from '$lib/components/Notice.svelte';
</script>
<Notice tone="warning" title="A decision is approaching">
<p>The operating buffer narrows before receivables arrive.</p>
</Notice>Web verifiedThe current notice pairs semantic tone with status or alert behavior and visible, text-based context.
iOS mappedUse an inline feedback or alert pattern according to urgency. Keep the same clear recovery language and avoid color-only meaning.
05 / Preference
Reading environment
Light and Dark are two environments with shared financial meaning—not two visual identities.One action and one forecast signal remain distinct in either environment.
Light is previewed below.
In settings or a preference moment where a person can intentionally choose how the product is read.
Treating the theme as a novelty control or changing the meaning of action, forecast, warning, or risk.
System, Light, and Dark are mutually exclusive choices. The selected state is visible, while the preview keeps the same balance and signal relationship.
The control behaves as a radio group with keyboard movement. Contrast and focus remain legible in either reading environment.
SpecificationsCurrent Web values
- Container
0.2rem padding · 0.15rem gapOne compact group contains all three choices.- Option height
min 2remThe guide header provides additional surrounding target space.- Option padding
0.35rem 0.55remCompact view reduces inline padding to 0.45rem.- Corner radius
999pxApplied to the group and each option.- Label
0.72rem · weight 800Compact view uses 0.68rem.- Keyboard
Arrow keys / Home / EndRadio focus follows selection and respects RTL direction.
Web measurements are implementation references. Preserve the design intent when adapting to iOS rather than copying these values literally.
ImplementationWeb verified · Svelte
<script lang="ts">
import ThemeSelector from '$lib/public/ThemeSelector.svelte';
</script>
<ThemeSelector />Web verifiedThe current Web preference control supports System, Light, and Dark choices with a keyboard-operable selected state.
iOS mappedUse a settings-row pattern and respect the system appearance by default. Preserve semantic roles rather than copying Web colors directly.
06 / Financial reading
Forecast visualization
A forecast is a direction to inspect, not a promise to decorate.7 dated forecast points. Use left and right arrow keys to inspect each point. Annotations: Vendor renewal, Receivables land.
To make movement, timing, range, and a meaningful financial event easier to inspect together.
Presenting an optimistic line as certainty or hiding the moment a buffer becomes constrained.
Current point, projected point, directional range, date cues, and selected annotations tell a more honest story than a line alone. A crossed zero threshold remains explicit.
The Web chart is keyboard-inspectable. Its iOS correspondence should offer equivalent point-level inspection and a non-visual reading of the trend.
SpecificationsCurrent Web values
- Responsive canvas
900 × 300 viewBoxThe SVG stretches within its host rather than fixing a screen size.- Plot inset
36 unitsKeeps the line, range, and endpoints within the viewBox.- Color roles
ink edge + cyan→mint lightThe accessible base stroke carries meaning; exact iOS color is the visual highlight.- Inputs
dated series / annotations / projected valueThe caller owns data preparation and disclosure language.- Inspection
← → / Home / End / EscapeEvery dated point is available without pointer input.- Focus
1px softened ring + 0.45rem offsetThe whole inspection surface receives focus.- Formatting
locale + ISO currencyVisible and announced values share the selected financial context.
Web measurements are implementation references. Preserve the design intent when adapting to iOS rather than copying these values literally.
ImplementationWeb verified · Svelte
<script lang="ts">
import ForecastChart from '$lib/components/ForecastChart.svelte';
</script>
<ForecastChart
{series}
{annotations}
{projectedDisplay}
locale="en-US"
currency="USD"
ariaLabel="30-day operating-account forecast"
/>Web verifiedThe current chart supports keyboard inspection, announced point values, annotations, and a clear negative-range cue.
iOS mappedUse a native financial trend presentation with equivalent point inspection, annotations, and accessible summary content.
07 / Financial reading
Ledger row
The smallest financial unit still needs to say what moved, when, and what it means for the balance.Payroll transfer
Production coverage. This specimen includes row and header selection, Square and Text color methods, Past Due, and the wide Date and Account columns. Loading and empty states belong to the surrounding ledger view.
For a transaction or scheduled event that needs title, timing, amount, and balance context in one scan.
Reducing the row to a colored amount, or separating its balance consequence from the event that caused it.
Event, recurrence, date, account, signed movement, and future balance have distinct jobs. Selection, saved Square or Text color, Past Due, ignored, and low-balance states are part of the production row contract.
Keep amounts and balances understandable in reading order. Status uses text, and each native selection checkbox names its transaction while retaining a 2.75rem target.
SpecificationsCurrent Web values
- Row padding
0.92rem compact · 0.82rem desktopThe first desktop row begins at 0.55rem beneath its sticky header.- Compact layout
content / valueTitle and metadata lead; amount and future balance align to the end.- Desktop columns
1fr / 8.5rem / 11remDedicated movement and future-balance columns begin at the desktop breakpoint.- Wide columns
minmax(16rem, 1.7fr) + 3–4 × minmax(8.5rem, 1fr)Date is promoted to a column; Account appears when the setting is enabled.- Content gap
0.5rem / 0.9rem compact · 0.32rem / 1.25rem desktopRow gap then column gap; headline and metadata retain a 0.5rem internal gap.- Type
1rem title / amount / balance · 0.87rem metadataThe authenticated interface uses its system UI font stack.- Status and tag
0.66rem · weight 700Text carries meaning alongside semantic color.- Color method
Square / TextA saved setting chooses the marker or text treatment; clear leaves the default ink.- Selection
1.5rem rail · 2.75rem targetThe native checkbox has a generous invisible hit area and explicit action label.- Ellipsis control
1.75rem / 28px circleUse --rr-ellipsis-control-size for transaction rows, Chart, Calendar, and future overflow menus.- Figures
tabular · end aligned · dir="auto"Keeps repeated values scannable across locales.- Balance states
future / ignored / lowIgnored is not zero; low balance adds text-backed risk context.
Web measurements are implementation references. Preserve the design intent when adapting to iOS rather than copying these values literally.
ImplementationWeb verified · Svelte
<script lang="ts">
import LedgerRow from '$lib/components/LedgerRow.svelte';
import { getTransactionColor } from '$lib/ledger/format';
let selected = $state(false);
const transactionColor = getTransactionColor('red');
</script>
<LedgerRow
title="Payroll transfer"
date="Fri, Jul 24"
amount="-$8,420.00"
amountTone="negative"
balance="$6,180.00"
statusLabel="Due today"
statusTone="due-today"
recurring
accountLabel="Operating"
{transactionColor}
transactionColorActive
colorApplicationMethod="Square"
lowBalance
selectable
{selected}
onSelectedChange={(value) => (selected = value)}
/>Web verifiedThe production row includes selection, saved Square or Text color application, responsive Date and Account columns, status treatment, numeric alignment, and future-balance context.
iOS mappedUse a native list-row hierarchy that preserves the event, status, amount, and resulting balance. Do not let compact layout remove the consequence.
08 / Financial reading
Account row
Account details and current balance are related, but they are not the same editing task.Bank Accounts
$20,775.55Production coverage. Bank-account and credit-card row states live here. Account editing uses the shared Popup forms pattern.
For accounts whose details and current balance need distinct, frequent editing paths.
Turning every account group into a card, or making the entire row one ambiguous action.
Account-name action, balance action and capsule, and quiet inter-row divider form the base row. Credit cards add explicit impact and balance labels for add, subtract, and ignore states.
Give both links outcome-specific names, keep their targets at least 44px, and never communicate ignored, added, or subtracted behavior through color or strike-through alone.
SpecificationsCurrent Web values
- Layout
minmax(0, 1fr) / autoCredit cards add a second row for impact and balance labels.- Row height
3.7rem bank · 4.55rem creditEach independent link retains a minimum 2.75rem target.- Inline inset
0.95remRows, group headers, and summary copy share one reading edge.- Gaps
0.85rem columns · 0.15rem metadataCredit-card context stays attached without loosening the row.- Bank type
1rem · weight 400Applied to both the account name and balance.- Balance capsule
0.28rem 0.62rem · radius 0.68remSurface fill, no border; the surrounding action supplies the larger target.- Divider
1px · semantic divider at 32%Inset 0.95rem and shown only between rows.- Actions
2 independent linksName edits account details; value opens balance-first editing.- Figures
tabular · end aligned · dir="auto"Ignored balances add explicit text to strike-through.- List context
border 0 · radius 0 · shadow noneHeading, total, whitespace, and quiet separators create hierarchy.
Web measurements are implementation references. Preserve the design intent when adapting to iOS rather than copying these values literally.
ImplementationWeb verified · Svelte
<script lang="ts">
import AccountRow from '$lib/components/AccountRow.svelte';
import type { RunrateAccount } from '$lib/ledger/types';
let { accounts, locale, currency, selectedAccountId } = $props<{
accounts: RunrateAccount[];
locale: string;
currency: string;
selectedAccountId: string | null;
}>();
</script>
<div class="accounts-section__list">
{#each accounts as account (account.id)}
<AccountRow {account} {locale} {currency} {selectedAccountId} />
{/each}
</div>Web verifiedThe production row uses separate name and balance links, compact regular-weight bank typography, a borderless value capsule, and text-backed credit-card impact.
iOS mappedPreserve the compact native-list rhythm, distinct editing intentions, explicit credit-card semantics, and 44pt targets. Adapt to native row behavior rather than copying Web pixels.