npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

star-rating-x

v5.0.0

Published

A fully-featured, accessible, customisable React rating library. 10 components, 5 hooks, TypeScript, zero dependencies.

Downloads

605

Readme

⭐ star-rating-x

The most complete React rating library. 10 Components · 5 Hooks · TypeScript · RTL · Zero dependencies.

npm version npm downloads license types

Live Demo · npm · GitHub


What's inside

| | Name | Description | | -------------- | ---------------------- | --------------------------------------- | | Components | StarRating | Core interactive star rating | | | StarRatingInput | Form-ready wrapper — RHF · Formik · Zod | | | StarRatingTooltip | Stars with rich popup tooltip on hover | | | RatingGroup | Rate multiple categories at once | | | RatingDistribution | Amazon-style bar chart breakdown | | | RatingSummary | Full review summary card | | | RatingWall | Masonry grid of review cards | | | RatingBadge | Compact inline ⭐ 4.8 (1.2k) badge | | | RatingPrompt | Smart timed/scroll/manual rating prompt | | Hooks | useRating | Manage rating state externally | | | useRatingField | Standalone validation — required · min | | | useRatingAnalytics | Average · NPS · trend · distribution | | | useRatingPersistence | Auto-save to localStorage with TTL | | | useRatingExport | Export ratings as CSV or JSON |


Installation

npm install star-rating-x
# or
yarn add star-rating-x

Import the CSS once at the top of your app:

import "star-rating-x/styles.css";

Next.js setup

App Router (Next.js 13+)

// app/layout.tsx
import "star-rating-x/styles.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
// app/components/ProductRating.tsx
"use client"; // ← required — StarRating uses state & browser events

import { StarRating } from "star-rating-x";

export default function ProductRating() {
  const [rating, setRating] = useState(0);
  return <StarRating value={rating} onChange={setRating} />;
}

Pages Router (Next.js 12 and below)

// pages/_app.tsx
import "star-rating-x/styles.css";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

Quick Start

import { StarRating } from "star-rating-x";
import "star-rating-x/styles.css";

function App() {
  const [rating, setRating] = useState(0);
  return <StarRating value={rating} onChange={setRating} />;
}

StarRating — Props

| Prop | Type | Default | Description | | ------------------- | ------------------ | -------------- | -------------------------------------------- | | value | number | — | Controlled value (0–count) | | defaultValue | number | 0 | Uncontrolled initial value | | count | number | 5 | Total number of stars | | precision | 1 \| 0.5 | 1 | Whole or half-star | | size | number \| string | 32 | Star size in px | | gap | number | 6 | Gap between stars in px | | shape | StarShape | "star" | Built-in icon shape | | theme | ThemeName | "gold" | Colour theme | | filledColor | string | — | Override filled colour | | emptyColor | string | — | Override empty colour | | strokeColor | string | — | Override stroke colour | | strokeWidth | number | 1.5 | SVG stroke width | | character | string \| fn | — | Emoji, text, or render fn | | customIcon | string \| fn | — | Custom SVG path or render fn | | filledGradient | string[] | — | Multi-stop gradient fill | | gradientDirection | string | "horizontal" | "horizontal" / "vertical" / "diagonal" | | compareValue | number | — | Ghost rating shown behind main stars | | compareLabel | string | "avg" | Label next to compareValue | | mountAnimation | boolean | false | Count-up on first render | | mountDuration | number | 800 | Mount animation duration ms | | celebrateOnMax | boolean | false | Confetti burst at max rating | | confettiColors | string[] | — | Confetti particle colours | | glowEffect | boolean | false | Glow around filled stars | | glowIntensity | number | 0.5 | Glow strength 0–1 | | loading | boolean | false | Skeleton shimmer placeholder | | allowUndo | boolean | false | Undo toast after each change | | undoTimeout | number | 4000 | Undo window duration ms | | onUndo | fn(prev) | — | Called when user undoes | | onRatingComplete | fn(v) | — | Fires after user finishes (debounced) | | debounceMs | number | 0 | Debounce delay for onRatingComplete | | readOnly | boolean | false | Disable interaction, keep styling | | disabled | boolean | false | Disable + grey out | | allowClear | boolean | true | Re-click current value to reset to 0 | | showValue | boolean | false | Show numeric label | | tooltips | string[] | — | Custom tooltip per star | | animation | AnimationType | "bounce" | Click animation | | direction | "ltr" \| "rtl" | "ltr" | Layout direction | | highlightSelected | boolean | false | Ring on selected star | | label | string | "Rating" | ARIA label | | onChange | fn(v) | — | Value change callback | | onHoverChange | fn(v\|null) | — | Hover change callback |


