East Blue
KeepReady
Voucher Details Component link

The expanded voucher card — merchant, offer, price and validity above a notched divider, with the description and terms below. Two ways of presenting the terms.

Keep — no longer a screen pretending to be a component
All four DS Health traits pass, and the previous Product Layer verdict is overturned. That verdict rested on this being "a 336×704 single-instance symbol with no variants" — a screen. What exists now is a 336-wide card with a real Layout setting, slots for the merchant logo and status badge, and text properties throughout. It composes the DS Accordion for the collapsible terms. The redesign into a component was deliberate.
In Context

Opens when a voucher is tapped from the grid or the list. Accordion keeps long terms collapsed so the price and validity stay above the fold; TextBlock shows shorter terms outright and links out to the full mechanics.

Live Preview
BrandAll branchesLimited
Voucher Title
PHP 200.00PHP 280.00
Validity: Mar 11 2023 - Mar 14 2023
For every 12 oz or larger beverage purchase, you’ll receive an Eco Tumbler Voucher for a FREE Tall Drink when you bring your personal cup with you on your next visit.
Terms & Conditions
  • Valid from March 11 to 14, 2021
  • Dine in, Take out, or Drive-thru: 11am until closing, or until supplies last
  • The promo is not valid in conjunction with other promos or discounts.
  • Metro Manila only.
Properties
Layout
hasLimitedOffer
hasCurrentPrice
hasOriginalPrice
hasValidityPeriod
hasLink
Merchant
Title
DS Health
Reusable
Pass
One card serves every voucher detail view. Merchant, badge, copy and terms all come from outside; nothing about a particular offer is baked in.
Self-contained
Pass
Carries its own surface, border, radius, the notched divider and both terms layouts. The divider is a real vector — a rectangle minus two circles, plus a hairline — not the raster it used to be.
Consistent
Pass
Reads Header → Details → strip → Body, and shares Price Container, #price, #originalPrice and #validity with Voucher.
Composable
Pass
Takes a logo through ⤷ LogoSlot and a status badge through ⤷ BadgeSlot, and the Accordion layout instances the DS Accordion rather than redrawing it.
Behavior
State iOS Android Figma Property Notes
Layout=Accordion N/A N/A 336 × 507 Terms collapse behind a DS Accordion instance. The only tappable part of the card.
Layout=TextBlock N/A N/A 336 × 472 Terms run as a paragraph with a link to the full mechanics. Drawn locally rather than instanced — see the note on that below.
⤷ LogoSlot N/A N/A 40 × 40 Merchant logo.
⤷ BadgeSlot N/A N/A Badge Offer status, top right of the header.
strip N/A N/A 336 × 16 Notched divider separating the offer summary from its detail.
Issues
  • It is a card now, not a screen. The previous version was one 336×704 symbol with no variants — the reason it was ruled Product Layer. What exists now is a card with a real setting, sized to sit inside a screen rather than to be one. The redesign was deliberate, so the Product Layer verdict no longer applies. C4 · Native Mappability
  • One setting replaced a set of visibility switches. Terms display used to be driven by booleans that toggled which child subtree rendered, so two paths could be on at once. Layout = Accordion | TextBlock makes them mutually exclusive. C2 · Variant & Property Naming
  • The notched divider is a vector. It was a raster image fill that could not resize or recolour. It is now a fill rectangle with a Union of two circles subtracted, plus a separator line. C6 · Asset & Icon Quality
  • The logo and badge are slots. logo-container and the frame that positioned the badge were ordinary layers. They are ⤷ LogoSlot and ⤷ BadgeSlot — real Figma slots, so a merchant supplies its own logo without detaching. C1 · Layer Structure & Naming
  • No layer name is used twice. Two frames were both called Content, and #title held both the merchant name and the offer name. They are now Details and Body, and the merchant line is #header with #subheader beneath — named for their place in the hierarchy rather than their content. C1 · Layer Structure & Naming
  • Price reads the same as it does on the card. #price and #originalPrice, one string each, matching Voucher — so the two components describe money the same way and the platform can format it for the locale. C2 · Variant & Property Naming
  • Names follow the system. termsAndCondition became Layout, its Text Block value became TextBlock, merchant-details became Merchant Details, and the boolean operation inside strip is shape rather than repeating its parent's name. C2 · Variant & Property Naming
