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