KeepNeeds Refinement
Tab Item Component link

A single tab inside Tabs — label, swappable Icon slot, optional Counter and red dot. 24 variants across State (Default/Hover/Disabled) × Orientation (Vertical/Horizontal) × Size (Medium/Large) × Placement (Leading/Trailing) × isSelected.

Rebuilt — structurally clean
The rebuild resolved every structural issue: isActive? renamed isSelected with lowercase true/false, a real Icon Slot replaces the placeholder circle in both orientations, the counter is a Counter INSTANCE on tokenised #EEF2F9, and a State axis (Default / Hover / Disabled) now ships alongside isSelected — 24 variants. Only Code Connect registration remains.
In Context

Tab Items appear inside the Tabs container. See the Tabs in-context preview for the full screen layout.

Live Preview
Label
Properties
isActive?
orientation
size
hasLeadingIcon
hasCounter
hasRedDot
DS Health
Reusable
Pass
Used by Tabs across both orientations and two sizes. The Icon Slot and Counter make one atom cover icon tabs, label-only tabs, and tabs with a count.
Self-contained
Pass
Carries its own layout, border, and typography. The counter is a Counter instance on #EEF2F9 rather than a locally drawn pill with raw hex, so token changes propagate.
Consistent
Pass
Five orthogonal props — State × Orientation × Size × Placement × isSelected. Booleans are lowercase true/false, matching the DS-wide standard, and State is kept separate from isSelected the way Radio Button models it. Placement is scoped to Horizontal by design, since Vertical stacks the icon above the label.
Composable
Pass
A real Icon Slot SLOT (32px vertical / 24px horizontal) lets consumers swap in any icon, and the counter instances the canonical component so its updates propagate. Nests inside Tabs as an instance.
Behavior
State iOS Android Figma Property Notes
Selected Yes Yes isSelected=true Brand #005CE5 indicator border on the container, brand label.
Unselected Yes Yes isSelected=false Muted label #6780A9, neutral #E5EBF4 border.
Horizontal Yes Yes Orientation=Horizontal Label beside the icon, 24px Icon Slot. Placement puts the icon leading or trailing.
Vertical Yes Yes Orientation=Vertical Icon above the label, 32px Icon Slot. Only Placement=Leading ships.
Counter Yes Yes Counter instance Pill on #EEF2F9, radius 99999. A real instance, so Counter updates propagate.
Red dot Yes Yes red-dot 6px #D61B2C notification dot. Modelled as a fixed layer by design rather than a boolean or slot.
Hover (pressed) Yes Yes State=Hover Touch-down feedback — container stroke darkens to #2340A9, the DS pressed navy. Named Hover here; maps to the platform pressed binding (configuration.isPressed / InteractionSource) since touch has no hover. Ships at isSelected=true.
Disabled Yes Yes State=Disabled Non-interactive tab. Ships at isSelected=false — the current tab is not disabled in practice.
Issues
  • Code Connect mappings not registered. All asset, composition, naming, and state blockers are resolved. Registration is unblocked but the SwiftUI / Compose mappings are not yet wired and the native component does not exist — snippets remain a Planned API. Note for whoever wires it: State=Hover maps to the pressed/isPressed binding on both platforms. C7 · Code Connect Linkability
  • v2.0: Property isActive? renamed isSelected — the trailing ? is gone from the generated type. Values remain capitalised and are tracked separately. (C2)
  • v2.0: Icon placeholder replaced with a real Figma Icon Slot SLOT — 32 × 32 vertical, 24 × 24 horizontal, both empty and swappable. The hardcoded grey icon-placeholder circle is gone. (C6)
  • v2.0: Counter is now a Counter INSTANCE rather than a locally drawn pill, so updates to the canonical component propagate to every Tab Item. (C6)
  • v2.0: Counter colours moved off raw hex — the instance ships on #EEF2F9, matching the DS counter token, replacing the hardcoded #ECF1FA / #0F3390 pair. (C3)
  • v2.0: Leading-icon behaviour unified across orientations — both Vertical and Horizontal now carry the same Icon Slot, and icon position is expressed by the Placement property rather than a vertical-always-renders rule plus a hasLeadingIcon boolean. (C2)
  • v2.0: red-dot as a fixed 6px #D61B2C ELLIPSE confirmed intentional — kept as a layer rather than promoted to a boolean property or slot. (C6)
  • v2.1: isSelected values renamed True/Falsetrue/false across all 12 variants — Tab Item is now on the DS-wide boolean standard alongside Radio Button, Select Item, and Select. Verified with no stray property values or conflicting variants. (C2)
  • v2.1: Interaction states added — a State axis (Default / Hover / Disabled) takes the set from 12 to 24 variants. State is kept orthogonal to isSelected rather than collapsed into it, because the two vary independently: isSelected is which tab is active, State is the transient interaction. Same model as Radio Button. (C5)
  • v2.1: State=Hover retained deliberately rather than renamed Pressed. The variants are painted #2340A9 (the DS pressed navy) and behave as the touch-down state; the term is a naming convention only. Tab Item is the sole component using Hover — Button, Radio Button, Select Item, and Select Group all say Pressed — so Code Connect should bind it to the platform pressed state. (C2)
  • v2.1: State coverage confirmed intentionalHover ships only at isSelected=true and Disabled only at isSelected=false, giving 24 of 36 theoretical cells. Reviewed and accepted as the useful set. (C5)
  • v2.1: Placement being meaningful only for Horizontal confirmed intentional — Vertical stacks the icon above the label, so a trailing (below-label) variant is not a layout GCash uses. (C2)
