A service tile — icon slot plus label and optional description — used inside the home Menu Grid and customizable shortcut surfaces. Type axis adds a "New" badge, an Add (+) overlay, or a Remove (–) overlay over the icon.
State=Disbaled on every horizontal/disabled variant. It must be renamed to Disabled. (2) The Type axis bundles "what icon is shown" (Default) with "what modifier overlays the icon" (New / Add / Remove). These are independent — a New shortcut might also be in Remove mode while editing. Split Type into badge: .none | .new and action: .none | .add | .remove so the matrix collapses from 24 variants to a leaner 2 × 3 × 2 × 2 = ~24 with semantic axes (and the invalid combos drop out).Used inside the home Menu Grid (the 4×N icon-and-label grid above the bills/transfer shortcuts) and inside the "Customize your home" reordering screen, where Add/Remove overlays appear over the icon during edit mode. Vertical orientation is the dominant home-grid usage; horizontal is reserved for list-style surfaces (e.g. the recent-services drawer).
State=Disbaled is misspelled in 6 variants (every horizontal/disabled combo). The label's color, the icon-slot fill, and the disabled-state opacity treatment also differ between Inactive and Disabled with no clear rule. C2| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Tap | Yes | Yes | Default state only | No Pressed treatment modeled. Tile should respond to touch-down (subtle bg darken or scale 0.97). |
| Inactive | N/A | N/A | Label dims to #C2CFE5 | Used when a service is temporarily unavailable but visible. Conflated visually with Disabled. |
| Disabled | N/A | N/A | Same visual as Inactive | No distinct treatment from Inactive. Should differ (e.g. 40% opacity + pointer-events:none vs. just-dimmed label). |
| Add (edit-mode) | Yes | Yes | Type=Add overlay | Green + circle in top-right of icon. Tappable as a separate target; tap area not annotated. |
| Remove (edit-mode) | Yes | Yes | Type=Remove overlay | Red – circle in top-right. Same tap-area issue as Add. |
| New badge | N/A | N/A | Type=New | Static red "New" badge. Should decay after first tap or after N days — no spec. |
-
State=Disbaledis misspelled across 6 variants. Every horizontal/disabled variant in the component set hasState=Disbaled(typo) in its component name. This propagates to Code Connect prop names and gets baked into native APIs. Rename toDisabledin Figma. C2 · Variant & Property Naming - Type axis bundles unrelated concerns.
Type = Default | New | Add | Removeconflates a content-presence flag (New = badge present), an action overlay (Add / Remove), and the neutral case (Default). These should be two orthogonal axes:badge: .none | .newandaction: .none | .add | .remove. Today, a "New" tile can't simultaneously be in Remove mode during edit. C2 · Variant & Property Naming - Inactive and Disabled render the same. No visual difference between
State=InactiveandState=Disabled— both dim the label to a muted blue. Functionally they should differ: Inactive = visible but not tappable for business reasons; Disabled = visible but not tappable for state reasons (form invalid, feature flag off). Pick distinct treatments. C5 · Interaction State Coverage - No Pressed state. Tiles are primary tap targets but have no pressed feedback. Add a transient treatment (8% darken on icon-slot bg, or a 0.97 scale) on touch-down. C5 · Interaction State Coverage
- Icon Action overlays are 12 × 12 but tap-area unannotated. The + and – Icon Action circles are tiny (12 px). They're separate tap targets from the tile itself but no minimum tap area is annotated. Native devs default to wrapping the whole tile, which kills the edit-mode UX. C5 · Interaction State Coverage
- Description text node is baked. Both
#labeland#descriptionare always present in the layer tree. Consumers can't hide just description (most home-grid usages don't need it). Should be an optional text slot. C4 · Native Mappability - Code Connect mappings not registered. Blocked on the State typo fix and the Type-axis split. C7 · Code Connect Linkability
- Fix the State=Disbaled typo. Rename in Figma across all 6 affected variants. Verify no consumer screens reference the old prop value via Code Connect. Rename
- Split Type into
badge+actionaxes. Target schema:badge: .none | .new+action: .none | .add | .remove. Each is an optional slot/overlay; combinations like "New + Remove" become expressible without new variants. Total variant count stays similar but covers the full matrix instead of the linear union. Property - Distinguish Inactive from Disabled. Inactive: label dims to
main/text/color/disabled, still tappable (e.g. opens an "unavailable" sheet). Disabled: 40% opacity on the whole tile +pointer-events: none+aria-disabled. Document the semantic difference. State - Promote description to an optional content slot. Most home-grid usages only show the label. Make description a slot that consumers fill when needed, instead of a baked text node with placeholder copy that ships in production by mistake. Slot
- Add a Pressed state. Touch-down: 8% darken on icon-slot bg + 0.97 scale on the whole tile. Touch-up: snap back. Improves perceived responsiveness on the home grid. State
- Annotate Icon Action tap-area. The 12 px + and – circles need a 44 × 44 (iOS) / 48 dp (Android) hit-area extension so users can reliably tap them in edit mode. Document this on the component. A11y
- Document edit-mode interaction model. Add and Remove overlays only appear when the parent (Menu Grid in edit mode) is reordering. Document the parent contract — the Service Item shouldn't need to know about the parent's edit state directly. Docs
Vertical: 64 × 72 — preamble (12 tall) above icon, 48 × 48 circular icon slot, then label + description. Horizontal: 120 × 64 — icon left, label/description right, preamble below. Type adds a "New" badge, a green +, or a red – over the icon. State dims the label for Inactive / Disabled.
Label color is the only thing State changes. Today Inactive and Disabled render identically — flagged as C5.
| Role | Token | Default | Inactive | Disabled |
|---|---|---|---|---|
| Label | service-item/label/{state} | #072592 | #C2CFE5 | #C2CFE5 |
| Icon-slot bg | service-item/icon/bg | #F6F9FD | #F6F9FD | #F6F9FD |
After the Type-axis split, the native API has two independent slot params (<code>badge</code>, <code>action</code>) instead of one fused Type enum.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| Type=Default | (no badge, no action) | (no badge, no action) |
| Type=New | badge: .new | badge = Badge.New |
| Type=Add | action: .add | action = Action.Add |
| Type=Remove | action: .remove | action = Action.Remove |
| State=Default / Inactive / Disabled | state: .default | .inactive | .disabled | state: ServiceItemState |
| Orientation=Vertical / Horizontal | orientation: .vertical | .horizontal | orientation: Orientation |
| Icon (slot) | icon: Image | icon: @Composable () -> Unit |
| #label | label: String | label: String |
| #description | description: String? | description: String? |
| #preamble | preamble: String? | preamble: String? |
| Requirement | iOS | Android |
|---|---|---|
| Tile role | Wrap as Button; .accessibilityLabel(label + (description.map { ", \($0)" } ?? "")). | Use Modifier.clickable + Role.Button; contentDescription = label + ", " + description. |
| New badge | .accessibilityValue("new") so VoiceOver reads "Send, new". | Append "new" to stateDescription. |
| Add / Remove overlay | Separate accessibility element — .accessibilityLabel("Add Send to home"). Hit area extended via .contentShape(Rectangle()) + larger frame. | Wrap as separate IconButton with explicit contentDescription; Modifier.minimumInteractiveComponentSize(). |
| Disabled | .disabled(true) + .accessibilityValue("disabled"). | enabled = false; stateDescription = "disabled". |
| Inactive | Still tappable; .accessibilityHint("Currently unavailable"). | Tappable; contentDescription appends "currently unavailable". |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Needs Refinement | Clean layer naming inside (preamble / content / Icon / border). The 24-variant matrix is the concern, not the layer tree. |
| C2 | Variant & Property Naming | Requires Rework | State=Disbaled typo + Type axis bundles badge with action overlays. |
| C3 | Token Coverage | Needs Refinement | Icon-slot fill and label color bound. New badge + Add/Remove action overlays use ad-hoc colors (#E11744, #16A34A) without registered tokens. |
| C4 | Native Mappability | Needs Refinement | Maps cleanly to a SwiftUI/Compose tile after the Type-axis split; description should be optional. |
| C5 | Interaction State Coverage | Requires Rework | No Pressed state. Inactive and Disabled render identically. Action-overlay tap-area unannotated. |
| C6 | Asset & Icon Quality | Ready | Icon slot accepts any vector instance — clean Slot architecture. |
| C7 | Code Connect Linkability | Not Mapped | Blocked on State typo + Type-axis split. |
Type × State × Orientation = 4 × 3 × 2 = 24 variants. Note 6 of them have State=Disbaled misspelled. Once Type splits into badge + action, the matrix collapses to ~8 production variants + slot-driven combinations.
| # | Type | State | Orientation | Size | Node |
|---|---|---|---|---|---|
| 1 | Default | Default | Vertical | 64 × 72 | 20210:2442 |
| 2 | New | Default | Vertical | 64 × 72 | 20210:2450 |
| 3 | Add | Default | Vertical | 64 × 72 | 20210:2460 |
| 4 | Remove | Default | Vertical | 64 × 72 | 20210:2469 |
| 5 | Default | Inactive | Vertical | 64 × 72 | 20210:2478 |
| 6 | New | Inactive | Vertical | 64 × 72 | 20210:2486 |
| 7 | Add | Inactive | Vertical | 64 × 72 | 20210:2496 |
| 8 | Remove | Inactive | Vertical | 64 × 72 | 20210:2505 |
| 9 | Default | Disabled | Vertical | 64 × 72 | 20210:2634 |
| 10 | New | Disabled | Vertical | 64 × 72 | 20210:2642 |
| 11 | Add | Disabled | Vertical | 64 × 72 | 20210:2652 |
| 12 | Remove | Disabled | Vertical | 64 × 72 | 20210:2661 |
| 13 | Default | Default | Horizontal | 120 × 64 | 20210:2514 |
| 14 | New | Default | Horizontal | 120 × 64 | 20210:2523 |
| 15 | Add | Default | Horizontal | 120 × 64 | 20210:2534 |
| 16 | Remove | Default | Horizontal | 120 × 64 | 20210:2544 |
| 17 | Default | Inactive | Horizontal | 120 × 64 | 20210:2554 |
| 18 | New | Inactive | Horizontal | 120 × 64 | 20210:2563 |
| 19 | Add | Inactive | Horizontal | 120 × 64 | 20210:2574 |
| 20 | Remove | Inactive | Horizontal | 120 × 64 | 20210:2584 |
| 21 | Default | Disabled | Horizontal | 120 × 64 | 20210:2594 |
| 22 | New | Disabled | Horizontal | 120 × 64 | 20210:2613 |
| 23 | Add | Disabled | Horizontal | 120 × 64 | 20210:2603 |
| 24 | Remove | Disabled | Horizontal | 120 × 64 | 20210:2624 |
Type × State × Orientation. Powers the home Menu Grid and the customize-home edit mode. Documentedbadge + action axes, distinguish Inactive from Disabled. Openbadge + action. Open