Skip to content

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.
Web verifiediOS mapped
Action hierarchyLive Web specimen
Hierarchy
State

Choose a hierarchy and state, then try the action.

Use

For a clear next step with a specific outcome: review, save, connect, or remove. Keep the primary treatment flat.

Avoid

Giving every action the primary treatment or using vague labels such as “Continue.”

Anatomy & states

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.

Accessible by design

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.25rem Small, medium, and large.
Padding
0.45rem 0.7rem → 0.85rem 1.1rem Scales with the selected size.
Corner radius
0.65rem / 0.8rem Small uses the tighter radius.
Primary treatment
Flat semantic fill · no elevation Hover changes color without lifting the control.
Label
0.82rem–1rem · weight 850 Medium inherits the surrounding interface size.
Content gap
0.45rem Between an icon and label.
Focus and motion
1px softened ring + 3px offset · 140ms Transitions are removed for reduced motion.
States
ready / disabled / loading Loading 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

Use the shared component so semantic hierarchy, focus, disabled behavior, and reduced motion remain consistent.

<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.
Web verifiediOS mapped
Labeled inputLive Web specimen

Use an address you monitor for account access and finance notifications.

General form field 2.75rem minimum height with one visible control boundary.
Compact popup control

2.5rem borderless control on a filled popup card.

Use

For one answer that affects a person’s account, notification, or financial decision.

Avoid

Using placeholder text as the only label or giving an error with no way to recover.

Anatomy & states

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.

Accessible by design

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.75rem Input and select; textarea starts at 7rem.
Control radius
0.75rem One semantic boundary surrounds the editable control.
Input padding
0.55rem 0.7rem Textarea uses 0.75rem.
Stack gap
0.45rem Label, help, control, and recovery remain grouped.
Type
0.86rem label · 0.8rem help/error Label weight 800; help line-height 1.45.
Focus
1px softened ring + 2px offset Error changes the boundary without replacing focus.
States
default / error / read-only / disabled Non-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 fill Compact 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

Use FormField for bordered general forms and keep its descriptive relationships aligned. Use the shared popup pattern for compact dialogs; do not restyle FormField globally.

<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.
Web verifiediOS mapped
Default surfaceOperating account

$18,420 available today

Quiet surfaceForecast context

Supporting detail stays present without competing for attention.

Selected surfaceNext decision

Review the payroll transfer before Friday.

Use

To group a balance, decision, or related controls into a legible unit.

Avoid

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.

Anatomy & states

Boundary, interior rhythm, content hierarchy, and optional selection state are enough. Elevation should support separation, not status.

Accessible by design

A surface does not automatically become interactive. Give actionable groups a clear control and a meaningful accessible name.

SpecificationsCurrent Web values
Corner radius
1rem Shared across default, quiet, and selected variants.
Boundary
1px semantic divider Part of the Surface contract; repeated financial lists use a flat list context instead.
Padding
0.8rem / 1.25rem / 1.5–3rem Small, medium, and responsive large.
Elevation
0 / 1 / 2 / 3 Semantic elevation roles, plus the shared inset highlight.
Variants
default / quiet / selected Variant communicates grouping or selection—not status.
Semantic element
div / article / aside / section Choose 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

Choose the semantic element from the content’s role; a Surface does not add interaction by itself.

<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.
Web verifiediOS mapped
Feedback toneLive Web specimen
A useful detail, not an interruption

The projected balance includes only scheduled movement already visible in this example.

Use

For a state that affects confidence, needs recovery, or explains a material change.

Avoid

Using a banner as decoration, or relying on its color to carry urgency on its own.

Anatomy & content

Signal, concise title, and next-useful detail. Tone should be reinforced by plain language: what happened, what it means, and what to do now.

Accessible by design

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.9rem Keeps the marker and message in one compact group.
Corner radius
0.85rem Slightly tighter than a general surface.
Content gap
0.65rem Between the semantic marker and message.
Marker
0.65rem + 0.22rem ring Color follows the selected semantic tone.
Type
0.86rem / 1.5 · title 0.9rem Compact but readable for status and recovery.
Announcement role
status · danger defaults to alert Use 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

Select tone from meaning and urgency; the shared component supplies the corresponding visual treatment and announcement role.

<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.
Web verifiediOS mapped
Reading preferencePublic Light
Projected balance$14,280

