wts-star-rating
v1.0.1
Published
Accessible, dependency-free star rating and review score component for JavaScript, TypeScript, Angular, React, Vue, Svelte, and Web Components.
Maintainers
Keywords
Readme
wts-star-rating
Accessible, dependency-free star rating, rating input, and review score component for vanilla JavaScript, TypeScript, Angular, React, Vue, Svelte, and Web Components. It supports fractional and half-star ratings, keyboard input, product-review interfaces, SSR, and an optional custom element.
The package is framework-agnostic by design:
- the core is a small DOM controller with no runtime dependencies;
- state can be read and updated without recreating the mount element;
- callbacks and bubbling DOM events work with any UI framework;
destroy()provides deterministic listener and DOM cleanup;- importing either entry point is safe during server-side rendering;
- the optional custom element is exported separately.
Install
npm install wts-star-ratingImport the stylesheet once:
import 'wts-star-rating/styles.css';Controller API
<div id="product-rating"></div>import { StarRating } from 'wts-star-rating';
import 'wts-star-rating/styles.css';
const rating = new StarRating({
element: '#product-rating',
value: 3.5,
max: 5,
step: 0.5,
hoverEnabled: true,
allowClear: true,
ariaLabel: 'Product rating',
formatValue: (value, max) => `${value} of ${max} stars`,
onInput({ previewing, value }) {
console.log(previewing ? 'preview' : 'restored', value);
},
onChange(detail) {
console.log(detail.value, detail.source);
},
});
rating.setValue(4); // silent, useful for syncing application state
rating.setValue(5, { emitChange: true });
rating.setReadonly(true);
console.log(rating.getValue());
// Call this when the owning view unmounts.
rating.destroy();Only the DOM appended by the controller is removed by destroy(). Creating a
second controller on the same element destroys the previous controller first.
To initialize every matching element, use the explicit collection helper:
import { createStarRatings } from 'wts-star-rating';
const ratings = createStarRatings('[data-rating]', {
hoverEnabled: true,
value: 2,
});
ratings.forEach((rating) => rating.destroy());Web Component
Importing wts-star-rating/element registers <wts-star-rating> once. The
registration is guarded when customElements is unavailable.
import 'wts-star-rating/element';
import 'wts-star-rating/styles.css';<wts-star-rating
aria-label="Product rating"
allow-clear
hover-enabled
max="5"
step="0.5"
value="3.5"
></wts-star-rating>The element exposes value, max, step, disabled, readOnly,
controller, options, getValue(), setValue(), and destroy().
Framework examples
Create the controller after the framework has mounted the host element and destroy it from the matching cleanup hook.
Angular
import {
afterNextRender,
Component,
DestroyRef,
ElementRef,
inject,
viewChild,
} from '@angular/core';
import { StarRating } from 'wts-star-rating';
import 'wts-star-rating/styles.css';
@Component({
selector: 'app-rating',
template: '<div #host></div>',
})
export class RatingComponent {
private readonly host = viewChild.required<ElementRef<HTMLElement>>('host');
private readonly destroyRef = inject(DestroyRef);
constructor() {
afterNextRender(() => {
const rating = new StarRating({
element: this.host().nativeElement,
value: 3,
onChange: ({ value }) => console.log(value),
});
this.destroyRef.onDestroy(() => rating.destroy());
});
}
}React
import { useEffect, useRef } from 'react';
import { StarRating } from 'wts-star-rating';
import 'wts-star-rating/styles.css';
export function Rating({ value, onChange }) {
const host = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!host.current) return;
const rating = new StarRating({
element: host.current,
value,
onChange: ({ value: next }) => onChange(next),
});
return () => rating.destroy();
}, []);
return <div ref={host} />;
}Call rating.setValue(value) from a second effect when the React prop can
change. Programmatic updates are silent by default, preventing feedback loops.
Vue
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { StarRating } from 'wts-star-rating';
import 'wts-star-rating/styles.css';
const host = ref<HTMLElement>();
let rating: StarRating | undefined;
onMounted(() => {
rating = new StarRating({
element: host.value!,
value: 3,
onChange: ({ value }) => console.log(value),
});
});
onBeforeUnmount(() => rating?.destroy());
</script>
<template><div ref="host" /></template>Options
| Option | Type | Default | Purpose |
| --- | --- | --- | --- |
| element | Element \| string | required | Mount element or selector |
| value | number | 0 | Initial value |
| max | number | 5 | Maximum rating |
| step | number | 1 | Pointer, keyboard, and value increment |
| hoverEnabled | boolean | false | Preview the pointer value |
| allowClear | boolean | false | Clear by selecting the current value |
| readonly | boolean | false | Present a non-interactive score |
| disabled | boolean | false | Disable interaction |
| showValue | boolean | true | Show the numeric value |
| ariaLabel | string | "Rating" | Accessible control name |
| formatValue | (value, max) => string | — | Format visible and ARIA value text |
| onInput | (detail) => void | — | Receive hover previews |
| onChange | (detail) => void | — | Receive committed changes |
setValue() clamps and snaps values to max and step. The controller also
provides setOptions(), setReadonly(), enable(), disable(), and
destroy().
Events
Committed changes emit:
star-rating-change, a bubblingCustomEvent<StarRatingChangeDetail>;change, a bubbling native event;- the
onChangecallback, when supplied.
Hover previews emit the matching star-rating-input, input, and onInput
notifications without changing getValue(). Their detail has
previewing: true. Leaving the control or pressing Escape restores the
committed value and emits one final input notification with
previewing: false.
host.addEventListener('star-rating-change', (event) => {
console.log(event.detail);
// { value, previousValue, source, container }
});Keyboard support includes arrow keys, Home, End, and—when allowClear is
enabled—Delete and Backspace. Escape cancels a hover preview. Horizontal
arrows and fractional pointer calculations automatically follow the computed
direction, including dir="rtl" layouts.
Styling
Override CSS custom properties on the mount element:
.review-rating {
--wts-star-rating-color: #ffb000;
--wts-star-rating-empty-color: #d8d8d8;
--wts-star-rating-focus-color: #155eef;
--wts-star-rating-gap: 0.2rem;
--wts-star-rating-size: 2.25rem;
}Migration from the prototype
The old prototype metadata accidentally used the name wts-start-rating.
This package uses the requested wts-star-rating name.
- Replace
elements: '.rating'withcreateStarRatings('.rating', options). - Import
wts-star-rating/styles.css. - Call
destroy()from framework cleanup hooks. - Use public
setValue()orsetOptions()for state synchronization. - The obsolete range-slider options declared by the prototype are removed.
