PlanUsageBar
Filled block showing what is left of a plan allowance.
.plan-usage-barOpen in Figma ↗This component has no description in Figma yet.
import SwiftUI import EandTokens // 48pt filled block: the fill shows what is LEFT, the amount reads inside it, // the category sits in the track opposite. Green normally, orange on low data. // Text the fill covers switches to inverse (white) ink. struct PlanUsageBar: View { let label: String; let remaining: Double; let total: Double; let unit: String var note: String? = nil var lowData: Bool = false var body: some View { GeometryReader { geo in ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: EandRadius.radius4) .fill(EandColor.surfaceCanvasInverse) RoundedRectangle(cornerRadius: EandRadius.radius4) .fill(lowData ? EandColor.orange500 : EandColor.green600) // Figma green/550 = #54bc72 .frame(width: geo.size.width * fraction) HStack { VStack(alignment: .leading, spacing: 0) { Text("\(Int(remaining)) \(unit) left") .font(.system(size: EandTypography.titleXs.size, weight: .semibold)) if let note { Text(note).font(.system(size: EandTypography.bodySm.size)) } } Spacer() Text(label).font(.system(size: EandTypography.buttonMd.size, weight: .medium)) } .padding(.horizontal, EandSpacing.lg) } } .frame(height: 48) } // Clamped: no allowance means nothing to draw, a top-up must not overflow the track. private var fraction: Double { total > 0 ? min(1, max(0, remaining / total)) : 0 } }
When to use
- Use to show the balance of one allowance — data, minutes, roaming — in a plan breakdown. Stack bars with a 4px gap, one per allowance.
- Don't use for progress through a task or flow (use ProgressBar or Stepper), or for a loyalty tier (use SmilesBalance).
Anatomy
A 48px rounded row where a coloured block shows how much of the allowance is left. The
amount reads inside the block; the category label sits in the track at the opposite end.
- Value — the amount, the
unitand "left", with an optionalnoteunderneath, such as "Expires in 3 days". - Label — the category, end-aligned.
remaining is what the block measures, not consumption. Pass total so the fill has something
to be a share of; with total={0} there is no allowance and nothing is drawn.
status is explicit rather than derived: default fills green, low-data orange. The point at
which an allowance counts as low is a product rule that differs for data, minutes and roaming,
so the bar does not guess it.
Text on the fill
Where the block sits under the text, the text switches to its filled state: white, and for
the value a Medium weight. The bar measures this itself, separately for the value and the
label: each flips once the fill covers it completely. Pin either one with valueState or
labelState when you need a fixed result, for example in a static mock-up.
Deprecated
used still works and is converted to remaining as total - used. Pass remaining.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
total | number | — | Plan allowance the block is measured against. |
label? | ReactNode | Data | Category name, right-aligned in the track — e.g. "Local Data". |
labelState? | 'default' | 'filled' | — | Ink of the category label. Omit to switch automatically once the fill covers it. |
note? | ReactNode | — | Second line under the value, e.g. "Expires 3 days". |
remaining? | number | — | Amount left in the plan. This is what the block shows and measures. |
status? | 'default' | 'low-data' | default | |
unit? | string | GB | |
used? | number | — | Deprecated. Pass remaining. Kept so used-based call sites keep working. |
valueState? | 'default' | 'filled' | — | Ink of the value and note. Omit to switch automatically once the fill covers them. |
Design
Figma source: .plan-usage-bar — etisalat Consumer App DSL V1.2 · states
- Track: 48px tall, 12px radius (
border-radius/4), onsurface/canvas/inverse, with 16px side padding (spacing/lg). - Fill: the
Progress statusaxis (28927:22814), green #54bc72 or orange #ff9f52. Two discrete states, not a gradient. - Value:
title/xsintext/default/default; filled,button/mdintext/default/inverse. The amount, unit and "left" are spaced by 2px (spacing/2xs). Note:body/smat 130%. - Label:
button/md, intext/default/defaultor, filled,text/default/inverse.
The bar is one part of the Figma plan-usage card
(38351:53904).
That card is a screen pattern, not a component; assemble it from the parts: a row of outline
Chips (40px, 4px apart) and the stacked bars (4px apart) in a 20px-radius card on
surface/canvas/default with 12px padding and an 8px gap, followed after 4px by a 72px
ActionBar prompting a top-up.