Recommendations
  • Register Code Connect mapping to EBTabItem. Wire State, Orientation, Size, Placement, and isSelected 1:1 to the SwiftUI / Compose API. Map State=Hover to the platform pressed binding (configuration.isPressed / InteractionSource), since touch has no hover. Docs
  • Rename isActive?selected with true/false. v2.1: Fully applied — the property is isSelected (trailing ? gone) and all 12 variants now carry lowercase true/false, matching Radio Button, Select Item, and Select. Code Connect can map straight to Bool. Rename
  • Unify the leading-icon slot. v2.0: Applied — both orientations carry the same Icon Slot, with icon position expressed by Placement instead of a vertical-only render plus a hasLeadingIcon boolean. Property
  • Replace the local counter pill with an instance. v2.0: Applied — Counter is a real INSTANCE, so its updates propagate to every Tab Item. Composition
  • Replace icon-placeholder with a swappable Icon slot. v2.0: Applied — a real Figma Icon Slot SLOT ships in both orientations. Slot
  • Add tokens for the counter. v2.0: Applied — the counter instance ships on #EEF2F9, matching the DS counter token rather than raw hex. Token
  • Add pressed and disabled states. v2.1: Applied — a State axis now ships (Default / Hover / Disabled), taking the set from 12 to 24 variants. State is orthogonal to isSelected, matching the Radio Button model rather than collapsing the two. State
Styles
Vertical · small — 360px screen
DES DEV

Icon above label. 32px icon, 16/16 label (Primary/Label/Base). Active + inactive shown side-by-side.

LabelLabel
Properties
selected
hasRedDot
Properties
Variant Vertical · small — 360px screen
State Active
Red dot false
Colors
Label #6780A9
Border #E5EBF4
Layout
Tab height 44px
Padding H 16px
Indicator height 3px below tab
Typography
Label style Primary/Label/Base
Label font Proxima Soft Bold · 16 / 16 · +0.25
Colors by State

Two states: active and inactive. Counter colors are currently hardcoded (flagged as C3).

Role Token ActiveInactive
Label main/tab/color/{active|inactive}/label #005CE5 #6780A9
Bottom border main/tab/color/{active|inactive}/border #005CE5 #E5EBF4
Counter bg — (hardcoded) #ECF1FA #ECF1FA
Counter label — (hardcoded) #0F3390 #0F3390
Red dot — (raster) Red 6×6px indicator
Vertical · large — 414px screen
DES DEV

Icon above label. 32px icon, 18/18 label (Primary/Label/Large). Optimized for 414px screens.

LabelLabel
Properties
selected
hasRedDot
Properties
Variant Vertical · large — 414px screen
State Active
Red dot false
Colors
Label #6780A9
Border #E5EBF4
Layout
Tab height 44px
Padding H 16px
Indicator height 3px below tab
Typography
Label style Primary/Label/Base
Label font Proxima Soft Bold · 16 / 16 · +0.25
Layout
Role Token
Vertical padding (top) 12px
Vertical padding (bottom) 16px
Horizontal padding (left/right) 12px
Icon size (vertical) 32 × 32
Icon size (horizontal leading) 24 × 24
Icon → label gap (vertical) 12px
Counter size 18px h × padding 6h / 4v
Counter radius 99999px (pill)
Red dot size 6 × 6 (top-right)
Bottom border 2px solid
Horizontal small width 105px
Horizontal large width 112px
Horizontal · small — label only + optional slots
DES DEV

Label-first row. Optional leading icon (24px) and trailing counter (18px pill). Red dot anchored to top-right.

Label0Label0
Properties
selected
hasLeadingIcon
hasCounter
hasRedDot
Properties
Variant Horizontal · small — label only + optional slots
State Active
Leading icon true
Counter true
Red dot false
Colors
Label #6780A9
Border #E5EBF4
Layout
Tab height 44px
Padding H 16px
Indicator height 3px below tab
Typography
Label style Primary/Label/Base
Label font Proxima Soft Bold · 16 / 16 · +0.25
Typography
Role Token Spec
Small Primary/Label/Base Proxima Soft Bold · 16 / 16 · +0.25
Large Primary/Label/Large Proxima Soft Bold · 18 / 18 · +0.25
Counter — (hardcoded) Proxima Soft Bold · 12 / 12 · +0.5
Horizontal · large
DES DEV

