rating-wc
Three-layer star rating widget: CSS-only selection and hover, JS-enhanced clear/events, form-associated web component.
Overview
A star rating widget built the Vanilla Breeze way — CSS-first with native HTML, enhanced with JS, wrapped in a web component for convenience. Supports half-stars, custom icons via <icon-wc>, read-only display, and native form participation.
Three Layers
The rating widget follows the progressive enhancement model:
- CSS-only — Author writes a fieldset with radio inputs.
:has()selectors handle visual selection and hover. Works without JS. - JS enhancement —
rating-init.jsadds clear/unrate (click selected star again),rating-changeevents, and screen reader announcements. - Web component —
<rating-wc>generates the fieldset internally. Form-associated viaElementInternals— no hidden inputs needed.
CSS-Only Usage
Write a <fieldset data-rating> with radio inputs. The CSS handles selection highlighting and hover preview via :has() selectors. No JavaScript required.
Half Stars
Add data-rating-half to the fieldset and use data-half="left" / data-half="right" on labels. Each star gets two radio inputs: one for the half value, one for the full value.
Custom Icons
Replace the star character with any <icon-wc> icon. The :has() selectors work the same way — they target the label, not the star character.
JS Enhancement
When rating-init.js is loaded (included in the default bundle), data-rating fieldsets gain:
- Clear/unrate: Click the already-selected star to deselect (radio unchecked, value resets to 0)
rating-changeevent: Fires on the fieldset with{ detail: { value } }- Screen reader announcements: Live region announces the current value
Web Component
The <rating-wc> web component generates the fieldset internally and adds form participation via ElementInternals.
Simple
Half-star with preset value
Read-only display
Custom icons
Attributes
| Attribute | Default | Description |
|---|---|---|
name |
— | Form field name (omit for read-only) |
value |
0 |
Current rating value |
max |
5 |
Number of stars |
label |
"Rating" |
Legend text (visually hidden) |
data-half |
— | Enable half-star increments |
data-readonly |
— | Display-only mode, no interaction |
data-icon |
— | Lucide icon name (uses <icon-wc>) |
required |
— | Makes rating required for form validation |
Form Participation
The web component uses ElementInternals to participate in native forms — no hidden inputs. It supports FormData, the required attribute, Constraint Validation API, form.reset(), and :invalid/:valid pseudo-classes.
Keyboard Navigation
| Key | Action |
|---|---|
| ArrowRight / ArrowDown | Select next star |
| ArrowLeft / ArrowUp | Select previous star |
| Tab | Move focus into/out of the rating group |
Keyboard navigation is provided natively by the radio group — no custom JavaScript needed.
Events
| Event | Detail | Description |
|---|---|---|
rating-change |
{ value: number } |
Fired on the fieldset when the rating value changes (including clear to 0). |
Accessibility
<fieldset>+<legend>groups the radios with an accessible label- Each radio has
aria-labelannouncing the star count - Native radio keyboard navigation (arrow keys) requires no custom handling
- Focus-visible outline appears on the active star label
- JS enhancement adds screen reader announcements via live region
- Read-only mode uses
role="img"with a descriptivearia-label
Styling
Override the rating color and size with CSS custom properties or direct selectors:
Progressive Enhancement
Each layer adds capability without breaking the previous one:
- No JS: CSS-only radios work for selection, hover preview, form submission, and keyboard navigation
- With JS: Clear/unrate, events, and announcements enhance the experience
- Web component: Convenience wrapper that generates the markup and adds form participation via
ElementInternals
Related Elements
<icon-wc>— Custom icons for rating labels<form-field>— Form field wrapper with validation<fieldset>— Native grouping element used internally