# React Rating Stars: Half Values, Hover and Keyboard

> Build React rating stars with half values, a hover preview, keyboard and touch support, read-only averages and the review summary the ratings feed.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Oct 19, 2026
- Category: [Component guides](https://wingo-ui.com/blog/category/components)
- Reading time: 9 min
- Canonical: https://wingo-ui.com/blog/react-rating-stars

## TL;DR

Good React rating stars need four things beyond a row of icons: half values picked by the pointer position, a hover preview that shows what a click will do, full keyboard control with a spoken value, and a touch scrub with 44px targets. The Wingo UI Rating covers these with allowHalf, labels and a controlled value, and the Reviews component turns the saved ratings into an average, a 5 to 1 breakdown and a filterable list.

You need React rating stars for a product review, a seller rating or a feedback form. Five star icons and a click handler take ten minutes. Then someone asks for half stars, a keyboard user tabs to the row and nothing moves, a phone user drags a thumb across the stars and the page scrolls instead, and the product page still has to turn 126 ratings into an average and a breakdown. This guide covers what a React star rating component has to handle, how the common packages compare, and full examples with the Wingo UI [Rating](https://wingo-ui.com/components/rating) and [Reviews](https://wingo-ui.com/components/reviews).

## What should a React rating component handle?

Six things. Five icons with a click handler cover only the first:

1. **Click or tap to rate**, with a way to clear the rating again.
2. **Half values** chosen by where the pointer lands on the star.
3. **A hover preview**, so the stars and the label show what a click will pick before it happens.
4. **Keyboard control** with a value a screen reader can speak ("Good", "3.5 of 5").
5. **A touch layout**: targets of at least 44px and a finger scrub that does not scroll the page.
6. **A read-only mode** for averages like 4.3, drawn as a fraction of a star.

The last one connects the input to the output. The stars people pick in a form are the same stars a product page shows next to the average, so one component should do both.

## How do the React star rating packages compare?

MUI Rating is the most complete if you already use Material UI, the small npm packages are fine for a quick display, and the Wingo UI Rating is a Pro source component built for touch and forms. Facts below come from each project's docs, README and the npm registry, checked October 2026.

| Option | What it is | Half values | Keyboard and screen readers | Last published |
|---|---|---|---|---|
| [MUI Rating](https://mui.com/material-ui/react-rating/) | Part of `@mui/material` (MIT) | `precision={0.5}` | Visually hidden radio inputs, one per star plus 0; arrow keys change the value | `@mui/material` 9.5.0, October 2026 |
| [@smastrom/react-rating](https://github.com/smastrom/react-rating) | Zero-dependency package (MIT) | Read-only floats, drawn to the nearest half | ARIA radio group; arrows move, Enter or Space confirms | 1.5.0, January 2024 |
| [react-rating-stars-component](https://github.com/ertanhasani/react-stars) | Fork of react-stars (ISC) | `isHalf`, on by default | `a11y` prop, on by default: arrow keys and number keys | 2.2.0, July 2020 |
| [Wingo UI Rating](https://wingo-ui.com/components/rating) | Pro source component, `npx wingo-ui@latest add rating` | `allowHalf`, by pointer position and 0.5 key steps | One slider with `aria-valuetext`, arrows, Page keys, Home, End and digits; touch scrub with 44px targets | Part of the library |

If your app is on Material UI, use MUI Rating: it is well documented and its hover label example (`onChangeActive`) covers the preview. If you only display a score, a small package or a read-only component is enough, though react-rating-stars-component has not been published since 2020. The rest of this guide is about the Wingo UI Rating.

## How do I add a React star rating to a form?

Give it a `label`, a controlled `value` and a `name`. With `name`, a hidden input submits the value, so a plain form or a Server Action reads it like any other field: `"0"` when empty, `"4.5"` with halves.

```bash
npx wingo-ui@latest add rating textarea button
```

```tsx
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Rating } from "@/components/ui/rating";
import { Textarea } from "@/components/ui/textarea";

const LABELS = ["Very poor", "Poor", "Okay", "Good", "Excellent"];

export function ReviewForm({ saveReview }: { saveReview: (data: FormData) => Promise<void> }) {
  const [rating, setRating] = useState(0);
  const [tried, setTried] = useState(false);

  return (
    <form
      noValidate
      className="flex flex-col gap-4"
      action={async (data) => {
        // the hidden input sends "0" while nothing is chosen
        if (Number(data.get("rating")) === 0) {
          setTried(true);
          return;
        }
        await saveReview(data);
      }}
    >
      <Rating
        label="How was your experience?"
        name="rating"
        value={rating}
        onValueChange={(next) => {
          setRating(next);
          if (next > 0) setTried(false);
        }}
        labels={LABELS}
        showLabel
        emptyLabel="Tap a star"
        required
        error={tried && rating === 0 ? "Choose a rating." : undefined}
      />
      <Textarea label="Comment" name="comment" maxLength={500} showCount minRows={2} />
      <Button type="submit">Send review</Button>
    </form>
  );
}
```

`labels` does two jobs: with `showLabel` the hovered or chosen label shows beside the stars, and each label is what a screen reader hears for that value. `required` only draws the mark after the label, so the check stays in your form, as above. `error` tints the empty stars red and sets `aria-invalid`. The label, description and error come from the free [Field](https://wingo-ui.com/components/field) anatomy, the same one every Wingo UI input uses.

Hover over the stars, then click one. Try Send rating without choosing anything first.

> Live demo (Rating): Hover the stars to preview a rating and its label, click to pick one, then press Send rating. Sending with no rating shows the error. Try it at [Rating](https://wingo-ui.com/components/rating) and install it with `npx wingo-ui@latest add rating`.

## How do half star ratings work in React?

Each star is split at its midpoint. With `allowHalf`, a pointer on the left half of a star gives x.5 and the right half gives the whole star (mirrored on right-to-left pages). The fill is the same icon drawn twice: an empty layer at low opacity and a colored layer clipped to a percentage, so a half star is a 50% clip of the full icon. That is why any lucide icon works through the `icon` prop, such as `<Heart />`. Another half star rating React pattern is a separate half glyph (react-rating-stars-component takes a `halfIcon` prop); the clip makes that unnecessary.

```tsx
<Rating aria-label="Rate this bike" allowHalf tone="warning" showValue locale="en-US" defaultValue={3.5} />
```

`tone="warning"` gives the classic amber stars; the default `neutral` is black in light mode and white in dark mode, and `color` takes any CSS color. `showValue` prints the number through `Intl` ("3.5" in en-US, "3,5" in de-DE) and reserves the width of the widest number, so the stars never shift while the value changes.

The hover preview follows the pointer. The part of the preview beyond the chosen value is dimmed, so you can see both the current rating and the one a click would set. After a click, the preview waits until the mouse reaches another value, so the result is not hidden under a still cursor.

> Live demo (Rating): allowHalf with amber stars: move the pointer across one star to see the half and the whole value, then use the arrow keys to step by 0.5. Try it at [Rating](https://wingo-ui.com/components/rating) and install it with `npx wingo-ui@latest add rating`.

## What do keyboard and screen reader users get?

The row is one Tab stop with `role="slider"`, `aria-valuemin`, `aria-valuemax`, `aria-valuenow` and `aria-valuetext`. The keys:

- **Right or Up** adds one step (0.5 with `allowHalf`), **Left or Down** removes one. Right and Left swap on right-to-left pages.
- **PageUp and PageDown** move a whole star.
- **Home** clears, or goes to the lowest value when `clearable={false}`. **End** jumps to the top.
- **Digits 1 to 9** pick a value directly. **0, Backspace and Delete** clear.

Radio groups are the other common pattern, and they are a fine choice: MUI uses hidden native radios, and @smastrom/react-rating an ARIA radio group. We picked one slider because the half steps, the Page keys and the digit shortcuts map onto a single value, and because `aria-valuetext` lets each value speak a word. Without `labels`, `getValueText` decides what is read (by default "3.5 of 5"); with them, whole values read the label ("Good"). Pass your own function to translate it.

A read-only rating is display only. It gets `role="img"` and a name from `getReadOnlyLabel` ("Rated 4.8 of 5"), leaves the tab order and draws fractions like 4.3 proportionally.

## How does the rating work on a phone?

A finger has no hover, so touch gets a scrub instead. Press on the stars, drag across them and lift to commit; the stars follow the finger, and `touch-action: none` on the row keeps the page from scrolling during the drag. Sliding past the first star clears the rating when `clearable` is on (the default).

On a coarse pointer every star becomes a 44px target, and the row spreads its icons to that pitch. If ten stars would overflow a narrow screen, the row shrinks the spacing between them so it still fits, and the number stays on the same line. The reasoning behind the 44px number is in our post on [minimum touch target size in React and Tailwind](https://wingo-ui.com/blog/minimum-touch-target-size-react-tailwind). On release, the filled stars pop on a small staggered spring; with reduced motion turned on in the system settings they fill without the animation.

## How do I show review stars and the summary they feed?

Collect ratings with the Rating, show them with the Reviews component. React review stars on a product page are the same Rating in read-only mode, placed inside a summary and a list. Reviews takes the list of reviews and a `summary` with the average, the count and the `distribution` as counts for 5, 4, 3, 2 and 1 stars. It renders the average with read-only half stars, five bars that filter the list on tap, a sort menu (a bottom sheet on phones), photos that open full screen, and Helpful votes that roll back if saving fails.

```tsx
import { Reviews, type ReviewData } from "@/components/ui/reviews";

export function ProductReviews({ reviews }: { reviews: ReviewData[] }) {
  return (
    <Reviews
      reviews={reviews}
      summary={{ average: 4.6, count: 126, distribution: [92, 21, 7, 3, 3] }}
      tone="warning"
      locale="en-US"
      onHelpfulChange={async (id, helpful) => {
        const res = await fetch(`/api/reviews/${id}/helpful`, { method: helpful ? "POST" : "DELETE" });
        if (!res.ok) throw new Error("Vote failed");
      }}
    />
  );
}
```

Pass `summary` from the server when the list is paged. Without it, Reviews calls `summarizeRatings(reviews)`, which only sees the loaded page: ratings round to the nearest star for the bars and the average keeps two decimals. The `ReviewsSummary` part renders the summary on its own, for a seller card or a product header.

Tap the 4 star bar, then tap it again. Open the sort menu and a review photo.

> Live demo (Reviews): Tap a bar in the breakdown to show only those reviews and tap it again to see all. Sort the list, open a photo full screen and mark a review as helpful. Try it at [Reviews](https://wingo-ui.com/components/reviews) and install it with `npx wingo-ui@latest add reviews`.

### How do review stars show up in Google?

Through `AggregateRating` structured data in the page; the component itself adds none. Google's [review snippet documentation](https://developers.google.com/search/docs/appearance/structured-data/review-snippet) requires `ratingValue` and at least one of `ratingCount` or `reviewCount`, assumes a best rating of 5 and a worst of 1 when you omit them, and excludes pages where a LocalBusiness or Organization controls the reviews about itself (checked October 2026). The same numbers you pass to `summary` fill it:

```tsx
const jsonLd = {
  "@context": "https://schema.org",
  "@type": "Product",
  name: "City bike",
  aggregateRating: { "@type": "AggregateRating", ratingValue: 4.6, reviewCount: 126 },
};
```

Rendering it safely in the App Router is covered in the [Next.js JSON-LD guide](https://wingo-ui.com/blog/nextjs-json-ld).

## What are the common star rating pitfalls?

Five, and each one is fixed by a prop or by sending the right numbers from the server:

- **No accessible name.** A rating without `label` needs an `aria-label`, or a screen reader announces an unnamed slider.
- **Halves with per-value icons.** `icons` (one face per value, like Frown, Meh, Smile, Laugh) lights up one icon at a time and ignores `allowHalf`. Use `icon` for halves.
- **Rounding the average.** A 4.4 drawn as four stars hides real information. Use a read-only rating, which draws the fraction.
- **Saving on every hover.** `onValueChange` fires on commit only: on the release of a tap or scrub and on each key press. Save there, never in a pointer handler.
- **Summaries from one page.** `summarizeRatings` on the first page of reviews is not the product's average. Send the totals from the server.

## How do I add it to my project?

Run `npx wingo-ui@latest add rating` (or `reviews`). The CLI copies the source into `components/ui`, adds the hooks and helpers it imports, and installs `motion` and `lucide-react`. Rating and Reviews are Pro components ([pricing](https://wingo-ui.com/pricing)); the [Textarea](https://wingo-ui.com/components/textarea), Field and Button used in the form above are free. For the rest of the form, the [React form components guide](https://wingo-ui.com/blog/react-form-components-guide) applies the same keyboard, touch and error rules to every input, and the [component guides category](https://wingo-ui.com/blog/category/components) has more single-component deep dives.

## Components in this post

- [Rating](https://wingo-ui.com/components/rating) (Pro): A React star rating to rate or show a score with stars or any icon: half values, a hover preview, a label per value and a fill that pops. Install: `npx wingo-ui@latest add rating`
- [Reviews](https://wingo-ui.com/components/reviews) (Pro): A React reviews section for products and sellers: average, stars, a 5 to 1 filter breakdown, and sortable reviews with photos and replies. Install: `npx wingo-ui@latest add reviews`

## FAQ

### How do I make a half star rating in React?

Split each star at its midpoint: a click on the left half gives x.5, the right half a whole star, and the fill is the same icon clipped to a percentage. In the Wingo UI Rating that is the allowHalf prop, which also makes the arrow keys move by 0.5.

### Should a star rating be a radio group or a slider?

Both are used. MUI Rating and @smastrom/react-rating build a radio group, while the Wingo UI Rating is one slider with aria-valuetext, so a screen reader hears "Good" or "3.5 of 5" instead of a bare number. Whichever you pick, give every value a text label and keep the current value announced.

### How do I show an average like 4.3 stars?

Render the rating read-only and let it draw the fraction. A read-only Wingo UI Rating paints 4.3 proportionally, keeps one decimal in the number and is announced as "Rated 4.3 of 5".

### Can Google show my review stars in search results?

It can, if the page has valid AggregateRating or Review structured data with ratingValue and a ratingCount or reviewCount. Pages where a LocalBusiness or Organization reviews itself are not eligible for the star feature.

### Is the Wingo UI Rating free?

No. Rating and Reviews are Pro components. The Field, Textarea and Button they are used with in this guide are free.

---

Source: https://wingo-ui.com/blog/react-rating-stars
