figma guide

Designing coupons and promo codes UI in Figma: discounts, validation, and handoff

Design promo code inputs, applied discounts, coupon cards, and cart totals in Figma with validation states, stacking rules, and Dev Mode specs for ecommerce checkout.

Published
Updated
Jun 20, 2026
Read time
8 min
Level
Beginner

Quick answer

Promo code UI spans an entry field (cart or checkout), validation feedback, an applied-coupon row in OrderSummary, and optional coupon cards on marketing surfaces—each state (empty, typing, loading, success, error, expired) must be explicit. Build PromoCodeField with input + Apply button, AppliedCoupon with code label and remove action, and DiscountRow in the totals block using price display tokens. Document stacking rules (one code vs multiple), auto-apply from URL, and free-shipping vs percentage vs fixed-amount discounts. Pair with cart/checkout, forms, and inline alerts. Start from the Figma guides hub.


Who this is for

  • Product designers shipping DTC stores, marketplaces, and subscription checkout with promotional pricing.
  • Design system teams aligning discount display across cart, checkout, PDP, and account order history.
  • Engineers implementing promo validation APIs, URL deep links (?promo=SAVE20), and tax recalculation after discounts.

Promo surfaces

SurfaceGoalKey components
Cart promo fieldApply before checkoutPromoCodeField, AppliedCoupon
Checkout promo fieldLast-chance applySame components—do not redesign
OrderSummary discount rowsShow savingsDiscountRow, strikethrough subtotal
PDP / PLP sale badgeSurface active promosBadge + optional code reveal
Coupon wallet (account)Saved offersCouponCard list
Marketing landingCopy code CTACouponCard hero variant

Verdict: one PromoCodeField + AppliedCoupon pair reused in cart and checkout—different copy is fine; different layouts create support tickets when totals look different between steps.


PromoCodeField anatomy

PartPurposeSpec tip
Label”Promo code” or “Discount code”Optional on compact cart—use placeholder
Text inputCode entryUppercase transform optional; trim whitespace
Apply buttonSubmit codeDisabled when empty; loading state during API
Helper textFormat hint”Enter code without spaces”
Error messageValidation failureBelow field, text/error token
Success messageApplied confirmationBrief—row in summary is primary feedback
PromoCodeField
├── Variant: layout=inline | stacked
├── Variant: state=empty | focus | loading | error | success | disabled
├── Property: code (string)
└── Layers:
    ├── Label (optional)
    ├── InputRow (input + Apply)
    ├── HelperText
    └── ErrorMessage / SuccessMessage

Use form input states for focus rings and error borders. On mobile, prefer stacked layout (full-width input, full-width Apply below) to avoid cramped inline fields.


Validation states

StateTriggerVisualCopy examples
EmptyDefaultNeutral borderPlaceholder: “Enter code”
FocusUser taps fieldFocus ring
LoadingApply clickedSpinner on button; input disabled
InvalidAPI 400Red border + message”This code isn’t valid”
ExpiredDate passedRed border”This offer expired on Jun 1”
Not eligibleCart rules failAmber alert”Add $25 more to use SAVE20”
Already appliedDuplicate submitInfo message”SAVE20 is already applied”
SuccessValid codeGreen check optional; show AppliedCoupon”20% off applied”

Prototype Apply → loading → success/error with interactive components swapping state property. Production should debounce double-clicks and disable Apply during loading.


AppliedCoupon component

PartPurposeSpec tip
Code labelWhat was appliedMonospace optional: SAVE20
Discount descriptionHuman-readable”20% off entire order”
Savings amountOptional inline”−$12.40” in price sm
Remove actionUnapply code”Remove” link or × icon
IconTag or ticket16–20px, icon/secondary
AppliedCoupon
├── Variant: density=compact | comfortable
├── Property: code (string)
├── Property: discountType (percent | fixed | shipping)
└── Layers:
    ├── Icon
    ├── CodeLabel + Description
    ├── SavingsAmount (optional)
    └── RemoveButton

Place above discount rows in OrderSummary or directly below the promo field—pick one pattern and document it. Removing a coupon should restore previous totals; show loading skeleton on totals during recalculation.


OrderSummary discount rows

