Skip to content
cennge-ui
GitHubv0.3.1, changelog

Rating

Stars to give a score and to show one: whole or half, by pointer or keyboard.

Rate payload-cache
Not rated yet
Docs quality
3.5 of 5, in half stars
4.2 average from 5 ratings
  1. Hover to preview, click to rate; a second click on the same star clears it. The average below follows.
  2. Tab lands on the chosen star, or the first before a rating; the arrows move and choose without wrapping round; Backspace clears.
  3. Docs quality takes half stars: the left half of a star picks .5, and the arrows step by half a star.
Live: the component itself, not a picture of it.

Field marks

Import
import { Rating } from "cennge-ui";
Size
3.8 KB gzip for this import, its dependencies aside
Pattern
WAI-ARIA radio group of stars; a slider with half stars or more than seven; an image when read-only
Keyboard
Arrow left Arrow right HomeEndSpaceBackspace Delete PageUpPageDown
State
Controlled with value and onValueChange, 0 for no rating; or uncontrolled with defaultValue
SSR
Rendered on the server in the Next.js check; hydrates without a warning
Tap size
Every star is at least 24 × 24 px, read-only ones aside
Tests
Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: form, rating

When to use#

Use a Rating to ask for a score on a short scale, five stars by default, and to show a score next to something: a release, a repository, a review. The same component reads both ways: interactive while people rate, isReadOnly when it shows an average such as 4.3.

Similar#

  • Slider picks a number on a longer or finer scale.
  • Radio names each choice in words; a Rating's choices are a count of stars.

Rate something#

The value is the number of stars, and 0 means no rating yet. A click on the chosen star, Backspace or Delete sets it back to 0; pass isClearable={false} to keep a rating once given.

Rate cennge-ui 0.3.1
value is 0: no rating
basic.tsx
import { useState } from "react";
import { Rating } from "cennge-ui";

export default function RateRelease() {
  const [stars, setStars] = useState(0);
  return (
    <Rating
      label="Rate cennge-ui 0.3.1"
      value={stars}
      onValueChange={setStars}
      description={stars === 0 ? "value is 0: no rating" : `value is ${stars}`}
    />
  );
}

Half stars#

With allowHalf, the left half of a star picks 3.5 and the right half 4. The control becomes a single slider: the arrows step by half a star, Page Up and Page Down by a whole one. A count above seven makes a slider too: past seven choices, a slider reads better than a row of radios.

Docs quality
half.tsx
import { Rating } from "cennge-ui";

export default function DocsQuality() {
  return <Rating label="Docs quality" allowHalf defaultValue={3.5} size="lg" />;
}

Showing an average#

isReadOnly shows any value, fractions included, as one picture: the last star fills to the fraction. It takes no focus and no clicks, and screen readers read its name with the value, "Average rating: 4.3 stars".

4.3ai-niche-scout
read-only.tsx
import { Rating } from "cennge-ui";

export default function AverageRating() {
  return (
    <div className="flex items-center gap-2 text-sm">
      <Rating isReadOnly value={4.3} aria-label="Average rating" size="sm" />
      <span className="font-semibold tabular-nums">4.3</span>
      <span className="text-muted">ai-niche-scout</span>
    </div>
  );
}

Your own icon#

icon replaces the star and is drawn twice with currentColor: in color when filled, muted when empty. Pass emptyIcon for a different empty shape, and getValueLabel so the spoken names match the picture.

How much do you like spredo-crypto-screener?
icon.tsx
import { Rating } from "cennge-ui";

const HEART = "M12 20.5s-7.5-4.6-7.5-10.1A4.4 4.4 0 0 1 12 7.6a4.4 4.4 0 0 1 7.5 2.8c0 5.5-7.5 10.1-7.5 10.1Z";

function Heart({ filled }: { filled?: boolean }) {
  return (
    <svg viewBox="0 0 24 24" aria-hidden="true" className="block size-full">
      <path d={HEART} fill={filled ? "currentColor" : "none"} stroke="currentColor" strokeWidth={1.75} strokeLinejoin="round" />
    </svg>
  );
}

export default function FavouriteRating() {
  return (
    <Rating
      label="How much do you like spredo-crypto-screener?"
      count={3}
      icon={<Heart filled />}
      emptyIcon={<Heart />}
      color="danger"
      defaultValue={2}
      getValueLabel={(value) => (value === 0 ? "No hearts" : `${value} ${value === 1 ? "heart" : "hearts"}`)}
    />
  );
}

In a form#

With a name, the rating is submitted as a number, or as an empty string without one. isRequired adds the asterisk, and aria-required on a row of star radios, but the browser does not check it, so check the value yourself. Here the error goes back through the Form, which shows it under the stars and moves focus to them; the next rating clears it.

Rate payload-cache 1.4.0*

Nothing sent yet

in-a-form.tsx
import { useState } from "react";
import { Button, Form, Rating, Textarea } from "cennge-ui";

export default function ReviewForm() {
  const [errors, setErrors] = useState<Record<string, string>>();
  const [sent, setSent] = useState<string | null>(null);

  return (
    <Form
      validationErrors={errors}
      onSubmit={(event) => {
        event.preventDefault();
        const rating = new FormData(event.currentTarget).get("rating");
        if (!rating) {
          setErrors({ rating: "Rate the release before sending" });
          return;
        }
        setErrors(undefined);
        setSent(String(rating));
      }}
      className="flex w-full max-w-96 flex-col gap-4"
    >
      <Rating name="rating" label="Rate payload-cache 1.4.0" isRequired />
      <Textarea name="review" label="What changed for you?" minRows={2} />
      <Button type="submit" color="primary" className="self-start">
        Send review
      </Button>
      <p aria-live="polite" className="text-sm text-muted">
        {sent === null ? "Nothing sent yet" : `Sent rating=${sent}`}
      </p>
    </Form>
  );
}

Accessibility#

Up to seven stars form a radiogroup, one radio per star named "1 star", "2 stars" and so on, with the chosen star, or the first before a rating, as the only tab stop. The arrows stop at the first and last star rather than wrapping round; clearing has keys of its own. Translate the names with getValueLabel. The empty stars keep a 3:1 contrast with the page in both themes, and hover previews never change the value.

Props#

PropTypeDefault
label
React.ReactNodeVisible label. Without it, pass aria-label.
–
description
React.ReactNode
–
errorMessage
React.ReactNode
–
value
number0 means no rating yet.
–
defaultValue
number
–
onValueChange
(value: number) => void
–
count
number
5
allowHalf
booleanHalf stars: the left half of a star picks 3.5, the right half 4.
–
isClearable
booleanA click on the chosen star, or Backspace / Delete, clears the rating back to 0.
true
isReadOnly
booleanShows a value, such as an average of 4.3, as a picture with a text name. Takes no input.
–
isDisabled
boolean
–
isRequired
boolean
–
isInvalid
boolean
–
getValueLabel
(value: number) => stringSpoken name of a value, for each star and for the read-only picture.
(value) => value === 0 ? "No rating" : `${value} stars`
icon
React.ReactNodeReplaces the star, e.g. a heart. Drawn twice with currentColor: in the colour when filled, muted when empty.
–
emptyIcon
React.ReactNodeThe empty version of icon.
icon
color
"default""primary""secondary""success""warning""danger""info"
"warning"
size
"sm""md""lg"
"md"
name
stringForm field name; the value is submitted as a number, or empty without a rating.
–
className
string
–
aria-label
string
–