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):
-
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]).
-
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.
Current behavior
Ratinglets 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:
Keyboard is floored at 1 — every keyboard branch clamps to a minimum of 1, so a keyboard user can never reach 0:
There is no
Delete/Backspace/Escape/"0"branch in Rating'sonKeyDown(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:
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 producednext === 0, so keyboard-to-0 was clearly never designed into this handler.Why it is a problem
role="radiogroup"control.Proposal
Make the zero/reset behavior consistent across modalities and document it. Options (pick one and document it):
Let keyboard reach 0 when clearing is intended. Add a
Delete/Backspace(and/orHome → 0) branch that sets the value to 0. This mirrors an existing in-library pattern —Selectalready implements clear-on-Delete/Backspace:Note the focus line at index.mjs:8642 must guard
next === 0(e.g. focustabStar's button, notbtnRefs.current[-1]).Or make click-to-clear opt-in. Introduce a
clearableprop; 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
RatingPropsdescribing 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.