Building Rating and Star Components with Tailwind CSS
AI generated
tw
Tailwind CSS · UI Pattern · Rating
Rating and Star Components
Getting half stars, hover preview and accessibility right with Tailwind

Star ratings look like a trivial component at first glance, but once half stars, an interactive input with a hover preview, and an accessible implementation are all required at the same time, there are more detail problems hiding inside than expected. This article shows how to cleanly implement both the read-only display of an average rating and the editable input variant with Tailwind CSS, including precise rendering of half stars and an accessibility approach that does not rely on visual stars alone.

14 min read Star Rating Rating System

1. Why star ratings need more care than they appear to

A star rating conveys a precise numeric value in very little visual space, usually a number between zero and five, often with one decimal place. That precision is exactly what makes the implementation more demanding than most other UI components: an average of 3.7 has to visually read as 3.7 rather than a roughly rounded 4, or the rating loses its meaning. At the same time, the same visual language has to work for both a purely read-only display and an interactive input where users submit their own rating through hover and click.

On top of that, stars as a purely visual symbol are fundamentally an accessibility problem if the underlying number is not also made available as text. Writing just five filled or empty symbols into the DOM makes the rating effectively invisible to screen reader users. This article therefore covers both the purely visual implementation with Tailwind and the semantic layer, which most rating implementations on the web tend to handle carelessly.

2. Stars with a gradient fill as the foundation for precise levels

The most robust approach for precise fill levels, even for awkward values like 3.7, is a gradient fill instead of a plain class toggle between filled and empty. Each star consists of two overlaid symbols: a gray background star as the base, and a colored foreground star whose visible portion is constrained by a linear gradient to exactly the percentage matching the fill level. For an overall value of 3.7, the first three stars get a full color fill, the fourth star gets a gradient stop at 70 percent, and the fifth stays entirely gray.

Technically this can be implemented with Tailwind using a combination of a relative container, an absolutely positioned overlay star, and an inline style declaration for the exact percentage, since Tailwind itself does not offer dynamic percentage values as a utility class. The base classes for size, color and positioning come entirely from Tailwind, only the variable fill level itself is set as a CSS custom property or inline style, which cleanly separates this one dynamic value from the otherwise static utility structure.


<div class="flex items-center gap-0.5" role="img" aria-label="3.7 out of 5 stars">
  <template x-for="i in 5" :key="i">
    <span class="relative inline-block h-5 w-5" aria-hidden="true">
      <!-- Background: always gray, full star shape -->
      <svg class="absolute inset-0 h-5 w-5 text-slate-200" viewBox="0 0 20 20" fill="currentColor">
        <path d="M10 1.5l2.7 5.8 6.3.6-4.7 4.3 1.3 6.2L10 15.3l-5.6 3.1 1.3-6.2L1 7.9l6.3-.6z" />
      </svg>
      <!-- Foreground: yellow, clipped to the fill level -->
      <svg
        class="absolute inset-0 h-5 w-5 text-amber-400"
        viewBox="0 0 20 20"
        fill="currentColor"
        :style="`clip-path: inset(0 ${100 - Math.max(0, Math.min(100, (rating - (i - 1)) * 100))}% 0 0)`">
        <path d="M10 1.5l2.7 5.8 6.3.6-4.7 4.3 1.3 6.2L10 15.3l-5.6 3.1 1.3-6.2L1 7.9l6.3-.6z" />
      </svg>
    </span>
  </template>
</div>

3. Half stars: clip-path versus gradient compared

For the common special case where only half stars occur, meaning ratings in 0.5 steps instead of arbitrary decimals, a simpler approach with exactly three states per star is often enough: empty, half and full. The half state can be handled either with clip-path: inset(0 50% 0 0) on the colored foreground star, as in the code example above but with a fixed 50 percent instead of a dynamic value, or through a linear gradient defined directly inside the SVG with two color stops at exactly 50 percent.

The clip-path approach is easier to understand and debug, since it works directly with visible percentage values, while the gradient variant requires a bit more SVG knowledge but also allows for smoother transitions with non-round percentages, for instance the precise average ratings mentioned earlier. For most use cases where users can only assign whole or half stars, the clip-path approach is entirely sufficient and stays noticeably easier to maintain, since no separate SVG gradient definitions need to be kept around.

