East Blue
KeepReady
Voucher Component link

The voucher card, in the two shapes the app uses — a portrait tile for grids and a landscape row for lists — across the three states a voucher can be in.

Keep — four components collapsed into one
All four DS Health traits pass. Voucher Card Horizontal, Horizontal Voucher, Vertical Voucher and Voucher Asset are gone; all four are now settings on this one component. AssetSize, Orientation and State each carry one meaning, and the artwork, status badge and discount badge are real slots instead of baked artwork with frozen strings. The two orientations are laid out differently on purpose — they are not the same card rotated.
In Context

Vertical fills the voucher grid on Discover and in campaign carousels, where cards sit side by side. Horizontal fills the list on My Vouchers, where each row needs room for a description and its own claim action.

Live Preview
Buy Load Pre-seeded SKU Voucher Sample
PHP 100.00PHP 150.00
This is the description of the voucher.
Validity: Dec 25 2022 - Jan 5 2023
Placeholder imageGET VOUCHERLimited
Properties
Orientation
AssetSize
State
hasValidityDate
hasDescription
hasBadge
hasDiscountBadge
hasOriginalPrice
Title
Description
Validity Date
Discounted Price
Original Price
DS Health
Reusable
Pass
One component covers the grid tile and the list row. Artwork, status badge and discount badge all arrive through slots, so a campaign supplies its own without touching the component.
Self-contained
Pass
Carries its own surface, ticket notch, type styles and all three states. The notch is a real vector — a rectangle with two circles subtracted — rather than the raster mask the old cards used.
Consistent
Pass
Both orientations use the same vocabulary: Content → Details, Price Container, and ⤷ AssetSlot / ⤷ BadgeSlot for the swappable parts. Text layers are #title, #description, #price, #originalPrice and #validity in both.
Composable
Pass
Takes a Badge instance in both badge slots and any artwork in the asset slot. Sits in grids, lists and carousels without a wrapper.
Behavior
State iOS Android Figma Property Notes
Orientation=Vertical N/A N/A 162 wide Artwork on top, then badge, title, description, price, validity. Carries the discount badge.
Orientation=Horizontal N/A N/A 312 / 344 wide Text on the left, artwork on the right with a fixed claim rail. No discount badge — there is no room for it.
AssetSize=Small N/A N/A 162 × 278 · 312 × 124 The default for both orientations.
AssetSize=Large N/A N/A 162 × 341 · 344 × 124 Taller artwork on vertical; a wider card on horizontal.
State=Default N/A N/A claimable Full colour. The badge reads the offer status, e.g. "Limited".
State=Used N/A N/A dimmed A #020E22 overlay at 56% darkens the artwork; the card stays legible.
State=Expired N/A N/A washed out A flat #C2CFE5 overlay replaces the artwork and the text recedes.
Issues
  • Both dimmed states are built from tokens now. A grey with no variable behind it was showing up in the file’s colour list. It was the badges and the claim rail in the two dimmed states — not the placeholder artwork they were first pinned on, which is a bound bg/color-bg rectangle under a raster image. All of them now stack bg/color-bg-main under an overlay, with only the overlay weight separating the states: 40% for Used, 24% for Expired. Used lands back on #9A9FA7, the colour that was already shipping, so this is a re-binding rather than a recolour. Every dimmed surface is now built the way the artwork filters are. C3 · Token Coverage
  • Both orientations round to the same 4 now. Vertical rounded at 4 and horizontal at 6, and they set it at different levels — vertical on the component itself, horizontal one frame in on Content. The values match. The structures do not, and will not: moving the radius out to the component makes horizontal stop rendering it even with clipping on. Documented as a known difference rather than left looking like an oversight. C1 · Layer Structure & Naming
  • The price layers agree on their names now. The discounted price was called #price in Default but #value in Used and Expired, so the same layer answered to two names depending on the state. Large vertical also called its wrapper price where every other version says Price Container. Both renamed. C1 · Layer Structure & Naming
  • The validity date used two different type styles. Vertical set it in Secondary/Default/Fine and horizontal in Secondary/Bold/Fine — one line of text, one job, two weights depending on which way the card faced. Both are Bold/Fine now. C2 · Variant & Property Naming
  • Four components became one. Voucher Card Horizontal, Horizontal Voucher, Vertical Voucher and Voucher Asset all described the same card. They are now AssetSize, Orientation and State on a single 12-version component, which is what stopped four near-identical cards from drifting apart. C2 · Variant & Property Naming
  • The card has states at all. Two of the old components had no state axis, so a used or expired voucher had to be drawn by hand. State = Default | Used | Expired covers it in every orientation and size, and there is a neutral default for the first time. C5 · Interaction State Coverage
  • Content is no longer frozen. Every string used to be hardcoded placeholder text, and the badges were fixed to whatever state the version represented. Title, description, price, original price and validity are all text layers now, and the badges are slots that take any Badge instance. C1 · Layer Structure & Naming
  • The discount is a slot, not baked artwork. "35% off" used to be burned into the image frame, so changing it meant editing artwork. It is now ⤷ DiscountSlot holding a Badge. C6 · Asset & Icon Quality
  • Artwork arrives through a real slot. The asset was a raster photograph with a partner wordmark burned in, and the frame that held it was an ordinary layer named to look like a slot. ⤷ AssetSlot is now a genuine Figma slot, so artwork swaps without detaching. C6 · Asset & Icon Quality
  • The ticket notch is a vector. It was a raster mask that could not resize or recolour. It is now Stub Cutout — a rectangle with two circles subtracted — so it scales with the card and takes the state colours. C6 · Asset & Icon Quality
  • The claim action is a real Button. "GET VOUCHER" was rotated text pretending to be a control. It is a Button - XSmall instance in the horizontal card's action rail. C4 · Native Mappability
  • The settings can map one to one. The old cards exposed six to eight independent booleans plus a four-value enum, a shape no native component matches. Three orthogonal enums, complete at twelve of twelve, map directly onto native parameters. C4 · Native Mappability
  • Names follow the system. Asset Size became AssetSize; DIscount Slot lost its stray capital and became ⤷ DiscountSlot; blurb became #validity and title became #title; Subtract became Stub Cutout; and the lowercase content / container / price frames are now Content, Container and Price Container. C1 · Layer Structure & Naming
  • Price is one string per value. The current price was split into #unit and #value while the original price was already a single string — and both were called #value, so nothing downstream could tell them apart. They are now #price and #originalPrice, which also lets the platform format currency for the locale rather than assuming symbol-then-number. C2 · Variant & Property Naming
