Skip to content

Rating

A fully accessible rating component with keyboard navigation, half-star support, and customizable appearance.

Features

  • ✅ Keyboard navigation (Arrow keys, Home/End)
  • ✅ Half-star ratings (optional)
  • ✅ Hover preview
  • ✅ Disabled and read-only states
  • ✅ Form integration with hidden input
  • ✅ WAI-ARIA radiogroup pattern
  • ✅ x-model support for two-way binding
  • ✅ Customizable number of stars

Installation

js
import Alpine from 'alpinejs'
import rating from 'alpine-headless-ui/rating'

Alpine.plugin(rating)
Alpine.start()

Examples

Basic Rating

A simple 5-star rating component.

Half-Star Ratings

Enable half-star precision with allowHalf: true.

Read-Only Display

Show ratings without allowing interaction.

Average rating: 4.0

With x-model

Use x-model for two-way binding.

Your rating:

Different Star Counts

Customize the number of rating items.

Disabled State

With Change Event

Selected:

Keyboard Shortcuts

The rating component supports keyboard navigation:

KeyAction
/ Increase rating by one step
/ Decrease rating by one step
HomeSet rating to 0
EndSet rating to maximum
TabMove focus to/from rating

API Reference

Component Props

PropTypeDefaultDescription
valuenumber0Initial rating value
countnumber5Total number of rating items
allowHalfbooleanfalseEnable half-star ratings
disabledbooleanfalseDisable interaction
readOnlybooleanfalseMake rating read-only
requiredbooleanfalseMake rating required (for forms)
namestring-Name for hidden input (form integration)
translationsobject-Custom ARIA labels (itemLabel function)
html
<div x-rating="{
  value: 3,
  count: 5,
  allowHalf: true,
  name: 'product-rating'
}">
  <!-- Parts -->
</div>

<!-- With x-model for two-way binding -->
<div x-data="{ rating: 0 }">
  <div x-rating="{ count: 5 }" x-model="rating">
    <!-- Parts -->
  </div>
</div>

Two-way binding with x-model:

The rating component is modelable, which means you can use Alpine's x-model directive for two-way binding.

html
<div x-data="{ userRating: 4 }">
  <div x-rating="{ count: 5 }" x-model="userRating">
    <!-- Parts -->
  </div>
  <p>Rating: <span x-text="userRating"></span></p>
</div>

Parts

PartDescription
x-ratingRoot container element
x-rating:rootAlternative root container
x-rating:labelLabel element for the rating
x-rating:controlContainer for the rating items
x-rating:itemIndividual rating item (star, icon, etc.)
x-rating:hidden-inputHidden input for form integration

x-rating:root

Container for the entire rating component.

Automatically receives:

  • data-disabled - Present when disabled
  • data-readonly - Present when read-only
html
<div x-rating:root>
  <!-- All parts -->
</div>

x-rating:label

Label element for the rating.

Automatically receives:

  • Proper id attribute for accessibility
html
<label x-rating:label>Rate this product</label>

x-rating:control

Container for the rating items.

Automatically receives:

  • role="radiogroup"
  • aria-label="Rating"
  • ARIA attributes for disabled/readonly/required states
  • Keyboard event handlers
html
<div x-rating:control class="flex gap-1">
  <!-- Rating items -->
</div>

x-rating:item

Individual rating item (star, icon, etc.).

Requires:

  • A value indicating the item's position (1-indexed)

Automatically receives:

  • role="radio"
  • aria-label - Descriptive label
  • aria-checked - Whether this item is selected
  • data-checked - Present when this item is part of the rating
  • data-highlighted - Present when highlighted (selected or hovered)
  • data-half - Present when showing half-star (if allowHalf: true)
  • tabindex - Manages keyboard focus
  • Click and hover handlers
html
<button x-rating:item="1" type="button">★</button>
<button x-rating:item="2" type="button">★</button>
<button x-rating:item="3" type="button">★</button>
<button x-rating:item="4" type="button">★</button>
<button x-rating:item="5" type="button">★</button>

x-rating:hidden-input

Hidden input for form integration.

Automatically receives:

  • type="text"
  • hidden attribute
  • name attribute (if configured)
  • Bound value
  • disabled and required attributes
html
<input x-rating:hidden-input />

Data Attributes

AttributeDescription
data-disabledPresent when disabled on root
data-readonlyPresent when read-only on root
data-checkedPresent when item is part of the current rating on item
data-highlightedPresent when item is highlighted (hovered or selected) on item
data-halfPresent when item shows half-star (when allowHalf: true) on item
html
<div x-rating:root class="data-disabled:opacity-50 data-readonly:cursor-default">
  <button x-rating:item="3" class="data-checked:text-yellow-500 data-highlighted:text-yellow-400 data-half:opacity-50">

  </button>
</div>

Events

EventDetailDescription
change{ value: number }Fired when rating value changes
html
<div x-rating x-on:change="console.log('Rating:', $event.detail.value)">
  <!-- Parts -->
</div>

Accessing State

You can access the rating API using $rating:

html
<div x-rating="{ value: 3, count: 5 }">
  <div x-data>
    <p x-text="'Current rating: ' + $rating.value"></p>
    <p x-show="$rating.isHovering">Hovering!</p>
  </div>

  <div x-rating:control>
    <!-- Items -->
  </div>
</div>

Available properties:

  • value (number) - Current rating value
  • hoveredValue (number) - Currently hovered value (-1 if not hovering)
  • focusedValue (number) - Currently focused item index (-1 if not focused)
  • isHovering (boolean) - Whether user is hovering over items
  • displayValue (number) - The value being displayed (hovered or actual)

Available methods:

  • setValue(newValue, dispatch?) - Set rating value programmatically

Accessibility

This component implements the WAI-ARIA radiogroup pattern.

Features

  • ✅ Proper role="radiogroup" on control
  • ✅ Proper role="radio" on items
  • ✅ ARIA labels for screen readers
  • aria-checked for current selection
  • ✅ Keyboard navigation with arrow keys
  • ✅ Roving tabindex for keyboard focus management
  • ✅ Proper disabled and readonly states

Best Practices

  • Always provide a label using x-rating:label
  • Ensure sufficient color contrast for all states
  • Test keyboard navigation thoroughly
  • Use the hidden input for form submissions
  • Provide clear visual feedback for hover and focus states

Example with Full Accessibility

html
<div x-rating="{
  value: 0,
  count: 5,
  required: true,
  name: 'product-rating'
}">
  <label x-rating:label>
    How would you rate this product? (Required)
  </label>

  <div x-rating:control class="flex gap-1">
    <button x-rating:item="1" type="button" class="text-3xl transition-colors data-highlighted:text-yellow-500 hover:text-yellow-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 rounded">★
      </button>
    <button x-rating:item="2" type="button" class="text-3xl transition-colors data-highlighted:text-yellow-500 hover:text-yellow-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 rounded">★
      </button>
    <button x-rating:item="3" type="button" class="text-3xl transition-colors data-highlighted:text-yellow-500 hover:text-yellow-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 rounded">★
      </button>
    <button x-rating:item="4" type="button" class="text-3xl transition-colors data-highlighted:text-yellow-500 hover:text-yellow-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 rounded">★
      </button>
    <button x-rating:item="5" type="button" class="text-3xl transition-colors data-highlighted:text-yellow-500 hover:text-yellow-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 rounded">★
      </button>
  </div>

  <input x-rating:hidden-input />
</div>

See Also

Built with Alpine.js and inspired by Zag.js