# Kvitt: components for `:core:design` Status: U1 proposal (2026-10-17), direction-independent anatomy, states and API shape. The look (colours, shapes, type) comes from the chosen direction's tokens (`tokens.md`). In U2 this list moves into `design-spec.md` with final sizes. Names are Compose function names; every component takes a `Modifier` and gets screenshot tests in light and dark, Swedish and English, at 100 % and 200 % font scale (M6). Rules for all components: * Colours and type only from `KvittTheme` (Material 3 `ColorScheme` + `KvittColors` extension + `KvittTypography`); no literal values. * Strings only from resources (`strings.csv` keys); components that need words take them as resolved `String` parameters or take the model value and resolve inside `:core:design` (shared strings live there, §14.7). * Touch targets ≥ 48 dp (`minimumInteractiveComponentSize`), TalkBack `contentDescription` / `semantics` as stated per component. * Material 3 Expressive APIs (`@ExperimentalMaterial3ExpressiveApi`) are used only behind a `:core:design` wrapper so the stable fallback named below can be swapped in one place. The implementing agent confirms each API's status from the official documentation at implementation time (CLAUDE.md › Fast-moving APIs). ## 1. Money and text ### `MoneyText` Formats and shows an amount in öre via `MoneyFormatter` (§9.8). Never shows a sign. * Params: `ore: Long`, `style: AmountStyle` (`Hero`, `Large`, `Medium`, `Small`), `tone: AmountTone` (`Neutral`, `Owed`, `Owing`, `Muted`), `maxLines` (default 1; never ellipsizes: overflow wraps to 2 lines or auto-steps down one type size for `Hero`/`Large`). * Tabular figures always (`fontFeatureSettings = "tnum"`). * Semantics: spoken form from `MoneyFormatter.spoken(ore)`: Swedish "3 250,00 kronor", English "3,250.00 Swedish kronor" (TalkBack reads "kr" and "SEK" poorly). ### `AmountInput` Large amount entry for S-7, S-9, S-19. Parses per §9.6 (string → öre, never floating point). * Params: `value: String`, `onValueChange`, `error: String?`, `size` (`Display`, `Field`). * States: empty (hint "0,00"), typing, error (supporting text, error colour), disabled. * Accepts `,` and `.`; displays the locale's separator; inserts no-break-space thousands grouping while typing. ### `AmountKeypad` (only if the chosen direction uses an in-app keypad) 3 × 4 grid: 1–9, decimal separator (locale), 0, backspace (TalkBack "Radera"); keys ≥ 64 dp tall; optional "Klar". Fallback: system keyboard `KeyboardType.Decimal`. ### `EffectLine` The one-line effect from the viewer's side (§9.2) for an `ExpenseEffect`. * Variants: `Lent(ore)` "Du lånade ut X" (owed tone) · `Borrowed(ore)` "Du lånade X" (owing tone) · `NotInvolved` "Inte inblandad" · `PaidForYourself` "Betalade för dig själv" (neutral). ### `BalanceWording` Wording + amount for a `BalanceView` (§9.3). * Variants: `Global` ("Anna är skyldig dig", "Du är skyldig Anna", "Ni är kvitt") and `Account` (… "Kvitt"). * Semantics: merged into one sentence. ## 2. People ### `PersonAvatar` * Params: `user: User`, `size` (24, 32, 40, 56, 96 dp), `shape` (direction token; fallback `CircleShape`). * Picture via Coil keyed by `user.id` + `pictureVersion` (AC-4.6); else initials (first letter of the first two words, uppercase, AC-4.5) on `avatar-1`/`avatar-2` chosen by a stable hash of the user id (the two colours are tokens). * States: loading (initials shown), picture, initials, uploading (progress ring overlay). * Semantics: the display name (or decorative when the name is next to it). ### `PersonChip` Avatar 24 dp + name ("Du" for the viewer). Variants: `Display`, `Selectable` (S-7 payer toggle, S-7 full-split bearer). Selected state per M3 FilterChip. ### `PayerToggle` Two `PersonChip`s in a connected group; one tap switches. Label "Betalt av". Semantics: "Betalt av: du. Dubbeltryck för att byta till Anna." ## 3. Categories and accounts ### `CategoryIcon` Material Symbols icon (token from §9.7) on a container in `cat--container` with the icon in `cat-`. Sizes 24/32/40/56 dp. Shape per direction (fallback circle or rounded square). Decorative when a name is shown next to it; else the category name. ### `CategoryChip` `CategoryIcon` (24) + name. Variants: `Display`, `Selectable`. Archived variant adds "(arkiverad)". ### `CategoryGrid` Picker grid in S-7 and S-19: tiles of icon + name, 4 columns at 411 dp (3 at 200 %), selected with outline/check. Only active categories. ### `CategoryIconPicker`, `ColorSwatchPicker` S-15 editor: 30 icons (6 columns), 12 colour swatches (6 columns), each 48 dp, selected with a check mark (not colour alone). Swatches have names for TalkBack ("Grön", "Orange", …). ### `AccountRow` Home and pickers: name, `BalanceWording.Account`, amount, secondary "11 utgifter i oktober". Variants: `Home`, `Picker` (no count), `Archived` (badge). 200 %: amount moves under the name. ## 4. Ledger ### `LedgerMonthHeader` "Oktober · 7 457,90 kr" (household spend); sticky in `LazyColumn`. Year added when not the current year. ### `ExpenseRow` `CategoryIcon`, description (1 line), date, `MoneyText` amount, `EffectLine`, optional account name (S-12), `SyncPendingMarker`. Heights per direction (56–88 dp). States: normal, pending, pressed. Semantics: "ICA Maxi Lindhagen, 1 247,60 kronor, 16 oktober, du lånade 623,80 kronor, väntar på synk". ### `SettlementRow` `payments` icon, "Anna betalade dig 800,00 kr", "· 2 konton", date, pending marker. ### `SyncPendingMarker` Small icon (`cloud_upload` or direction equivalent) in `pending`; label "Väntar på synk". ## 5. Balance and sync ### `BalanceHero` Home's global balance (S-4) and the account header (S-5, smaller variant). * Anatomy: both `PersonAvatar`s, `BalanceWording.Global`, `MoneyText(Hero)`, optional action button "Gör upp" / "Gör upp det här kontot". * States: `Owed` (partner owes you: owed colours, button), `Owing` (you owe: owing colours, button), `Settled` ("Ni är kvitt"/"Kvitt", no amount, no button), `Empty` (no accounts: welcome text), `Loading` (skeleton). * 200 %: wording and amount each take a full line; avatars may shrink to 40 dp. * Semantics: one merged sentence + the button. ### `SyncStatusLine` From `SyncSummary` + connectivity. * Variants: `Synced` ("Synkad för 3 min sedan"), `Syncing` ("Synkar…"), `Offline` ("Offline"), with suffix "· 2 ändringar väntar på synk" (plural); `Warning` (rejections or clock skew) with icon and warning colour. * Tap → S-16 (whole line is the 48 dp target). Relative time updates every minute. ### `UpdateBanner` S-17 states: `Available` (text + "Uppdatera" + "Senare"), `Downloading` (determinate `LinearProgressIndicator` + percent), `Failed` (text + "Försök igen"). Fallback for Expressive wavy progress: standard linear. ### `NeedsConnectionHint` Inline supporting text / banner "Kräver anslutning" with `cloud_off` icon; used on every online-only control (§9.9). Variant `Banner` for whole forms (S-6, S-14, S-15, S-19). ## 6. Split and settle ### `SplitModeSelector` Single-select connected buttons (Expressive `ButtonGroup`/connected toggle buttons; fallback `SingleChoiceSegmentedButtonRow`): "Kontots standard 60/40", "Egen fördelning", "Exakta belopp", "Allt på en person". Wraps to 2 rows (Swedish), 4 rows at 200 % (a vertical radio list fallback when the row cannot fit). ### `RatioEditor` Slider (0–100, step 1, haptic at 50) + two percent `TextField`s summing to 100 (two decimals) + preset chips (50/50, 60/40, 40/60). Used in S-6 and S-7 `ratio`. Semantics: slider "Jacobs andel, 60 procent". ### `ExactSplitEditor` Two money fields: the typed one editable, the other read-only "beloppet minus …"; error "Andelen är större än beloppet". ### `FullSplitPicker` Two `PersonChip`s: "Allt på Jacob" / "Allt på Anna". ### `SplitPreview` "Du 206,00 kr · Anna 206,00 kr" + the effect the row will show. Data from `ExpenseRepository.preview`; hidden when null. Optional bar showing the two shares as proportions (decorative). ### `SplitPicker` (composite) The S-7 split section: `SplitModeSelector` + the mode's editor + `SplitPreview`; collapsed state is a single chip "Delning: Kontots standard 50/50" which expands it in one tap. ### `DirectionSwitcher` S-9 "Du betalar Anna" with avatars and an arrow; swap button `swap_horiz` (TalkBack "Byt riktning"). ### `AllocationEditor` S-9 list of `AllocationRow`s + "Lägg till konto" + `RemainingIndicator`. * `AllocationRow`: account name, debt line ("Du är skyldig 3 084,50 kr"), `AmountInput(Field)`, remove (`close`, "Ta bort Huset från fördelningen"), warning line (`warning` icon + "Det här reglerar mer än som är skyldigt i Japan 2027") when `overSettles`. Variant `Fixed` (per-account settle: no remove). * `RemainingIndicator`: "Kvar att fördela 0,00 kr" (neutral/positive), "250,00 kr kvar" (warning), "250,00 kr för mycket" (error), always words + colour. ## 7. Timeline ### `TimelineItem` Anatomy: actor avatar (24 dp; system icon `autorenew` for recurring, `upload_file` for import), sentence, "· 16 okt. 19:40"; vertical connector between items. * Variants: `Create`, `Update` (one line per `FieldChange`, sentence built from field + old → new, values formatted by `MoneyFormatter`/`DateFormatter`, ids already names), `Delete`, `PendingLocal` ("Din ändring väntar på synk", pending colour, dashed connector), `System` (recurring/import). * Field names (sv/en) in `strings.csv` (`timeline_field_*`). ### `Timeline` Column of `TimelineItem`, oldest first, newest last (AC-12.4), with the section title "Historik". ## 8. Charts (`:core:design`, Vico or Canvas fallback per §6.2) ### `ChartCard` Container: title, optional subtitle (range), the chart, legend, and an invisible-but-spoken summary sentence (`semantics { contentDescription = summary }`). States: loaded, empty ("Inga utgifter i den här perioden"), loading (shimmer; only when the computation takes > 300 ms). ### `StackedBarChart` Buckets on x (month "maj" or week "v. 41"), kronor on y (whole kronor, 3–5 gridlines, compact "20 tkr" labels in sv, "20k" in en), segments coloured by group (category colour or `chart-cat-n` for accounts, stable order = legend order), 2 dp gap between segments; selected bar highlighted, others dimmed; tap → callback(bucketKey). Each bar is a focusable semantics node ("September, 41 484 kronor"). Horizontal scroll when more than ~8 buckets; newest visible. ### `ChartLegend` Wrapping row of swatch + label; only groups with data; static (filtering by legend is not in the spec). ### `BreakdownBarList` Rows: icon/swatch, name, proportional bar, amount, percent (one decimal, AC-14.10). Sorted descending. Tap → callback(groupId). 200 %: bar on its own line. ### `FilterBar` S-11 filters: range chip with menu (`DropdownMenu` of `RangePreset`s), three binary connected toggles. Wraps to two rows. ## 9. Scaffolding ### `KvittScaffold` Top app bar (small, 64 dp; back or close), content, optional FAB slot, snackbar host, edge-to-edge insets. ### `KvittNavigationBar` Three items (ia.md §1). Expressive `ShortNavigationBar` (fallback `NavigationBar`). Selected icon filled. Labels always visible. ### `AddExpenseFab` Extended FAB "Lägg till utgift" (icon `add`), collapses to icon-only on scroll; Expressive size variants (fallback `ExtendedFloatingActionButton`). ### `EmptyState` Icon, title, body, optional primary action. Used by S-3, S-4, S-5, S-11, S-16, S-19. ### `ConfirmDialog` Title, body, confirm (destructive variant in `error`), dismiss. ### `SectionHeader`, `SettingsRow`, `CollapsibleSection` Settings rows (icon, title, optional trailing value/badge, chevron), section headers ("Konton" + trailing action), "Arkiverade (n)" collapse with rotation of the chevron. ### `InviteCodeDisplay`, `InviteCodeField` Code in three groups of four, mono/tabular, large; selectable; TalkBack spells characters ("7 K 3 M, Q X 9 D, 2 H P A"). Field variant normalises as typed (§8.3). ### `Skeleton` Shimmer placeholders for the first-sync loading state only.