Skip to content

Rating: keyboard cannot clear to 0 while pointer click can — modality-divergent reset behavior #83

Description

@agungadip

Current behavior

Rating lets a pointer user unset the rating (return to 0 / "no rating") but never lets a keyboard user do the same.

Click clears to 0 — clicking the currently-selected star toggles the value back to 0:

// apps/web/node_modules/twico-ui/dist/index.mjs:8676
onClick: () => set(n === val ? 0 : n),

Keyboard is floored at 1 — every keyboard branch clamps to a minimum of 1, so a keyboard user can never reach 0:

// apps/web/node_modules/twico-ui/dist/index.mjs:8632-8643
const onKeyDown = (e) => {
  if (!interactive) return;
  let next;
  if (e.key === "ArrowRight" || e.key === "ArrowUp") next = Math.min(count, val + 1);
  else if (e.key === "ArrowLeft" || e.key === "ArrowDown") next = Math.max(1, val - 1); // floor 1
  else if (e.key === "Home") next = 1;   // Home → 1, never 0
  else if (e.key === "End") next = count;
  else return;
  e.preventDefault();
  set(next);
  btnRefs.current[next - 1]?.focus();
};

There is no Delete / Backspace / Escape / "0" branch in Rating's onKeyDown (verified by grep — every such handler in the bundle belongs to a different component).

0 is an intended, documented state, not an edge case — the type declarations describe the value range as including 0:

// apps/web/node_modules/twico-ui/dist/index.d.ts:1352-1355
/** Controlled value (0…count). */
value?: number;
/** Uncontrolled initial value. @default 0 */
defaultValue?: number;

Net effect: a pointer user can produce the value set {0, 1, …, count}, while a keyboard user is confined to {1, …, count}.

Corroborating detail: btnRefs.current[next - 1]?.focus() (index.mjs:8642) would index [-1] if the keyboard path ever produced next === 0, so keyboard-to-0 was clearly never designed into this handler.

Why it is a problem

  • Accessibility. For an optional/clearable rating field, a keyboard-only user cannot clear a previously-set value back to "no rating" — a capability that is trivially available to mouse users. This is a keyboard-parity gap for an interactive role="radiogroup" control.
  • Consistency. The two input modalities produce different reachable value sets from the same component, and the intended zero/reset semantics are undocumented — so consumers can't reason about what a keyboard user can express.

Proposal

Make the zero/reset behavior consistent across modalities and document it. Options (pick one and document it):

  1. Let keyboard reach 0 when clearing is intended. Add a Delete/Backspace (and/or Home → 0) branch that sets the value to 0. This mirrors an existing in-library pattern — Select already implements clear-on-Delete/Backspace:

    // apps/web/node_modules/twico-ui/dist/index.mjs:2480
    if (!open && clearable && current != null && (e.key === "Delete" || e.key === "Backspace")) { ... }

    Note the focus line at index.mjs:8642 must guard next === 0 (e.g. focus tabStar's button, not btnRefs.current[-1]).

  2. Or make click-to-clear opt-in. Introduce a clearable prop; when false, clicking the selected star does not reset to 0 (so both modalities agree on a floor of 1), and when true, expose the keyboard clear from option 1 as well.

Either way, add a JSDoc note on RatingProps describing whether/how the rating can be reset to 0.

twico-ui 1.4.0 · found during an exhaustive, source-grounded audit while building a production admin dashboard. Filed as part of a batch — feel free to merge/close any you consider covered.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreleased

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions