The Tabs container composes a row of Tab Item instances. 12 variants across Orientation (Vertical/Horizontal) × Size (Medium/Large) × Tabs Count (2/3/4).
Orientation × Size × Tabs Count. Every cell is a real Tab Item instance, so atom changes propagate. The fixed Tabs Count axis and the absence of a scrollable variant are both intentional — GCash tabs are capped at 2–4. Only Code Connect registration remains.Contexts are illustrative. Final screens will reference actual GCash patterns. Tabs sit below a Title Bar to switch between screen sections.
Orientation × Size × Tabs Count = a complete 2 × 2 × 3 matrix with no invalid cells.Tab Item INSTANCE, so atom changes propagate automatically. Orientation and Size forward down to the nested items.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Horizontal | Yes | Yes | Orientation=Horizontal | Tabs laid out in a row with the label beside the optional icon. Medium 48px tall, Large 50px. |
| Vertical | Yes | Yes | Orientation=Vertical | Tabs stacked with the icon above the label, 92px tall. Used for rail-style navigation. |
| Tab count | Yes | Yes | Tabs Count=2 | 3 | 4 | Container width adapts to the number of tabs. Capped at 4 by design — GCash tab bars do not exceed four destinations. |
| 5+ tabs / scrollable | N/A | N/A | — | Not modelled by design. The 2–4 cap is deliberate, so horizontal overflow and scrolling do not arise. |
- Code Connect mappings not registered. The rename and container structure are settled, so registration is unblocked — but the SwiftUI / Compose mappings are not yet wired and the native component does not exist. Snippets remain a Planned API. C7 · Code Connect Linkability
- v2.0: Component renamed
Tab→ Tabs (plural) — the container is no longer confusable with the Tab Item atom. (C2) - v2.0: Rebuilt to 12 variants across
Orientation(Vertical / Horizontal) ×Size(Medium / Large) ×Tabs Count(2 / 3 / 4) — a complete 2 × 2 × 3 matrix, up from the earlier count-only split. (C2) - v2.0:
Tabs Countas a fixed variant axis confirmed intentional — GCash tab bars are capped at 2–4 destinations, so a Slot-based container is not needed here. (C2) - v2.0: Absence of a scrollable / overflow variant confirmed intentional — follows directly from the 2–4 cap; horizontal overflow never occurs. (C5)
- v2.0: Composition verified — every cell is a real
Tab ItemINSTANCE rather than a redrawn tab, so atom changes propagate to all 12 variants. (C4)
- Register Code Connect mapping to
EBTabs. WireOrientation,Size, andTabs Countto the SwiftUI / Compose API, forwarding orientation and size down to the nestedEBTabItemchildren. Docs
- Rename "Tab" → "Tabs". v2.0: Applied — the container is now plural, disambiguating it from the Tab Item atom. Rename
- Drop the
tabsCountvariant. v2.0: Reviewed and closed as not needed — the 2–4 cap is deliberate, so a flexible slot-based container would add complexity without covering a real case. Property - Add a scrollable variant. v2.0: Reviewed and closed as not needed — follows from the 2–4 cap; overflow never occurs. State
4 Tab Items in an equal-width flex row. 248px total width.
| Role | Token |
|---|---|
| Total width (2 tabs) | 124px |
| Total width (3 tabs) | 186px |
| Total width (4 tabs) | 248px |
| Per-tab width | 62px (flex 1 0 0) |
| Gap between tabs | 0 (shared border-bottom) |
| Shadow | Depth/D4 — 0 0 8px #73819A1A |
3 Tab Items in an equal-width flex row. 186px total width.
2 Tab Items in an equal-width flex row. 124px total width.
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") }
| Figma Property | SwiftUI | Compose |
|---|---|---|
| items | items: [EBTabItem] | items: List<EBTabItem> |
| selectedIndex | @Binding selection: Int | selectedIndex: Int |
| onSelect | onSelect: (Int) -> Void | onSelect: (Int) -> Unit |
| scrollable | .scrollable(true) | scrollable: Boolean |
EBTabs( items: [ EBTabItem(label: "Overview", icon: .image(Image("overview"))), EBTabItem(label: "Details"), EBTabItem(label: "History") ], selection: $selectedIndex )
EBTabs( items = listOf( EBTabItem(label = "Overview", icon = painterResource(R.drawable.overview)), EBTabItem(label = "Details"), EBTabItem(label = "History") ), selectedIndex = state, onSelect = { state = it } )
| Requirement | iOS | Android |
|---|---|---|
| Tab list role | Automatic via TabView | Automatic via TabRow (Material semantics) |
| Selected state announced | .accessibilityAddTraits(.isSelected) on active tab | selected = true in semantics |
| Keyboard / focus navigation | iOS handles via focus traits | Compose handles via focus semantics |
Do
Use 2–4 tabs for primary navigation within a screen. Tabs should represent peer sections of equal importance.
Don't
Use tabs for sequential flows (Step 1 / Step 2 / Step 3) — use a Stepper component instead.
Do
Keep tab labels short (one or two words). If labels exceed 12 characters, use icons only or switch to vertical orientation.
Don't
Nest Tabs inside Tabs — creates navigation ambiguity and accessibility issues.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | Tab 1, Tab 2, ... container, icon-label. Semantic. |
| C2 | Variant & Property Naming | Requires Rework | Component named singular "Tab"; tabsCount uses string values and shouldn't be a variant at all. |
| C3 | Token Coverage | Ready | Shadow and spacing bound to tokens. Colors live on the Tab Item atom. |
| C4 | Native Mappability | Ready | Maps cleanly to TabView / TabRow. |
| C5 | Interaction State Coverage | Needs Refinement | Active/inactive covered via Tab Item. No scrollable pattern for 5+ tabs. |
| C6 | Asset & Icon Quality | Ready | Children are Tab Item instances. |
| C7 | Code Connect Linkability | Needs Refinement | Not mapped. |
After the recommended restructure these 3 variants collapse to 1 flexible container accepting a list.
| tabsCount | Width | Node ID |
|---|---|---|
| 2 | 124px | 18482:33259 |
| 3 | 186px | 18482:33255 |
| 4 | 248px | 18482:33250 |
tabsCount is a variant property — Should be removed; the container should accept a list of Tab Items instead of exposing a fixed count enum.
Open