Recommendations
  • Note that only the accordion is tappable. The card has no states of its own by design. The accordion header expands and collapses, and TextBlock's "See full promo mechanics." is a link; everything else is read-only. Say so in the Code tab so nobody wires a tap target around the whole card. Docs
  • Announce the card as a region, with the terms reachable. Group the merchant, offer, price and validity so a screen reader gives the offer in one pass, then expose the terms as their own region. In the Accordion layout the collapsed state must be announced, or the terms are invisible to anyone not looking at the screen. A11y
  • Give the discount a place, or confirm it belongs only on the card. Voucher carries a discount badge in its vertical layout; this card shows the original price struck through but no discount badge. That may be right — the saving is visible from the two prices — but the two components currently tell the story differently. Family
  • Document that the two layouts compose differently. v2.0.1: Applied — the property mapping now carries it. TextBlock draws its own terms frame and owns the three text layers in it, so a link is something this component renders. Accordion places a Terms and Conditions Accordion instance and inherits whatever that component draws, so hasLink has nothing to act on and the rows arrive as content rather than as strings. Two shapes behind one enum, and the choice between them follows the terms you have to show. Docs
Structure
TextBlock
BrandAll branchesLimited
Voucher Title
PHP 200.00PHP 280.00
Validity: Mar 11 2023 - Mar 14 2023
For every 12 oz or larger beverage purchase, you’ll receive an Eco Tumbler Voucher for a FREE Tall Drink when you bring your personal cup with you on your next visit.
Terms & Conditions
Valid from March 11 to 14, 2023. Dine in, Take out, or Drive-thru: 11am until closing, or until supplies last. The promo is not valid in conjunction with other promos or discounts. Metro Manila only.
See full promo mechanics.
Properties
hasLimitedOffer
hasCurrentPrice
hasOriginalPrice
hasValidityPeriod
hasLink
Properties
Layout TextBlock
hasLimitedOffer true
hasCurrentPrice true
hasOriginalPrice true
hasValidityPeriod true
hasLink true
⤷ LogoSlot (slot) 2 items
⤷ BadgeSlot (slot) 2 items
Colors
Card #FFFFFF
Borders and separator #E5EBF4
#header #0A2757
#subheader #6780A9
#title #0A2757
#price #005CE5
#originalPrice #90A8D0
#validity #6780A9
#description #445C85
Badge #2340A9
Badge label #FFFFFF
Terms #title #0A2757
Terms #message #445C85
Terms #footer #005CE5
Typography
#header Primary/Label/Base
#subheader Secondary/Bold/Caption
#title Primary/Headlines/Block
#price Primary/Label/Base
#originalPrice Primary/Label/Light/Base
#validity Primary/Label/Light/Small
#description Secondary/Bold/Base
Terms #title Secondary/Bold/Base
Terms #message Secondary/Bold/Base
Terms #footer Secondary/Bold/Base
Layout
Height 472 — Hug
Width 336
Radius 8
Padding H 0
Padding V 0
Gap 0
Alignment Top left
Colors by Layout

Everything above the notched strip is identical in the two layouts — the header, the details block and the strip itself match to the pixel. Only the terms presentation differs, and it differs in kind rather than in colour: TextBlock draws its own frame and owns those three text layers, while Accordion places a Terms and Conditions Accordion instance and inherits whatever that component paints. That is also why the link is TextBlock-only.

Role Token TextBlockAccordion
Card bg/color-bg-main #FFFFFF #FFFFFF
Borders and separator border/color-border-weak #E5EBF4 #E5EBF4
#header text/color-text #0A2757 #0A2757
#subheader text/color-text-weaker #6780A9 #6780A9
#title text/color-text #0A2757 #0A2757
#price text/color-text-primary #005CE5 #005CE5
#originalPrice text/color-text-weakest #90A8D0 #90A8D0
#validity text/color-text-weaker #6780A9 #6780A9
#description text/color-text-weak #445C85 #445C85
Badge bg/color-bg-info — set by the Badge instance in ⤷ BadgeSlot #2340A9 #2340A9
Badge label text/color-text-inverse #FFFFFF #FFFFFF
Terms #title text/color-text #0A2757 –
Terms #message text/color-text-weak #445C85 –
Terms #footer text/color-text-primary #005CE5 –
Terms surface bg/color-bg — set by the Accordion instance – #F6F9FD
Terms label text/color-text-weak — set by the same instance – #445C85
Accordion
BrandAll branchesLimited
Voucher Title
PHP 200.00PHP 280.00
Validity: Mar 11 2023 - Mar 14 2023
For every 12 oz or larger beverage purchase, you’ll receive an Eco Tumbler Voucher for a FREE Tall Drink when you bring your personal cup with you on your next visit.
Terms & Conditions
  • Valid from March 11 to 14, 2021
  • Dine in, Take out, or Drive-thru: 11am until closing, or until supplies last
  • The promo is not valid in conjunction with other promos or discounts.
  • Metro Manila only.