4. Interactive hover preview during rating input

In the editable variant, every star has to react to hover individually and show a preview of what the rating would look like if the user clicked at that position right now. That means all stars up to the hover position render as fully colored, while the actually saved rating only becomes visible again after the pointer leaves the entire rating area. With Alpine.js this can be solved with a separate hoverIndex variable, kept apart from the actual rating variable, which resets to null on mouseleave on the container.

It matters that the hover preview stays clearly recognizable as provisional and does not feel exactly like the final selection, usually through a slightly reduced opacity or a subtle scale effect on the hovered star itself. The actual click then commits the final value, and a brief visual confirmation effect, such as a short flash or a small scale animation, gives the user feedback that the rating was actually applied and is not just temporarily visible as a hover state.

5. Read-only display versus editable input

The read-only variant, as typically shown below a product name as an average rating, needs no hover or click handlers at all and should not show a pointer cursor either, since that would falsely suggest interactivity. Here the plain gradient or clip-path rendering from the earlier section is entirely sufficient, complemented by an accompanying text with the exact numeric value and the number of ratings, for instance '4.3 out of 5 (128 ratings)', since the purely visual rendering alone can rarely be read precisely enough on its own.

The editable variant, by contrast, absolutely needs a cursor-pointer on every individual star, a visible hover preview as described in the previous section, and usually also a way to clear the rating again, for instance by clicking the already-active last star a second time. Both variants should be clearly distinguishable in the code through a prop or a flag such as readonly, so the same base component can be reused for both cases instead of maintaining two entirely separate implementations.

6. Accessibility: aria-label instead of purely visual rendering

Since stars are purely decorative SVG graphics, they carry no information accessible to screen readers on their own, even if their fill level renders with perfect visual accuracy. The entire star group therefore needs a wrapping container with role="img" and an aria-label containing the exact value as text, for instance aria-label="3.7 out of 5 stars", while every individual star inside the group gets aria-hidden="true", so screen readers do not incorrectly announce five separate, meaningless graphics.

For the editable variant, a plain role="img" is no longer sufficient, since the component is now interactive. A semantic radio group fits well here, where each star acts as role="radio" inside a role="radiogroup", with its own aria-label per star such as aria-label="Rate 4 stars". This structure additionally enables native keyboard navigation with the arrow keys between the individual rating levels, something that would be entirely missing with a purely click-based interaction that lacks semantic structure.

7. Size variants for different contexts

Star ratings show up in very different contexts, from a compact display inside a product list to a prominent input component on a review detail page, and need correspondingly different sizes. With Tailwind this is cleanly handled with a small, fixed set of size classes, for example h-3.5 w-3.5 for the compact list view, h-5 w-5 as the default size, and h-8 w-8 for the large, interactive input variant, rather than allowing arbitrary in-between values that would undermine visual consistency across the whole application.

At the compact size it is also worth tightening the gap between individual stars, for example from gap-0.5 to something even smaller, since five stars at full default spacing quickly eat up too much horizontal space in a tight product list. For the large, interactive variant, on the other hand, the spacing can safely be more generous, since each star needs to serve as an independent, easily hittable click target, especially on touch devices.

8. Color themes and dark mode adjustments

The classic star color is a warm yellow or amber, usually text-amber-400, which stands out well against the neutral gray of the unfilled star on a light background. In dark mode this color needs adjusting, since an overly muted amber loses contrast against a dark background, while the gray background star should also switch from a light gray to a darker gray tone such as dark:text-slate-600 in dark mode, so it does not itself read as an active star.

For applications with their own brand color scheme, the star color can generally be replaced with the primary accent color, the only requirement being that the contrast between filled and empty states stays clear enough in both color modes, ideally with a contrast ratio of at least 3:1 between the two states. A too-subtle difference between full and empty states makes the rating hard to read, especially at small display sizes inside product lists.

9. SVG icons versus icon fonts for stars

