East Blue
KeepNeeds Refinement
Service Item Component link

A service tile — asset slot, label and optional description — used in the home Menu Grid and customizable shortcut surfaces, with an optional New badge and Add or Remove edit overlays.

Keep — all findings resolved
Rebuilt on node 4692:21582 in the 2026 Working File, and every finding from the original assessment has landed. The State=Disbaled typo is corrected, Type is split into Badge and Action, Inactive and Disabled render differently, a Pressed state ships across all eight combinations, description is a real Figma Slot, and layer naming follows §3 and §7 throughout with both slots kebab-cased per §4. Three things are settled as decisions rather than fixes: badges are suppressed during edit mode, so 32 is the complete matrix rather than 32 of 48; the Container frame in the Horizontal variants is what creates the side-by-side layout and is correctly absent from Vertical; and the 12×12 overlay tap target is specified at 44×44pt / 48×48dp with its own accessibility label. The Add and Remove glyph construction is parked as accepted debt in the shared icon source. All four DS Health traits pass; the only item still open is Code Connect, blocked until the native library exists.
In Context

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

Cash In New Send + Bills
Live Preview
Label
Properties
State
Orientation
Badge
Action
Content
Label
DS Health
Reusable
Pass
Covers both the home-grid tile and the list-row layout from one component, and the Type axis that used to fuse badge with action is now two independent properties.
Self-contained
Pass
Owns its label, preamble and slot styling, and all four states carry their own colors. The Badge and the Add/Remove overlays are shared instances by design, so a fix to either propagates rather than drifting.
Consistent
Pass
Four axes in PascalCase per §1 with Title Case values per §5, layer naming in the §7 hierarchy as Preamble → Label → Description, and both slots kebab-cased per §4 as Asset-Slot and Description-Slot. The two structural differences are deliberate and recorded: Container exists only in Horizontal because only Horizontal needs a row frame, and the matrix stops at 32 because badges are suppressed while an edit overlay is present.
Composable
Pass
Asset Slot and Description Slot are both real Figma Slots, so a consumer supplies their own artwork and can show a label without inheriting placeholder description copy.
Behavior
State iOS Android Figma Property Notes
Default Yes Yes State=Default Label at #072592 on a #F6F9FD asset slot.
Pressed Yes Yes State=Pressed Label deepens to #071969; asset slot darkens to #EEF2F9. Released on finger-up.
Inactive Yes Yes State=Inactive Label at #6780A9. Visible and still tappable — the service exists but is temporarily unavailable.
Disabled N/A N/A State=Disabled Label at #C2CFE5, the system disabled foreground. Not tappable.
New badge N/A N/A Badge=New A 29×12 Badge instance over the asset slot. Decay rule — first tap, or N days — is still unspecified.
Add / Remove overlay Yes Yes Action=Add | Remove A 12×12 control in the asset slot corner, a separate tap target from the tile. Hit area is not annotated.
Issues
  • State=Disbaled typo corrected. v2.0: Rebuilt on node 4692:21582 in the 2026 Working File. All 32 variants read State=Disabled. The misspelling would have propagated into Code Connect prop names and from there into generated native constants, where it is far more expensive to unwind. (C2 · Rename) C2 · Variant & Property Naming
  • Type split into Badge and Action. v2.0: The axis that bundled a content flag with an overlay action is gone. The schema is now State × Orientation × Badge = None | New × Action = None | Add | Remove, all PascalCase per §1 with Title Case values per §5. Each axis names one thing. (C2 · Property) C2 · Variant & Property Naming
  • Inactive and Disabled now render differently. v2.0: The two states had been visually identical. They now separate cleanly — Inactive holds the label at #6780A9, a readable muted blue for a service that is visible but temporarily unavailable, while Disabled drops to #C2CFE5, the system disabled foreground. A user can tell which situation they are looking at. (C5 · State) C5 · Interaction State Coverage
  • Pressed state added. v2.0: State=Pressed ships across all 8 orientation/badge/action combinations — the label deepens to #071969 and the asset slot darkens #F6F9FD → #EEF2F9. Tiles are the primary tap target on the home grid, so touch-down feedback was the most-felt gap in the previous assessment. (C5 · State) C5 · Interaction State Coverage
  • Description promoted to a real content slot. v2.0: Description Slot is a genuine SLOT node rather than a baked text layer with placeholder copy. Home-grid usages that only need a label no longer carry description content they have to remember to clear. Asset Slot is likewise a real Slot. (C4 · Slot) C4 · Native Mappability
  • Layer naming pass landed, and both slots are kebab-case. v2.1: Verified on the live node. #preamble → Preamble, #label → Label, and the wrapping frames preamble / content / border → Preamble / Content / Border. The two slots also moved onto the §4 convention as Asset-Slot and Description-Slot — kebab-case for slots, PascalCase for frames, which is exactly the distinction §4 draws. Text layers now sit in the §7 hierarchy as Preamble → Label → Description. One layer was missed and is tracked below. (C1 · Rename) C1 · Layer Structure & Naming
  • Rename sweep completed — #description → Description. v2.2: Verified on the live node (4692:21589). Every text layer now reads without the legacy sigil, in the §7 hierarchy as Preamble → Label → Description, with Asset-Slot and Description-Slot kebab-cased per §4. No layer in the set carries the old convention. (C1 · Rename) C1 · Layer Structure & Naming
  • Badges are suppressed in edit mode — matrix confirmed complete at 32. v2.2: Closed by owner decision. Badge and Action are deliberately mutually exclusive: while a user is customising their home screen, a red New badge competes with the Add/Remove affordance for the same corner of the tile and for the same attention, so the badge is suppressed for the duration of edit mode. The 16 Badge=New × Action=Add|Remove combinations are therefore unsupported rather than unbuilt, and 32 is the complete matrix. Native implementations should hide the badge whenever an edit overlay is present rather than stacking them. Recorded so a consumer meeting the gap in the variant picker finds a rule instead of assuming an omission. (C2 · Property) C2 · Variant & Property Naming
  • Container in the Horizontal variants confirmed intentional. v2.2: The asymmetry is structural, not an oversight. Horizontal lays the asset and the text side by side, which needs its own auto-layout frame to hold that row; Vertical stacks the same children directly and needs no wrapper. Adding a Container to Vertical would introduce a frame that does nothing, and removing it from Horizontal would break the layout it exists to create. Native implementations should read Orientation as the layout switch — a row versus a column — rather than expecting one tree shape across both. (C1) C1 · Layer Structure & Naming
  • Overlay tap-area specified. v2.2: The Add and Remove overlays render at 12×12 in the corner of Asset-Slot, and each is a separate tap target from the tile beneath it — tapping the tile opens the service, tapping the overlay adds or removes it. The glyph stays 12×12 visually; the touch target must be expanded around it to 44×44pt on iOS and 48×48dp on Android, centred on the glyph and extending beyond the tile bounds where necessary. On iOS use a transparent .contentShape(Rectangle()) sized to the target rather than growing the visible circle; on Android set the minimum touch target on the clickable modifier rather than padding the icon. The overlay must sit above the tile in hit-test order so its region wins, and it needs its own accessibility label — “Add {service}” / “Remove {service}” — separate from the tile’s. Without this an implementer either ships an untappable control or wraps the whole tile, and wrapping the tile breaks edit mode. This spec lives here rather than as a Figma annotation: the review has read-only Figma access, so the note itself still needs adding in the file. (C5 · A11y) C5 · Interaction State Coverage
  • Add and Remove glyph construction deferred. v2.2: Parked by owner decision rather than fixed. The overlays’ source component is built from Ellipse 53 plus Rectangle 2620 and Rectangle 2621 — two white bars over a #12AF80 circle — rather than a vector glyph on the icon grid, so the default shape names carry no meaning and the plus will not scale or recolor like the rest of the icon set. It sits in the shared icon source rather than in Service Item, so fixing it is the icon owner’s call and affects every consumer equally. Recorded as a known, accepted debt rather than closed as correct. (C6 · Asset) C6 · Asset & Icon Quality