Properties
hasLimitedOffer
hasCurrentPrice
hasOriginalPrice
hasValidityPeriod
Properties
Layout Accordion
hasLimitedOffer true
hasCurrentPrice true
hasOriginalPrice true
hasValidityPeriod true
hasLink – TextBlock only
⤷ LogoSlot (slot) 2 items
⤷ BadgeSlot (slot) 2 items
Colors
Card #FFFFFF
Borders and separator #E5EBF4
#header #0A2757
#subheader #6780A9
#title #0A2757
#price #005CE5
#originalPrice #90A8D0
#validity #6780A9
#description #445C85
Badge #2340A9
Badge label #FFFFFF
Terms surface #F6F9FD
Terms label #445C85
Typography
#header Primary/Label/Base
#subheader Secondary/Bold/Caption
#title Primary/Headlines/Block
#price Primary/Label/Base
#originalPrice Primary/Label/Light/Base
#validity Primary/Label/Light/Small
#description Secondary/Bold/Base
Terms #label Secondary/Bold/Base
Layout
Height 507 — Hug
Width 336
Radius 8
Padding H 0
Padding V 0
Gap 0
Alignment Top left
Installation Planned API

iOS — Swift Package Manager

.package(url: "https://github.com/AY-Org/eb-ds-ios", from: "1.0.0")

Android — Gradle (Kotlin DSL)

implementation("com.eastblue.ds:voucher:1.0.0")

Import

import EastBlueDS
import com.eastblue.ds.voucher.*

Planned API — the native library does not exist yet. The artifact is the Voucher family: this component, <a href="/components/voucher">Voucher</a> and the other five members all ship in <code>com.eastblue.ds:voucher</code> and import <code>com.eastblue.ds.voucher.*</code>.

Property Mapping

One enum, five booleans and two slots. The booleans are not parameters: each asks whether an optional piece of content is present, so the optional itself is what a caller passes. The two layouts compose differently, and that is the one thing a mapping has to carry forward. TextBlock draws its own terms frame and owns the three text layers inside it, so a link is something this component renders. Accordion places a Terms and Conditions Accordion instance and inherits whatever that component draws — which is why hasLink has nothing to act on there, and why the terms rows arrive as content rather than as strings. Natively that is two shapes behind one enum, not one shape with a flag.

