A tappable card used inside a horizontally scrolling carousel — image, title, and optional description.
type property conflates a content variant (default vs with icon) with a loading state (skeleton) — these should be orthogonal axes. Banner image, dimmer, and icon badge are all hardcoded placeholders instead of instance slots. No pressed/focused state despite the card being tappable. Most importantly, this component is 1 of 5 near-duplicate "carousel card / item" components that should consolidate to 1–2 canonical primitives.Carousel Card lives in a horizontal scroller — typically a "Featured" or "For You" rail on a home or category screen. Cards are peeked (part of the next one visible) to signal scrollability.
Title
Description here.
Description here.
#e6e1ef multiply dimmer + a hardcoded #c2c6cf icon circle. None of these are instance slots, so consumers can't swap media or icons cleanly.type enum mixes content axes with a loading state. Naming diverges from the sibling Carousel - Item family, which uses position and a different anatomy.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default | Yes | Yes | type=default | Banner image + title + 2-line description. No overlay. |
| With icon | Yes | Yes | type=with icon | Adds a bottom gradient shadow on the banner and a circular icon badge (30×30, blue fill) at the bottom-left of the banner. |
| Skeleton (loading) | Yes | Yes | type=skeleton loader | Gray block for the banner; bar placeholders for title + 2 description lines. |
| Pressed | N/A | N/A | Not built | Card is tappable (navigates to detail) — needs a pressed state: scale-down 0.98 or a subtle bg tint. |
| Focused | N/A | N/A | Not built | Keyboard/D-pad navigation — needs an outline ring for TV/tablet surfaces and accessibility. |
-
typeenum conflates a content variant with a loading state.defaultandwith icondescribe content shape;skeleton loaderdescribes a loading state. Split into two orthogonal axes:variant = default | with-iconandisLoading: Boolean(or a siblingCarouselCardSkeletoncomponent). C2 · Variant & Property Naming - Banner is a hardcoded raster placeholder. Ships a static
replace-this-assetPNG plus a purple#e6e1efmix-blend-multiplydimmer layer. Neither is a Figma Slot — designers must detach and redraw to swap media. Expose an image slot and drop the fixed dimmer (tint should be optional + tokenized). C6 · Asset & Icon Quality - Icon badge on "with icon" variant is a filled circle, not an Icon instance. A
#c2c6cf24×24 circle sits inside a 30×30 blue pill — there's no instance swap, so brand icons, service glyphs, or Avatars can't be dropped in without detaching. C6 · Asset & Icon Quality - No pressed or focused state. Carousel cards are tappable and navigate somewhere — pressed feedback (scale or tint) is expected, and focused is needed for keyboard/D-pad surfaces. Only Default + skeleton are modeled today. C5 · Interaction State Coverage
- Code Connect mappings not registered. Blocked until the
typesplit, the image/icon slots, and the family consolidation land. C7 · Code Connect Linkability
- Consolidate Carousel Card + Carousel - Discount Card into a single
Carousel Card. The two are the same anatomy — 140-wide vertical card with banner + block-content — differing only in visual treatment. Merge into one component with avariantprop (default/discount) and let the discount-specific overlay (price tag, strikethrough, etc.) live as an overlay slot. Today's 5-component family collapses to 2:Carousel CardandCarousel Item. Family - Consolidate Carousel - Item + Carousel Item - Center + Carousel Item - Side into a single
Carousel Item. "Center" and "side" describe a peek carousel's runtime layout position, not a component variant — a carousel layout computes which item is center vs side, it shouldn't be baked into the component as an enum. Merge these 3 into oneCarousel Itemand let the parent carousel apply the peek transform. Family - Split
typeintovariant+isLoading.variant = default | with-icondescribes content shape.isLoading: Boolean(or a dedicatedCarouselCardSkeletonsibling) describes the loading state. Keeps content and state axes orthogonal and matches how Generic Card proposes to handle skeleton. Property - Adopt Figma Slots for banner and icon badge. Banner becomes an image slot accepting any frame; icon badge becomes an Icon / Avatar instance slot. Native maps banner →
AsyncImage/AsyncImageslot and icon →@ViewBuilder(SwiftUI) or@Composable(Compose). Slot - Replace the hardcoded purple dimmer with an optional tint token. The
#e6e1efmix-blend-multiplylayer is a loudly-colored overlay that shouldn't ship as a default on every banner. Make it an optionaloverlayprop bound tomain/carousel/color/overlay(currently unbound). Token - Add pressed + focused states. Pressed: scale 0.98 or bg tint on the full card. Focused: 2px outline ring in
border/focus. Tappable components need both. State - Rename to clarify hierarchy. After consolidation,
Carousel Card(this component + Discount Card) handles the full-width banner pattern;Carousel Item(peek variants) handles the peek pattern. Avoid overlap in naming between the two. Rename - Document the skeleton treatment as a shared DS convention. Carousel Card, Generic Card, and other card primitives all ship first-class skeletons — call out the pattern in the guidelines so card-family consistency holds as more components adopt it. Docs
- See siblings: Generic Card (horizontal list row) — same "card + skeleton + tappable" pattern, different layout. Keep skeleton treatment aligned across both. Family
Banner image + title + 2-line description. The banner ships a placeholder PNG dimmed by a purple multiply layer — replace both with your real media.
Title
Description here.
Description here.
Promotional carousel card with heading, description, and pagination dots.
| Role | Token | Default |
|---|---|---|
| Surface | bg/color-bg-main | #FFFFFF |
| Heading | carousel/color/label-header | #2340A9 |
| Description | carousel/color/description | #6780A9 |
| Active dot | bg/color-bg-primary | #005CE5 |
| Inactive dot | bg/color-bg-strong | #EEF2F9 |
Default layout + a bottom-left icon badge on the banner. A gradient shadow along the lower third improves icon contrast against bright imagery.
Icon-led carousel card with a tinted icon container and stacked title/description.
| Role | Token | Default |
|---|---|---|
| Surface bg | main/card/bg | #FFFFFF |
| Icon container bg | main/card/icon/bg | #E8F1FF |
| Title | main/card/title | #0A2757 |
| Description | main/card/description | #3C4A5C |
Loading placeholder: banner becomes a flat light-gray block; title and description become bar placeholders. Card total height drops to 212 (vs 215 default) due to the 16 top gap in the content block.
Loading state — content slots become rounded grey rectangles on the card surface.
| Role | Token | Default |
|---|---|---|
| Skeleton bg | main/skeleton/bg | #EEF2F9 |
| Surface bg | main/card/bg | #FFFFFF |
| Figma Property | SwiftUI | Compose |
|---|---|---|
type: default | with icon | skeleton loader | variant: default | with-icon + isLoading: Boolean | variant: EBCarouselCardVariant + isLoading: Bool |
| (hardcoded raster) | image: Frame (slot) | image: () -> Image |
| (hardcoded text) | title: String | title: String |
| (hardcoded text) | description: String | description: String? |
| (hardcoded circle) | icon?: Icon (slot) | icon: EBIcon? |
| (hardcoded purple multiply) | overlay?: Color (token) | overlay: Color? |
| (not modeled) | onTap?: () -> Void | onTap: (() -> Void)? |
| Requirement | iOS | Android |
|---|---|---|
| Card as a button | Whole card wrapped in Button with combined accessibilityLabel (title + description). | Modifier.clickable { onClick() }.semantics(mergeDescendants = true) on the column. |
| Combined announcement | "Send Money, Locally or abroad, same day" — VoiceOver reads title then description. | Same reading order — TalkBack follows composition. |
| Image alt | If the banner carries meaning, pass accessibilityLabel on the image; otherwise mark as decorative. | contentDescription set when banner is content-bearing, null when decorative. |
| Min touch target | Card is 140×215 — comfortably above 44 pt ✓ | 140 dp × 215 dp — above 48 dp ✓ |
| Loading state | Announce "Loading" once on mount; suppress per-placeholder announcements. | contentDescription = "Loading" on the skeleton container. |
| Focus ring | Add a .focused() modifier → 2 px outline for tvOS/iPad keyboard. | Modifier.focusable() + border in border/focus token. |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | Clean container / banner / block-content hierarchy. Layer names are semantic. |
| C2 | Variant & Property Naming | Needs Refinement | type conflates content variant with loading state — split into variant + isLoading. |
| C3 | Token Coverage | Needs Refinement | Title / description / skeleton fills bound to tokens. Banner PNG, purple dimmer, and icon glyph color are not. |
| C4 | Native Mappability | Needs Refinement | Maps cleanly to VStack / Column in a horizontal scroller once image/icon slots and skeleton split land. |
| C5 | Interaction State Coverage | Needs Refinement | Default + skeleton built. Missing pressed + focused. |
| C6 | Asset & Icon Quality | Needs Refinement | Banner is a raster placeholder PNG; icon is a drawn circle, not an Icon instance; purple dimmer is hardcoded. |
| C7 | Code Connect Linkability | Not Mapped | Blocked on property split, slot adoption, and family consolidation. |
type (3) = 3 variants. default and with icon share the same overall dimensions (140 × 215); skeleton loader is 140 × 212 due to a different content padding.
| type | Node | Dimensions | Anatomy |
|---|---|---|---|
| default | 23:121312 | 140 × 215 | Banner + title + 2-line description. |
| with icon | 23:121322 | 140 × 215 | Default + gradient shadow + bottom-left 30×30 icon badge on banner. |
| skeleton loader | 23:121334 | 140 × 212 | Flat banner + 3 bar placeholders (title 16, desc 10, desc 10 @ 97w). |
type into variant + isLoading, adopt image + icon slots, drop the hardcoded purple dimmer, add pressed/focused states, and consolidate the 5-component Carousel family. OpenEBCarouselCardSkeleton. NotedCarousel Card (default / discount) and Carousel Item (peek). Openvariant for content, isLoading for state. Open