A horizontal-scroll item used as a building block for product/promo carousels.
Carousel Item (or fold into Carousel Card), strip mode in favour of a proper appearance mode set, replace the raster background with a background slot, vectorize the chevron, and add pressed state.Carousel - Item is one card in a horizontal swipe carousel — typically a promotional banner stack on the Home or Dashboard screen. The center item is emphasized; side items peek in at reduced opacity/scale. In today's Figma file, those visual states exist as separate components (Item, Item - Center, Item - Side) rather than being driven by the carousel container.
hasPreamble vs with Icon). type=Headline Only is actually "with Preamble + Heading Only" — mis-named.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default | Yes | Yes | mode × type × hasTextLink × hasPreamble | Static banner with tap target on the whole card (if hasTextLink) or on the button only. |
| Focused (center) | N/A | N/A | Carousel Item - Center (312×160) | Modeled as a sibling component. Should be a scroll-progress-driven style inside the carousel container. |
| Peeking (side) | N/A | N/A | Carousel Item - Side (312×146) | Modeled as a sibling component. Should be a scroll-progress-driven style inside the carousel container. |
| Pressed | N/A | N/A | Not built | Banner is tappable — needs a subtle scale-down or overlay tint on press. |
| Disabled | N/A | N/A | — | Promotional banner — disabled state isn't meaningful. |
- Position is a separate component, not a runtime style. Carousel - Item (282), Carousel Item - Center (312×160), and Carousel Item - Side (312×146) all share the same 10-variant schema. Center/side should be carousel-container-driven visual states via scale + opacity on scroll progress — not distinct DS components. C1 · Layer Structure & Naming
- Property casing is inconsistent. Booleans are camelCase (
hasPreamble,hasTextLink) but enum values are Title-Cased or hyphenated with lowercase (Light Text,with Icon,Headline Only). Pick one convention. C2 · Variant & Property Naming -
type=Headline Onlyis misleading. That variant renders Preamble + Heading + Button (no description). Either rename tonoDescriptionor collapse into a booleanhasDescription. C2 · Variant & Property Naming -
modeis about text color, not appearance. The enum decides text color only (inverse on dark photos vs dark on light photos). Native platforms should infer contrast from the background image or expose a properappearanceenum — not a layer-level token swap. C4 · Native Mappability - Background image has no slot. The image is baked into the variant — product teams can't drop in their own artwork without detaching the instance. C4 · Native Mappability
- Chevron is a raster
shape_fullPNG. Doesn't scale cleanly and can't accept a token-bound tint. Ships twice (once permode). C6 · Asset & Icon Quality - Icon placeholder in
with Iconis a drawn grey circle. Not a swappable Icon or Avatar instance. C6 · Asset & Icon Quality - No pressed state. Banner is tappable but only Default is modeled. C5 · Interaction State Coverage
- Code Connect mappings not registered. Blocked until the three siblings are consolidated and the image + icon slots are adopted. C7 · Code Connect Linkability
- Consolidate Carousel - Item + Center + Side into a single
Carousel Item. All three share the same 10-variant schema; only dimensions differ. Pick one canonical size (312×160 is the most common) and let the carousel container handle center-vs-side styling via scroll progress. Preferred target: fold into Carousel Card if content shape is compatible, otherwise keep asEBCarouselItemsibling. Family - Let the carousel container own focus/peek styling. Center-item scale, side-item opacity, and peek offset are layout concerns — not component variants. On iOS use
.scrollTransition { content, phase in content.opacity(phase.isIdentity ? 1 : 0.6).scaleEffect(phase.isIdentity ? 1 : 0.94) }. On Android usegraphicsLayerkeyed offHorizontalPagerpage offset. Composition - Add a
backgroundslot. Replace the baked-in raster with a Figma Slot that accepts an image, gradient, or illustration instance. Native:background: AnyView(SwiftUI) /background: @Composable () -> Unit(Compose). Slot - Replace
modewith anappearanceenum.appearance: light | dark(matching Button's conventions) — picks the full text/link color set in one go rather than Figma overriding each layer. Later, auto-derive from background luminance if tooling supports it. Property - Rename
type=Headline OnlytohasDescription: false. The current name doesn't describe what that variant renders. CollapsetypetoDefault | with Iconand move description visibility to a boolean. Rename - Add a leading icon slot (Icon or Avatar). Replace the grey circle placeholder with a proper Figma Slot that accepts an Icon or Avatar instance. Slot
- Vectorize the chevron. Swap the raster
shape_fullfor a vector glyph — one instance, token-bound color, crisp at any scale. Asset - Add pressed state. Subtle scale-down (0.98) or dark overlay (6–8% black) on press — banners are tappable and need tap feedback. State
- Document the carousel container. The peek behaviour, snap-to-center, page indicator, and auto-advance belong on a dedicated
EBCarouselcontainer component — not in each item. Family - Announce as a link / button. The whole card is tappable — VoiceOver and TalkBack should read heading + description + "Button" as a single actionable announcement. A11y
The most common variant — heading + description + button link, inverse text over a dark background image. Used on promotional carousels when the photo has dark tones.
Single carousel slide with heading and description on a white surface.
| Role | Token | Default |
|---|---|---|
| Heading | carousel/color/label-header | #2340A9 |
| Description | carousel/color/description | #6780A9 |
| Surface | bg/color-bg-main | #FFFFFF |
| Active dot | bg/color-bg-primary | #005CE5 |
Preamble + headline only — no description line. Use when the headline itself is the full message. Name is misleading: the variant actually requires Preamble + Heading + Button, with description hidden.
Same palette as Card 1 — variant differs in slot composition, not color.
| Role | Token | Default |
|---|---|---|
| Heading | carousel/color/label-header | #2340A9 |
| Description | carousel/color/description | #6780A9 |
| Surface | bg/color-bg-main | #FFFFFF |
| Active dot | bg/color-bg-primary | #005CE5 |
| Figma Property | SwiftUI | Compose |
|---|---|---|
mode: Light Text | Dark Text | appearance: light | dark | .ebAppearance(.light) |
type: Default | with Icon | Headline Only | type: default | withIcon + hasDescription: Bool | leadingIcon: Image?, hasDescription: Bool |
hasPreamble | preamble?: String | preamble: String? |
hasTextLink | actionLabel?: String | actionLabel: String? |
| (baked raster background) | background: Image | Gradient (slot) | background: AnyView |
| (drawn grey circle) | part of leadingIcon slot | leadingIcon: Image? |
| (raster chevron) | showChevron: Bool (vector) | showChevron: Bool = true |
| (not modeled) | onTap: () -> Void | onTap: (() -> Void)? |
| Carousel - Item | merge into Carousel Item | position is carousel-container-driven |
| Requirement | iOS | Android |
|---|---|---|
| Whole-card tap target | Wrap in Button with combined accessibilityLabel (preamble + heading + description + action). | Modifier.clickable().semantics(mergeDescendants = true). |
| Page-change announcements | Announce current page index via accessibilityValue on the carousel container. | semantics { contentDescription = "Banner 2 of 5" } on the pager. |
| Contrast on image | Designer ensures 4.5:1 between text + underlying image area; optional scrim overlay. | Same — ensure WCAG AA against the image region. |
| Reduce motion | Skip scale + opacity transitions when UIAccessibility.isReduceMotionEnabled. | Check Settings.Global.TRANSITION_ANIMATION_SCALE; skip graphicsLayer animation. |
| Min touch target | 282×160 ≫ 44 pt ✓ | 282×160 ≫ 48 dp ✓ |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Requires Rework | Position-specific duplicates (Item / Center / Side) should be one component with container-driven styling. |
| C2 | Variant & Property Naming | Needs Refinement | Mixed casing; Headline Only is mis-named. |
| C3 | Token Coverage | Ready | Text colors bind to main/carousel/color/*, text/color-text-inverse*, radius + shadow tokens. |
| C4 | Native Mappability | Requires Rework | Background is a baked raster — needs to become a slot. Position-as-variant doesn't map to pager APIs. |
| C5 | Interaction State Coverage | Needs Refinement | Default only — no pressed state for a tappable card. |
| C6 | Asset & Icon Quality | Needs Refinement | Raster chevron; drawn circle icon placeholder. |
| C7 | Code Connect Linkability | Not Mapped | Blocked until consolidation + slots land. |
mode (2) × type (3) × hasTextLink (2) × hasPreamble (2) = 24 combinatorial variants, but only 10 are modeled — the author pruned invalid combinations (e.g. Headline Only without Preamble, with Icon with TextLink). Each mode has 5 variants.
| Mode | Type | hasTextLink | hasPreamble | Node | Dimensions |
|---|---|---|---|---|---|
| Light Text | Default | yes | no | 18543:2807 | 282 × 160 |
| Light Text | with Icon | no | no | 18543:2818 | 282 × 160 |
| Light Text | Default | yes | yes | 18543:2826 | 282 × 160 |
| Light Text | Headline Only | yes | yes | 18543:2839 | 282 × 160 |
| Light Text | Default | no | no | 18543:2850 | 282 × 160 |
| Dark Text | Default | yes | no | 18543:2856 | 282 × 160 |
| Dark Text | with Icon | no | no | 18543:2867 | 282 × 160 |
| Dark Text | Default | yes | yes | 18543:2875 | 282 × 160 |
| Dark Text | Headline Only | yes | yes | 18543:2888 | 282 × 160 |
| Dark Text | Default | no | no | 18543:2899 | 282 × 160 |
hasPreamble vs with Icon); Headline Only mis-named. Openmode should be appearance. Open