PlanCard
The plans card — an overline, the plan name, partner logos, a Discount badge and the price.
plans-portableOpen in Figma ↗Use for
A compact plan card used to present a named plan or subscription tier with its price, bundled service logos, and an optional promotional badge. Use in contexts where plan information needs to be surfaced at a glance alongside other content — carousel rows, comparison panels, or summary screens.
Not for
Full plan detail pages or primary plan selection flows — use a full-size plan card for those. Promotional or offer-only content with no plan name or price — use a card-features or offer component instead.
Choosing
Set color-scheme=default for light surface contexts. Set color-scheme=inverse for dark surface contexts — the card background switches to surface/base/midnight and all text and badge tokens invert accordingly. Do not apply color-scheme=inverse on a light screen background; ensure the surrounding context is dark. Background color is content-driven, not status-driven — any named color can be used for any plan; there is no fixed color assignment per plan type.
Set services=on when the plan has bundled add-ons to surface via the logo-row. Set services=off when there are no bundled services — the logo-row slot collapses. Set badge=on when there is an active promotional offer (Discount or New) to highlight. Set badge=off when there is no offer. The badge color contrasts with the card background — a red badge on light cards, a light badge on dark cards. Do not override badge color manually; it is set by the color-scheme property.
For the logo-row, use size=md (40px) to match this card's standard context. Toggle individual logo slots (logo1–logo5) to match the number of actual partner logos. Enable xMore only when logos exceed the visible slots; the overflow pill must reflect the actual count of hidden logos. Maximum 3 logos visible before overflow. Set direction=rtl for Arabic.
Rules
The plan label sits top-left using body/md (14px regular) with dir=auto. The plan title sits below it using heading/md (24px bold) with dir=auto, max 2 lines, wraps allowed. Do not truncate the title to a single line. The logo-row sits in the header, 8px below the title. The price sits at the bottom of the card using title/md (20px semi-bold). The "from" label above the price uses body/md subtle — show it when the price is a starting price, hide it otherwise. The badge sits above the price row in the footer. Card is fixed at 222×300px. Do not resize the instance. For RTL, set direction=rtl at the component level — all text fields use dir=auto and will flip automatically.
import SwiftUI import EandTokens // plans-portable: 222×300 (row-4), r16, padding 16, white. Header (gap 4): overline, // 20pt heading/md title, logo row md. Footer pinned to the bottom: lg Discount badge, // "from", then the title/md price. struct PlanCard: View { var body: some View { VStack(alignment: .leading, spacing: EandSpacing.xs) { Text("Postpaid").font(.system(size: EandTypography.bodyMd.size)) .foregroundColor(EandColor.textDefaultSubtle) Text("Freedom Live 200") .font(.system(size: EandTypography.headingMd.size, weight: EandTypography.headingMd.weight)) .lineLimit(2) LogoRow(logos: [Image("brand-smiles")], more: 4).padding(.top, EandSpacing.sm) Spacer(minLength: 0) OfferBadge(text: "Discount", size: .lg) // red, 24pt Text("from").font(.system(size: EandTypography.bodyMd.size)) .foregroundColor(EandColor.textDefaultSubtle) Text("AED 200/mo") .font(.system(size: EandTypography.titleMd.size, weight: EandTypography.titleMd.weight)) } .foregroundColor(EandColor.textDefaultDefault) .padding(EandSpacing.lg) .frame(width: 222, height: EandSection.bodyHeightRow4, alignment: .topLeading) .background(EandColor.surfaceBaseInverse) .clipShape(RoundedRectangle(cornerRadius: EandRadius.radius6)) } }
When to use
- Use to compare plans in a carousel, in a Section of size
lg. - Don't use for products, devices or add-ons (use ProductCard), or for deals with a like count (use DealCard).
Anatomy
- Header —
category, the overline, inbody/mdsubtle;namein bold 20px, up to two lines; then the logo row of 32px partner logos with a +N bubble. PasslogosandmoreLogos, or leavesmileson for placeholders. - Footer — bottom-aligned: the
lgDiscount badge (discount={true}labels it "Discount"), the "from" line (from), thenpriceandperiod.
category and period accept null to hide them.
colorScheme="default" is the white card; colorScheme="inverse" is the midnight card with
white ink. Both use the same red Discount badge.
<PlanCard name="Entertainment plans" category="Postpaid" price="AED 1250" period="/mo" discount="20% off" />
<PlanCard colorScheme="inverse" name="Data plans" discount={false} from={false} price="AED 300" />Deprecated
variant is now colorScheme: midnight becomes inverse. brand has no Figma
counterpart.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
category? | ReactNode | Postpaid | Figma overline-string. null hides the overline. |
colorScheme? | 'default' | 'inverse' | — | Figma color-scheme axis. Takes precedence over variant. |
discount? | ReactNode | true | Figma badge — the lg Discount badge in the footer. true labels it "Discount". |
from? | boolean | true | Figma price-from — the "from" line above the price. |
logos? | ReactNode[] | — | Logos for the logo row; implies smiles. |
moreLogos? | number | — | The +N bubble at the end of the logo row. Default 4 with placeholders. |
name? | ReactNode | Entertainment plans | Figma title-string. |
period? | ReactNode | /mo | Figma price-monthly. null hides it. |
price? | ReactNode | AED 1250 | Figma price-string. |
smiles? | boolean | true | Figma logo-row (md, 32px logos). Shows Smiles placeholders unless logos is given. |
variant? | 'default' | 'brand' | 'midnight' | — | Deprecated. Use colorScheme. |
width? | string | number | 222 |
Design
Figma source: plans-portable — etisalat Consumer App DSL V1.2
- 222 × 300 (
section/body/height/row-4) at a 16px radius (border-radius/6), padding 16 (spacing/lg), onsurface/base/inverseorsurface/base/midnight. - Header and footer gaps are 4 (
spacing/xs); the logo row has 8px above it (spacing/sm). - The name applies the
heading/mdtext style: 20px at 110%. The price istitle/md. - Inverse ink binds
text/default/inverseandtext/default/inverse-subtle. The +N bubble issurface/glass/white/mdon the inverse card andsurface/glass/midnight/lgon the default one.