Same anatomy as horizontal small but with 18/18 label and 112px cell width.

Label0Label0
Properties
selected
hasLeadingIcon
hasCounter
hasRedDot
Properties
Variant Horizontal · large
State Active
Leading icon true
Counter true
Red dot false
Colors
Label #6780A9
Border #E5EBF4
Layout
Tab height 44px
Padding H 16px
Indicator height 3px below tab
Typography
Label style Primary/Label/Base
Label font Proxima Soft Bold · 16 / 16 · +0.25
Installation Planned API

iOS — Swift Package Manager

// In Xcode: File → Add Package Dependencies
"https://github.com/AY-Org/eb-ds-ios"

Android — Gradle (Kotlin DSL)

dependencies {
    implementation("com.eastblue.ds:tabs:1.0.0")
}
Property Mapping
Figma PropertySwiftUICompose
isActive?=Yes/No selected: Bool selected: Bool
orientation=vertical/horizontal orientation: EBTabOrientation .orientation(.vertical)
size=small/large size: EBTabSize .size(.small)
hasLeadingIcon + vertical always-on icon leading: Icon? leading: Image?
hasCounter=true/false counter: Int? counter: Int?
hasRedDot=true/false showBadge: Bool showBadge: Bool
label: String title: String
SwiftUI
ios/Components/Tabs/EBTabItem.swift
Jetpack Compose
android/components/tabs/EBTabItem.kt
Usage Snippets Planned API
Usage
// Vertical tab with icon
EBTabItem(label: "Overview", leading: Image("overview"))
    .selected(true)

// Horizontal tab with counter
EBTabItem(label: "Inbox", counter: 12)
    .orientation(.horizontal)
    .selected(false)

// Horizontal tab with red-dot indicator
EBTabItem(label: "Alerts", showBadge: true)
    .orientation(.horizontal)
// Vertical tab with icon
EBTabItem(
    label = "Overview",
    leading = painterResource(R.drawable.overview),
    selected = true
)

// Horizontal tab with counter
EBTabItem(
    label = "Inbox",
    orientation = EBTabOrientation.Horizontal,
    counter = 12,
    selected = false
)

// Horizontal tab with red-dot indicator
EBTabItem(
    label = "Alerts",
    orientation = EBTabOrientation.Horizontal,
    showBadge = true
)
Accessibility
RequirementiOSAndroid
Selected state .accessibilityAddTraits(.isSelected) selected = true in semantics
Counter value .accessibilityValue("\(count) unread") contentDescription includes the count
Red dot .accessibilityLabel("New") or append to label Append to contentDescription
Tap target Icon-only cells need 44pt hit area Cells meet 48dp with padding
Usage Guidelines

Do

Use vertical orientation when tabs have icons + short labels (iconic nav). Use horizontal when labels are longer or icons aren't semantically needed.

Don't

Mix orientations within the same Tabs container.

Do

Use counter for numeric indicators (unread counts, items). Use showBadge for "new / unseen" indicators where the count is unimportant.

Don't

Stack counter + red-dot on the same tab — the counter already communicates "there's something here."

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Semantic: container, icon-label, icon, label, counter-container, red-dot.
C2 Variant & Property Naming Requires Rework isActive? Yes/No + inconsistent leading-icon slot across orientations.
C3 Token Coverage Requires Rework Counter bg #ECF1FA and label #0F3390 are hardcoded.
C4 Native Mappability Ready Custom cell on iOS, Material Tab content on Android.
C5 Interaction State Coverage Requires Rework Pressed and disabled states not defined.
C6 Asset & Icon Quality Requires Rework Placeholder circle instead of Icon slot; counter is duplicated, not a Badge instance.
C7 Code Connect Linkability Needs Refinement Not mapped.
Variants Inventory (8 total)

isActive?orientationsizeNode ID
Yesverticalsmall18482:33263
Noverticalsmall18482:33270
Yesverticallarge18482:33277
Noverticallarge18482:33284
Yeshorizontalsmall18482:33291
Nohorizontalsmall18482:33300
Yeshorizontallarge18482:33309
Nohorizontallarge18482:33318
1.0.0 — April 2026Major
Initial Assessment · node 18482:33262
Component assessed — 8 variants across isActive × orientation × size. Horizontal variants expose optional leading icon, counter, and red-dot slots. Documented
Initial
Property naming issuesisActive? has a ? and uses Yes/No. Leading-icon slot behaves differently across orientations. Open
C2 Open
Counter colors hardcoded#ECF1FA bg and #0F3390 label are raw hex. Should use tokens or instance the Badge component. Open
C3 Open
No pressed/disabled states — State coverage limited to active/inactive. Open
C5 Open
Icon placeholder + duplicated counter — Icon should be a swappable slot; counter should instance the canonical Badge. Open
C6 Open
Code Connect mappings — Not registered. Open
C7 Open