East Blue
KeepReady
Counter Component link

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

Keep — all findings resolved
All four DS Health traits pass and nothing is open. State and hasLimit follow the naming guidelines, every layer has a semantic name, and Count and Limit are text properties.
In Context

How the counter appears in real product screens — a limit counter (10 / 10) beside the Send to list in Express Send, and a count (2) beside the People section when splitting a bill.

Counter component shown on two GCash screens: a 10 / 10 limit counter next to Send to on the Express Send recipient list, and a 2 counter next to People on a split-bill screen
Live Preview
0/10
Properties
State
hasLimit
Content
Count
Limit
maxDisplay
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
Pass
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.
Composable
Pass
Hugs content width, drops into any inline layout (Section Header, Tab Item) without manual sizing.
Behavior
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.
Issues
  • with limit renamed to hasLimit. v2.0: Rebuilt on node 4675:21497. The property is now hasLimit with True/False values — a real Figma boolean toggle that maps directly to Swift Bool / Kotlin Boolean. The State axis 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 because overview.appliedRecommendations does not yet exist in the schema. (Rename) C2 · Variant & Property Naming
  • State=Disabled semantics confirmed. v2.0: Closed by owner decision — Disabled describes the control context (a Counter sitting inside a disabled row or field), not a zero count. Zero-count styling is derived from count in 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 at limit when count > limit. Handled in code; the Figma Overflow layer 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
  • Disabled variant width evened up. v2.1: State=Disabled, hasLimit=True went from 61 × 24 to 53 × 24, matching its Default sibling for identical content; the component set narrowed from 109 to 101. Achieved by removing the Overflow frame 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 are Separator (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:19238 and 4681:19235 went from #overflow-value to Plus — 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:21501 and 4675:21505 renamed to Limit, resolving the duplicate-Count collision 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
  • Container renamed to LimitGroup. v2.4: 4675:23030 and 4675:23007 renamed from the generic Container to LimitGroup. 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 — Count and Limit are 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
  • Overflow layer binding confirmed. v2.4: Closed on owner confirmation — the Overflow frames in the two hasLimit=False circle 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 nonsensical 0 / 10+ rendering. Clamping stays in code. (C5) C5 · Interaction State Coverage

Component Playground

Layout

Typography

Colors

Installation Planned API

iOS — Swift Package Manager

.package(url: "https://github.com/AY-Org/eb-ds-ios", from: "1.0.0")

Android — Gradle (Kotlin DSL)

implementation("com.eastblue.ds:counter:1.0.0")

Import

import EastBlueDS
import com.eastblue.ds.counter.*
Property Mapping

State and hasLimit are the set’s two variant properties. Count and Limit are exposed as text properties (owner confirmation, v2.4); the Overflow frame’s Plus glyph is property-bound. Neither the text properties nor the overflow binding are readable with the plugin.

Figma PropertySwiftUICompose
State — Default, Disabled .disabled(true) enabled = false
hasLimit — True, False .ebLimit(10) when True limit = 10 when True, else null
Count (text) count: Int count = 0
Limit (text) .ebLimit(_:) limit = 10
Overflow › Plus (property-bound glyph) maxDisplay: Int = 99 — renders "99+" past it maxDisplay = 99 — renders "99+" past it
Usage Snippets Planned API
Counter
EBCounter(count: 0)
    .ebLimit(10)
EBCounter(
    count = 0,
    limit = 10
)
Accessibility
RequirementiOSAndroid
Context-aware label .accessibilityLabel("3 unread") or "3 of 10 used" — the number alone is meaningless. contentDescription = "3 unread" / "3 of 10 used" on the pill.
Zero and overflow Announce "no unread" at 0 and "more than 99" past maxDisplay — never read the plus glyph. Same two phrasings; the plus glyph is decorative.
Disabled context The pill inherits the disabled trait of the row or field it sits in; it is never disabled on its own. Semantics merge with the parent row; no separate enabled handling on the pill.
Contrast #072592 on #EEF2F9 is 10.4:1; the disabled #C2CFE5 is decorative and must not carry information. Same values — the pill colours are fixed, not themed.
Usage Guidelines

Do

Use the single integer for how-many counts — unread, pending, notifications.

Don't

Don’t use the single integer for progress against a capacity.

Do

Use hasLimit for slots used against a capacity — "3 / 10 beneficiaries".

Don't

Don’t let count exceed limit; clamp it in code.

Do

Keep maxDisplay at 99 so the pill stays two digits plus the glyph.

Don't

Don’t show four-digit counts — "999+" is the widest the pill should get.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Count, Separator, LimitGroup › Limit, Overflow › Plus — read on 4675:21502 and 4675:21508.
C2 Variant & Property Naming Ready State and hasLimit with Title Case values; the boolean is a real True / False.
C3 Token Coverage Ready All four text layers resolve to Primary/Label/Small. Colour variable bindings are not readable with the plugin — confirm in Dev Mode.
C4 Native Mappability Ready One enum, one boolean, two text values — maps 1:1 onto EBCounter.
C5 Interaction State Coverage Ready Default and Disabled built for both hasLimit values; the pill is not a tap target, so no Pressed.
C6 Asset & Icon Quality Not Applicable No icons or images — text layers only.
Variants Inventory (4 total)

2 State × 2 hasLimit = 4 variants

StatehasLimitNodeDimensions
DefaultTrue4675:2150253 × 24
DefaultFalse4675:2150824 × 24
DisabledTrue4675:2149853 × 24
DisabledFalse4675:2273424 × 24
1.0.2 — September 2026Patch
Style and Code tabs re-read · node 4675:21497
Style tab rebuilt from the four live variants. One card — every property is State or a boolean — with State and hasLimit in the panel; colours, the Primary/Label/Small text style and the 53 / 24 widths read off 4675:21502 and 4675:21508. Padding, gap, alignment and colour tokens are not readable with the plugin and stay —.
Docs
Code tab regrouped on the State × hasLimit schema. Installation on the solo coordinates, Property Mapping one row per property with the overflow rule, Usage Snippet and Guidelines added, Scorecard notes tied to the live nodes, Variants Inventory moved from the 18482 nodes to the four live nodes.
Docs
1.0.1 — September 2026Patch
Documentation backfill · node 4675:21497
Resolved issues recorded. 13 resolved issues on the Overview tab had no changelog row: with limit renamed to hasLimit; Applied — Rename recommendation shipped; State=Disabled semantics confirmed; Overflow clamp rule settled; Two use cases documented; Counter ↔ Badge relationship documented; Disabled variant width evened up; Count and separator layers renamed; Overflow glyph layers renamed to Plus; Text layer naming complete; Container renamed to LimitGroup; Text properties confirmed exposed; Overflow layer binding confirmed.
Docs
1.0.0 — April 2026Major
Initial Assessment · node 18482:71321
Verdict: Fix — Keep both formats (single integer + slash). Rename with limit → hasLimit, parameterize count + limit, add 99+ overflow. Variant count stays at 4. Open
Schema
C2 — Boolean naming — with limit: yes/no → hasLimit: 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