GrRating
Collect and display a star rating, with half steps and a read-only mode.
Machine-translated from the Russian original, not yet reviewed. Read the original
When to take it
- the rating is given in one click — a review, the quality of support, the difficulty of a task;
- the rating is fractional —
allowHalffor halves; - an explanation is needed beside it —
textslabels the steps with words: “Poor”, “Excellent”; - the rating is shown rather than given —
readonlyfor the average rating in a product card.
When to take something else
| Need | Take |
|---|---|
| The scale is numeric and wide | GrSlider |
| An exact number is needed | GrNumberInput |
| There are few options and they are named | GrRadioGroup |
| Show a share rather than a rating | GrProgressBar |
The rating in words
<GrRating v-model="score" :texts="['Awful', 'Poor', 'Fair', 'Good', 'Excellent']" show-text />
texts holds one label per division. The label goes both into the visible text and into
aria-valuetext (“4 of 5, good”): that is the whole point of a rating — a screen reader reads the
score in words rather than as a bare number. A fractional value is rounded up to its division, and
an array shorter than max leaves the upper divisions without a label.
formatText is stronger than texts for the visible text — the consumer may have a wording of
their own.
The preview
Hovering shows the rating under the cursor and emits hoverChange; leaving the scale and losing
the focus return the model and send hoverChange(null).
The handler hangs on the scale itself rather than on the whole component: the caption lies beside
the scale, and a cursor moved onto it has to put the preview out — otherwise it sticks. In
readonly and disabled there is no preview at all.
There is no preview from the keyboard: the arrows commit the rating at once, and hoverChange is
not emitted there.
The compact look
<GrRating :model-value="3" readonly compact show-text />
compact draws only the filled symbols — for tables and lists, where five stars in every row eat
up the width. A half counts as a symbol: “2.5” is drawn with three. For a screen reader the compact
mode is no different — the role and the text label are the same.
The modes and accessibility
An interactive scale implements the slider pattern: role="slider",
aria-valuemin/max/now/valuetext, the arrows, Home/End. The read-only mode is a
role="img" with a text label, so that the rating is read as a single phrase rather than as a set
of elements.
disabled dims the scale with the --gr-disabled-fg token rather than with transparency:
opacity dilutes colours tuned to AA.
Styling
| Point | What it sets |
|---|---|
size | xs…lg, read from GrConfigProvider |
tone | the colour of the fill from the tone scale |
--gr-rating-color | the colour of the fill pointwise, stronger than tone |
--gr-rating-void-color | the colour of an unfilled symbol |
icon / the #symbol slot | a symbol of your own instead of the built-in star |
The default symbol is an inline SVG: the icon masks of i-lucide-star give an outline only. A
symbol of your own is a Vue component or an icon class from your UnoCSS build (i-lucide-*
requires your presetIcons, see
“Icons”).
The native form
The name prop renders an input[type="hidden"] with the value of the rating. 0 means “not
selected”: the input is not rendered, as with an unchecked radio button.
Playground 14
Loading…
<GrRating />Install
npm i @feugene/granularityImport
import { GrRating } from '@feugene/granularity/components/GrRating'API
Props
| Prop | Type | default | Description |
|---|---|---|---|
tone | "primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined | "warning" | The tone of the fill; overridden pointwise by the `--gr-rating-color` variable. |
icon | string | Component | undefined | undefined | A symbol instead of the built-in star: a Vue component or the class of an icon from your UnoCSS build (`'i-lucide-heart'` — then your `presetIcons` is needed, see `docs/installation.md`). |
disabled | boolean | undefined | false | — |
readonly | boolean | undefined | false | Display only: without input and without focus. |
invalid | boolean | undefined | false | The visual and ARIA state of an error. |
required | boolean | undefined | false | A mandatory field (`aria-required`). |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | — |
ariaLabel | string | undefined | undefined | — |
clearable | boolean | undefined | false | A repeated click on the current score resets it to `0`. |
name | string | undefined | undefined | The name for a native form: a hidden input with the value; `0` means "not chosen", and the input is not rendered. |
max | number | undefined | 5 | The number of symbols on the scale. |
compact | boolean | undefined | false | A compact look: only the filled symbols are drawn. It makes sense together with `readonly` — in lists and tables five stars in every row eat the width. |
allowHalf | boolean | undefined | false | Half scores: a click on the left half of a symbol gives `.5`. |
showText | boolean | undefined | false | Show a numeric label to the right of the scale. |
formatText | ((value: number) => string) | undefined | undefined | The format of the label. By default — the value itself. Stronger than `texts`. |
texts | string[] | undefined | undefined | Labels by step: "3 out of 5, fine" instead of "3 out of 5". They go both into the visible text and into `aria-valuetext` — that is what the rating exists for. An array shorter than `max` leaves the upper steps without a label. |
modelValuerequired | number | — | The current score. A fractional one (`3.5`) is supported with `allowHalf`. |
Slots
| Slot | Type | Description |
|---|---|---|
symbol | { index: number; filled: boolean; } | A symbol of your own instead of the star. `filled` is the filled half of the symbol. |
text | { value: number; } | A label beside the score. |
Events
| Event | Type | Description |
|---|---|---|
update:modelValue | [value: number] | — |
change | [value: number] | — |
clear | [] | — |
focus | [event: FocusEvent] | — |
blur | [event: FocusEvent] | — |
hoverChange | [value: number | null] | — |
Methods / Expose
| Methods / Expose | Type | Description |
|---|---|---|
focus | () => void | — |
blur | () => void | — |
Examples 3
Basic rating
Score: 4 — click a star, or use arrow keys, Home / End.
<script setup lang="ts">
import { ref } from 'vue'
import { GrRating } from '@feugene/granularity'
const score = ref(4)
</script>
<template>
<div class="grid gap-4">
<GrRating v-model="score" show-text aria-label="Rate the delivery" />
<p class="text-sm text-[var(--gr-muted-fg)]">
Score: <code>{{ score }}</code> — click a star, or use arrow keys, Home / End.
</p>
</div>
</template>Half stars, clearable and read-only
- Anna K.
Arrived a day earlier than promised.
- Mark T.
Good quality, packaging could be better.
- Elena P.
Exactly as described.
<script setup lang="ts">
import { ref } from 'vue'
import { GrRating } from '@feugene/granularity'
const myScore = ref(3.5)
const reviews = [
{ author: 'Anna K.', score: 5, text: 'Arrived a day earlier than promised.' },
{ author: 'Mark T.', score: 3.5, text: 'Good quality, packaging could be better.' },
{ author: 'Elena P.', score: 4, text: 'Exactly as described.' },
]
</script>
<template>
<div class="grid gap-6">
<div class="grid gap-2">
<span class="text-sm font-medium">Your rating</span>
<GrRating
v-model="myScore"
allow-half
clearable
show-text
:format-text="(v) => (v ? `${v} / 5` : 'Not rated')"
aria-label="Your rating"
/>
</div>
<ul class="grid gap-3">
<li v-for="review in reviews" :key="review.author" class="grid gap-1">
<div class="flex items-center gap-2">
<GrRating :model-value="review.score" readonly allow-half size="sm" />
<span class="text-sm font-medium">{{ review.author }}</span>
</div>
<p class="text-sm text-[var(--gr-muted-fg)]">
{{ review.text }}
</p>
</li>
</ul>
</div>
</template>Custom symbol, tone and size
<script setup lang="ts">
import { ref } from 'vue'
import { GrRating } from '@feugene/granularity'
const likes = ref(3)
const difficulty = ref(2)
const size = ref(4)
// Подписи по делениям: диктор читает «4 из 5, хорошо», а не голое число.
const service = ref(4)
const serviceTexts = ['Ужасно', 'Плохо', 'Нормально', 'Хорошо', 'Отлично']
</script>
<template>
<div class="grid gap-6">
<div class="grid gap-2">
<span class="text-sm font-medium">Custom symbol and tone</span>
<GrRating
v-model="likes"
icon="i-lucide-heart"
tone="danger"
aria-label="How much you liked it"
/>
</div>
<div class="grid gap-2">
<span class="text-sm font-medium">Own colour via CSS variable</span>
<GrRating
v-model="difficulty"
:max="4"
style="--gr-rating-color: var(--gr-info)"
aria-label="Difficulty"
/>
</div>
<div class="grid gap-2">
<span class="text-sm font-medium">Labels per step</span>
<GrRating
v-model="service"
:texts="serviceTexts"
show-text
aria-label="Service quality"
/>
</div>
<div class="grid gap-2">
<span class="text-sm font-medium">Compact read-only (for tables and lists)</span>
<div class="flex items-center gap-6">
<GrRating :model-value="3" readonly compact show-text aria-label="Compact rating" />
<GrRating :model-value="4.5" readonly compact allow-half show-text aria-label="Compact half rating" />
</div>
</div>
<div class="grid gap-2">
<span class="text-sm font-medium">Sizes and disabled</span>
<div class="flex items-center gap-6">
<GrRating v-model="size" size="sm" aria-label="Small" />
<GrRating v-model="size" size="md" aria-label="Medium" />
<GrRating v-model="size" size="lg" aria-label="Large" />
<GrRating :model-value="2" disabled aria-label="Disabled" />
</div>
</div>
</div>
</template>Accessibility
- APG pattern
slider (интерактивный) / img (readonly)