Recommendations
  • Check the dimmed badge label against its background. The badge keeps a white label on both dimmed surfaces: 2.79:1 on Used #9A9FA7 and 1.73:1 on Expired #C2C5CA, against the 4.5:1 that 12px bold normally needs. WCAG exempts text inside an inactive component, and a Used or Expired voucher is exactly that, so this is not a defect. It is worth a look anyway, because that badge is the thing telling someone the voucher is dead — the one label on a dead card that still has a job. Switching it to text/color-text would read 5.22:1 and 8.42:1 without touching the surfaces. A11y
  • Rename the Filter overlay's shape to match the asset's. The Stub Cutout rename reached the asset panel but not the Filter frames used by Used and Expired, where the same shape is still called Subtract — the Figma operation rather than what it draws. Small, and the last of that rename to land. Rename
  • Let the platform format the price. Pass an amount and a currency code, not a preformatted string. NumberFormatter with .currency on iOS and NumberFormat.getCurrencyInstance(locale) on Android both place the symbol, separator and grouping correctly for the user's locale. Docs
  • Announce the card as one element with its state. Title, price, original price, validity and badge read separately is five fragments. Group them, include the saving rather than making the user compare two numbers, and put the state in the label so "Used" and "Expired" are not carried by dimming alone. A11y
  • Document what Used and Expired mean for interaction. Both dim the card, but a used voucher may still open its details while an expired one may not. The design does not say which, and the difference matters for whether the card stays tappable. Docs
  • Retire the four superseded components in Figma. Once usages are migrated, delete Voucher Card Horizontal, Horizontal Voucher, Vertical Voucher and the standalone Voucher Asset. Their assessments are kept on this site as the record of what drove the consolidation. Family
Structure
Horizontal
Buy Load Pre-seeded SKU Voucher Sample
PHP 100.00PHP 150.00
This is the description of the voucher.
Validity: Dec 25 2022 - Jan 5 2023
Placeholder imageGET VOUCHERLimited
Properties
AssetSize
State
hasValidityDate
hasDescription
hasBadge
hasOriginalPrice
Properties
Orientation Horizontal
AssetSize Small
State Default
hasValidityDate true
hasDescription true
hasBadge true
hasDiscountBadge – vertical only
hasOriginalPrice true
Title (text) Buy Load Pre-seeded SKU Voucher Sample
Description (text) This is the description of the voucher.
Validity Date (text) Validity: Dec 25 2022 - Jan 5 2023
Discounted Price (text) PHP 100.00
Original Price (text) PHP 150.00
⤷ AssetSlot (slot) 12 items
⤷ BadgeSlot (slot) 12 items
⤷ DiscountSlot (slot) – not present in Horizontal
Colors
Card #FFFFFF
Artwork panel #F6F9FD
#title #0A2757
#description #445C85
#validity #445C85
#price #005CE5
#originalPrice #C2CFE5
State filter –
Badge #005CE5
Claim rail #0A2757
Typography
#title Primary/Multi-line Label/Base
#price Primary/Label/Small
#description Secondary/Default/Small Caption
#originalPrice Secondary/Default/Caption
#validity Secondary/Bold/Fine
Layout
Height 124 — Hug
Width 312
Radius 4 — set on Content, left corners only
Padding H 0
Padding V 0
Gap 0
Alignment Top left
Colors by State

Every colour Voucher paints. State walks every text layer one step down the weak ramp, and twice over for Expired — the title runs text → weak → weaker, everything else weak → weaker → weakest. #originalPrice is the one exception: it starts at disabled and has nowhere left to go. The badges and the claim rail are nested instances, so they are named rather than valued. The two badges and the claim rail dim through the same surface-plus-overlay the artwork filters use, on one base with only the overlay weight moving: bg/color-bg-main under bg/color-bg-overlay at 40% for Used (#9A9FA7) and under bg/color-bg-overlay-weak at 24% for Expired (#C2C5CA). Every colour on the card is bound.

Role Token DefaultUsedExpired
Card bg/color-bg-main #FFFFFF #FFFFFF #FFFFFF
Artwork panel bg/color-bg #F6F9FD #F6F9FD #F6F9FD
Artwork filter bg/color-bg-overlay-strong – #020E22 @ 56% –
Artwork filter bg/color-bg-disabled – – #C2CFE5
#title text/color-text · -weak · -weaker #0A2757 #445C85 #6780A9
#description text/color-text-weak · -weaker · -weakest #445C85 #6780A9 #90A8D0
#validity text/color-text-weak · -weaker · -weakest #445C85 #6780A9 #90A8D0
#price text/color-text-primary · -weaker · -weakest #005CE5 #6780A9 #90A8D0
#originalPrice text/color-text-disabled #C2CFE5 #C2CFE5 #C2CFE5
Badge set by the Badge instance in ⤷ BadgeSlot #005CE5 – –
Badge · Used bg/color-bg-main under bg/color-bg-overlay 40% – #9A9FA7 –
Badge · Expired bg/color-bg-main under bg/color-bg-overlay-weak 24% – – #C2C5CA
Discount badge set by the Badge instance in ⤷ DiscountSlot — Vertical only #0A2757 – –
Discount badge · Used bg/color-bg-main under bg/color-bg-overlay 40% – #9A9FA7 –
Discount badge · Expired bg/color-bg-main under bg/color-bg-overlay-weak 24% – – #C2C5CA
Claim rail set by the instance in ⤷ AssetSlot — Horizontal only #0A2757 – –
Claim rail · Used bg/color-bg-main under bg/color-bg-overlay 40% – #9A9FA7 –
Claim rail · Expired bg/color-bg-main under bg/color-bg-overlay-weak 24% – – #C2C5CA
Vertical
Placeholder image35% off
Limited
Buy Load Pre-seeded SKU Voucher Sample
This is the description of the voucher.
PHP 100.00PHP 150.00
Validity: Dec 25 2022 - Jan 5 2023
Properties
AssetSize
State
hasValidityDate
hasDescription
hasBadge
hasDiscountBadge
hasOriginalPrice
Properties
Orientation Vertical
AssetSize Small
State Default
hasValidityDate true
hasDescription true
hasBadge true
hasDiscountBadge true
hasOriginalPrice true
Title (text) Buy Load Pre-seeded SKU Voucher Sample
Description (text) This is the description of the voucher.
Validity Date (text) Validity: Dec 25 2022 - Jan 5 2023
Discounted Price (text) PHP 100.00
Original Price (text) PHP 150.00
⤷ AssetSlot (slot) 12 items
⤷ BadgeSlot (slot) 12 items
⤷ DiscountSlot (slot) 6 items
Colors
Card #FFFFFF
Artwork panel #F6F9FD
#title #0A2757
#description #445C85
#validity #445C85
#price #005CE5
#originalPrice #C2CFE5
State filter –
Badge #005CE5
Discount badge #0A2757
Typography
#title Primary/Multi-line Label/Base
#price Primary/Label/Small
#description Secondary/Default/Small Caption
#originalPrice Secondary/Default/Caption
#validity Secondary/Bold/Fine
Layout
Height 278
Width 162
Radius 4
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: all seven members ship in <code>com.eastblue.ds:voucher</code> and import <code>com.eastblue.ds.voucher.*</code>.