Themes

"gold" · "fire" · "ocean" · "neon" · "rose" · "mono" · "violet" · "sunset" · "mint"

<StarRating theme="fire"   />
<StarRating theme="ocean"  />
<StarRating theme="violet" />

// Override colours directly:
<StarRating filledColor="#FF6B6B" emptyColor="#FFE0E0" strokeColor="#CC0000" />

// Multi-stop gradient fill:
<StarRating filledGradient={["#FBBF24", "#F97316", "#EF4444"]} />
<StarRating filledGradient={["#38BDF8", "#6366F1"]} gradientDirection="diagonal" />

Shapes

"star" · "heart" · "circle" · "diamond" · "thumb" · "flag" · "lightning" · "flower"

<StarRating shape="heart"     theme="rose"   />
<StarRating shape="lightning" theme="neon"   />
<StarRating shape="diamond"   theme="violet" />

Animations

"bounce" (default) · "pulse" · "wiggle" · "pop" · "none"

<StarRating animation="wiggle" />

Half-star Precision

<StarRating precision={0.5} defaultValue={3.5} />

Character / Emoji Mode

// Same emoji for every star
<StarRating character="😊" count={5} />

// Different emoji per position via render fn
const emojis = ["😡", "😕", "😐", "😊", "🤩"];
<StarRating character={({ index, fill }) => fill > 0 ? emojis[index] : "⬜"} />

// Any text or symbol
<StarRating character="✦" theme="violet" />

Custom SVG Icon

// SVG path string (viewBox 0 0 24 24)
<StarRating customIcon="M12 2C8 2 4 6 4 10c0 5 8 12 8 12s8-7 8-12c0-4-4-8-8-8z" />

// Full render function
<StarRating
  customIcon={({ fill, fillColor, size }) => (
    <svg viewBox="0 0 24 24" width={size} height={size}>
      <circle cx="12" cy="12" r="10" fill={fillColor} />
    </svg>
  )}
/>

Compare Mode

<StarRating
  value={myRating}
  onChange={setMyRating}
  compareValue={4.2}
  compareLabel="community avg"
  showValue
/>

Mount Animation

<StarRating value={4} mountAnimation mountDuration={800} readOnly showValue />

// Replay by changing the key:
<StarRating key={replayKey} value={4} mountAnimation readOnly />

Glow Effect

<StarRating glowEffect glowIntensity={0.6} theme="gold" />

Skeleton Loading

<StarRating loading={isLoading} value={rating} />

Undo Last Rating

<StarRating
  value={rating}
  onChange={setRating}
  allowUndo
  undoTimeout={5000}
  onUndo={(prev) => console.log("Reverted to", prev)}
/>

onRatingComplete + Debounce

// Fires only after user stops changing — perfect for API calls
<StarRating onRatingComplete={(value) => submitToAPI(value)} debounceMs={800} />

Confetti on Max Rating

<StarRating
  celebrateOnMax
  confettiColors={["#FBBF24", "#F97316", "#EC4899", "#8B5CF6"]}
/>

RTL Support

<StarRating direction="rtl" />

Read-only Display

<StarRating value={4.5} precision={0.5} readOnly showValue />

StarRatingInput — Form Field

import { StarRatingInput } from "star-rating-x";

<StarRatingInput
  label="Rate your experience"
  helperText="Your feedback helps us improve"
  required
  errorMessage={errors.rating?.message}
/>;

React Hook Form + Zod

import { useForm, Controller } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { StarRatingInput } from "star-rating-x";

