react ~19 KB 0 deps v0.3.0 ↗ GitHub ↗

@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

PropTypeDefaultDescription
countnumber5Number of stars.
valuenumberControlled value (0..count).
defaultValuenumber0Uncontrolled 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) => voidFires during hover.
allowHalfbooleantrueEnable half-step values.
clearablebooleantrueRe-clicking active value clears it.
readOnlybooleanfalseDisplay only, no interaction.
sizesm | md | lgmdSize.
toneneutral | primary | success | warning | dangerwarningFill color.
iconReactNodestar SVGCustom icon for both layers.
showValuebooleanfalseShow numeric value badge.
labelReactNodeLabel above.
hintReactNodeHelper text below.
Edit this page on GitHub