Types
Vertical
Label
Properties
State
Badge
Action
Properties
Orientation Vertical
State Default
Badge None
Action None
Colors
Asset placeholder #F6F9FD
Label #072592
Preamble #90A8D0
Description #445C85
Border #D7E0EF
New badge background #D61B2C
New badge label #FFFFFF
Add mark #12AF80
Remove mark #D61B2C
Mark glyph #FFFFFF
Typography
Label Primary/Label/Fine
Preamble Secondary/Heavy/Fine
Description Secondary/Bold/Small Caption
Layout
Height 72
Width 64
Radius 0
Padding H —
Padding V —
Gap —
Border 0
Alignment —
Colors by State
Role Element Token Value
Default Asset placeholder — #F6F9FD
Pressed Asset placeholder — #EEF2F9
Default Label — #072592
Inactive Label — #6780A9
Pressed Label — #071969
Disabled Label — #C2CFE5
Default Preamble — #90A8D0
Default Description — #445C85
Inactive Description — #90A8D0
Pressed Description — #0A2757
Disabled Description — #C2CFE5
Default Border — #D7E0EF
Default New badge background — #D61B2C
Default New badge label — #FFFFFF
Default Add mark — #12AF80
Default Remove mark — #D61B2C
Default Mark glyph — #FFFFFF
Horizontal
Label
Properties
State
Badge
Action
Properties
Orientation Horizontal
State Default
Badge None
Action None
Colors
Asset placeholder #F6F9FD
Label #072592
Preamble #90A8D0
Description #445C85
Border #D7E0EF
New badge background #D61B2C
New badge label #FFFFFF
Add mark #12AF80
Remove mark #D61B2C
Mark glyph #FFFFFF
Typography
Label Primary/Label/Fine
Preamble Secondary/Heavy/Fine
Description Secondary/Bold/Small Caption
Layout
Height 64
Width 120
Radius 0
Padding H —
Padding V —
Gap —
Border 0
Alignment —
Installation Planned API