RowWhen shownDisplay
SubtotalAlwaysPre-discount item total
Item-level discountsPer-line promos”Item discount” −$X
Order-level discountCode applied”Promo SAVE20” −$X
Free shippingShipping promo”Free shipping” −$X or strikethrough shipping
Total savings (optional)Marketing emphasis”You save $24.00” in text/success
TaxAfter discountsNote: tax base may change
TotalFinalBold price lg
Discount typeSummary labelPDP/PLP signal
Percentage”20% off”Sale badge on cards
Fixed amount”$10 off orders $50+“Threshold copy in alert
Free shipping”Free shipping”Truck icon + badge
BOGO”Buy 1 get 1”Line-item split in cart

Show strikethrough on subtotal only when a visible discount applies—avoid fake “was” pricing without a real reference price (regulatory risk in some markets).


CouponCard (wallet and marketing)

PartPurposeSpec tip
HeadlineOffer value”20% off sitewide”
CodeCopy targetSAVE20 + copy button
ExpiryUrgency”Expires Jun 30”
EligibilityRules”Orders $50+ · Excludes sale items”
CTAApply or shop”Apply” auto-fills cart field
StateActive / used / expiredGray out expired; checkmark on used
CouponCard
├── Variant: context=wallet | marketing | inline
├── Variant: state=active | applied | expired | used
└── Layers:
    ├── ValueHeadline
    ├── CodeRow (code + CopyButton)
    ├── MetaRow (expiry + rules)
    └── CTA

Copy button triggers toast “Code copied.” Deep link ?promo=SAVE20 should auto-apply on cart load—note in Dev Mode spec, not only in marketing copy.


Stacking and business rules

Document these in a spec table—design and engineering must match:

RuleUI impact
One code per orderHide promo field after apply; Remove before new code
Stackable codesMultiple AppliedCoupon rows; max count message
Auto-apply best dealNo field needed—show banner “Best offer applied”
Employee / loyalty codesSeparate field or account-only wallet
Subscription first-orderDifferent copy on checkout step 1
Incompatible with gift cardError: “Can’t combine with gift card balance”
ScenarioRecommended UX
Code works but $0 discount”Code applied—no additional savings on this order”
Better code availableSuggest swap: “Use SAVE25 instead? [Switch]“
Price changed after applyAlert + recalculate totals

Responsive behavior

BreakpointPromo fieldApplied coupon
MobileStacked input + buttonFull width below items
TabletInline or stackedSame as mobile in drawer cart
DesktopInline in cart sidebarAbove OrderSummary rows

In cart drawer, collapse promo behind “Have a promo code?” disclosure to reduce noise—expand on tap with focus on input.


Handoff checklist

ItemDev Mode annotation
Validation API response codesMap to error copy table
Stacking policyMax codes, exclusion list
URL auto-apply param?promo= behavior
Tax recalculationOrder of discount → tax
Remove coupon endpointOptimistic UI + rollback
Copy-to-clipboardCouponCard + field paste
Analyticsapply_promo, remove_promo, promo_error
Order historyShow applied code on order detail

Use Dev Mode checklist and share DiscountRow props with checkout OrderSummary.


Common mistakes

MistakeWhy it hurtsFix
Different promo UI in cart vs checkoutUser confusionSingle component set
No loading state on ApplyDouble-submit bugsDisable input + button
Error only in toastMissed on mobileInline error under field
Discount not reflected in line items”Where did savings go?”Show per-line or summary row
Expired code with generic errorSupport volumeSpecific expired copy + date
Hiding remove actionStuck wrong codeAlways show Remove on AppliedCoupon

  1. Audit discount types your store supports (%, fixed, shipping, BOGO).
  2. Build PromoCodeField with full validation state matrix.
  3. Add AppliedCoupon and DiscountRow to existing cart OrderSummary.
  4. Create CouponCard for wallet and campaign landing mocks.
  5. Document stacking rules on a spec page linked from components.
  6. Prototype apply → error → remove → reapply flow.
  7. Align order history to show code + savings on past orders.
  8. Run a11y check on error announcements and copy buttons.

FAQ

Collapsible “Have a promo code?” or always visible?

Collapsible on mini-cart and mobile checkout reduces clutter; always visible on full cart page where users expect it. Use the same expanded component in both.

Show savings on product cards before cart?

Show sale price and badge when a catalog-wide promo is active; hide unique one-time codes until cart unless marketing requires teaser copy.

Gift cards vs promo codes?

Separate UI—gift card balance is a payment method, not a discount row. If mutually exclusive, say so in error copy.

Design silent apply with success banner in cart; fallback to manual entry if session expired.


Next steps

Share on X

§ Keep reading

Related guides.