Use it for
Use BbRating for a small whole-number judgement such as an order review.
Use something else when
BbSlider— the value is continuous or fractionalBbRadioGroup— each point needs a word label
Pass Through
Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.
Validate an order review
Name the fieldset with legend, seed a number | null model, and clear validation as soon as the user picks a value.
<template>
<form
class="flex max-w-sm flex-col gap-3"
novalidate
@submit.prevent="onSubmit"
>
<BbRating
id="review-score"
v-model="score"
clearable
description="Order #10482, delivered on 8 July 2026."
:errors="errors"
legend="Rate your order"
name="order-review"
required
@update:model-value="errors = []"
/>
<BbTextarea
v-if="score !== null && score <= 3"
id="review-followup"
v-model="feedback"
label="What went wrong?"
name="order-feedback"
placeholder="Late delivery, damaged packaging…"
:rows="3"
/>
<BbButton class="self-start" type="submit" variant="primary">
Submit review
</BbButton>
<p v-if="sent" class="text-sm opacity-70">Thanks — review received.</p>
</form>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbButton, BbRating, BbTextarea } from 'bitboss-ui';
const score = ref<number | null>(null);
const feedback = ref<string | null>(null);
const errors = ref<string[]>([]);
const sent = ref(false);
const onSubmit = () => {
if (score.value === null) {
errors.value = ['Pick a rating before submitting.'];
sent.value = false;
return;
}
sent.value = true;
};
</script>
The stars are native radio inputs: Tab enters the group once and arrow keys change the value. name is optional in v3 unless a native post needs a known key.
Clear and choose the scale
clearable adds the only path back to unrated: click the selected star again or press Delete or Backspace.
3 of 5
<template>
<div class="flex max-w-sm flex-col gap-2">
<BbRating
id="clearable-order"
v-model="score"
clearable
hint="Click the selected star again, or press Delete, to go back to unrated."
legend="Rate your order"
name="order"
persistent-hint
/>
<p class="text-sm opacity-70">
{{ score === null ? 'Not rated yet (null)' : `${score} of 5` }}
</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbRating } from 'bitboss-ui';
// Cleared is `null`, not `0` — so the model has to admit null.
const score = ref<number | null>(3);
</script>
The cleared value is null, never 0. Explain the gesture in a persistent hint. stars changes both the number of choices and maximum value; beyond about ten, prefer labels or a slider.
Display an existing score
Use readonly for a score people may inspect; unlike disabled, it stays in the tab order.
<template>
<div class="flex max-w-sm flex-col gap-1">
<span class="text-sm font-medium">{{ product.name }}</span>
<!-- Round for the glyphs; the exact figure is text, because there is no
such thing as half a star here. -->
<BbRating
id="product-rating"
hide-legend
:legend="`Average rating for ${product.name}`"
:model-value="Math.round(product.rating)"
name="product-rating"
readonly
size="sm"
>
<template #append>
<span class="ml-2 text-sm opacity-70">
{{ product.rating.toFixed(1) }} · {{ reviewCount }} reviews
</span>
</template>
</BbRating>
</div>
</template>
<script setup lang="ts">
import { BbRating } from 'bitboss-ui';
import { products } from '~/demo-data';
// The average is 4.6. Passed raw it would fill four stars, because a star
// fills only once the value reaches it — so round for the glyphs, and print
// the real figure next to them.
const product = products[0]!;
const reviewCount = 128;
</script>
There are no half stars. Round for the glyphs and print the precise decimal and sample size in the append slot.
Glyph and layout
The icon slot replaces only the glyph and receives size as a resolved CSS length string.
The glyph is decoration — the accessible name stays on the fieldset.
<template>
<div class="flex max-w-sm flex-col gap-2">
<BbRating
id="glyph-love"
v-model="love"
legend="How much do you love this release?"
name="love"
>
<!-- `size` arrives resolved, as a CSS length string. Pass it through. -->
<template #icon="{ checked, size }">
<BbIcon
:class="checked ? 'text-rose-500' : 'opacity-30'"
icon="lucide:heart"
:size="size"
/>
</template>
</BbRating>
<p class="text-sm opacity-70">
The glyph is decoration — the accessible name stays on the fieldset.
</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbIcon, BbRating } from 'bitboss-ui';
const love = ref<number | null>(4);
</script>
Pass that size through without arithmetic. input-position now works in v3; remove v2 alignment workarounds. The removed color prop moves to the local --color custom property.