const schema = z.object({
  rating: z.number().min(1, "Please select at least 1 star"),
});

const { control } = useForm({ resolver: zodResolver(schema) });

<Controller
  name="rating"
  control={control}
  render={({ field, fieldState }) => (
    <StarRatingInput
      {...field}
      label="Product rating"
      required
      errorMessage={fieldState.error?.message}
    />
  )}
/>;

StarRatingTooltip

import { StarRatingTooltip } from "star-rating-x";

<StarRatingTooltip
  tooltips={["Terrible 😡", "Bad 😕", "Okay 😐", "Good 😊", "Amazing! 🤩"]}
  tooltipRenderer={({ value, label }) => (
    <span>
      <strong>{value}★</strong> — {label}
    </span>
  )}
  tooltipPlacement="top"
/>;

RatingGroup

import { RatingGroup } from "star-rating-x";

<RatingGroup
  categories={[
    { key: "quality", label: "Food Quality" },
    { key: "service", label: "Service" },
    { key: "value", label: "Value" },
  ]}
  values={ratings}
  onChange={(key, val, allValues) => setRatings(allValues)}
  showAverage
  overallLabel="Total Score"
  dividerColor="#334155"
  showValues
  theme="gold"
/>;

| Prop | Type | Default | Description | | ------------------- | ----------------------- | ----------- | ---------------------------- | | categories | {key,label}[] | required | Categories to rate | | values | Record<string,number> | — | Controlled values | | defaultValues | Record<string,number> | {} | Uncontrolled initial values | | onChange | fn(key,val,all) | — | Change callback | | showAverage | boolean | false | Show overall average row | | overallLabel | string | "Overall" | Average row label text | | averagePrecision | 1\|0.5 | 0.5 | Average row precision | | showValues | boolean | false | Show numeric value per row | | labelWidth | number | 120 | Label column width px | | rowGap | number | 12 | Gap between rows px | | dividerColor | string | "#e5e7eb" | Divider colour above average | | averageLabelStyle | CSSProperties | — | Style for overall label |


RatingDistribution

import { RatingDistribution } from "star-rating-x";

<RatingDistribution
  data={{ 5: 842, 4: 321, 3: 98, 2: 34, 1: 22 }}
  theme="gold"
  showCount
  onFilter={(star) => setFilter(star)}
  activeFilter={filter}
/>;

RatingSummary

import { RatingSummary } from "star-rating-x";

<RatingSummary
  average={4.3}
  total={1317}
  distribution={{ 5: 842, 4: 321, 3: 98, 2: 34, 1: 22 }}
  reviews={recentReviews}
  showReviews
  maxReviews={3}
  onWriteReview={() => openModal()}
  theme="gold"
/>;

RatingWall

import { RatingWall } from "star-rating-x";

<RatingWall
  reviews={reviews}
  columns={3}
  maxItems={9}
  showMore
  sortBy="highest"
  onHelpful={(r) => markHelpful(r.id)}
  theme="gold"
/>;

Review object shape:

{
  id?: string | number;
  author?: string;
  avatar?: string;
  rating: number;
  title?: string;
  text?: string;
  date?: string;
  verified?: boolean;
  helpful?: number;
}

RatingBadge

import { RatingBadge } from "star-rating-x";

// Sizes: "xs" | "sm" | "md" | "lg"
<RatingBadge value={4.8} count={1247} theme="gold" size="md" />
<RatingBadge value={4.1} compact theme="ocean" size="sm" />

RatingPrompt

import { RatingPrompt } from "star-rating-x";

// Show after 5 seconds
<RatingPrompt
  trigger="time"
  delay={5000}
  message="Enjoying the app?"
  onRate={(value) => submitRating(value)}
  onDismiss={() => setShow(false)}
  placement="bottom-right"
  theme="gold"
/>

// Show after 60% scroll
<RatingPrompt trigger="scroll" scrollPercent={60} onRate={fn} onDismiss={fn} />

// Manual
<RatingPrompt trigger="action" visible={show} onRate={fn} onDismiss={fn} />

useRating

