@mshafiqyajid/react-rating
Headless star-rating hook and styled component for React. Half-step support, hover preview, custom icons (hearts, thumbs, anything), full keyboard nav, CSS clip-path half-fill. Async onChange (Promise-driven pending state with revert on rejection). Form-input contract: name + hidden input, error / invalid / required.
Playground #
Rate this
Props
TSX
import { RatingStyled } from "@mshafiqyajid/react-rating/styled";
import "@mshafiqyajid/react-rating/styles.css";
<RatingStyled />Install #
npm install @mshafiqyajid/react-rating Quick start #
import { RatingStyled } from "@mshafiqyajid/react-rating/styled";
import "@mshafiqyajid/react-rating/styles.css";
const [value, setValue] = useState(0);
<RatingStyled
count={5}
value={value}
onChange={setValue}
showValue
/> Recipes #
Read-only display (e.g. average rating)
<RatingStyled
count={5}
value={4.5}
readOnly
showValue
label="Average rating"
/> Heart icon
<RatingStyled
count={5}
tone="danger"
icon={<HeartIcon />}
defaultValue={3}
/> Async submit (Promise-driven pending state)
Return a Promise from onChange to drive data-pending automatically. Stars are non-interactive while the promise is in flight; on rejection the value reverts.
<RatingStyled
count={5}
defaultValue={0}
onChange={async (next) => {
await fetch("/api/reviews", {
method: "POST",
body: JSON.stringify({ stars: next }),
});
}}
/> Per-star labels with onHover
const labels = ["Terrible", "Poor", "OK", "Good", "Great"];
const [hovered, setHovered] = useState<number | null>(null);
<>
<RatingStyled
count={5}
value={value}
onChange={setValue}
onHover={setHovered}
/>
<span>{labels[Math.ceil((hovered ?? value) - 1)] ?? ""}</span>
</> Headless #
import { useRating } from "@mshafiqyajid/react-rating";
const { items, rootProps, value, displayValue } = useRating({
count: 5,
defaultValue: 3,
allowHalf: true,
});
return (
<div {...rootProps} className="rate">
{items.map((item) => (
<span key={item.index} {...item.itemProps} data-fill={item.fill}>
{item.fill === 1 ? "★" : "☆"}
</span>
))}
<span>{displayValue} / 5</span>
</div>
); API #
RatingStyled
| Prop | Type | Default | Description |
|---|---|---|---|
| count | number | 5 | Number of stars. |
| value | number | — | Controlled value (0..count). |
| defaultValue | number | 0 | Uncontrolled initial. |
| onChange | (value: number) => void | Promise<void> | — | Fires on commit. Return a Promise to auto-drive a pending state; reverts on rejection. |
| onHover | (value: number | null) => void | — | Fires during hover. |
| allowHalf | boolean | true | Enable half-step values. |
| clearable | boolean | true | Re-clicking active value clears it. |
| readOnly | boolean | false | Display only, no interaction. |
| size | sm | md | lg | md | Size. |
| tone | neutral | primary | success | warning | danger | warning | Fill color. |
| icon | ReactNode | star SVG | Custom icon for both layers. |
| showValue | boolean | false | Show numeric value badge. |
| label | ReactNode | — | Label above. |
| hint | ReactNode | — | Helper text below. |