ComponentsData & statusBadge
On this page
Data & status

Badge released

A short word wearing a tone: a status, a count, a label beside something that already has a name.

  • Import@pacit/components/badge
  • Selectorpct-badge

No APG pattern applies, and none is invented — the reasoning is under Accessibility ↓

DraftPaid3 overdue

badge.demo.ts — the same file runs here and is highlighted under Code

Start here

Usage

One import per component, and the shortest possible use — the package itself is installed once, on the start page.

Import this component
import { PctBadge } from '@pacit/components/badge';
ExportedWhat it is
PctBadgecomponentpct-badgeA badge: a word wearing a tone — Draft, Active, Overdue.
<pct-badge>Draft</pct-badge>
Examples

Every state, running

Each example is the component itself, not a screenshot. Open the code under any of them.

Tones

Four tones and the absence of one. A badge with no tone wears the skin's quiet surfaces; the four named ones are PctTone, the same list the toast and the button read, and every word over every box is a measured pair on both themes.

Draft3 overdueExpiringPaidScheduled

A column of statuses

Where a tone earns its keep: one column, one word per row, and the colour repeating what the word already says. The word is what a screen reader reads — the tone is for the eye that scans the column and stops at the red one. The last row binds null, which is how a badge asks for no tone at all.

InvoiceState
#1043Paid
#1044Due in 3 days
#1045Overdue
#1046Scheduled
#1047Draft
API

What it reads, and what it writes back

Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.

A badge: a word wearing a tone — Draft, Active, Overdue.

Inputs

NameTypeWhat it does
tonedefault nullPctTone | nullThe tone the word wears, or null for none — the default, and the quiet box the skin's own surfaces paint. It repeats the text, never replaces it.

Outputs

None of its own. The native events — (click), (focus), the form's submit — are the platform's, and two-way models above carry the rest.

What it writes on the element

The hooks a stylesheet or a test can hold. Stable by contract — the parts inventory gate fails the build when one disappears.

AttributeBound toNote
classpct-badge
data-pct-tonetone()
Styling

Parts to select, tokens to override

Two ways in, both stable. No class names to depend on.

Parts

In a test, PctBadgeHarness from @pacit/components/testing reads a part by the name below and a state by its attribute — the harness gate holds the names to the package.

No parts of its own — the element you write is the whole of it.

Tokens

Every value the component reads, with the light and the dark default. Override a token at any scope and it cascades like any custom property.

TokenStyles
--pct-badge-bg{pct.surface-100}#f1f5f9 · #1e293bBackground of the badge.
--pct-badge-bg-danger{pct.danger}#b91c1c · #f87171Background of the badge in the danger tone.
--pct-badge-bg-info{pct.info}#2563eb · #60a5faBackground of the badge in the info tone.
--pct-badge-bg-success{pct.success}#15803d · #4ade80Background of the badge in the success tone.
--pct-badge-bg-warning{pct.warning}#b45309 · #fbbf24Background of the badge in the warning tone.
--pct-badge-border{pct.border}#e2e8f0 · #334155Edge colour of the badge.
--pct-badge-border-danger{pct.danger}#b91c1c · #f87171Edge colour of the badge in the danger tone.
--pct-badge-border-info{pct.info}#2563eb · #60a5faEdge colour of the badge in the info tone.
--pct-badge-border-success{pct.success}#15803d · #4ade80Edge colour of the badge in the success tone.
--pct-badge-border-warning{pct.warning}#b45309 · #fbbf24Edge colour of the badge in the warning tone.
--pct-badge-fg{pct.text}#0f172a · #ffffffText colour of the badge.
--pct-badge-fg-danger{pct.on-danger}#ffffff · #000000Text colour of the badge in the danger tone.
--pct-badge-fg-info{pct.on-info}#ffffff · #0f172aText colour of the badge in the info tone.
--pct-badge-fg-success{pct.on-success}#ffffff · #000000Text colour of the badge in the success tone.
--pct-badge-fg-warning{pct.on-warning}#ffffff · #000000Text colour of the badge in the warning tone.
--pct-badge-font-size{pct.font.size.sm}13pxFont size of the badge.
--pct-badge-font-weight{pct.font.weight.medium}500Font weight of the badge.
--pct-badge-gap{pct.space.2}4pxGap between the badge's text and its icon.
--pct-badge-padding-x{pct.space.2}4pxHorizontal padding of the badge.
--pct-badge-radius{pct.radius.md}8pxCorner radius of the badge.

Theming

A brand override in a few declarations. Scope it to a theme attribute, a page or one container — the component never notices.

[data-theme='brand'] {
  --pct-badge-bg: #ccfbf1;
  --pct-badge-fg: #134e4a;
  --pct-badge-border: #99f6e4;
}
DraftPaid3 overdue
Accessibility

Measured, not assumed