Figma PropertySwiftUICompose
Layout — TextBlock, Accordion layout: EBVoucherDetailsLayout layout: EBVoucherDetailsLayout
hasLimitedOffer — true, false derived — badge != nil derived — badge != null
hasCurrentPrice — true, false derived — price != nil derived — price != null
hasOriginalPrice — true, false derived — originalPrice != nil derived — originalPrice != null
hasValidityPeriod — true, false derived — validity != nil derived — validity != null
hasLink — true, false derived — linkLabel != nil — TextBlock only derived — linkLabel != null — TextBlock only
⤷ LogoSlot (slot) @ViewBuilder logo: () -> Logo logo: @Composable () -> Unit
⤷ BadgeSlot (slot) badge: (() -> Badge)? = nil badge: (@Composable () -> Unit)? = null
Usage Snippets Planned API
TextBlock — terms in full, with a link out
EBVoucherDetails(
    layout: .textblock,
    merchant: "Brand",
    branch: "All branches",
    title: "Voucher Title",
    price: "PHP 200.00",
    originalPrice: "PHP 280.00",
    validity: "Mar 11 2023 - Mar 14 2023",
    description: "For every 12 oz or larger beverage purchase…",
    badge: { EBBadge("Limited") },
    linkLabel: "See full promo mechanics."
) {
    EBBrandLogo(image: "brand-mark")
}
EBVoucherDetails(
    layout = EBVoucherDetailsLayout.Textblock,
    merchant = "Brand",
    branch = "All branches",
    title = "Voucher Title",
    price = "PHP 200.00",
    originalPrice = "PHP 280.00",
    validity = "Mar 11 2023 - Mar 14 2023",
    description = "For every 12 oz or larger beverage purchase…",
    badge = { EBBadge("Limited") },
    linkLabel = "See full promo mechanics."
) {
    EBBrandLogo(image = "brand-mark")
}
Accordion — terms collapsed behind a header
EBVoucherDetails(
    layout: .accordion,
    merchant: "Brand",
    branch: "All branches",
    title: "Voucher Title",
    price: "PHP 200.00",
    originalPrice: "PHP 280.00",
    validity: "Mar 11 2023 - Mar 14 2023",
    description: "For every 12 oz or larger beverage purchase…",
    badge: { EBBadge("Limited") }
) {
    EBBrandLogo(image: "brand-mark")
}
EBVoucherDetails(
    layout = EBVoucherDetailsLayout.Accordion,
    merchant = "Brand",
    branch = "All branches",
    title = "Voucher Title",
    price = "PHP 200.00",
    originalPrice = "PHP 280.00",
    validity = "Mar 11 2023 - Mar 14 2023",
    description = "For every 12 oz or larger beverage purchase…",
    badge = { EBBadge("Limited") }
) {
    EBBrandLogo(image = "brand-mark")
}
Accessibility
RequirementiOSAndroid
The offer summary reads as one unit Group merchant, title, price and validity with .accessibilityElement(children: .combine) Modifier.semantics(mergeDescendants = true) on the Details frame
Collapsed terms are announced as collapsed The Accordion supplies .isExpanded and its own toggle label Role.Button with stateDescription of expanded or collapsed
The mechanics link is a link .accessibilityAddTraits(.isLink) Role.Button with a descriptive onClickLabel
The notched divider is decorative .accessibilityHidden(true) contentDescription = null
Usage Guidelines

Do

Use Accordion when the terms run long enough to push the price off screen.

Don't

Don't collapse two lines of terms; the extra tap buys nothing.

Do

Use TextBlock with a link out when the full mechanics live elsewhere.

Don't

Don't put the entire mechanics text in the block — that is what the link is for.

Do

Let the merchant supply its own logo through the slot.

Don't

Don't detach the card to swap a logo.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Header → Details → strip → Body, no duplicated names, text layers all prefixed.
C2 Variant & Property Naming Ready One enum with two clean values, five booleans all on the has* prefix, and two slots. The one wrinkle is that hasLink exists on both layouts while only TextBlock has a #footer for it to act on — Accordion’s terms come from a nested instance.
C3 Token Coverage Ready Every colour resolves to a system token, and all ten are the ones the file’s own selection list carries: bg/color-bg-main, bg/color-bg, bg/color-bg-info, border/color-border-weak, text/color-text and its four weak steps, plus text/color-text-inverse on the badge label. All ten text styles resolve too.
C4 Native Mappability Ready A card with a header, a summary and a body. The two layouts compose differently on purpose — TextBlock draws its own terms frame and owns the text inside it, Accordion places a Terms and Conditions Accordion instance and inherits its content — and which one you reach for depends on the terms you have to show. Natively that is two shapes behind one enum: hasLink only reaches TextBlock, and the accordion’s rows arrive as content rather than as strings. Documented in the property mapping rather than smoothed over.
C5 Interaction State Coverage Not Applicable Display only by design. The accordion header and the mechanics link own their own states.
C6 Asset & Icon Quality Ready The notched divider is a vector; the logo arrives through a slot. No rasters.
Variants Inventory (2 total)

1 Layout setting × 2 values. Everything above the notched divider is identical between them.

