Rating

Pixel hearts or stars for a score, with half steps, hover preview and keys.

zweiundeins <sb-rating> rating stars hearts score form Since 2026-09-22 2.9 kB0Open in playground Edit on GitHub

Playground

A row of pixel hearts (or stars) for a score. Point to preview, click to pick, or use the arrow keys. The live value is the value property, so data-bind works, and a new value attribute from the server replaces it.

Examples

Bound to a signal

Score:
<div data-signals="{_score: 3}" style="display: grid; gap: 12px">
  <sb-rating label="How was the launch?" data-bind:_score__prop.value></sb-rating>
  <span>Score: <b data-text="$_score"></b></span>
</div>

Half steps and stars

<div style="display: grid; gap: 12px">
  <sb-rating icon="star" precision="0.5" value="2.5"></sb-rating>
  <sb-rating icon="star" precision="0.5" value="4" max="10" size="sm"></sb-rating>
</div>

Read only

For showing a score, e.g. an average: readonly turns it into an image with a text alternative ("4.5 of 5").

<sb-rating readonly value="4.5" precision="0.5" size="sm"></sb-rating>

Clearable

With clearable, picking the current value again resets it to 0.

<sb-rating clearable value="2"></sb-rating>

With commands

Give it a name, and it emits sb-change with { name, value } when a value is picked: ready to post as a command. With confirm, it sets :state(pending) until the server's re-rendered attribute matches, and revert() goes back to the server's value when a command is rejected. See Commands and components.

Theming

The filled colour is --sb-rating-color (hearts default to --sb-danger, stars to --sb-warn); empty units use --sb-border-strong.

Accessibility

The row is a slider (Arrow keys change the value by one step, Home clears it, End fills it), named by label or "Rating", with aria-valuetext like "3 of 5". Read-only ratings are an img with the same text.

Installation

Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.

<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
     It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
  { "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>

<sb-rating value="3.5" precision="0.5" size="lg"></sb-rating>

<!-- In production, pin today's catalog instead of the latest: the browser then
     refuses any file that changed. Add "integrity" to the import map above: the
     hashes from https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/importmap.json and Datastar's, below. -->
<!--
<script type="importmap">
  { "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" },
    "integrity": { "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js": "sha384-vUxZojLrF1Ar3de5h7VINqhJXBgjyZS4U49pHvGa87kum6j5Xn6JRziuARNw5ELG", "…": "…from importmap.json" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/autoloader.js" integrity="sha384-IvSCUjEWPS+wwXunU+48x96KmyWDx/Y4LeCwCL8cWFqJ/zHmTsTOsSMrzTOcpW3D"></script>
-->

<!-- Or load just this component, pinned to this version: -->
<!-- <script type="module" src="https://starbase.zweiundeins.gmbh/c/rating@c6d948a68247/rating.js" integrity="sha384-TmQRJgbAWvy4Sy745a6pcNj9N63ahkuFOB0YeUkJVR+dgipTsOPMYY77OK/fCOgD"></script> -->

Size

Each file compressed on its own, the way it is served (gzip -9, brotli -11). Datastar and Rocket are shared by every component and not counted.

FileOriginalgzipbrotli
rating.js 8.1 kB3.4 kB2.9 kB

API reference

Props

AttributeTypeDefaultDescription
valuenumber0The value. A new value from the server replaces it; the live value is the value property.
maxnumber5Number of hearts (or stars).
precision"1" | "0.5""1"Step: whole or half units.
icon"heart" | "star""heart"Pixel sprite.
size"sm" | "md" | "lg""md"Size.
labelstring""Visible label (also the accessible name).
readonlybooleanfalseShow the value; no interaction.
clearablebooleanfalsePicking the current value again clears it to 0.
disabledbooleanfalseDisable interaction.
confirmbooleanfalseServer-confirmed value: :state(pending) while the local value differs from the server's value attribute (see revert()).
namestring""Name reported in sb-change (e.g. the field of a command).

Events

NameDescription
changeWhen the value is committed.
sb-changeSame moment. detail: { name, value }: ready for a command.