StarRating
Introduction
MStarRating is a feature-rich star rating component. It supports full and half-star ratings, readonly display, custom total stars and icons, keyboard interaction, badge display, and highlighted focus state.
Usage
Default 5-star rating system. Click a star to write to v-model:
<script setup lang="ts">
const value = ref(3)
</script>
<template>
<MStarRating v-model="value" />
</template>
allowHalf Half-Star Rating
allowHalf allows each star to take a half value. Clicking the left or right half records .5 or a whole number rating respectively:
<script setup lang="ts">
const value = ref(3.5)
</script>
<template>
<MStarRating v-model="value" allow-half />
</template>
clearable Clearable
clearable allows clicking the current value again or pressing Backspace to reset the rating to zero — useful for optional fields:
<script setup lang="ts">
const value = ref(2.5)
</script>
<template>
<MStarRating v-model="value" allow-half clearable />
</template>
readonly Readonly Mode
readonly displays the rating visually without allowing interaction — used for review and result display:
<script setup lang="ts">
const value = ref(4)
</script>
<template>
<MStarRating v-model="value" readonly />
</template>
max Total Stars
max adjusts the number of stars to support finer-grained rating scales:
<script setup lang="ts">
const value = ref(5)
</script>
<template>
<MStarRating v-model="value" :max="7" />
</template>
emptyIcon Rating Icons
emptyIcon, filledIcon, and halfIcon can replace the default icons entirely. Use with allowHalf to render half values:
<script setup lang="ts">
const value = ref(2.5)
</script>
<template>
<MStarRating
v-model="value"
allow-half
empty-icon="i-lucide-heart"
filled-icon="i-lucide-heart"
half-icon="i-lucide-heart-handshake"
/>
</template>
showBadge Rating Badge
showBadge shows the current score badge by default. Set to false to show only the stars:
<script setup lang="ts">
const value = ref(3)
</script>
<template>
<MStarRating v-model="value" :show-badge="false" />
</template>
color Color
color sets the color of selected stars and the badge:
<script setup lang="ts">
const value = ref(4)
</script>
<template>
<MStarRating v-model="value" color="primary" />
</template>
highlight Highlight Focus
highlight adds a ring-style highlight to the rating control to emphasize the currently interactive item:
<script setup lang="ts">
const value = ref(3)
</script>
<template>
<MStarRating v-model="value" highlight />
</template>
buttonProps Button Properties
buttonProps is passed to each star's button, allowing uniform adjustment of variant, padding, and other underlying styles:
<script setup lang="ts">
const value = ref(3)
</script>
<template>
<MStarRating v-model="value" :button-props="{ variant: 'soft', size: 'xs' }" />
</template>
disabled Disabled State
disabled prevents interaction and reduces opacity simultaneously:
<script setup lang="ts">
const value = ref(4)
</script>
<template>
<MStarRating v-model="value" disabled />
</template>
Examples
Inheriting Field Context
When placed inside UFormField, it receives field size and error state, and rating icons update with the form state:
<template>
<UFormField label="Satisfaction" size="xs" error="Example error state">
<MStarRating />
</UFormField>
</template>
Inside UFieldGroup
When placed alongside a reset button inside UFieldGroup, they share size — suitable for combining actions in compact form rows:
<template>
<UFieldGroup size="xs">
<MStarRating />
<UButton icon="i-lucide-rotate-ccw" color="neutral" variant="subtle" />
</UFieldGroup>
</template>
Event Callbacks
Click, hover, and keyboard interactions fire update:modelValue, change, and hover in sequence:
<script setup lang="ts">
const toast = useToast()
const rating = ref(0)
const hovering = ref<number | null>(null)
function handleChange(value: number) {
toast.add({
title: '评分已更改',
color: 'success',
description: `您选择的评分是 ${value} 星`
})
}
function handleHover(value: number | null) {
hovering.value = value
}
</script>
<template>
<div class="space-y-2">
<MStarRating v-model="rating" @change="handleChange" @hover="handleHover" />
<div class="text-sm text-gray-500">
<div>当前评分: {{ rating }}</div>
<div>悬停星级: {{ hovering ?? '-' }}</div>
</div>
</div>
</template>
Custom Slots
Add prefix, suffix, or a custom badge via slots:
<script setup lang="ts">
const rating = ref(4)
</script>
<template>
<MStarRating v-model="rating">
<template #prefix>
<span class="text-sm text-gray-500 mr-2">评分:</span>
</template>
<template #badge="{ label }">
<span class="text-sm text-primary font-semibold ml-2"> {{ label }} 分 </span>
</template>
<template #suffix>
<span class="text-xs text-gray-400 ml-2">(128 评价)</span>
</template>
</MStarRating>
</template>
Keyboard Navigation
The component supports full keyboard interaction for improved accessibility:
- Arrow keys:
←→↑↓increment/decrement the rating (supports half-star stepping) - Shortcuts:
Homesets the minimum rating,Endsets the maximum rating - Number keys:
0-9jump directly to the corresponding rating - Clear keys:
Backspace/Deleteclear the rating (requiresclearableto be enabled)
API
Props
| Prop | Default | Type |
|---|---|---|
max | 5 | number最大星级数 |
emptyIcon | 'i-lucide-star' | any未选中星星的图标 |
filledIcon | 'i-lucide-star' | any选中星星的图标 |
halfIcon | 'i-lucide-star-half' | any半星图标 |
id | string | |
name | string | |
buttonProps | ButtonProps | |
color | "primary" | "secondary" | "info" | "success" | "warning" | "error" | "important" | "neutral"选中星星的颜色 | |
size | 'md' | "xs" | "sm" | "md" | "lg" | "xl"星星大小 |
modelValue | 0 | number |
showBadge | true | boolean是否显示评分徽章 |
disabled | boolean是否禁用 | |
highlight | booleanHighlight the ring color like a focus state. | |
readonly | boolean是否只读 | |
allowHalf | boolean是否允许半星 | |
clearable | boolean是否允许清除评分 | |
ui | Record<string, ClassNameValue> & { root?: SlotClass; stars?: SlotClass; star?: SlotClass; } |
Emits
| Event | Type |
|---|---|
update:modelValue | [value: number] |
change | [value: number] |
hover | [value: number] |
Slots
| Slot | Type |
|---|---|
prefix | { value: number; max: number; } |
badge | { value: number; max: number; label: string; } |
suffix | { value: number; max: number; } |
Theme
export default defineAppConfig({
ui: {
starRating: {
slots: {
root: 'inline-flex items-center gap-1',
stars: 'flex items-center gap-0.5',
star: 'transition-all duration-150'
},
variants: {
interactive: {
true: {
star: 'cursor-pointer hover:scale-110'
}
},
disabled: {
true: {
star: 'cursor-not-allowed opacity-50'
}
},
readonly: {
true: {
star: 'cursor-default'
}
},
fieldGroup: {
horizontal: {
root: 'rounded-md bg-default ring ring-inset ring-accented px-1 not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]'
},
vertical: {
root: 'rounded-md bg-default ring ring-inset ring-accented px-1 not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
}
}
}
}
}
})