Property Mapping

Sixteen properties: three enums, five booleans, five text properties and three slots. The five booleans are not parameters. Each one asks whether an optional piece of content is present, so natively the optional itself is the parameter — pass a validityDate and it shows, leave it out and it does not. Adding a separate hasValidityDate alongside validityDate would let a caller ask for a date that is never drawn. hasDiscountBadge carries one further constraint: Horizontal has no ⤷ DiscountSlot at all, so the parameter has nothing to act on there.

Figma PropertySwiftUICompose
Orientation — Horizontal, Vertical orientation: EBVoucherOrientation orientation: EBVoucherOrientation
AssetSize — Small, Large assetSize: EBVoucherAssetSize = .small assetSize: EBVoucherAssetSize = Small
State — Default, Used, Expired state: EBVoucherState = .default state: EBVoucherState = Default
hasValidityDate — true, false derived — validityDate != nil derived — validityDate != null
hasDescription — true, false derived — description != nil derived — description != null
hasBadge — true, false derived — badge != nil derived — badge != null
hasDiscountBadge — true, false derived — discountBadge != nil — Vertical only derived — discountBadge != null — Vertical only
hasOriginalPrice — true, false derived — originalPrice != nil derived — originalPrice != null
Title (text) title: String title: String
Description (text) description: String? description: String? = null
Validity Date (text) validityDate: String? validityDate: String? = null
Discounted Price (text) price: String price: String
Original Price (text) originalPrice: String? originalPrice: String? = null
⤷ AssetSlot (slot) @ViewBuilder asset: () -> Asset asset: @Composable () -> Unit
⤷ BadgeSlot (slot) badge: (() -> Badge)? = nil badge: (@Composable () -> Unit)? = null
⤷ DiscountSlot (slot) discountBadge: (() -> Badge)? = nil discountBadge: (@Composable () -> Unit)? = null
Usage Snippets Planned API
Horizontal — a list row with its claim rail
EBVoucher(
    title: "Buy Load Pre-seeded SKU Voucher Sample",
    price: "PHP 100.00",
    originalPrice: "PHP 150.00",
    description: "This is the description of the voucher.",
    validityDate: "Validity: Dec 25 2022 - Jan 5 2023",
    orientation: .horizontal,
    assetSize: .small
) {
    EBVoucherAsset(image: "voucher-hero")
}
EBVoucher(
    title = "Buy Load Pre-seeded SKU Voucher Sample",
    price = "PHP 100.00",
    originalPrice = "PHP 150.00",
    description = "This is the description of the voucher.",
    validityDate = "Validity: Dec 25 2022 - Jan 5 2023",
    orientation = EBVoucherOrientation.Horizontal,
    assetSize = EBVoucherAssetSize.Small
) {
    EBVoucherAsset(image = "voucher-hero")
}
Vertical — a grid card with a discount badge
EBVoucher(
    title: "Buy Load Pre-seeded SKU Voucher Sample",
    price: "PHP 100.00",
    originalPrice: "PHP 150.00",
    description: "This is the description of the voucher.",
    validityDate: "Validity: Dec 25 2022 - Jan 5 2023",
    orientation: .vertical,
    assetSize: .large,
    discountBadge: { EBBadge("35% off") }
) {
    EBVoucherAsset(image: "voucher-hero")
}
EBVoucher(
    title = "Buy Load Pre-seeded SKU Voucher Sample",
    price = "PHP 100.00",
    originalPrice = "PHP 150.00",
    description = "This is the description of the voucher.",
    validityDate = "Validity: Dec 25 2022 - Jan 5 2023",
    orientation = EBVoucherOrientation.Vertical,
    assetSize = EBVoucherAssetSize.Large,
    discountBadge = { EBBadge("35% off") }
) {
    EBVoucherAsset(image = "voucher-hero")
}
Vertical — a voucher that has been used
EBVoucher(
    title: "Buy Load Pre-seeded SKU Voucher Sample",
    price: "PHP 100.00",
    originalPrice: "PHP 150.00",
    description: "This is the description of the voucher.",
    validityDate: "Validity: Dec 25 2022 - Jan 5 2023",
    orientation: .vertical,
    assetSize: .small,
    state: .used
) {
    EBVoucherAsset(image: "voucher-hero")
}
EBVoucher(
    title = "Buy Load Pre-seeded SKU Voucher Sample",
    price = "PHP 100.00",
    originalPrice = "PHP 150.00",
    description = "This is the description of the voucher.",
    validityDate = "Validity: Dec 25 2022 - Jan 5 2023",
    orientation = EBVoucherOrientation.Vertical,
    assetSize = EBVoucherAssetSize.Small,
    state = EBVoucherState.Used
) {
    EBVoucherAsset(image = "voucher-hero")
}
Accessibility
RequirementiOSAndroid
The card is one element .accessibilityElement(children: .combine) Modifier.semantics(mergeDescendants = true)
State is spoken, not only dimmed Append "Used" or "Expired" to the label stateDescription carries it
The saving is stated "PHP 100, reduced from PHP 150" rather than two bare numbers Same, built into contentDescription
Artwork is decorative .accessibilityHidden(true) on the image contentDescription = null
Usage Guidelines

