StarRating
AtomFive-star rating indicator. Read-only by default with decimal/half-star rendering; pass `readonly={false}` + `onChange` for interactive whole-star selection.
(312 reviews)
<%- include('modules/ui/StarRating', { value: 4.7, size: 'sm', caption: '(312 reviews)' }) %>
<%- include('modules/ui/StarRating', { value: 3.5, size: 'md' }) %>
<%- include('modules/ui/StarRating', { value: 2.2, size: 'lg' }) %>
Selected: –
<%- include('modules/ui/StarRating', {
value: 0,
readonly: false,
size: 'lg',
ariaLabel: 'Pick a rating'
}) %>
<%
var _value = (typeof locals.value === 'number') ? locals.value : 0;
var _size = locals.size || 'md';
var _readonly = (typeof locals.readonly === 'boolean') ? locals.readonly : true;
var _ariaLabel = locals['aria-label'] || locals.ariaLabel || '';
var _caption = locals.caption || '';
var _className = locals.className || '';
var TOTAL_STARS = 5;
// clamp
if (isNaN(_value)) _value = 0;
if (_value < 0) _value = 0;
if (_value > TOTAL_STARS) _value = TOTAL_STARS;
var sizeClasses = {
sm: 'w-3.5 h-3.5',
md: 'w-5 h-5',
lg: 'w-7 h-7',
}[_size] || 'w-5 h-5';
var gapClasses = {
sm: 'gap-0.5',
md: 'gap-1',
lg: 'gap-1.5',
}[_size] || 'gap-1';
var defaultLabel = _value.toFixed(1) + ' out of ' + TOTAL_STARS + ' stars';
var labelText = _ariaLabel || defaultLabel;
var isInteractive = !_readonly;
// unique id for interactive mode JS scoping
var _rid = 'sr_' + Math.random().toString(36).slice(2, 9);
%>
<% if (!isInteractive) { %>
<% for (var i = 1; i <= TOTAL_STARS; i++) {
var filled = _value >= i;
var half = !filled && _value >= (i - 0.5);
var iconClass = filled ? 'fa-solid fa-star' : (half ? 'fa-solid fa-star-half-stroke' : 'fa-regular fa-star');
var colorClass = (filled || half) ? 'text-warning' : 'text-text-disabled';
%>
<% } %>
<% if (_caption) { %>
<%- _caption %>
<% } %>
<% } else { %>
<% for (var k = 1; k <= TOTAL_STARS; k++) {
var kFilled = _value >= k;
var kChecked = _value === k;
%>
<% } %>
<% if (_caption) { %>
<%- _caption %>
<% } %>
<% } %>