One action and one forecast signal remain distinct in either environment.

Light is previewed below.

Use

In settings or a preference moment where a person can intentionally choose how the product is read.

Avoid

Treating the theme as a novelty control or changing the meaning of action, forecast, warning, or risk.

Anatomy & states

System, Light, and Dark are mutually exclusive choices. The selected state is visible, while the preview keeps the same balance and signal relationship.

Accessible by design

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 gap One compact group contains all three choices.
Option height
min 2rem The guide header provides additional surrounding target space.
Option padding
0.35rem 0.55rem Compact view reduces inline padding to 0.45rem.
Corner radius
999px Applied to the group and each option.
Label
0.72rem · weight 800 Compact view uses 0.68rem.
Keyboard
Arrow keys / Home / End Radio 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

Use the persisted Theme Selector for the public header. Use the preference control alone only when another controller owns saving and recovery.

<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.
Web verifiediOS mapped
Direction with contextUse arrow keys to inspect

7 dated forecast points. Use left and right arrow keys to inspect each point. Annotations: Vendor renewal, Receivables land.

Use

To make movement, timing, range, and a meaningful financial event easier to inspect together.

Avoid

Presenting an optimistic line as certainty or hiding the moment a buffer becomes constrained.

Anatomy & states

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.

Accessible by design

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 viewBox The SVG stretches within its host rather than fixing a screen size.
Plot inset
36 units Keeps the line, range, and endpoints within the viewBox.
Color roles
ink edge + cyan→mint light The accessible base stroke carries meaning; exact iOS color is the visual highlight.
Inputs
dated series / annotations / projected value The caller owns data preparation and disclosure language.
Inspection
← → / Home / End / Escape Every dated point is available without pointer input.
Focus
1px softened ring + 0.45rem offset The whole inspection surface receives focus.
Formatting
locale + ISO currency Visible 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

Give every chart a unique idPrefix. Prepare the dated series and plain-language annotations before rendering the visual.

<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.
Web verifiediOS mapped
Product transaction hierarchyLive Web specimen
Content state
Color method
Row selection
TransactionDateAccountAmountFuture balance
Due Today

Payroll transfer

-$8,420.00
$18,420.00

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.

Use

For a transaction or scheduled event that needs title, timing, amount, and balance context in one scan.

Avoid

Reducing the row to a colored amount, or separating its balance consequence from the event that caused it.

Anatomy & states

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.

Accessible by design

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 desktop The first desktop row begins at 0.55rem beneath its sticky header.
Compact layout
content / value Title and metadata lead; amount and future balance align to the end.
Desktop columns
1fr / 8.5rem / 11rem Dedicated 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 desktop Row gap then column gap; headline and metadata retain a 0.5rem internal gap.
Type
1rem title / amount / balance · 0.87rem metadata The authenticated interface uses its system UI font stack.
Status and tag
0.66rem · weight 700 Text carries meaning alongside semantic color.
Color method
Square / Text A saved setting chooses the marker or text treatment; clear leaves the default ink.
Selection
1.5rem rail · 2.75rem target The native checkbox has a generous invisible hit area and explicit action label.
Ellipsis control
1.75rem / 28px circle Use --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 / low Ignored 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

Format values before passing them to the row. Resolve saved colors through getTransactionColor, pass the saved Square or Text method, and connect selectable rows to explicit selection state and callbacks.

<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.
Web verifiediOS mapped
Use

For accounts whose details and current balance need distinct, frequent editing paths.

Avoid

Turning every account group into a card, or making the entire row one ambiguous action.

Anatomy & states

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.

Accessible by design

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) / auto Credit cards add a second row for impact and balance labels.
Row height
3.7rem bank · 4.55rem credit Each independent link retains a minimum 2.75rem target.
Inline inset
0.95rem Rows, group headers, and summary copy share one reading edge.
Gaps
0.85rem columns · 0.15rem metadata Credit-card context stays attached without loosening the row.
Bank type
1rem · weight 400 Applied to both the account name and balance.
Balance capsule
0.28rem 0.62rem · radius 0.68rem Surface 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 links Name 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 none Heading, 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

Keep the shared row inside a flat account list. Group-level actions such as Add account use the standard Button; they are not custom row chrome.

<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.

Runrate Brand & Component Guide

Production Web is the source of truth · iOS correspondence where noted