Pattern: none — a badge is text and only text (0053): the word is projected, already part of the document's sentence, and the component adds a box around it and nothing audible to it. No role, no ARIA, no string of its own. Named in the class JSDoc.

Keyboard

keyeffecttest

Empty by design: a badge is running text, holds no control, and takes no focus — the platform provides nothing here because there is nothing to provide (req-api-platform).

What is measured, and what is not yet

Every line is a test in three engines or a gate on every commit. The gaps are listed the same way, each with the requirement that will close it.

MeasuredARIA pattern named in the class JSDoclibs/components/badge/src/badge.ts
MeasuredKeyboard map tested key by keythe empty map is the claim — nothing here is focusable, and the axe sweep's aria-hidden-focus family has nothing to bite on a host that hides nothing and holds no control
Measuredaxe audit on the component's own sandbox viewapps/sandbox-e2e/src/a11y.spec.ts — /badge in SBX_ROUTES
MeasuredVisual screenshotapps/sandbox-e2e/src/visual.spec.ts — badge-tones and badge-tones-rtl
Measuredforced-colors: active — no state carried by colour alonelibs/components/badge/src/badge.scss — every tone drops to CanvasText on Canvas with the border standing, which is the component's own argument made visible. One :host rule inside the media block is enough, and the instrument for that is static: a tone rule here assigns --pct-badge-* and paints no property of its own, so check-styles point 7 finds nothing of a higher specificity for the block to lose to. No e2e case could say this — Chromium and Firefox substitute colours in the mode whichever rule won, and webkit is excluded from the suite (lesson-70). What apps/sandbox-e2e/src/forced-colors.spec.ts does read is the other half: every tone arrives at the palette's one word, and the edge is still drawn
n/aprefers-reduced-motion — duration from a tokennot applicable — no motion at all
n/aTouch target ≥ 24×24 px outrightnot applicable — nothing here is a target
MeasuredSize axis aligned to --pct-control-height-*deliberately not: a badge is not a control and stands on no control axis — apps/sandbox-e2e/src/badge.spec.ts measures the box UNDER the smallest control height, and that reading is the promise
GapDensity axisnone — gap: the same one every component here has (req-token-density)
MeasuredRTL — no physical properties + a dir="rtl" screenshotlibs/components/badge/src/badge.scss — logical padding and a flex gap, nothing directional. The screenshot: badge-tones-rtl
MeasuredSSR + hydration with no NG05xxapps/sandbox-e2e/src/hydration.spec.ts — /badge in SBX_ROUTES
n/aFormsnot applicable — a word holds no value a form owns
By designParts registered in the inventorynone — deliberately: the host is the box and there is nothing inside it to address; tools/check-parts.mjs counts zero and the README table carries the entrypoint
MeasuredTokens registered + an entry in contrast.policy.jsonlibs/tokens/src/contrast.policy.json — fourteen entries: every word over its box (AA error), every toned box and its edge against the page (UI error — the box IS the statement), the untoned edge (UI warn); libs/tokens/tokens.snapshot.md
n/aStrings through PCT_TEXTSnot applicable — the component draws no text of its own
MeasuredEntrypoint size budgetlibs/components/size.snapshot.md — ./badge reaches for no other entrypoint at all: PctTone comes in under import type, which the build erases, so the internal-deps column stays - and the row moved on its own bytes only. No PCT_TEXTS key either, so not one other row of the snapshot moved
MeasuredScreen-reader logRead 2026-09-16 from docs/acr/at/ — three readers, whole sandbox, and /badge is the one view whose walk found no stop of its own at all: every stop in the three logs belongs to the sandbox scaffold. A badge takes no focus, which is the point of it, so a Tab walk cannot reach one and cannot confirm silence either. What would: a reader reading the page rather than walking it
MeasuredA docs page with live examplesapps/sandbox/src/app/views/badge/ (the sandbox view). The published site: /components/badge — prerendered, the demo's own source is the code tab
MeasuredUnit + mutationlibs/components/badge/src/badge.spec.ts — 8 cases; libs/components/mutation.snapshot.md — badge.ts measured in the same full run as the avatar's row
Evidence

What the repository holds for this component

Read at build time from the tracked snapshots, not typed here.

81.82%mutation score9 of 11 mutants killed
6 × 3e2e cases × enginesplus the shared audits: axe, forced colors, preferences, size, hydration
14colour pairs measuredlight and dark, at every token build
2visual baselinesChromium, the sandbox's own pictures
3elements in the preview0 listeners · 1 render to settle · 2099 µs in jsdom, held exactly but for the clock

Known limitations

  • No count overlay. The 3 on a bell's corner is a different component — anchoring, a live region for the changing number, truncation — and it does not exist yet.
  • The tone cannot verify the word. tone="danger" beside the text OK is a page lying to its users in a way no component can see; the contract is prose, and the empty-badge warning is its enforceable corner.
  • No size input. A badge is sized by its own type token; one that wants a control's height is a button or a chip wanting to be quiet.