A small numeric badge used to display unread or pending counts on icons and rows.
4675:21497 in the 2026 Working File and cleared through v2.4. State and hasLimit follow the Property Naming Guidelines; all four variants are dimensionally consistent; every frame and text layer carries a correct semantic name (Count ×4, Separator ×2, Limit ×2, Plus ×2, LimitGroup ×2). Count and Limit are exposed as text properties and the Overflow affordance is property-bound. All four DS Health traits pass. The only item still open is Code Connect registration, blocked until the native library exists. 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.
State and hasLimit follow the Property Naming Guidelines, all four variants are dimensionally consistent, and every frame and text layer carries a correct, distinct semantic name — Count, Separator, Limit, Plus, LimitGroup.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default | Yes | Yes | State=Default | Brand-blue label (#072592) on the neutral chip. The normal presentation. |
| Disabled | Yes | Yes | State=Disabled | Muted label (#C2CFE5) on the same bg. Documented as a disabled context — a Counter sitting inside a disabled row or field. Zero-count styling is no longer modeled as a variant; derive it from count in code. |
| With limit | Yes | Yes | hasLimit=True | Renders "N / M" (e.g. "3 / 10") — for slot/limit displays like "beneficiaries used". |
| Without limit | Yes | Yes | hasLimit=False | Renders a single integer in a 24×24 circle. Used for unread counts, inbox badges. |
| Pressed / Focused | N/A | N/A | — | Counter is display-only — no interactive states. |
| Overflow (99+) | Yes | Yes | <code>Overflow</code> layer | A hidden Overflow frame holding a + glyph sits in the two hasLimit=False circle variants only — it was removed from the slash format in v2.1. Single-integer clamps at maxDisplay (default 99) → "99+"; slash format clamps at limit. Clamping is handled in code. |
- Code Connect mappings not registered. Blocked until the native library exists. Everything upstream is ready:
Statemaps to a two-value enum,hasLimitto aBool, andCount/Limitto text parameters. Registration is mechanical once there is a component to link to. C7 · Code Connect Linkability
-
with limitrenamed tohasLimit. v2.0: Rebuilt on node4675:21497. The property is nowhasLimitwithTrue/Falsevalues — a real Figma boolean toggle that maps directly to SwiftBool/ KotlinBoolean. TheStateaxis was also recased to PascalCase, bringing both properties in line with the Property Naming Guidelines. (C2) C2 · Variant & Property Naming - Applied — Rename recommendation shipped. v2.0: Applied —
with limit: yes/no→hasLimit: True/False, exactly as recommended. Logged here rather than in an Applied tab becauseoverview.appliedRecommendationsdoes not yet exist in the schema. (Rename) C2 · Variant & Property Naming -
State=Disabledsemantics confirmed. v2.0: Closed by owner decision —Disableddescribes the control context (a Counter sitting inside a disabled row or field), not a zero count. Zero-count styling is derived fromcountin code and is deliberately not modeled as a variant. This also settles the contrast question: the muted label (#C2CFE5 on #EEF2F9, 1.4:1) is disabled text, which WCAG 1.4.3 exempts. (Docs) C2 · Variant & Property Naming - Overflow clamp rule settled. v2.0: Closed by owner decision — no further spec work required. Single-integer format clamps at
maxDisplay(default 99) and renders "99+"; slash format clamps atlimitwhencount > limit. Handled in code; the FigmaOverflowlayer only supplies the+glyph. (C5) C5 · Interaction State Coverage - Two use cases documented. v2.0: Closed — single-integer answers how many of X are there (notifications, unread, pending); slash format shows progress against capacity (slots used, steps completed). Captured in the Behavior table and the Style tab; no further action. (Docs) C4 · Native Mappability
- Counter ↔ Badge relationship documented. v2.0: Closed — Counter is numeric (a count or progress); Badge is a status or tag label (Success, Premium). Teams pick by whether the content is a number. No further action. (Docs) C4 · Native Mappability
-
Disabledvariant width evened up. v2.1:State=Disabled, hasLimit=Truewent from 61 × 24 to53 × 24, matching itsDefaultsibling for identical content; the component set narrowed from 109 to 101. Achieved by removing theOverflowframe from the slash-format variants rather than excluding it from layout. Instances no longer jump 8px when switching state. (C1) C1 · Layer Structure & Naming - Count and separator layers renamed. v2.1: Applied — all four count layers are now
Count(4675:21499·4675:21503·4675:22735·4675:21509) and both slash glyphs areSeparator(4675:21500·4675:21504). The property surface is legible for the first time. Two limit layers were misnamed in the same sweep — tracked as an open issue. (Rename) C1 · Layer Structure & Naming - Overflow glyph layers renamed to
Plus. v2.2:4681:19238and4681:19235went from#overflow-valuetoPlus— PascalCase, no hash prefix, per the Property Naming Guidelines. Every layer in the two circle variants now carries a correct semantic name. (C2) C2 · Variant & Property Naming - Text layer naming complete. v2.3:
4675:21501and4675:21505renamed toLimit, resolving the duplicate-Countcollision in the slash-format variants. Every text layer in the set now carries a correct, distinct semantic name —Count×4,Separator×2,Limit×2,Plus×2 — verified by characters, not position. Closes the C1 naming issue opened at initial assessment. (C1) C1 · Layer Structure & Naming -
Containerrenamed toLimitGroup. v2.4:4675:23030and4675:23007renamed from the genericContainertoLimitGroup. Every frame and text layer in the set now carries a semantic name — C1 is fully clean. (C1) C1 · Layer Structure & Naming - Text properties confirmed exposed. v2.4: Closed on owner confirmation —
CountandLimitare exposed as text properties, so consumers set values without detaching. This closes the "hardcoded text" issue raised at initial assessment. Not independently verifiable from the read-only assessment tooling, which cannot read component property definitions. (C2) C2 · Variant & Property Naming -
Overflowlayer binding confirmed. v2.4: Closed on owner confirmation — theOverflowframes in the twohasLimit=Falsecircle variants (4681:19237·4681:19234) are property-bound, not manual visibility overrides. Their removal from the slash-format variants in v2.1 was deliberate, and resolves the nonsensical0 / 10+rendering. Clamping stays in code. (C5) C5 · Interaction State Coverage
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 | Ready | hasLimit + State follow the naming guidelines; Count and Limit are exposed as text properties. |
| 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 | Ready | Display-only — no interactive states needed. Overflow affordance is property-bound and the clamp rule is settled. |
| C6 | Asset & Icon Quality | Not Applicable | No assets. |
| C7 | Code Connect Linkability | Not Mapped | Blocked until the native library exists. State → enum, hasLimit → Bool, Count/Limit → text parameters. |
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