# React Awesome Stars Rating
[](https://www.npmjs.com/package/react-awesome-stars-rating)
[](https://github.com/fedoryakubovich/react-awesome-stars-rating/actions/workflows/ci.yml)
[](https://github.com/fedoryakubovich/react-awesome-stars-rating/blob/main/LICENSE)
An accessible star rating component for React. Half stars from pointer position, full keyboard control, any scale or palette — in approximately 3 kB gzipped with no runtime dependencies.
**[Live demo](https://react-awesome-stars-rating-orpin.vercel.app/)** · **[Storybook](https://react-awesome-stars-rating-orpin.vercel.app/storybook/)**

## Highlights
- Approximately 3 kB gzipped (ESM under 3 kB), no runtime dependencies
- Half-star precision from pointer position, plus arrow-key stepping
- `role="slider"` with live `aria-valuenow` / `aria-valuetext`, verified with axe-core
- ESM + CJS with correct types for each, and a UMD bundle for CDN use
- Ships `'use client'`, so it drops into a React Server Component tree unwrapped
- Works with React Hook Form, Formik and TanStack Form
## Requirements
React 18 or React 19 (`react` and `react-dom` are peer dependencies). The
published browser package has no Node runtime requirement. Installed-consumer
CI verifies React 18 and 19 across Node 22, 24 and 26, including server
rendering and hydration.
Upgrading from 0.x? See the [migration guide](https://github.com/fedoryakubovich/react-awesome-stars-rating/blob/main/MIGRATION.md).
## Installation
```bash
npm install react-awesome-stars-rating
```
### CDN
React 19 no longer publishes UMD builds, so load the package as a module:
```html
```
A UMD bundle is still published at `dist/index.umd.cjs` for pages that already
expose React as a global, and expects `window.React` to be present.
## Usage
```tsx
import { useState } from 'react';
import ReactStarsRating from 'react-awesome-stars-rating';
export default function Example() {
const [rating, setRating] = useState(3.5);
return ;
}
```
Use `value` with a state-updating `onChange` when your application owns the
rating. For a self-managed input, use ``.
Do not switch between controlled and uncontrolled usage during an input's lifetime.
### Clear a rating
Keep zero as the empty rating and provide an explicitly labeled button:
```tsx
const [rating, setRating] = useState(0);
<>
>;
```
### Display an average
Use `readOnly` for aggregate scores, with visible text so the number and review
count are available without interpreting the star graphic:
```tsx
<>
4.3 out of 5 · 128 reviews
>
```
With `isHalf`, the SVG displays partial fill for fractional values; editing
still selects half-star steps. Do not round an average just to display it.
### Next.js App Router
The published entry point includes `"use client"`. A Server Component can render
a read-only rating with serializable props. Put state and callbacks inside your
own Client Component (`"use client"` at the top of its file), as in the usage
example above; do not pass ordinary callback functions from a Server Component.
## Props
| Name | Description | Type | Default |
| :--------------- | :---------------------------------------- | :----------------------------------------- | :-------------- |
| id | Identifier | `string` | auto-generated |
| value | Controlled current value | `number` | `undefined` |
| defaultValue | Initial uncontrolled value | `number` | `0` |
| onChange | Called when value changes | `(value: number) => void` | `() => {}` |
| onBlur / onFocus | Native slider focus callbacks | `React.FocusEventHandler` | `undefined` |
| dir | Layout, fill and horizontal key direction | `'ltr' \| 'rtl'` | `'ltr'` |
| name | Hidden native form input name | `string` | `undefined` |
| form | Associated form ID | `string` | `undefined` |
| disabled | Disable interaction and form submission | `boolean` | `false` |
| readOnly | Prevent editing | `boolean` | `false` |
| isEdit | Deprecated inverse editing switch | `boolean` | `true` |
| isHalf | Allow half stars | `boolean` | `true` |
| count | Number of stars | `number` | `5` |
| size | Star size (px) | `number` | `25` |
| starGap | Gap between stars | `number` | `0` |
| className | Container class | `string` | `''` |
| style | Container style and CSS variables | `ReactStarsRatingStyle` | `undefined` |
| primaryColor | Active star color | `string` | `'orange'` |
| secondaryColor | Inactive star color | `string` | `'grey'` |
| hoverColor | Optional pointer-preview color | `string` | `undefined` |
| isArrowSubmit | Arrow keys trigger `onChange` immediately | `boolean` | `false` |
| ariaLabel | Accessible label (if no `ariaLabelledBy`) | `string` | `'Star rating'` |
| ariaLabelledBy | ID of element that labels the control | `string` | `undefined` |
| getValueText | Formats accessible value text | `(value, count) => string` | English text |
## Keyboard and accessibility
The control renders as a single `role="slider"` element with `aria-valuemin`, `aria-valuemax`, `aria-valuenow` and a readable `aria-valuetext` ("3.5 of 5"). Values outside `0…count` are clamped before they are reported.
| Key | Behaviour |
| :-------------- | :------------------------------------------------------------ |
| `←` / `↓` | Decrease by half a star (`isHalf`) or a whole star |
| `→` / `↑` | Increase by half a star (`isHalf`) or a whole star |
| `Home` / `End` | Move to the minimum (`0`) or maximum (`count`) |
| `Enter` | Report the current value through `onChange` |
| `Tab` away/back | Report an unsubmitted keyboard change and remain re-focusable |
With `isArrowSubmit`, each arrow step reports through `onChange` immediately rather than waiting for `Enter` or blur.
Pointer changes commit only when a primary, left-button gesture starts on the
control and completes. Right/middle clicks, unrelated releases and canceled
gestures do not submit a rating. Touch dragging uses the same gesture tracking.
`onFocus` and `onBlur` receive native React focus events without replacing the
internal focus handling. A pending keyboard change is committed before `onBlur`,
so form libraries can mark the field touched with its latest value.
When `readOnly` is `true` the control is inert regardless of `isArrowSubmit`: no hover preview, no keyboard, `tabIndex={-1}`, and `aria-readonly="true"`. The older `isEdit={false}` API remains supported but is deprecated.
The slider explicitly reports `aria-orientation="horizontal"`. Use
`getValueText` to localize its spoken value:
```tsx
`${value} von ${count} Sternen`}
/>
```
### Right-to-left layouts
Pass `dir="rtl"` explicitly to reverse star order, half-star filling, pointer
selection and horizontal keyboard direction. The default is `ltr`, independent
of ancestor direction. In RTL, `ArrowLeft` increases and `ArrowRight` decreases;
`ArrowUp`, `ArrowDown`, `Home` and `End` retain their normal behavior. Star gaps
use logical spacing in both directions.
```tsx
```
### Hover preview color
Set `hoverColor` to distinguish the pointer preview from both the saved value
and inactive stars. It is opt-in, so omitting it preserves the original
two-color presentation.
```tsx
```
When the pointer previews `1.5`, the first `1.5` stars use `hoverColor`, the
remainder of the saved value uses `primaryColor`, and inactive stars use
`secondaryColor`. When previewing above the saved value, only the additional
preview region uses `hoverColor`.
### Styling
The forwarded `ref`, `className`, `style`, `title`, and `data-*` attributes
target the slider container. CSS variables provide lightweight theming:
```tsx
```
The default focus-visible ring uses the system `Highlight` color, and SVG
stars allow browser forced-colors adjustments for high-contrast modes.
## Server components
The package carries the `'use client'` directive, so it can be imported directly from a server component in the Next.js App Router without a wrapper.
## Form Integrations
### Native HTML forms
Pass `name` to contribute a hidden value to `FormData`. `defaultValue` enables
uncontrolled usage and native form reset behavior.
```tsx
```
Use `value` with `onChange` for controlled state. Supplying `value` always
takes precedence over `defaultValue`. Use `form="form-id"` when the rating is
rendered outside its associated form.
An uncanceled native reset restores `defaultValue` without calling `onChange`.
Calling `event.preventDefault()` in the form's `onReset` preserves the rating.
Controlled values remain the responsibility of the parent.
HTML hidden inputs do not participate in native constraint validation, so a
`required` attribute would be misleading and is intentionally not exposed.
For required ratings, validate the controlled value or submitted `FormData`
and expose the result with `aria-invalid` and descriptive error text.
For example, inside a component, validate on submit and focus the rating when
invalid (also validate submitted data on your server):
```tsx
const [rating, setRating] = useState(0);
const [error, setError] = useState(false);
const ratingRef = useRef(null);
const errorId = useId();
;
```
Import `useState`, `useRef` and `useId` from React for this recipe.
### React Hook Form
```tsx
import { Controller, useForm } from 'react-hook-form';
import ReactStarsRating from 'react-awesome-stars-rating';
const { control } = useForm({ defaultValues: { rating: 3.5 } });
(
)}
/>;
```
### Formik
```tsx
import { Field } from 'formik';
import ReactStarsRating from 'react-awesome-stars-rating';
{({ field, form }) => (
form.setFieldValue('rating', value)}
onBlur={() => form.setFieldTouched('rating', true)}
/>
)}
;
```
### TanStack Form
```tsx
import { useForm } from '@tanstack/react-form';
import ReactStarsRating from 'react-awesome-stars-rating';
type FormValues = { rating: number };
const form = useForm({
defaultValues: { rating: 3.5 },
onSubmit: ({ value }) => {
console.log('tanstack-form submit', value);
},
});
{(field) => (
)}
;
```
## Development
### Maintenance and feedback
The library is in maintenance mode: new feature development is paused while
we collect real-world feedback. Bug fixes, accessibility reports and security
updates remain welcome. Use the repository's bug and feature issue forms to
include reproduction steps, versions and the user need behind a request.
Automated keyboard, axe and browser checks do not establish physical-device
screen-reader compatibility. VoiceOver on iPhone and TalkBack on Android still
need manual verification; see `docs/accessibility-testing.md` in the repository
for the procedure and recorded results.
```bash
npm install
npm run dev # demo app
npm run storybook # Storybook
npm run test # Vitest
npm run coverage # Vitest with coverage thresholds
npm run lint # ESLint
npm run format # Prettier check
npm run typecheck # TypeScript
npm run build # library build (ESM + CJS + UMD + types) → dist/
npm run verify:package # render every entry point from dist/
npm run lint:package # publint + are-the-types-wrong on the packed tarball
npm run size # size-limit budgets for every entry point
npm run test:e2e # Chromium, Firefox and WebKit interaction/a11y tests
npm run verify:links # README HTTPS links (requires network)
npm run verify:site # public demo + Storybook smoke tests (requires Chromium/network)
npm run build:site # demo site → dist-site/
```
Pre-commit (Husky) runs lint-staged (Prettier + ESLint on staged files) and commitlint (Conventional Commits).
## CI / Releases
- **CI** — on push/PR to `main`: format, lint, commitlint, typecheck, tests, coverage, build, package verification, publint/attw, size budgets, Storybook and site builds.
- **Deploy** — Vercel builds the demo site on every push (see `vercel.json`), serving Storybook at `/storybook`.
- **Release** — [semantic-release](https://github.com/semantic-release/semantic-release) on push to `main`: analyzes Conventional Commits, publishes to npm and creates a GitHub release.
- **Recovery** — the manually dispatched `Verify or repair a published release` workflow verifies an exact npm version and its provenance, and can recreate a missing GitHub release only when its tag already exists.
- **Security** — CodeQL and OpenSSF Scorecard publish findings to GitHub code scanning; dependency review checks pull requests for vulnerable dependencies.
- **Documentation health** — changed documentation/check scripts, pushes to `main`, a weekly schedule and manual runs check README links and the deployed demo/Storybook. These external-service checks are separate from release gates. If npm's website blocks automated requests, the check explicitly reports that limitation and verifies the package through the registry instead.
Browser failures retain screenshots, traces and an HTML report in GitHub Actions
artifacts for seven days. Local browser tests require `npx playwright install`.
Development and release tooling use the Node 22 version in `.nvmrc`; consumer
compatibility is tested separately.
Published-release verification cryptographically checks the Sigstore signature
and transparency proofs, requires this repository's `ci.yml` identity on `main`,
and checks the signed package digest against the downloaded tarball. The signed
source commit must match the version's Git tag; release CI also requires it to
match the current build commit. Verification never republishes the package.
Publishing uses npm trusted publishing (OIDC) from `.github/workflows/ci.yml`; the workflow does not use an npm token.
No manual tagging: merge to `main` with commits like `feat: ...` or `fix: ...` to trigger a release.
## License
MIT