FixNeeds Refinement
Counter Component link

A small numeric badge used to display unread or pending counts on icons and rows.

Fix — parameterize values and clean boolean naming
Both formats (single-integer + slash) belong. The fixes are: rename with limithasLimit with true/false; parameterize count: Int and limit: Int?; add 99+ overflow handling. Variant count stays at 4.
In Context

Counter appears inline with text to show counts — section headers for unread notifications, tab item badges for pending items, limit/slot usage displays.

Live Preview
5
Content
count
limit
maxDisplay
Properties
with limit
state
DS Health
Reusable
Pass
Generic count primitive — used across Section Header, Tab Item, and standalone notification contexts.
Self-contained
Pass
Owns its typography, color tokens, and radius. Nothing external required to render.
Consistent
Partial
with limit uses yes/no strings instead of true/false. Count/limit values are hardcoded text — not usable for real counts without detaching.
Composable
Pass
Hugs content width, drops into any inline layout (Section Header, Tab Item) without manual sizing.
Behavior
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.
Open Issues
  • with limit uses yes/no strings. Should be hasLimit: true/false for direct Swift Bool / Kotlin Boolean mapping. 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: Int and limit: 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
Design Recommendations
  • Rename with limit to hasLimit. Change values from yes/no strings to true/false. Aligns with the boolean naming convention used across the DS. Rename
  • Expose count and limit as 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 state from count. Empty when count == 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 > maxDisplay renders "99+". Slash format: count > limit should 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
Variants
Empty — with limit
DES DEV

Slash format showing zero progress against a limit ("0 / 10"). Muted label on neutral bg. Used when no slots are filled yet.

0 / 10
Properties
State
With Limit
Count
Limit
Properties
state empty
with limit yes
Example text 0 / 10
Colors
Background #EEF2F9
Label #C2CFE5
Layout
Height 24
Padding 0 × 8 (hug width)
Corner radius 99 (pill)
Example width 53 (for "0 / 10")
Typography
Style Primary/Label/Small
Font Proxima Soft Bold
Size / line-height 14 / 14
Letter-spacing +0.25
Alignment center
Empty — Colors

Counter at zero — neutral grey chip with muted label.

Role Token Default
Background counter/color/empty/bg #EEF2F9
Label counter/color/empty/label #3C4A5C
Filled — with limit
DES DEV

Slash format with a filled count ("10 / 10"). Brand-blue label on neutral bg. Used when capacity is at or approaching the limit.

10 / 10
Properties
State
With Limit
Count
Limit
Properties
State Filled
Has limit Yes
Char count 120 / 200
Colors
Counter color #3C4A5C
Limit color #3C4A5C
Separator color #3C4A5C
Layout
Padding (top) 4
Alignment right
Gap 0
Typography
Style Caption/Regular
Font Proxima Soft
Size 12
Line-height 16
Filled — Colors

Counter showing a numeric value on the same surface.

Role Token Default
Label main/counter/label #3C4A5C
Background counter/color/empty/bg #EEF2F9
Single integer
DES DEV

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.

5
Properties
State
With Limit
Count
Limit
Properties
with limit no
state empty | filled
Example text 0
Colors
Empty bg #EEF2F9
Empty label #C2CFE5
Filled bg #EEF2F9
Filled label #072592
Layout
Min-width 24 (circle for single digit)
Max-width hug (grows with digit count)
Padding 0 × 8
Typography
Label style Primary/Label/Small
Label font Proxima Soft Bold · 14 / 14 · +0.25
Active — Colors

Active counter highlight (e.g. unread or pending).

Role Token Default
Background counter/color/active/bg #EEF2F9
Label counter/color/active/label #005CE5
Property Mapping
Figma PropertySwiftUICompose
(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
Accessibility
RequirementiOSAndroid
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.
Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Clean one-layer structure (container + label).
C2 Variant & Property Naming Requires Rework with limithasLimit (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.
Variants Inventory (4 total)

state (2) × with limit (2) = 4 variants. Both formats are kept — they solve different problems: single-integer for counts, slash for progress.

#Nodestatewith limitFormatExampleDimensions
118482:71322emptyyesslash0 / 1053 × 24
218482:71324filledyesslash10 / 1059 × 24
318482:71326emptynosingle integer025 × 24
418482:71328fillednosingle integer024 × 24
1.0.0 — April 2026Major
Initial Assessment · node 18482:71321
Verdict: Fix — Keep both formats (single integer + slash). Rename with limithasLimit, parameterize count + limit, add 99+ overflow. Variant count stays at 4. Open
Schema
C2 — Boolean namingwith limit: yes/nohasLimit: true/false. Direct Swift Bool / Kotlin Boolean mapping. Open
C2
C2 — Parameterize values — Expose count: Int + limit: Int?; drop hardcoded text. Derive state from count. Open
C2
C5 — Overflow — Add maxDisplay (default 99); counts beyond render "99+" in single-integer format, and clamp in slash format. Open
C5
C7 — Code Connect — Trivial once parameterization + rename land. Open
C7