const { value, handlers, reset } = useRating({ initialValue: 3 });
<StarRating value={value} {...handlers} />;

useRatingField

const rating = useRatingField({ required: true, minValue: 1 });

<StarRatingInput
  {...rating.field}
  label="Your rating"
  required
  errorMessage={rating.errorMessage}
/>;

| Return | Description | | -------------- | ----------------------------------------------- | | field | {value, onChange, onBlur} — spread onto input | | handlers | {value, onChange} — spread onto StarRating | | error | Validation error string or null | | touched | Has user interacted | | isDirty | Value changed from initial | | isValid | No errors | | showError | touched && !!error | | errorMessage | Error when touched, else null | | reset | Restore initial state |


useRatingAnalytics

const stats = useRatingAnalytics([5, 4, 5, 3, 5, 4, 2, 5]);

// stats.average         → 4.1
// stats.nps             → 62
// stats.trend           → "improving"
// stats.percentPositive → 75%
// stats.distribution    → { 5:4, 4:2, 3:1, 2:1, 1:0 }

useRatingPersistence

const { value, onChange } = useRatingPersistence(`product-${id}`, {
  defaultValue: 0,
  ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
});

<StarRating value={value} onChange={onChange} />;

useRatingExport

const { exportCSV, exportJSON, copyJSON } = useRatingExport(ratingsData, {
  filename: "reviews",
  csvFields: ["id", "author", "rating", "date"],
});

<button onClick={exportCSV}>Download CSV</button>
<button onClick={exportJSON}>Download JSON</button>

TypeScript

Full .d.ts declarations ship with the package.

import type {
  StarRatingProps,
  StarRatingInputProps,
  StarRatingTooltipProps,
  RatingGroupProps,
  RatingDistributionProps,
  RatingSummaryProps,
  RatingWallProps,
  RatingBadgeProps,
  RatingPromptProps,
  ReviewItem,
  RatingCategory,
  StarShape,
  ThemeName,
  AnimationType,
  BadgeSize,
  PromptTrigger,
  PromptPlacement,
  AnalyticsResult,
  IconRenderContext,
  CharacterRenderContext,
  UseRatingResult,
  UseRatingFieldResult,
  UseRatingPersistenceResult,
  UseRatingExportResult,
} from "star-rating-x";

Accessibility

  • role="slider" with aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext
  • Full keyboard navigation — Home End
  • aria-label per star (or custom tooltips)
  • aria-required + aria-invalid on StarRatingInput
  • aria-live="polite" on undo toast
  • Focus-visible ring
  • Respects prefers-reduced-motion

Changelog

v5.0.0

  • RatingBadge — compact inline badge
  • RatingSummary — full Amazon-style review summary card
  • RatingWall — masonry grid of review cards
  • RatingPrompt — smart timed / scroll / manual rating prompt
  • useRatingAnalytics — average, NPS, trend, distribution
  • useRatingPersistence — localStorage with TTL
  • useRatingExport — CSV / JSON export
  • glowEffect + glowIntensity props
  • loading skeleton shimmer prop
  • allowUndo + undoTimeout + onUndo props
  • onRatingComplete + debounceMs props

v4.0.0

  • filledGradient + gradientDirection — multi-stop gradient fill
  • compareValue + compareLabel — ghost comparison rating
  • celebrateOnMax + confettiColors — confetti burst at max
  • RatingGroupoverallLabel, dividerColor, averageLabelStyle, rowGap

v3.0.0

  • StarRatingInput — form-ready field (RHF · Formik · Zod)
  • StarRatingTooltip — rich popup tooltip component
  • RatingDistribution — Amazon-style bar chart
  • RatingGroup multi-category component
  • useRatingField — standalone validation hook

v2.0.0

  • character prop — emoji, text, or render function
  • customIcon prop — custom SVG path or render function
  • mountAnimation + mountDuration — count-up on render

v1.0.1

  • 📦 npm metadata — homepage, bugs, funding, author URL

v1.0.0

  • 🎉 Initial release — 9 themes, 8 shapes, 4 animations, half-star, RTL, keyboard nav, useRating

License

MIT © Abdelrahman Ayman