A container for ads, promos, and sponsored placements. Three versions — Receipt, Banner, and Promo — each built around one swappable asset area.
Variant setting, each built the same way — Container wrapping a ⤷ AssetSlot and an optional Content block. Layer names share one vocabulary across every version. Media flows through the slot, so no campaign artwork ships inside the design system. Code Connect stays unmapped because the native library doesn't exist yet — a dev-side dependency, not a design gap.Each version has a home in the app: Receipt sits inside transaction and receipt flows, Banner runs inline in a feed or content column, and Promo is the dashboard tile that also fills the Ad Carousel rail.
Container → ⤷ AssetSlot → Content. Text layers share one vocabulary — #title, #header, #description — so the same name means the same thing everywhere.⤷ CarouselSlot, and stands alone inline. No sibling component per placement.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Variant=Receipt | N/A | N/A | EBAdSpace(.receipt) | 300 × 250. Asset only — no text. |
| Variant=Banner | N/A | N/A | EBAdSpace(.banner) | 320 × 262. Carries #title above the asset. |
| Variant=Promo | N/A | N/A | EBAdSpace(.promo) | 224 × 208. Fills the Ad Carousel rail. |
| ⤷ AssetSlot | N/A | N/A | content slot | Swappable area for product-supplied media. |
- The setting holds versions, not sizes. The
Sizesetting was renamed toVariant. Its values — Receipt, Banner, Promo — name where the ad goes, which is what they always described.Sizeis reserved for XS–XL under the naming guidelines. C2 · Variant & Property Naming - All three versions share one anatomy. Receipt used to hang its asset area straight off the root while the other two wrapped theirs. It now has the same
Container, so every version readsContainer→⤷ AssetSlot. The slot was renamed to⤷ AssetSlotin all three, matching the convention Tooltip uses. C1 · Layer Structure & Naming - Text layers no longer contradict their contents. A layer called
#labelheld the text "Title", while a different layer called#titleheld "Header". They are now#titleand#header— each name matches what it holds, in every version. C1 · Layer Structure & Naming
- Retire the old component in Sticker Sheets v2. This assessment now points at
6507:74166in the 2026 Working File. The previous component at18563:9789is deprecated. Swap the remaining Figma usages, then delete it along with the Placeholder Banner and Promo Cards Images asset libraries once zero usage is confirmed. Family - Ship imagery as product assets, not design system assets. Product teams export their own 1×/2×/3× images and pass them into
⤷ AssetSlot. The design system ships the container, type styles, radius, and slot placeholder — never the artwork. Asset - Revisit whether the carousel container should stay ad-specific. Ad Carousel is a title, an optional button, and a rail of cards — nothing in it is about ads. Swap Ad Space for a Carousel Card and it becomes the dashboard rail. Keeping it ad-specific was a deliberate call this pass, to avoid a name collision with Carousel Card and Carousel Item. Worth revisiting when the Carousel family is reviewed. Family
- Propose a
main/ad-space/color/*token namespace. Shipmain/ad-space/color/surface,main/ad-space/color/title,main/ad-space/color/header, andmain/ad-space/color/description. A dedicated namespace makes dark mode and partner-branded surfaces tractable instead of inheriting generic background tokens. Token - Bake impression and tap tracking into the native component. The iOS view and Android composable should emit
onImpression(50% visible for at least one second) andonTap. Document the contract in the Code tab so every consumer team wires analytics the same way. Docs - Treat ads as labeled buttons, not decorative images. Every Ad Space is tappable and leads somewhere, so the whole surface should expose one accessibility label built from
#headerand#description, plus an "Advertisement" trait to meet App Store and Play Store disclosure norms. A11y - Watch the Banner title override inside the carousel. Banner carries
#titleabove the asset, Promo has only#headerand#description, and Receipt has no text. Inside Ad Carousel the Banner title is switched off on each copy so it doesn't repeat the rail heading. That is a per-copy override rather than a property, so a designer who swaps the copy gets the title back. AhasTitleboolean would remove the risk if it proves annoying in practice. Slot - See siblings: Ad Carousel wraps this component in a scrolling rail. Keep the asset slot and placeholder treatment aligned with Carousel Card as the Carousel family is reviewed. Family
Asset only, no text. Sits inside transaction and receipt flows where the surrounding screen already supplies the context.
Asset only. The purple slot placeholder is an authoring aid and never renders in product.
| Role | Token | Default |
|---|---|---|
| Surface | bg/color-bg-main | #FFFFFF |
The widest version. Carries #title above the asset and a #header + #description block beneath. Inside the Ad Carousel rail the title is switched off so it doesn't repeat the rail heading.
Three text roles. #title and #header share a size and weight but not a colour.
| Role | Token | Default |
|---|---|---|
| Surface | bg/color-bg-main | #FFFFFF |
| #title | text/color-text-heading | #072592 |
| #header | text/color-text-link | #005CE5 |
| #description | text/color-text-body | #445C85 |
The dashboard tile, and the default card inside the Ad Carousel rail. Same Content block as Banner, without the title above the asset.
Same palette as Banner, minus the title role.
| Role | Token | Default |
|---|---|---|
| Surface | bg/color-bg-main | #FFFFFF |
| #header | text/color-text-link | #005CE5 |
| #description | text/color-text-body | #445C85 |
Swift Package Manager
.package(url: "https://github.com/gcash/east-blue-ios", from: "1.0.0")
Gradle
implementation("com.gcash.eastblue:components:1.0.0")
Planned API — the native library does not exist yet. Snippets show the intended shape, not shipped code.
Figma properties mapped to the intended native parameters.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| Variant | AdSpaceVariant (.receipt / .banner / .promo) | variant: AdSpaceVariant |
| ⤷ AssetSlot | @ViewBuilder content: () -> Content | content: @Composable BoxScope.() -> Unit |
| #title | title: String? (Banner only) | title: String? (Banner only) |
| #header | header: String? | header: String? |
| #description | description: String? | description: String? |
EBAdSpace(.receipt) { Image(ad.creative).resizable() }
EBAdSpace(variant = AdSpaceVariant.Receipt) { AsyncImage(model = ad.creative, contentDescription = null) }
EBAdSpace( .banner, title: "Sponsored", header: ad.headline, description: ad.blurb ) { Image(ad.creative).resizable() }
EBAdSpace( variant = AdSpaceVariant.Banner, title = "Sponsored", header = ad.headline, description = ad.blurb ) { AsyncImage(ad.creative, null) }
EBAdCarousel(title: "Sponsored") { ForEach(ads) { ad in EBAdSpace(.promo, header: ad.headline) { Image(ad.creative) } } }
EBAdCarousel(title = "Sponsored") { ads.forEach { ad -> EBAdSpace(AdSpaceVariant.Promo, header = ad.headline) { AsyncImage(ad.creative, null) } } }
| Requirement | iOS | Android |
|---|---|---|
| The whole card is one tappable element | .accessibilityElement(children: .combine) with a single .isButton trait | Modifier.clickable on the container, children marked clearAndSetSemantics |
| Disclosed as advertising | .accessibilityHint("Advertisement") | contentDescription prefixed with "Advertisement" |
| Label built from the text layers | #header then #description, joined | Same order in contentDescription |
| Asset is not announced separately | .accessibilityHidden(true) on the image | contentDescription = null on the image |
Do
Pass campaign artwork into ⤷ AssetSlot from the product's own asset pipeline.
Don't
Don't add campaign images to the design system library.
Do
Use Promo inside an Ad Carousel when the rail should read as scrollable.
Don't
Don't expect a peek from Banner in a rail; at 320 wide it fills the shell by design.
Do
Let Receipt carry no text where the surrounding screen already gives context.
Don't
Don't force a header onto Receipt by detaching the copy.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | All three versions read Container → ⤷ AssetSlot. Text layers renamed so each name matches its contents. |
| C2 | Variant & Property Naming | Ready | Size renamed to Variant; values name the placement, which is what they always described. |
| C3 | Token Coverage | Needs Refinement | Colours are attested as token-bound but were not verifiable with read-only tools. A main/ad-space/color/* namespace is proposed. |
| C4 | Native Mappability | Ready | A container with an image slot and two optional text rows maps directly to both platforms. |
| C5 | Interaction State Coverage | Not Applicable | Display-only surface — confirmed by design. Tap handling belongs to the host. |
| C6 | Asset & Icon Quality | Ready | No raster ships inside the component. The Slot Block placeholder is the standard authoring aid. |
| C7 | Code Connect Linkability | Not Mapped | Blocked — the native library does not exist yet. |
| Aspect | Status | Notes |
|---|---|---|
| Property naming | Ready | Variant (3 values), ⤷ AssetSlot (slot), and three text layers map one to one. |
| Token coverage | Needs Refinement | Text colours need a Dev Mode check before mapping. |
| Registration | Not Mapped | Blocked until the native library exists. |
1 Variant setting × 3 values = 3 versions. Content is supplied through the slot and the text layers, not through extra versions.
| Variant | Size | Text layers | Node |
|---|---|---|---|
| Receipt | 300 × 250 | none | 6518:74485 |
| Banner | 320 × 262 | #title · #header · #description | 5703:38546 |
| Promo | 224 × 208 | #header · #description | 6528:77243 |
banner-sm … hero-md enum is gone. The component is now Variant = Receipt | Banner | Promo on a new node in the 2026 Working File. The previous component at 18563:9789 in Sticker Sheets v2 is deprecated.
RebuiltSize renamed to Variant. The values name where the ad goes rather than how big it is, so Size is freed for its XS–XL meaning under the naming guidelines.
ResolvedContainer wrapper the other two already had, and the slot was renamed ⤷ AssetSlot everywhere.
Resolved#label (holding "Title") became #title, and the old #title (holding "Header") became #header, in every version.
ResolvedisLoading and the caption removed. Neither exists on the rebuilt component. The skeleton treatment and the optional caption string documented against the old seven-size schema no longer apply.
RemovedAd Space with a 7-value size enum grouped into three families: banner (IAB / AdMob), promo (dashboard tile), hero (full-width).
Initialtype=hifi | midfi was a placeholder-authoring crutch, not a runtime variant. Replaced with an orthogonal isLoading boolean. Content flows through the content slot regardless of state.
Resolvedcarousel=yes pseudo-variant retired — The legacy "carousel preview" variant was a static 3-card Figma layout, not a runtime carousel. Multi-ad rails are now authored by composing multiple AdSpace size="hero-md" instances inside the DS EBCarousel.
Resolvedcontent slot; product teams provide imagery from their own asset pipeline.
Resolvedbanner-* sizes map to GADBannerView / AdView. promo-* / hero-* map to a single EBAdSpace view/composable. Clean enum, boolean, string, slot signatures ready for Code Connect CLI registration.
Resolvedmain/ad-space/color/surface, main/ad-space/color/caption, main/ad-space/color/loading-skeleton. Replaces ad-hoc use of generic surface tokens across the legacy trio.
InitialonImpression (50% visible ≥1s) and onTap. AdMob-backed banner-* inherits the SDK's tracking; product-owned promo-* / hero-* wire consumer analytics via the callbacks.
Initial