Rating
Rating lets someone give a score by filling a row of stars, such as reviewing a product, a course, or a stay. It also displays an existing score on its own, such as the average of every review a product received.
Dependencies
When to use
Use a rating when someone scores something on a short, familiar scale,
such as a product review, the feedback at the end of a course, or the
satisfaction survey after a support request. Set display-only to show a
score nobody edits, such as a product's average review on a listing page.
For a score on a long or precise scale, or a choice between named levels
rather than a score, see Alternatives below.
Install & usage
Pick a framework in the toolbar above and these snippets adapt.
npm install @eunomia/elements
import "@eunomia/elements/rating.js";
import "@eunomia/elements/icon.js";
import "@eunomia/elements/label.js";
<eun-rating name="stay" label="How was your stay?"></eun-rating>
Importing the file registers <eun-rating> as a custom element, with no
further setup needed. It works with any framework, or none, since it's a
standard web component. The stars are drawn by eun-icon, and the label
by eun-label, so import both as well.
npm install @eunomia/elements
<script type="module">
import "@eunomia/elements/rating.js";
import "@eunomia/elements/icon.js";
import "@eunomia/elements/label.js";
</script>
<eun-rating name="stay" label="How was your stay?"></eun-rating>
npm install @eunomia/elements
import "@eunomia/elements/rating.js";
import "@eunomia/elements/icon.js";
import "@eunomia/elements/label.js";
function StayRating() {
return <eun-rating name="stay" label="How was your stay?" />;
}
npm install @eunomia/elements
"use client";
import "@eunomia/elements/rating.js";
import "@eunomia/elements/icon.js";
import "@eunomia/elements/label.js";
export function StayRating() {
return <eun-rating name="stay" label="How was your stay?" />;
}
npm install @eunomia/elements
<script setup>
import "@eunomia/elements/rating.js";
import "@eunomia/elements/icon.js";
import "@eunomia/elements/label.js";
</script>
<template>
<eun-rating name="stay" label="How was your stay?" />
</template>
npm install @eunomia/elements
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";
import "@eunomia/elements/rating.js";
import "@eunomia/elements/icon.js";
import "@eunomia/elements/label.js";
@Component({
selector: "app-stay-rating",
template: `<eun-rating name="stay" label="How was your stay?"></eun-rating>`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class StayRatingComponent {}
Alternatives
| You want… | Prefers |
|---|---|
| A score on a long or precise scale, such as a percentage | eun-slider |
| A choice between named levels, such as Poor, Fair and Good | eun-radio-list |
Guidance
- Set
labelto the question being scored, such as "How was your stay?", so the group is announced with it instead of the generic "Rating" - Keep the default five stars unless your audience already knows another scale
- Set
labelon adisplay-onlyrating too whenever the score has a specific meaning, such as "Average customer rating" - Add
clearablewhen giving a score is optional, since a picked rating can't be removed otherwise
- Using a rating for anything other than a score, such as a quantity or a choice between named options: see Alternatives above
- Giving
iconandempty-iconthe same shape, since the rating would then be told apart by color alone (see the Accessibility tab) - Lowering the contrast of the stars with
--rating-coloror--rating-empty-colorbelow 3:1 against the background - Overriding colors with inline styles instead of the
--rating-*CSS variables
Live testing
Properties
Rating <eun-rating>
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| default-value | number | — | The rating applied when the field connects, and restored on form reset |
| display-only | boolean | false | Only displays the rating, which can then be any decimal value, such as an average |
| clear-label | string | — | The accessible label of the clear button. Defaults to the localized string for the current locale (English: "Clear rating") |
| empty-icon | EunomiaIconName | 'star_outline' | The icon drawn for the empty part of the rating. Keep its shape different from icon, so the rating doesn't rely on color alone |
| hide-error | boolean | false | Whether the errors are hidden. The hint stays visible |
| max | number | 5 | The number of icons, which is also the highest rating |
| step | 1 | 0.5 | 1 | Whether a rating is picked by whole icons or by halves |
| clearable | boolean | false | Shows a button that removes the rating, once one is picked |
| icon | EunomiaIconName | 'star' | The icon drawn for the filled part of the rating |
| value | number | — | The current rating. Empty when nothing is picked yet |
Import the exact TypeScript type behind any property above, see
Properties
JS-only — no matching HTML attribute, set these from a script or a template binding.
| Name | Type | Default | Description |
|---|---|---|---|
| label | string | — | The title label of the field. While displayOnly is set, it isn't shown and starts the accessible name instead |
| instructions | string | — | Instructions displayed below the label |
| hideLabel | boolean | false | Whether the label is visually hidden. It still gives the field its accessible name, and the instructions stay visible |
| name | string | — | The field name |
| locale | string | — | The language of the accessible labels and of the decimal separator. Defaults to the shared default locale |
| disabled | boolean | false | Whether the field is disabled |
| readonly | boolean | false | Whether the field is read only |
| required | boolean | false | Whether a rating must be picked |
| hideError | boolean | false | Whether the errors are hidden. The hint stays visible |
| invalid | boolean | false | Shows the field as invalid without any message, for an error displayed elsewhere |
| error | string | — | An error computed outside the field, by the form or by the server. While set, the field is invalid and shows this message right away |
| hint | string | — | A short message displayed under the field |
| validators | Array<Validators<number | undefined>> | — | The list of validation rules applied to the rating |
Slots
| Name | Description |
|---|---|
| label | Custom label content, falling back to the label property. Not rendered while displayOnly is set |
| instructions | Custom instructions content, falling back to the instructions property. Not rendered while displayOnly is set |
Events
| Name | Type | Description |
|---|---|---|
| eunchange | ChangeEvent | Fired whenever the rating is picked, changed or cleared |
Every event above follows the same naming convention, covered in
CSS custom properties
| Name | Description |
|---|---|
| --rating-size | Sets the width and height of each icon |
| --rating-gap | Sets the space between two icons |
| --rating-color | Sets the color of the filled icons |
| --rating-empty-color | Sets the color of the empty icons |
| --rating-error-color | Sets the color of the error message, and of the empty icons while in error |
| --rating-hint-color | Sets the color of the hint message |
| --rating-focus-outline-color | Sets the color of the focus outline drawn around the focused icon and the clear button |
| --field-label-font-size | Sets the font size of the label |
| --field-message-font-size | Sets the font size of the hint, error messages and instructions |
Basic
Nothing is picked until someone clicks a star, or reaches the group with
Tab and uses the arrow keys or Space.
<eun-rating name="course" label="Rate this course"></eun-rating>
Display only
display-only shows a score nobody edits. Its value then keeps every
decimal, such as an average of 4.3, and partly fills the last star. The
label isn't shown, and starts the name the score is announced with
instead: "Average customer rating: 4.3 out of 5" here.
<div class="rating-summary">
<eun-rating
display-only
value="4.3"
label="Average customer rating"
></eun-rating>
<span>4.3 · 128 reviews</span>
</div>
.rating-summary {
display: flex;
align-items: center;
gap: 8px;
}
Configuration
Half stars
step="0.5" lets someone pick half a star: the first half of a star
picks the half, its second half the whole star. The arrow keys then move
by halves too.
<eun-rating
name="film"
label="Rate this film"
step="0.5"
value="3.5"
></eun-rating>
Number of stars
max sets how many stars are drawn, which is also the highest score.
<eun-rating name="difficulty" label="Difficulty" max="3" value="2"></eun-rating>
Clearable
clearable adds a button after the stars once a score is picked, to
remove it. Focus then moves back to the stars, since the button disappears
with the score.
<eun-rating
name="delivery"
label="Rate the delivery (optional)"
clearable
value="2"
></eun-rating>
Icons
icon sets the icon drawn for the filled part of the rating, and
empty-icon the one drawn for the empty part. Pick two different shapes,
such as a filled icon and its outline, so the score stays readable without
relying on color.
<eun-rating
name="recipe"
label="Did you like this recipe?"
icon="favorite"
empty-icon="favorite_outline"
value="3"
style="--rating-color: #be185d;"
></eun-rating>
States
Disabled
Disabled ratings remain visible but can't be changed, and are excluded from the tab order and from form submission.
<eun-rating name="stay" label="Disabled" value="3" disabled></eun-rating>
Readonly
A readonly rating stays focusable and its value still submits, but it
can't be changed. Unlike disabled, its score remains easy to read.
<eun-rating name="stay" label="Readonly" value="4" readonly></eun-rating>
Required
A required rating needs a score to be valid.
<eun-rating name="stay" label="How was your stay?" required></eun-rating>
With hint
hint renders a short message under the stars, replaced by the error
message once the rating becomes invalid (see Error below).
<eun-rating
name="stay"
label="How was your stay?"
hint="1 is poor, 5 is excellent"
></eun-rating>
Error
The error shows once a score is picked, once focus leaves the stars, or
when the form is submitted. The empty stars then take the error color, and
the message replaces the hint. validators add any rule of your own.
<eun-rating
name="support"
label="How helpful was our support?"
required
hint="1 is not helpful, 5 is very helpful"
></eun-rating>
document.querySelector("eun-rating").validators = [
{
isValid: (value?: number) => value === undefined || value >= 2,
message: "Tell us more in the comment below",
},
];
Integration
Forms
Like every other Eunomia field, eun-rating is a real
<form>'s
submission and reset. The score is submitted as a number, such as 3.5,
and nothing is submitted while no score is picked. A reset restores
default-value, or removes the score when there's none.
<form id="my-form">
<eun-rating name="food" label="Food" default-value="4"></eun-rating>
<eun-rating name="service" label="Service" step="0.5" clearable></eun-rating>
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</form>
const form = document.querySelector("#my-form");
// Fires whenever a score is picked, changed or cleared.
form.addEventListener("eunchange", () => {
console.log(Object.fromEntries(new FormData(form)));
});
// Fires on reset (native form event), not on eunchange. `setTimeout` lets
// the ratings' own update settle first.
form.addEventListener("reset", () => {
setTimeout(() => console.log(Object.fromEntries(new FormData(form))));
});
Language
locale sets the language of every label read by screen readers, and of
the decimal separator: in French, a star is announced as "2,5 sur 5" and
the clear button as "Effacer la note". It defaults to the language set for
the whole page with setDefaultLocale().
<eun-rating
name="sejour"
label="Comment s'est passé votre séjour ?"
locale="fr"
step="0.5"
clearable
value="4.5"
></eun-rating>
Server-side rendering
The stars are drawn inside the rating's shadow root, so a server-rendered
rating stays empty until the element upgrades, then pushes the content
around it. Link rating-ssr.css in the page's <head> to draw the label
and the stars from the first paint:
<link
rel="stylesheet"
href="node_modules/@eunomia/elements/dist/ssr/rating-ssr.css"
/>
A bundler that resolves package exports can reach it as
@eunomia/elements/rating-ssr.css. It reads value or default-value,
max up to 10, display-only, readonly, disabled, clearable and the
--rating-* custom properties, and fills the stars up to the tenth, such
as 4.3. Write those attributes in the server-rendered markup for them to
apply. A few things only show with the upgrade, in the space already
reserved for them: the required asterisk, the clear button, and stars
drawn with a custom icon or empty-icon. instructions and hint
appear with the upgrade too, and do take up new space.
Custom
Override the --rating-* CSS variables, listed in full in the API tab.
<eun-rating class="custom-rating" label="Custom rating" value="3"></eun-rating>
.custom-rating {
--rating-size: 32px;
--rating-gap: 8px;
--rating-color: #b7791f;
}
The custom properties that change colors, sizes and spacing, at a glance. The API tab lists every property of the component.
| Variable | Sets |
|---|---|
--rating-size |
Sets the width and height of each icon |
--rating-gap |
Sets the space between two icons |
--rating-color |
Sets the color of the filled icons |
--rating-empty-color |
Sets the color of the empty icons |
--rating-error-color |
Sets the color of the error message, and of the empty icons while in error |
--rating-hint-color |
Sets the color of the hint message |
--rating-focus-outline-color |
Sets the color of the focus outline drawn around the focused icon and the clear button |
Keyboard interactions
The stars form a single tab stop: the picked star, or the first one while nothing is picked. The arrow keys pick the score as they move, and never wrap around from the last star to the first.
| Key | Action |
|---|---|
Tab / Shift+Tab |
Moves focus to the stars, then to the clear button when it's shown |
ArrowRight / ArrowDown |
Raises the score by step, up to max. ArrowLeft in a right-to-left page |
ArrowLeft / ArrowUp |
Lowers the score by step, down to the first star. ArrowRight in a right-to-left page |
Home |
Picks the lowest score |
End |
Picks the highest score |
Space |
Picks the focused star while nothing is picked yet |
Enter / Space on clear |
Removes the score and moves focus back to the stars |
Semantics and screen readers
Aria attributes
Each possible score is a real <input type="radio">, so its role, its
checked state and its position ("3 of 5") come from the browser itself.
On top of that native baseline:
role="radiogroup"on the stars, named bylabel, or by the localized "Rating" when there's nonearia-labelon each radio: its score, such as "3 out of 5", or "2.5 out of 5" with half stars, localized throughlocalearia-describedby: points to the instructions, then to the hint or the error, whenever they're renderedaria-required,aria-readonlyandaria-disabledon the group, only present whilerequired,readonlyordisabledis setaria-invalidon the group: kept in sync withvalid, oncedisabledorhideErrorare also accounted for, and forced totruewhileinvalidis setaria-readonlyandaria-disabledon the host as well, like every other fieldaria-labelon the clear button:clear-label, or the localized "Clear rating"
Every icon is aria-hidden, since the score is already carried by the
radios.
Label accessibility
Set label to the question being scored, such as "How was your stay?".
The group is announced with it, then each star with its score, so a screen
reader user hears "How was your stay?, radio group, 4 out of 5, checked".
Without a label, the group falls back to the generic "Rating", which
doesn't say what is being scored.
Screen reader restitution
By default, screen readers read out the group's name, then the focused star's score, its checked state and its position, such as "How was your stay?, radio group, 4 out of 5, radio button, checked, 4 of 5". The order may vary depending on the screen reader and its configuration. The disabled state is rendered differently depending on the screen reader:
- VoiceOver (macOS/iOS): dimmed
- NVDA and JAWS: unavailable
- Narrator and TalkBack: disabled
Display only
A display-only rating renders the stars as a single image
(role="img"), named after the score: "Rating: 4.3 out of 5", or
"Average customer rating: 4.3 out of 5" with a label. Screen readers
read it in one go, and it never takes focus, since there's nothing to
interact with. With no value, it reads "Rating: Not rated".
Field states
Disabled vs. readonly
disabled removes the stars from the tab order, blocks every change, and
excludes the score from form submission. readonly keeps the picked star
focusable and its score submitted, but blocks every change by pointer and
keyboard, since the HTML readonly attribute has no effect on radios. It
communicates "you can't change this right now", not "this doesn't apply".
Prefer readonly whenever the score still matters to the user.
Required field
required means "a score must be picked". The error is announced through
aria-invalid and the message linked with aria-describedby. Add a
visible note near the top of the form as well, rather than relying on the
error message alone.
Visual
Colors and shapes
The filled and the empty parts of the rating use two different shapes, a filled star and an outlined one, so the score stays readable with any color vision, in grayscale, and in Windows high contrast mode, where both are drawn in the system text color.
Graphics needed to understand content must reach a contrast of at least
3:1 against the page background. The empty stars always do, since their
color is derived from the text and surface colors of the theme. The filled
stars take the theme's primary color, which the theme generator keeps
above that ratio in dark themes. In light themes, it's the brand color as
is: the default theme reaches 4.3:1 on white, but a very light brand color
falls short, in which case set --rating-color to a darker shade. The
golden stars of the Custom example, #b7791f, reach 3.6:1 on white.
Target size
Each star is a single target of 24 by 24 pixels by default, even with half
stars: where the pointer lands in the star decides between the half and
the whole score, so no target is ever half as wide. Keep --rating-size
at 24px or more.
Reference links