Do

Use Vertical in grids and carousels, Horizontal in lists where each row needs a description.

Don't

Don't put Horizontal in a two-column grid; at 312 wide it will not fit.

Do

Let State follow the voucher's own status.

Don't

Don't set State by hand — an expired voucher showing as claimable is worse than no card.

Do

Supply artwork through the asset slot.

Don't

Don't burn the discount or the partner logo into the image; both have their own slots.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Both orientations use one vocabulary. Text layers all carry the # prefix and no name is used twice.
C2 Variant & Property Naming Ready Three enums with clean values, five booleans all on the has* prefix, and five text properties named for what they hold. The one wrinkle is that hasDiscountBadge exists on both orientations while only Vertical has a ⤷ DiscountSlot for it to act on.
C3 Token Coverage Ready Every surface, every text layer, both artwork filters and every dimmed badge and rail are bound to system tokens — the bg/color-bg-overlay-strong and bg/color-bg-overlay* the file uses, not the main/voucher/* namespace this note once proposed. The dimmed states were the last gap and closed during the review.
C4 Native Mappability Ready Three enums plus slots map directly. The claim action is a real Button instance rather than rotated text.
C5 Interaction State Coverage Ready Default, Used and Expired across every orientation and size. Pressed belongs to the host list or the claim button.
C6 Asset & Icon Quality Ready The notch is a vector subtract, both state filters are blended groups rather than flattened art, and the Used badges and claim rail dim through a surface plus an overlay, while Expired still swaps in a flat grey. Artwork and both badges are slots. The one raster is the placeholder inside ⤷ AssetSlot’s default content — an image a real voucher replaces, sitting on a bound bg/color-bg rectangle.
Variants Inventory (12 total)

2 Orientation × 2 AssetSize × 3 State = 12 versions, complete with no gaps.

OrientationAssetSizeCard sizeCount
VerticalSmall162 × 2783
VerticalLarge162 × 3413
HorizontalSmall312 × 1243
HorizontalLarge344 × 1243
View full Orientation × AssetSize × State breakdown (12 rows)
OrientationAssetSizeStateCard sizeNode
VerticalSmallDefault162 × 2785372:38310
VerticalSmallUsed162 × 2785372:38560
VerticalSmallExpired162 × 2785372:38536
VerticalLargeDefault162 × 3415372:38326
VerticalLargeUsed162 × 3415372:38342
VerticalLargeExpired162 × 3415372:38366
HorizontalSmallDefault312 × 1245372:38390
HorizontalSmallUsed312 × 1245372:38409
HorizontalSmallExpired312 × 1245372:38436
HorizontalLargeDefault344 × 1245372:38463
HorizontalLargeUsed344 × 1245372:38482
HorizontalLargeExpired344 × 1245372:38509
2.1.0 — September 2026Minor
Style, Code and Overview passes on the 2026 Working File · node 5372:38309
Both dimmed states are built from tokens now. A grey with no variable behind it was showing in the file’s colour list. It was the badges and the claim rail in Used and Expired — not the placeholder artwork, which is a bound bg/color-bg rectangle under a raster image. All of them now stack bg/color-bg-main under an overlay, with only the weight separating the states: 40% for Used, 24% for Expired. Used lands back on #9A9FA7, the colour that was already shipping, so this is a re-binding rather than a recolour.
C3 resolved
The validity date used two different type styles. Vertical set it in Secondary/Default/Fine and horizontal in Secondary/Bold/Fine — one line of text, one job, two weights depending on which way the card faced. Both are Bold/Fine now.
C2 resolved
The price layers agree on their names. The discounted price was #price in Default but #value in Used and Expired, so one layer answered to two names depending on state. Large vertical also called its wrapper price where every other version says Price Container. Both renamed.
C1 resolved
The two orientations round to the same 4. Vertical rounded at 4 and horizontal at 6, set at different levels — vertical on the component, horizontal one frame in on Content. The values match now. The structures do not and will not: moving the radius out to the component stops horizontal rendering it even with clipping on, so it is recorded as a known difference rather than left looking like an oversight.
C1 resolved
The property panel was reordered so Orientation comes first. That settles which property drives the Style tab. §3.1 names the first panel property, and the same section forbids making cards per size — which is what AssetSize is. Orientation was the right axis on the evidence too: the two are not one card rotated but two compositions, ordering their content differently and carrying different slots.
C2 resolved
One card became two. A single card covered all 12 versions. Orientation now gets a card each, with AssetSize, State and the five booleans as controls.
Docs
The panel documented 3 of 16 properties. Five booleans, five text properties and one slot were missing — every one of them invisible to get_node_info, which returns variant properties only. They came from the property panel itself. The Properties section and the demo panel now carry all 16.
Docs
Six token names were invented. text/color-text-heading, -body, -link, bg/color-bg-subtle and two main/voucher/* overlay names appear nowhere in the file’s selection colours. Every hex was right; every name was made up. They survived because the page had no colours table — spec cards do not render token, so the names were never drawn anywhere a reader could check them.
C3 resolved
Colour was documented as fixed when it moves with State. Every text layer steps one down the weak ramp per state, twice over for Expired — the title runs text → weak → weaker, everything else weak → weaker → weakest. The page had a single invented #9BB0D0 standing in for the whole Expired column. A Colors by State table now carries all of it.
C3 resolved
Layout described the wrong frame. The seven keys were reporting the Content box — a derived Padding V of 0 that is 8, and a gap of 9.5 that is not a gap at all but Auto distributing leftover space. The component itself is 0 padding and 0 gap: the artwork butts straight against the content. All seven keys are read from the Auto layout panels now, none derived.
Docs
The preview drew in one face where the component uses two. .eb-preview-vch declared font-family: inherit. Three of the five text layers are Secondary/*, so BarkAda is the root and #title and #price name Proxima Soft themselves. inherit happened to give the majority the right face and the other two the wrong one, which is why nobody caught it by eye.
Docs
Five things the preview drew that the component does not. The claim rail was an overlay rather than a sibling inside the artwork panel, which put the badge and the stub notches on the wrong edges and centred the placeholder underneath the rail. The vertical notch sat at the panel’s bottom edge instead of 32 above it. The prices ran side by side in both orientations when vertical stacks them at zero gap. The state filters were flat fills rather than the blended groups they are — which is why Expired erased the artwork it is meant to wash. And the perforation between the notches was missing entirely.
Docs
The preview drew copy no variant contains. The filled price read "Savings account". Each Type now draws what the field draws, and the DEV snippets are generated from the same source, so the two cannot drift.
Docs
Property Mapping listed layers as if they were properties. Nine rows, three of them bundling text layers in pairs — #title / #description, #price / #originalPrice. Sixteen rows now, one per panel property. The five booleans are marked derived rather than given parameters: each asks whether an optional is present, so the optional is the parameter and a separate hasValidityDate would let a caller ask for a date that never draws.
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 and imports com.eastblue.ds.voucher.*, shared by all seven members.
Docs
The two tabs disagreed on whether State is a parameter. The Style tab’s snippet said it was not and never emitted it; the new mapping made it one. State is a parameter here, unlike the interaction states on other components: Used and Expired are facts about the voucher that the caller holds and the card cannot work out for itself. Neither page failed on its own — the contradiction only existed between them.
C4 resolved
The Variants Inventory promised a breakdown it did not have. The collapsible was labelled 12 rows and held 4, each folding the three states into one cell, with no node IDs. All 12 are there now, each with its node.
Docs
Usage Snippets were keyed to use-cases, and Code Connect is emptied. "A grid of vouchers", "A list row with its claim action", "A used voucher" become one per Orientation plus Used to show the state parameter. The three Code Connect rows are gone; C7 stays blocked while no native library exists.
Docs
The dimmed badge keeps a white label. 2.79:1 on Used and 1.73:1 on Expired, against the 4.5:1 that 12px bold normally needs. WCAG exempts text inside an inactive component and a spent voucher is exactly that, so this is not a defect — but that badge is the one label on a dead card that still has a job, so it is logged as a recommendation rather than waved through. text/color-text would read 5.22:1 and 8.42:1 without touching the surfaces.
A11y
2.0.0 — August 2026Major
Consolidated rebuild — node 5372:38309
Four components became one. Voucher Card Horizontal, Horizontal Voucher, Vertical Voucher and Voucher Asset fold into AssetSize × Orientation × State.
Family
C5 — a state axis exists. Two of the old components had none; Used and Expired had to be drawn by hand.
C5 resolved
C1 — content unfrozen. Every string was hardcoded placeholder; all are now text layers, and both badges are slots.
C1 resolved
C6 — rasters replaced. The ticket notch is a vector subtract, the artwork arrives through a real slot, and the "35% off" burned into the image is now a Badge in ⤷ DiscountSlot.
C6 resolved
C4 — "GET VOUCHER" is a Button. It was rotated text with no control behind it.
C4 resolved
C2 — naming aligned: AssetSize, ⤷ AssetSlot, ⤷ BadgeSlot, ⤷ DiscountSlot, Stub Cutout, #title, #validity, and Content / Container / Price Container in PascalCase.
C2 resolved
C2 — price split resolved. #unit + #value and a second #value became #price and #originalPrice, clearing a duplicate-name collision and letting the platform format currency.
C2 resolved
The Filter overlay's shape is still called Subtract — the last of the Stub Cutout rename to land.
Rename