A neutral-background promotional banner with an image or icon, a text stack (preamble, heading, description), and an optional button link.
with link, with button, with preamble, with icon) don't survive codegen. with link + with button describe mutually exclusive CTAs and should be one action enum. with icon is too narrow — a leading asset slot accepting Icon / Avatar / Illustration / Image is more reusable. Property = Within A Container | Full Width is a padding/layout concern owned by the parent. Background image and chevron should be vector slots. Finally, Banner and Carousel - Item share enough DNA to be one component with carousel behaviour on the container.Banner is used in-flow as a promotional callout — typically between sections on a Home or Dashboard screen. "Within A Container" leaves horizontal padding on either side so the banner sits as a card; "Full Width" bleeds edge-to-edge. The image or icon sits on the opposite side of the text per the position axis.
main/banner/color/*. But the chevron on the button link is a raster shape_full PNG, and the icon placeholder is a drawn circle — neither is self-contained as a vector instance.with link, with button, with preamble, with icon) — no other DS component uses spaces in property names. Property = Within A Container | Full Width is a padding concern named like a semantic mode. with link + with button are mutually exclusive but modeled as independent booleans.HStack / Compose Row. Duplicates ~95% of Carousel - Item's schema — the two should be one component.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default | Yes | Yes | Property × position × with link × with button × with preamble × with icon | Static banner; whole card is the tap target when an action is present. |
| Pressed | N/A | N/A | Not built | Tappable banner lacks pressed feedback — needs a subtle scale-down or overlay tint. |
| Focused | N/A | N/A | Not built | Keyboard / D-pad focus ring needed when used in an a11y-first flow. |
| Within A Container | N/A | N/A | Property=Within A Container | 12 px outer padding + 8 px corner radius around the banner card. Owned by the parent layout on native — not a component variant. |
| Full Width | N/A | N/A | Property=Full Width | No outer padding, no corner radius. Also owned by the parent layout. |
- Property names use spaces.
with link,with button,with preamble,with iconaren't valid identifiers in any native codegen target. Should be camelCase:hasLink,hasAction,hasPreamble,hasLeadingAsset. C2 · Variant & Property Naming -
Propertyis a meta-name, not a semantic one. Its values (Within A Container/Full Width) describe outer padding and corner radius — a layout concern the parent should own. Rename topaddingif kept, or drop the axis entirely and let the consumer control width + padding. C2 · Variant & Property Naming -
with link+with buttonencode mutually exclusive CTAs as independent booleans. Both are text + chevron link styles — the distinction is cosmetic. This schema admits the impossible statewith link=yes+with button=yes(excluded by authoring convention, not by the schema). Collapse into oneactionenum. C2 · Variant & Property Naming - Sparse cartesian variant space. 5 boolean-ish axes × 2 container modes would yield 64 combinations; only 20 ship. Author manually pruned invalid combos — that logic should live in the schema (enums, mutually exclusive props), not in variant authoring discipline. C1 · Layer Structure & Naming
- No first-class image slot. The image is an instance of a separate "Banner Asset Placeholder" component — product teams swap via instance-override rather than a declared Figma Slot. Should be a named
assetslot accepting an Image, Illustration, or Gradient. C4 · Native Mappability -
with icon=yesrenders a drawn grey circle. The icon slot is a flat#C2C6CFcircle, not a swappable Icon / Avatar / Illustration instance. Can't carry a token-bound color or glyph. C6 · Asset & Icon Quality - Chevron is a raster
shape_fullPNG. The "learn more" arrow ships as an<img>asset. Doesn't scale cleanly and can't take a token tint. C6 · Asset & Icon Quality - No pressed / focused / disabled states. The whole banner is tappable but only Default is modeled. Native platforms need pressed (tap feedback) and focused (keyboard/D-pad) at minimum. C5 · Interaction State Coverage
- Container padding modeled as a component variant.
Within A Containeradds 12 px outer padding and wraps the inner card in a rounded container;Full Widthdrops both. On native this is a layout-level decision (parent gives Banner its width + padding), not a Figma variant. C4 · Native Mappability - Code Connect mappings not registered. Blocked until properties are renamed, axes are collapsed, and asset/background slots are adopted. C7 · Code Connect Linkability
- Rename space-separated booleans to camelCase.
with link→hasLink,with button→hasAction,with preamble→hasPreamble,with icon→hasLeadingAsset. Matches the DS-wide naming fix applied to Carousel Item and the Form family. Rename - Collapse
with link+with buttoninto oneactionenum.action: .none | .link("Label") | .button("Label"). Mutually exclusive CTAs shouldn't be modeled as independent booleans — the schema should makewith link=yes + with button=yesunrepresentable. Property - Replace
with iconboolean with a leadingassetslot. Accept an Icon, Avatar, Illustration, or Image instance. Native:leadingAsset: @ViewBuilder(SwiftUI) /leadingAsset: @Composable () -> Unit(Compose). Eliminates the rigid icon-only placeholder. Slot - Add a
background/ image slot. Replace the "Banner Asset Placeholder" instance with a first-class Figma Slot that accepts an Image, Illustration, or Gradient. Native:background: AnyView/background: @Composable () -> Unit. Slot - Rename
Property→ drop it or make itpadding.Within A ContainervsFull Widthis a padding + radius concern owned by the parent layout on native. Either remove the axis entirely (the banner fills whatever width its parent hands it) or rename topadding: .container | .full. Property - Rename
position→imagePosition. More specific and self-documenting. Keep as.left | .rightenum. Rename - Consolidate with Carousel - Item. Both components have preamble / heading / description / button / image-with-position slots. The only difference is peek/snap behaviour — which belongs on the carousel container (scroll snap + scale/opacity transforms), not a sibling component. Target: one
EBBannerused standalone or insideEBCarousel. Family - Vectorize the chevron. Swap the raster
shape_fullfor a vector Icon instance — crisp at any scale, token-bound tint. Asset - Add pressed + focused states. Pressed: 0.98 scale or 6–8% overlay on tap. Focused: 2 px focus ring at
border/focus. Banners are always tappable and need both feedback signals. State - Announce as a single actionable element. VoiceOver and TalkBack should read preamble + heading + description + action label as one announcement with a button/link role. A11y
The most content-rich variant — preamble + heading + description + button link, with the image on the right and the content column left-aligned. Wraps in a rounded card with 12px outer padding.
Banner that nests inside a parent surface. Card bg is transparent; only typography roles carry color tokens.
| Role | Token | Default |
|---|---|---|
| Card bg | banner/color/bg | transparent |
| Preamble | banner/color/preamble | #072592 |
| Heading | banner/color/heading | #072592 |
| Description | banner/color/description | #6780A9 |
| Action label | banner/color/action | #005CE5 |
| Chevron tint | banner/color/chevron | #005CE5 |
Edge-to-edge variant — no outer padding, no corner radius. The banner's own 16px padding sits directly against the screen edges. Used when the banner is the hero element of the section.
Edge-to-edge banner with a white surface and an optional faint border tint.
| Role | Token | Default |
|---|---|---|
| Card bg | banner/color/bg | #FFFFFF |
| Border | banner/color/border | #DFECFF |
| Heading | banner/color/heading | #072592 |
| Description | banner/color/description | #6780A9 |
| Action label | banner/color/action | #005CE5 |
| Chevron tint | banner/color/chevron | #005CE5 |
Icon-led variant — replaces the image with a drawn grey circle placeholder. No action CTA. Used when the banner is informational rather than promotional.
Same nested-card palette as Card 1, with a leading icon role replacing the preamble.
| Role | Token | Default |
|---|---|---|
| Card bg | banner/color/bg | transparent |
| Icon tint | banner/color/icon | #005CE5 |
| Heading | banner/color/heading | #072592 |
| Description | banner/color/description | #6780A9 |
| Action label | banner/color/action | #005CE5 |
| Figma Property | SwiftUI | Compose |
|---|---|---|
Property: Within A Container | Full Width | padding: container | none (or drop) | Parent layout |
position: left | right | imagePosition: left | right | imagePosition: .left |
with preamble | preamble?: String | preamble: String? |
with link + with button | action: .none | .link | .button | action: EBBannerAction? |
with icon | leadingAsset slot | leadingAsset: () -> AnyView |
| (Banner Asset Placeholder instance) | background: Image | Illustration slot | background: AnyView |
| (raster chevron) | vector Icon | Built into .link/.button action |
| (not modeled) | onTap: () -> Void | onTap: (() -> Void)? |
| Requirement | iOS | Android |
|---|---|---|
| Whole-card tap target | Wrap in Button with combined accessibilityLabel (preamble + heading + description + action). | Modifier.clickable().semantics(mergeDescendants = true). |
| Role announcement | .accessibilityAddTraits(.isButton) when action is set. | Role.Button inside semantics. |
| Decorative image | .accessibilityHidden(true) on the background image view. | contentDescription = null on the background Image. |
| Focus ring | Default SwiftUI focus ring (tvOS + iPadOS keyboard nav). | D-pad focus: 2 dp outline at border/focus. |
| Min touch target | 360 × (93-176) ≫ 44 pt ✓ | 360 × (93-176) ≫ 48 dp ✓ |
Do
Use Banner for in-flow promo / info callouts between content sections. Keep the heading to 1-2 lines.
Don't
Use both a link and a button — action is single-CTA by design.
Do
Pick imagePosition based on reading flow — LTR locales usually prefer .left image + right-aligned text.
Don't
Stack Banner inside a carousel — compose EBBanner inside EBCarousel instead.
Do
Use preamble for category context (PROMO, NEW, TIP). Let the parent layout own outer padding.
Don't
Bake text into the image — background is decorative only. Don't ship the drawn-circle icon placeholder.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Requires Rework | Sparse cartesian — 5 boolean-ish axes × 2 container modes yields 64 combos; 20 ship. Container-padding axis conflates layout with identity. |
| C2 | Variant & Property Naming | Requires Rework | Property names use spaces (with link, with button, with preamble, with icon). Property is a meta-name. with link + with button encode mutually exclusive CTAs as two booleans. |
| C3 | Token Coverage | Ready | All colors bind to main/banner/color/*; radii to radius/radius-3; spacing to space/space-*; bg to main/banner/color/bg. |
| C4 | Native Mappability | Requires Rework | Image is an instance (not a slot); container-padding axis doesn't map to native; icon-only asset axis is rigid. |
| C5 | Interaction State Coverage | Requires Rework | Only Default — no pressed, focused, or disabled for a tappable banner. |
| C6 | Asset & Icon Quality | Requires Rework | Raster shape_full chevron. Drawn grey circle icon placeholder. Image asset is a separate component instance. |
| C7 | Code Connect Linkability | Not Mapped | Blocked on property renames, axis collapse, and slot adoption. |
Property (2) × position (2) × with link × with button × with preamble × with icon — if all boolean combinations shipped, 2 × 2 × 2 × 2 × 2 × 2 = 64 variants. The author pruned invalid combos (with link=yes + with button=yes, with icon=yes + with button=yes, etc.) and shipped 20 variants — 10 per Property mode, mirrored across position=left|right.
| Content shape | Property | Count | Example nodes (left · right) |
|---|---|---|---|
| heading + desc + button | Within A Container | 2 | 756:82659 · 756:82653 |
| heading + desc + button | Full Width | 2 | 756:82669 · 756:82667 |
| heading + desc + icon (no action) | Within A Container | 2 | 756:82657 · 756:82658 |
| heading + desc + icon (no action) | Full Width | 2 | 756:82668 · 756:82672 |
| preamble + heading + desc + button | Within A Container | 2 | 756:82655 · 756:82656 |
| preamble + heading + desc + button | Full Width | 2 | 756:82664 · 756:82662 |
| heading + desc + link | Within A Container | 2 | 756:82654 · 756:82671 |
| heading + desc + link | Full Width | 2 | 756:82663 · 756:82670 |
| heading + desc (no action) | Within A Container | 2 | 756:82665 · 756:82666 |
| heading + desc (no action) | Full Width | 2 | 756:82661 · 756:82660 |
with link, with button, with preamble, with icon, and meta-named Property. Mutually exclusive CTAs modeled as independent booleans. OpenProperty=Within A Container | Full Width conflates layout with identity. Open