A small numeric badge used to display unread or pending counts on icons and rows.
with limit → hasLimit with true/false; parameterize count: Int and limit: Int?; add 99+ overflow handling. Variant count stays at 4.Counter appears inline with text to show counts — section headers for unread notifications, tab item badges for pending items, limit/slot usage displays.
with limit uses yes/no strings instead of true/false. Count/limit values are hardcoded text — not usable for real counts without detaching.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Empty | Yes | Yes | state=empty | Count is 0. Muted label, same bg. Used to indicate "nothing pending". |
| Filled | Yes | Yes | state=filled | Count is greater than 0. Brand-blue label, same bg. Used when there's activity to surface. |
| With limit | Yes | Yes | with limit=yes | Renders "N / M" (e.g. "3 / 10") — for slot/limit displays like "beneficiaries used". |
| Without limit | Yes | Yes | with limit=no | Renders a single integer. Used for unread counts, inbox badges. |
| Pressed / Disabled | N/A | N/A | — | Counter is display-only — no interactive states. |
| Overflow (99+) | N/A | N/A | Not modeled | Real counts can exceed 99 (unread messages, notifications). Need overflow display ("99+") — not built today. |
-
with limitusesyes/nostrings. Should behasLimit: true/falsefor direct SwiftBool/ KotlinBooleanmapping. C2 · Variant & Property Naming - Count and limit values are hardcoded text. "0 / 10" and "10 / 10" are baked into each variant — consumers must detach to show any other value. Expose
count: Intandlimit: Int?as parameters so the component renders its own formatted string. C2 · Variant & Property Naming - No overflow handling for large counts. Inbox counts routinely exceed 99 (unread messages, notifications). The single-integer format needs "99+" display; the slash format needs equivalent overflow when count exceeds the limit. Not modeled today. C5 · Interaction State Coverage
- Code Connect mappings not registered. Trivial once parameterization and boolean rename land. C7 · Code Connect Linkability
- Rename
with limittohasLimit. Change values fromyes/nostrings totrue/false. Aligns with the boolean naming convention used across the DS. Rename - Expose
countandlimitas parameters.count: Int(required) +limit: Int?(optional — activates slash format when set). Drop the text overrides; the component formats the string itself. Eliminates the "detach to change the number" anti-pattern. Property - Derive
statefromcount. Empty whencount == 0, filled otherwise. Removes one property the consumer has to set manually. Allow explicit override for edge cases. Property - Add overflow handling for both formats. Single-integer:
count > maxDisplayrenders "99+". Slash format:count > limitshould clamp display or render "limit+" to prevent visual overflow. Pattern used in Material / Apple badges. State - Document the two use cases. Single-integer = how many of X are there (notifications, unread, pending). Slash format = progress against capacity (slots used, steps completed). Clarify in the spec so teams pick the right format. Docs
- Document Counter ↔ Badge relationship. Counter = numeric (count, progress). Badge = status/tag label (Success, Premium). Teams reach for the wrong one without this guidance. Docs
Slash format showing zero progress against a limit ("0 / 10"). Muted label on neutral bg. Used when no slots are filled yet.
Counter at zero — neutral grey chip with muted label.
| Role | Token | Default |
|---|---|---|
| Background | counter/color/empty/bg | #EEF2F9 |
| Label | counter/color/empty/label | #3C4A5C |
Slash format with a filled count ("10 / 10"). Brand-blue label on neutral bg. Used when capacity is at or approaching the limit.
Counter showing a numeric value on the same surface.
| Role | Token | Default |
|---|---|---|
| Label | main/counter/label | #3C4A5C |
| Background | counter/color/empty/bg | #EEF2F9 |
Standalone count — notifications, unread messages, pending items. Hugs tightly around the digit (24 × 24 for single digit, grows for 2+ digits). Empty state shown muted; filled state shown in brand-blue. Pairs with overflow handling ("99+") once <code>count</code> is parameterized.
Active counter highlight (e.g. unread or pending).
| Role | Token | Default |
|---|---|---|
| Background | counter/color/active/bg | #EEF2F9 |
| Label | counter/color/active/label | #005CE5 |
| Figma Property | SwiftUI | Compose |
|---|---|---|
| (hardcoded text "0 / 10", "10 / 10") | count: Int | count: Int |
with limit: yes | no | limit: Int? (nil = single-integer format; set = slash format) | limit: Int? |
state: empty | filled | derived from count (0 = empty, >0 = filled) | auto, with override |
| (not modeled) | maxDisplay: Int = 99 | maxDisplay: Int = 99 |
| Requirement | iOS | Android |
|---|---|---|
| Context-aware label | Set .accessibilityLabel("5 unread messages") — screen readers should hear what the number means, not just the digits. | Set contentDescription = "5 unread messages". |
| Zero state | When count == 0, default behavior (hideWhenZero: true) removes the pill from the accessibility tree entirely. Best practice — nothing to announce. | Same — hidden at zero by default. |
| Overflow | Announce the actual count, not "99+" — e.g. "247 unread". The "99+" is a visual truncation, not the truth. | Same — screen reader gets the real number. |
| Contrast | Filled: #072592 on #EEF2F9 = 11.8:1 ✓. Empty: #C2CFE5 on #EEF2F9 = 1.4:1 — fails AA. Empty is decorative (shown only when the user opts out of hideWhenZero); don't use for counts that must be read. | Same ratios apply. |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | Clean one-layer structure (container + label). |
| C2 | Variant & Property Naming | Requires Rework | with limit → hasLimit (bool). Parameterize count/limit. |
| C3 | Token Coverage | Ready | Surface + label bound to main/counter/color/*. |
| C4 | Native Mappability | Ready | Maps to a tiny EBCounter view/composable — Text inside a Capsule. |
| C5 | Interaction State Coverage | Needs Refinement | Display-only — no interactive states needed. But overflow ("99+") for large counts is missing. |
| C6 | Asset & Icon Quality | Not Applicable | No assets. |
| C7 | Code Connect Linkability | Not Mapped | Trivial once parameterization + boolean rename land. |
state (2) × with limit (2) = 4 variants. Both formats are kept — they solve different problems: single-integer for counts, slash for progress.
| # | Node | state | with limit | Format | Example | Dimensions |
|---|---|---|---|---|---|---|
| 1 | 18482:71322 | empty | yes | slash | 0 / 10 | 53 × 24 |
| 2 | 18482:71324 | filled | yes | slash | 10 / 10 | 59 × 24 |
| 3 | 18482:71326 | empty | no | single integer | 0 | 25 × 24 |
| 4 | 18482:71328 | filled | no | single integer | 0 | 24 × 24 |
with limit → hasLimit, parameterize count + limit, add 99+ overflow. Variant count stays at 4. Openwith limit: yes/no → hasLimit: true/false. Direct Swift Bool / Kotlin Boolean mapping. Opencount: Int + limit: Int?; drop hardcoded text. Derive state from count. OpenmaxDisplay (default 99); counts beyond render "99+" in single-integer format, and clamp in slash format. Open