Two technical approaches suit the star shape itself: inline SVG paths as in the code example above, or an icon font with a star glyph whose fill is controlled through the font glyph's CSS color. Inline SVG has the clear advantage that the fill level can be controlled precisely through clip-path or a gradient, while an icon font relies on a second, overlaid font glyph with a clip-path for half or odd fill levels, which in practice is barely simpler than the SVG variant.

Inline SVG also avoids the well-known flash of unstyled glyphs that can occur with externally loaded icon fonts before the font file has fully loaded. Since stars are usually above the fold and therefore visible immediately, that flash absolutely needs to be avoided, which is why inline SVG is generally the more robust and recommended choice for rating components over icon fonts.

Variant Size (Tailwind) Interaction ARIA role
Compact (list) h-3.5 w-3.5 None, read only role="img"
Standard (read-only) h-5 w-5 None, read only role="img"
Editable (form) h-8 w-8 Hover preview + click role="radiogroup"
Compact editable h-5 w-5 Click without hover preview role="radiogroup"

Mironsoft

Tailwind CSS architecture, design systems, and performance

Tailwind frontends that stay maintainable despite thousands of utility classes?

We review existing Tailwind projects for bloated class lists, inconsistent design tokens, and unused CSS remnants, then build a design system that scales cleanly instead of getting messier with every component.

Design System Review

Checking tokens, spacing scale, and component consistency for maintainability.

Performance Optimization

Systematically reducing CSS bundle size, purge configuration, and load times.

Component Architecture

Building reusable, well-structured components instead of sprawling class lists.

10. Summary

Rating and Star Components with Tailwind: The Essentials at a Glance

Fill level

Gradient or clip-path for precise percentages, not just a coarse filled/empty toggle.

Hover preview

Separate hover variable kept apart from the saved value, with a clear visual distinction.

Accessibility

role=img with aria-label for read-only, role=radiogroup with keyboard navigation for editable.

Technique

Inline SVG over icon fonts, for precise clip-path control and no fill flash.

11. FAQ: Rating and Star Components with Tailwind: The Essentials at a Glance

1Why isn't a simple filled/empty class per star always enough?
With odd averages like 3.7, a single star can no longer be binary filled or empty. A gradient or clip-path makes it possible to render the exact percentage of one individual star.
2Is clip-path or an SVG gradient the better choice for half stars?
For fixed half steps, clip-path is easier to understand and debug. An SVG gradient pays off mainly when arbitrary, non-round percentages need to be rendered.
3How do you prevent the hover preview from being confused with the saved rating?
Both values should be kept in separate variables, with the hover variable resetting immediately when the pointer leaves the container. A subtle visual difference, such as reduced opacity, helps as well.
4Why does the read-only variant still need an aria-label?
Because the stars themselves, as SVG graphics, carry no information a screen reader can interpret. Without an aria-label, the rating stays completely invisible to screen reader users regardless of visual precision.
5Should the number of ratings always be shown alongside the stars?
Yes, wherever available. A plain star count with no context about sample size can be misleading, since five stars from a single rating carries a different weight than from 500.
6How large should the click targets be at minimum in the editable variant?
For touch devices, the entire clickable element per star should be at least 44 by 44 pixels, even if the visible star shape itself is smaller. Padding around the SVG solves this easily.
7Can star ratings be built entirely without JavaScript, using only CSS?
For the read-only display, yes, through plain CSS with a gradient or clip-path. For the interactive hover preview and click handler, JavaScript or a framework such as Alpine.js is required though.
8How do you handle keyboard navigation in the editable variant?
With role=radiogroup and role=radio per star, arrow key navigation works natively through browser semantics. Space or Enter then commits the currently focused rating as the click equivalent.
9Why is inline SVG recommended over icon fonts for stars?
Inline SVG allows precise clip-path and gradient control without an extra font file, and avoids the brief flash of unstyled glyphs when loading external icon fonts, which is especially disruptive for above-the-fold content.
10Should the star color be adjusted to match the brand color scheme?
That is possible and common, as long as the contrast between filled and empty states stays clear enough. A contrast ratio of at least 3:1 between the two states should not be undercut.