iOS — Swift Package Manager

.package(url: "https://github.com/AY-Org/eb-ds-ios", from: "1.0.0")

Android — Gradle (Kotlin DSL)

implementation("com.eastblue.ds:service-item:1.0.0")

Import

import EastBlueDS
import com.eastblue.ds.serviceitem.*
Property Mapping

State, Orientation, Badge and Action are the set’s four variant properties. Asset-Slot and Description-Slot are Figma Slots; Label and Preamble are text layers read from 4692:21583. The Preamble, Description-Slot and Border layers are hidden in every variant — the booleans that show them are not readable with the plugin.

Figma PropertySwiftUICompose
State — Default, Inactive, Pressed, Disabled .ebInactive(true) · .disabled(true) · Pressed is the platform touch-down inactive = true · enabled = false · Pressed from interactionSource
Orientation — Vertical, Horizontal .ebOrientation(.vertical / .horizontal) orientation = EBServiceItemOrientation.Vertical / Horizontal
Badge — None, New .ebBadge(.new) badge = EBServiceItemBadge.New
Action — None, Add, Remove .ebAction(.add / .remove) { } action = EBServiceItemAction.Add / Remove { }
Asset-Slot (slot) asset: Image asset = { Icon(…) }
Description-Slot (slot, hidden by default) .ebDescription("…") description = "…"
Label (text) EBServiceItem("Label", …) label = "…"
Preamble (text, hidden by default) .ebPreamble("…") preamble = "…"
Usage Snippets Planned API
Vertical
EBServiceItem("Label", asset: Image("service"))
    .ebOrientation(.vertical)
EBServiceItem(
    label = "Label",
    asset = { Icon(…) },
    orientation = EBServiceItemOrientation.Vertical
)
Horizontal
EBServiceItem("Label", asset: Image("service"))
    .ebOrientation(.horizontal)
EBServiceItem(
    label = "Label",
    asset = { Icon(…) },
    orientation = EBServiceItemOrientation.Horizontal
)
Accessibility
RequirementiOSAndroid
Tile as a button Whole tile in Button; accessibilityLabel is the label plus "new" when the badge shows. Modifier.clickable { onTap() }.semantics(mergeDescendants = true) on the tile.
Add / Remove mark A second Button with accessibilityLabel("Add to favourites") / "Remove"; the 12 pt mark needs a 44 pt hit area. Separate IconButton with contentDescription; 48 dp touch target around the 12 dp mark.
Inactive vs disabled Inactive keeps the button trait and adds .accessibilityValue("unavailable"); Disabled uses .disabled(true). Inactive stays clickable with stateDescription = "unavailable"; Disabled sets enabled = false.
Minimum target 64 × 72 pt tile ≥ 44 pt. 64 × 72 dp tile ≥ 48 dp.
Usage Guidelines

Do

Use Vertical in the service grid, Horizontal in a single-column list.

Don't

Don’t mix orientations in one grid.

Do

Show Badge New for the first launch of a service only.

Don't

Don’t pair New with an Add or Remove mark — the set does not build it.

Do

Use Add and Remove only inside edit mode of the favourites grid.

Don't

Don’t leave a Remove mark on a tile outside edit mode.

Do

Keep the label to one line; the tile is 64 px wide.

Don't

Don’t wrap a two-word service name — shorten it.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Needs Refinement Preamble, Asset-Slot, Content › Label / Description-Slot, Border — read on 4692:21583. One variant (4692:21601) still names its preamble text #preamble; the Preamble, Description and Border layers are hidden in every variant.
C2 Variant & Property Naming Ready State, Orientation, Badge, Action with Title Case values; the Disbaled typo is gone.
C3 Token Coverage Ready Label → Primary/Label/Fine, Preamble → Secondary/Heavy/Fine, Description → Secondary/Bold/Small Caption. Colour variable bindings are not readable with the plugin — confirm in Dev Mode.
C4 Native Mappability Ready Four enums, two slots, two strings — maps 1:1 onto EBServiceItem.
C5 Interaction State Coverage Ready Default, Inactive, Pressed and Disabled built for both orientations, every badge and every action.
C6 Asset & Icon Quality Ready Asset-Slot is a Slot; the New badge is a Badge instance; Add and Remove are 12 × 12 vector instances.
Variants Inventory (32 total)

4 State × 2 Orientation × (Badge None, Badge New, Action Add, Action Remove) = 32 variants; New never pairs with an Action

