Skip to content

State label component

components specs/components/state-label.kmd

The status pill for a code-hosting entity (issue or pull request) whose value is drawn from a per-entity state machine, not a free tag. Distinct from a notification badge (count/dot) and a chip (user-editable filter): a state label is read-only, single-valued, and semantically colored by state. Modeled after GitHub Primer StateLabel. Used by Koder Flow issue lists, PR headers, dashboards, and any surface that shows VCS entity status.

When this spec applies

Primary triggers

All triggers

Specification body

Component — State label

Status: v0.1.0 — Draft. Promoted from the GitHub Primer parity scan (meta/docs/stack #092). Live URL once rendered: kds.koder.dev/<locale>/components/components-state-label.html. Source: https://primer.style/components/state-label

R1 — What it is (and is not)

A state label is a read-only pill bound to ONE value from a fixed, entity-specific state machine (R2). It is NOT:

  • a badge (specs/components/badges.kmd) — that signals a count/dot;
  • a chip (specs/components/chips.kmd) — that is user-editable / a filter input;
  • a free-text tag/label (issue labels are a separate, multi-valued, user-defined concept).

There is exactly one state label per entity instance.

R2 — State machines (closed sets)

The valid values and their semantics depend on the entity:

Issue

StateMeaningSemantic colorIcon
OpenActive issuesuccess (open-green)issue-opened
Closed — completedResolved as donedone (purple)issue-closed
Closed — not plannedClosed without actionneutral (grey)skip/circle-slash

Pull request

StateMeaningSemantic colorIcon
DraftNot yet ready for reviewneutral (grey)git-pull-request-draft
OpenReady / in reviewsuccess (open-green)git-pull-request
MergedMerged into targetdone (purple)git-merge
ClosedClosed unmergeddanger (red)git-pull-request-closed

No other values exist. The state is derived from entity data, never set by hand in the view.

R3 — Anatomy

  • Pill container with a leading icon + a text label.
  • Sized in two scales: small (inline, in lists/tables) and default (entity header). Both keep the icon+label pair.
  • Fill uses the state's semantic color at the established surface contrast; the label text meets WCAG AA on that fill.

R4 — Color is never the only signal

Each state is distinguished by icon + text label, not color alone (WCAG 1.4.1). The icon shape per row in R2 is mandatory; a state label without its icon is non-conformant.

R5 — Tokens

All fills, text, and icon colors come from Verge semantic tokens (specs/themes/verge.kmd) — --kds-color-open, --kds-color-done, --kds-color-danger, --kds-color-neutral (or their canonical names). No raw hex. Light/dark are handled by the token layer, not by the component.

R6 — Accessibility

  • The label renders its state in words; the icon is decorative (aria-hidden) since the text carries meaning.
  • When used as the accessible name of an entity row, the state is part of that row's label ("Issue #214, Open").
  • Non-interactive: a state label is not a button/link. If a surface makes the surrounding row clickable, the click target is the row, not the pill.

R7 — i18n

The visible state label is translated per specs/i18n/contract.kmd ("Open"→"Aberto", "Merged"→"Mesclado", etc.). The underlying state KEY (used in code/filters) stays stable and language-invariant.

Não-escopo

  • User-defined issue labels (multi-valued, colored tags) — separate.
  • The state-transition rules themselves (owned by Koder Flow backend).
  • Review-decision states (approved / changes-requested) — those belong to the PR review surface (specs/components/ review spec, #138).

Requirements (testable)

Requirement: Read-only single value bound to one entity {#req-state-label-read-only-single-value}

A state label SHALL be read-only, single-valued, and bound to exactly one value derived from entity data; it SHALL NOT be user-editable, accept free text, or render more than one state per entity instance.

Scenario: Renders exactly one state per entity

  • GIVEN an issue entity whose derived state is Open
  • WHEN the state label is rendered for that entity
  • THEN exactly one state label element is present
  • AND its value is Open

Scenario: State comes from entity data, not manual input

  • GIVEN a state label bound to a pull-request entity
  • WHEN the view is inspected for editable controls
  • THEN the state label exposes no text input, editable field, or user-settable value
  • AND the displayed state equals the value derived from the entity data

Requirement: Value drawn from a closed per-entity state machine {#req-state-label-closed-state-machine}

A state label SHALL only display a value from the fixed set defined for its entity type — Issue (Open, Closed — completed, Closed — not planned) or Pull request (Draft, Open, Merged, Closed) — each mapped to its established semantic color, and SHALL NOT render any value outside that set.

Scenario: Issue closed-not-planned maps to neutral

  • GIVEN an issue entity whose derived state is Closed — not planned
  • WHEN its state label is rendered
  • THEN the visible label reads Closed — not planned
  • AND its fill uses the neutral semantic color

Scenario: Pull request merged maps to done

  • GIVEN a pull-request entity whose derived state is Merged
  • WHEN its state label is rendered
  • THEN the visible label reads Merged
  • AND its fill uses the done semantic color

Scenario: Out-of-set value is rejected

  • GIVEN a state label targeting an issue entity
  • WHEN a value not in the issue state set (e.g. Merged) is supplied
  • THEN the component does not render that value as a valid issue state

Requirement: State distinguished by icon plus text, never color alone {#req-state-label-icon-plus-text}

Each state SHALL be distinguished by both its mandatory per-state icon and its text label so meaning survives without color (WCAG 1.4.1); a state label rendered without its icon SHALL be treated as non-conformant.

Scenario: Icon and text both present

  • GIVEN a pull-request state label with state Open
  • WHEN it is rendered
  • THEN a leading icon element is present
  • AND a text label reading Open is present

Scenario: Missing icon is non-conformant

  • GIVEN a state label whose text label is present but whose leading icon is absent
  • WHEN conformance is evaluated
  • THEN the state label is flagged as non-conformant

Requirement: Accessible non-interactive state exposure {#req-state-label-accessibility}

The state label SHALL render its state in words with the icon marked decorative (aria-hidden) so the text carries the accessible meaning, and SHALL be non-interactive — it SHALL NOT be a button or link, and any surrounding clickable target belongs to the row, not the pill.

Scenario: Icon is decorative, text carries meaning

  • GIVEN a state label with state Closed
  • WHEN its accessibility tree is inspected
  • THEN the icon is marked aria-hidden
  • AND the accessible name conveys the word Closed

Scenario: Pill is not an interactive target

  • GIVEN a state label inside a clickable entity row
  • WHEN the state label element is examined
  • THEN it exposes no button or link role
  • AND the click target is the surrounding row rather than the pill

Requirement: Translated visible label with stable state key {#req-state-label-i18n}

The visible state text SHALL be translated per the i18n contract while the underlying state KEY used in code and filters SHALL remain stable and language-invariant.

Scenario: Visible label translated, key unchanged

  • GIVEN a state label whose state key is Open
  • WHEN it is rendered in the pt-BR locale
  • THEN the visible text reads Aberto
  • AND the state key exposed to code and filters remains Open

References