LayoutSizeTermsNode
Accordion336 × 507DS Accordion instance, collapsible5450:32408
TextBlock336 × 472Locally drawn block with a mechanics link5450:32877
2.0.1 — September 2026Patch
Style and Code tabs rebuilt to the content guides — node 5542:32287
One card became two. A single card covered both versions. Layout is the only variant property and the only axis, so TextBlock and Accordion each get a card and the five booleans become controls.
Docs
The panel documented 1 of 8 properties. Five booleans and both slots were missing. The booleans are invisible to get_node_info, which returns variant properties only — they exist nowhere but the property panel. The Properties section and the demo panel now carry all eight.
Docs
Six of nine token names were invented. border/color-border-subtle, text/color-text-heading, -link, -placeholder and main/badge/info/bg appear nowhere in the file’s selection colours. Every hex was right; every name was made up. Four more colours had no row at all — the description, the badge label and the terms block’s three text layers. The ten names now on the page are exactly the ten the file uses.
C3 resolved
The preview drew in one face where the component uses two. .eb-preview-vdet declared font-family: inherit. Five of the seven layers this component always draws are Primary/*, so Proxima Soft is the root and the branch line, the description and the whole terms block name BarkAda for themselves.
Docs
Four layers resolving to one text style rendered four different ways. The description, the block message, the accordion’s row labels and the link are all Secondary/Bold/Base, and were set at 14/21 weight 500, 13/20, 13/18 and 13 bold. One rule covers all four now, which is what stops them drifting again.
Docs
Three things were missing a declaration rather than holding a wrong value. The badge label had no line-height and no tracking, so it read small and its pill sat short. The two prices had no line-height either, so each inherited the page’s and rendered 25.6 against a container that is 18 — the row was 7 too tall before anything else was wrong. And Merchant Details had no display, so its two spans laid out inline and the brand name sat beside the branch line instead of above it.
Docs
The badge hugged to the wrong width. An even 2px 8px gave the right 16 height by coincidence and made the pill 68 wide against Figma’s 52. It is 4 either side of a 44-wide label now, with 2 above and below, and reconstructs on both axes.
Docs
The accordion drew substitutes for two pieces of artwork. The chevron was an invented 16 × 16 path at stroke-width 1.6; it is the same 32 × 32 Chevron Up instance Select places, M23 20L16 13L9 20 at stroke-width 2. The row ticks were a ✓ text glyph in a pseudo-element, standing in for a 16 × 16 stroked path nobody had exported. The list gap was 12 where the item positions measure 6.
C6 resolved
Layout described a frame the component does not have. Body carried padding: 12px 16px 16px, which inset both terms treatments by 16 — they run the full 336, and only the Description insets its own text. The seven keys are the component’s own, and the heights reconstruct exactly: 64 + 95 + 16 + 297 = 472 and 64 + 95 + 16 + 332 = 507.
Docs
Property Mapping listed layers as if they were properties. Seven rows, four of them bundling text layers in pairs — #header / #subheader, #title / #description, #price / #originalPrice, #validity. Eight rows now, one per panel property, with the five booleans marked derived: each asks whether an optional is present, so the optional is what a caller passes.
Docs
The install block pointed at coordinates that will never exist. gcash/east-blue-ios and com.gcash.eastblue:components:1.0.0, with no Import line. It now cites the Voucher family artifact com.eastblue.ds:voucher:1.0.0, the same one Voucher uses.
Docs
The two layouts compose differently, and that is now written down. C4 had been asking for it. TextBlock draws its own terms frame and owns the three text layers in it, so a link is something this component renders. Accordion places a Terms and Conditions Accordion instance and inherits whatever it draws — which is why hasLink has nothing to act on there. Two shapes behind one enum, chosen by the terms you have to show. C4 moves to Ready and the recommendation to Applied.
C4 resolved
Usage Snippets were keyed to use-cases, and Code Connect is emptied. "Long terms, collapsed" and "Short terms, shown outright" become one per Layout value. The three Code Connect rows are gone; C7 stays blocked while no native library exists.
Docs
2.0.0 — August 2026Major
Rebuilt as a component — node 5542:32287
Product Layer verdict overturned. The old assessment ruled this a screen — one 336×704 symbol with no variants. The redesign into a 336-wide card with a real setting was deliberate.
Family
C2 — visibility booleans became one setting. Layout = Accordion | TextBlock makes the two terms paths mutually exclusive; previously both could render.
C2 resolved
C6 — the notched divider is a vector, replacing a raster image fill.
C6 resolved
C1 — logo and badge became real slots: ⤷ LogoSlot and ⤷ BadgeSlot.
C1 resolved
C1 — duplicate layer names cleared. Two frames named Content became Details and Body; the two #title layers became #header / #subheader and #title.
C1 resolved
C2 — price aligned with Voucher: #price and #originalPrice.
C2 resolved
The two layouts compose differently — Accordion instances the DS component, TextBlock draws its own block. Confirmed intentional.
Docs