OrientationState × Badge / ActionCount
VerticalDefault, Inactive, Pressed, Disabled × None, New, Add, Remove16
HorizontalDefault, Inactive, Pressed, Disabled × None, New, Add, Remove16
View full State × Orientation × Badge × Action breakdown (32 rows)
StateOrientationBadgeActionNodeDimensions
DefaultVerticalNoneNone4692:2158364 × 72
DefaultVerticalNewNone4692:2159164 × 72
DefaultVerticalNoneAdd4692:2160164 × 72
DefaultVerticalNoneRemove4692:2161064 × 72
InactiveVerticalNoneNone4692:2161964 × 72
InactiveVerticalNewNone4692:2162764 × 72
InactiveVerticalNoneAdd4692:2163764 × 72
InactiveVerticalNoneRemove4692:2164664 × 72
PressedVerticalNoneNone4703:1826464 × 72
PressedVerticalNewNone4703:1838764 × 72
PressedVerticalNoneAdd4711:1844264 × 72
PressedVerticalNoneRemove4711:1862464 × 72
DisabledVerticalNoneNone4692:2177564 × 72
DisabledVerticalNewNone4692:2178364 × 72
DisabledVerticalNoneAdd4692:2179364 × 72
DisabledVerticalNoneRemove4692:2180264 × 72
DefaultHorizontalNoneNone4692:21655120 × 64
DefaultHorizontalNewNone4692:21664120 × 64
DefaultHorizontalNoneAdd4692:21675120 × 64
DefaultHorizontalNoneRemove4692:21685120 × 64
InactiveHorizontalNoneNone4692:21695120 × 64
InactiveHorizontalNewNone4692:21704120 × 64
InactiveHorizontalNoneAdd4692:21715120 × 64
InactiveHorizontalNoneRemove4692:21725120 × 64
PressedHorizontalNoneNone4711:18705120 × 64
PressedHorizontalNewNone4711:18714120 × 64
PressedHorizontalNoneAdd4711:18726120 × 64
PressedHorizontalNoneRemove4711:18739120 × 64
DisabledHorizontalNoneNone4692:21735120 × 64
DisabledHorizontalNewNone4692:21754120 × 64
DisabledHorizontalNoneAdd4692:21744120 × 64
DisabledHorizontalNoneRemove4692:21765120 × 64
1.0.2 — September 2026Patch
Style and Code tabs re-read · node 4692:21582
Style tab rebuilt from the 32 live variants. Cards follow Orientation — Vertical, Horizontal — with State, Badge and Action as panel controls; geometry, colours and the three text styles read off 4692:21583, 4692:21655 and the State variants against the exports. Padding, gap, alignment and colour tokens are not readable with the plugin and stay —.
Docs
Code tab regrouped on the four-property schema. Installation on the solo coordinates, Property Mapping one row per property with the two slots, Usage Snippets per Orientation, Guidelines added, Scorecard regraded, Variants Inventory on the 32 live nodes with a summary and collapsed breakdown.
Docs
Hidden layers and one stray sigil. Preamble, Description-Slot and Border are hidden in every variant, and 4692:21601 names its preamble text #preamble where its siblings say Preamble. Filed from the scorecard for the Component Review.
C1
1.0.1 — September 2026Patch
Documentation backfill · node 4692:21582
Resolved issues recorded. 11 resolved issues on the Overview tab had no changelog row: State=Disbaled typo corrected; Type split into Badge and Action; Inactive and Disabled now render differently; Pressed state added; Description promoted to a real content slot; Layer naming pass landed, and both slots are kebab-case; Rename sweep completed — #description → Description; Badges are suppressed in edit mode — matrix confirmed complete at 32; Container in the Horizontal variants confirmed intentional; Overlay tap-area specified; Add and Remove glyph construction deferred.
Docs
1.0.0 — May 2026Major
Initial Assessment · node 20210:2441
Component assessed — 24 variants across Type × State × Orientation. Powers the home Menu Grid and the customize-home edit mode. Documented
Initial
Verdict: Fix — Rename the State=Disbaled typo, split Type into orthogonal badge + action axes, distinguish Inactive from Disabled. Open
Family
C2 — State=Disbaled typo — Misspelled across 6 horizontal/disabled variants. Will propagate to Code Connect props unless fixed in Figma. Open
C2
C2 — Type axis bundles badge + action — Default / New / Add / Remove conflates content presence with overlay action. Split into badge + action. Open
C2
C5 — Inactive vs Disabled — Render identically today. Need distinct visual + interaction treatments. Open
C5
C5 — Tap-area + Pressed — No Pressed state; Icon Action overlays are 12 px with no annotated hit-area extension. Open
C5
C7 — Code Connect — Not registered. Blocked on the State typo and Type-axis split. Open
C7