Rating

Star rating — read-only by default, interactive when given a change handler.

moleculesSource
import { Rating } from '@elirobinson/react/components/molecules/Rating';

Styles: @elirobinson/react/styles/molecules/Rating.css — already included when you import @elirobinson/react/styles.css.

Weeknight Pasta
U10 Practice Plan
Sheet-Pan Dinners
Show code
import { Rating } from '@elirobinson/react/components/molecules/Rating';

export default function Basic() {
  return (
    <div className="demo-col">
      <div className="demo-row">
        <span>Weeknight Pasta</span>
        <Rating value={4} />
      </div>
      <div className="demo-row">
        <span>U10 Practice Plan</span>
        <Rating value={5} />
      </div>
      <div className="demo-row">
        <span>Sheet-Pan Dinners</span>
        <Rating value={3} />
      </div>
    </div>
  );
}

When to use it

Rating switches its own mode based on whether you pass onValueChange: omit it and you get a read-only star display for showing an existing score (a recipe card, a testimonial); pass it and the stars become interactive buttons for collecting one. You don't choose the mode explicitly — the prop you pass decides it.

Interactive

Tap a star to rate this recipe.

Show code
import { useState } from 'react';

import { Rating } from '@elirobinson/react/components/molecules/Rating';

export default function Interactive() {
  const [value, setValue] = useState(0);

  return (
    <div className="demo-col">
      <Rating value={value} onValueChange={setValue} />
      <p>
        {value > 0 ? `You rated this recipe ${value} out of 5.` : 'Tap a star to rate this recipe.'}
      </p>
    </div>
  );
}

Props

PropTypeDefaultDescription
valuerequirednumber
maxnumber5
onValueChange((value: number) => void)

Also accepts all Omit<HTMLAttributes<HTMLDivElement>, 'role'> props.

Accessibility

  • Read-only mode (no onValueChange): renders role="img" on the outer element with aria-label="{value} out of {max} stars" — a screen reader hears the whole rating as one static image-like value, not a list of individual stars.
  • Interactive mode (onValueChange passed): each star is a native <button type="button"> with aria-label="Rate {n} out of {max} stars" and aria-pressed reflecting whether that star is filled.
  • value is clamped to the 0max range before it's used in the accessible label, so an out-of-range value (negative, or above max) never produces a nonsensical announcement.
  • Keyboard: in interactive mode, each star button is independently focusable — Tab moves star to star, Enter and Space activate whichever one has focus. There's no arrow-key traversal between stars; this is a row of buttons, not a slider.
  • Star buttons follow dense inline sizing (MUI/shadcn scale), not the 44px primary-control minimum.
  • The ref forwards to the outer element in both modes.

Do

  • Omit onValueChange for a rating you're only displaying — it gets you the cheaper role="img" markup.
  • Show the numeric value alongside the stars for anyone who can't easily count filled icons.
  • Keep max consistent across a list of ratings so stars are visually comparable.
  • Confirm a submitted rating with text, not just the filled stars — "You rated this 4 out of 5."

Don't

  • Pass onValueChange to a rating the user shouldn't be able to change, like an aggregate average.
  • Expect arrow-key adjustment — Rating is a button row, not an ARIA slider.
  • Use Rating for a binary like/dislike — a single toggle or Chip fits that better.
  • Rely on star color alone to convey the value; the aria-label already carries the number.