What You'll Build in This Tutorial
By the end of this guide, you'll have created a reusable React component that:
- Renders full, half, and empty stars dynamically based on numeric ratings
- Supports hover preview functionality showing users what rating they'll select
- Maintains consistent star count (default: 5 stars) regardless of rating value
- Includes proper accessibility attributes for screen reader compatibility
- Offers customization options for colors, sizes, and maximum star count
Star rating components have become essential in modern web applications. Whether you're building an e-commerce product review system, a movie rating application, or a customer feedback form, the ability to capture nuanced user opinions through half-star ratings provides better data granularity and improved user experience. Half-star precision allows users to express more accurate sentiment, reducing the ambiguity that comes with whole-number-only ratings.
For example, a product rated 4.5 stars conveys significantly more information than simply "good" or "very good." This granular feedback helps other potential customers make informed purchasing decisions and provides sellers with actionable insights for improvement. In customer feedback forms, half-star ratings can distinguish between a "good enough" experience (3 stars) and one that exceeded expectations (4 stars), enabling businesses to better understand service quality and identify areas for enhancement.
Everything you need to build a professional rating system
Dual Implementation Approaches
Choose between simple icon-based rendering or advanced SVG gradient technique for quarter-star precision
Interactive Hover States
Real-time preview shows users their selected rating before clicking
Accessibility-First Design
Proper ARIA labels, keyboard navigation, and screen reader support built-in
Highly Customizable
Control colors, sizes, maximum stars, and read-only mode through props
Understanding The Star Rating Logic
Before diving into the code, let's understand the fundamental logic behind star rating calculations. The approach relies on simple mathematical operations to determine how many full stars, half stars, and empty stars to display based on a numeric rating value.
Core Calculations
The star rating system operates on three key calculations:
Full Stars: The number of fully lit stars is determined by taking the floor of the rating value. For a rating of 4.3, this means four full stars will be displayed. The Math.floor() function ensures we always get a whole number, dropping any decimal portion.
Half Star Determination: A half star appears when the rating contains a decimal value. The simplest approach checks whether the rating is not a whole number using Number.isInteger(). If the rating has any decimal component, exactly one half star will be added.
Empty Stars: The remaining stars to complete the total are calculated by subtracting the count of full stars plus half stars from the total star count.
const totalStars = 5;
const fullStars = Math.floor(rating);
const halfStars = Number.isInteger(rating) ? 0 : 1;
const emptyStars = totalStars - (fullStars + halfStars);
This three-value approach provides a clean, predictable way to render any rating from 0 to 5 with half-star precision. Understanding these calculations is fundamental to React component development and building interactive user interfaces.
1import { FaStar, FaStarHalfAlt, FaRegStar } from "react-icons/fa";2 3const StarRating = ({ rating }) => {4 const totalStars = 5;5 6 // Calculate star types7 const fullStars = Math.floor(rating);8 const halfStars = Number.isInteger(rating) ? 0 : 1;9 const emptyStars = totalStars - (fullStars + halfStars);10 11 // Build the stars array12 const starsArray = [];13 14 // Add full stars15 for (let i = 0; i < fullStars; i++) {16 starsArray.push(<FaStar key={`full-${i}`} color="gold" />);17 }18 19 // Add half star if needed20 if (halfStars) {21 starsArray.push(<FaStarHalfAlt key="half" color="gold" />);22 }23 24 // Add empty stars25 for (let i = 0; i < emptyStars; i++) {26 starsArray.push(<FaRegStar key={`empty-${i}`} color="gray" />);27 }28 29 return (30 <div style={{ display: "flex", gap: "4px", alignItems: "center" }}>31 {starsArray}32 </div>33 );34};35 36export default StarRating;Adding Interactive Features
To create a truly interactive rating component, we need to add hover functionality that shows users what rating they'll select before they click. This requires managing state to track the hover preview.
The interactive component maintains two pieces of state: the current rating value (passed from the parent) and a hover state that temporarily overrides the displayed rating during user interaction. When the user moves their mouse away from the stars, the hover state resets, returning to show the actual rating value.
This pattern provides immediate visual feedback, helping users understand exactly what they're selecting. The key is using a logical OR operation: ratingValue <= (hover || value). This means when hovering, we preview the potential rating, but when not hovering, we display the actual selected value. The hover state essentially acts as a temporary override, creating a smooth preview experience that enhances usability without complex conditional logic.
For frontend development projects, implementing interactive components with proper state management is essential for creating responsive, user-friendly applications.
1import { useState } from "react";2import { FaStar, FaStarHalfAlt, FaRegStar } from "react-icons/fa";3 4const InteractiveStarRating = ({ value, onChange }) => {5 const [hover, setHover] = useState(null);6 const totalStars = 5;7 8 return (9 <div style={{ display: "flex", gap: "4px" }}>10 {[...Array(totalStars)].map((_, index) => {11 const ratingValue = index + 1;12 13 return (14 <FaStar15 key={index}16 color={ratingValue <= (hover || value) ? "gold" : "gray"}17 onClick={() => onChange(ratingValue)}18 onMouseEnter={() => setHover(ratingValue)}19 onMouseLeave={() => setHover(null)}20 style={{ cursor: "pointer", transition: "color 200ms" }}21 />22 );23 })}24 </div>25 );26};27 28export default InteractiveStarRating;Advanced: SVG Gradient Approach for Fractional Ratings
For applications requiring even more granular feedback, the SVG gradient technique enables quarter-star or finer precision. This approach divides each star into segments and uses linear gradients to fill partial portions based on the rating value.
How It Works
The SVG gradient approach creates invisible linear gradient definitions that are applied to star icons based on the rating calculation. When a rating falls between whole numbers, the gradient creates a smooth visual transition from colored to transparent. The click position within each star determines the fractional value, allowing ratings like 3.25, 3.5, or 3.75 to be displayed accurately.
The key innovation is using click position detection: instead of just clicking a star, we calculate where within the star the user clicked. By getting the bounding rectangle of the star element and dividing the click position by the total width, we determine what fraction of the star was "selected." This fractional approach provides users with more expressive rating options beyond simple half-stars.
Implementing advanced SVG techniques like this is a key skill in modern UI development, where precise visual control and creative interactions differentiate exceptional user experiences.
1const calculateRating = (index, event) => {2 const star = event.currentTarget;3 const rect = star.getBoundingClientRect();4 const x = event.clientX - rect.left;5 const width = rect.width;6 const clickPosition = x / width;7 8 let fraction = 1;9 if (clickPosition <= 0.25) fraction = 0.25;10 else if (clickPosition <= 0.5) fraction = 0.5;11 else if (clickPosition <= 0.75) fraction = 0.75;12 13 return index + fraction;14};Accessibility Considerations
Building inclusive rating components requires careful attention to accessibility. Screen reader users must be able to understand and interact with the rating system through proper ARIA attributes and semantic HTML.
Key Accessibility Features
ARIA Labels: Add descriptive labels that communicate the current rating to screen readers. The aria-label attribute should state the current rating out of the total, such as "Rating 4.5 out of 5 stars." Implementing proper ARIA labels ensures blind users receive the same information as sighted users.
<button
role="radio"
aria-checked={ratingValue === value}
aria-label={`Rate ${ratingValue} out of ${totalStars} stars`}
tabIndex={0}
>
<FaStar color={ratingValue <= (hover || value) ? "gold" : "gray"} />
</button>
Keyboard Navigation: Ensure users can interact with the rating using only keyboard controls. Stars should be focusable, and pressing Enter or Space should select a rating. This typically requires wrapping stars in button elements or adding tabIndex and role attributes. The keyboard navigation pattern ensures users who cannot use a mouse can still provide ratings.
Visual Feedback: Maintain clear distinction between filled and empty stars that doesn't rely solely on color. Consider adding size differences, borders, or other visual cues that help users with color vision deficiencies understand the rating. Adding focus states with visible outlines helps all users understand which star is currently selected.
Accessibility is a cornerstone of inclusive web design, ensuring your applications work for everyone regardless of ability or assistive technology.
Customization Options
A well-designed star rating component should be easily customizable to fit different design systems. Consider adding props for controlling colors, sizes, maximum rating values, and read-only modes.
Props Interface
The following props interface enables the component to adapt to various design requirements while maintaining a consistent API:
interface StarRatingProps {
value: number; // Current rating value (can be fractional)
onChange?: (value: number) => void; // Callback when rating changes
className?: string; // Custom CSS classes
iconSize?: number; // Size of star icons in pixels
maxStars?: number; // Maximum number of stars (default: 5)
readOnly?: boolean; // Whether rating can be changed
color?: string; // Color of filled stars
emptyColor?: string; // Color of empty stars
}
The readOnly prop is particularly useful for displaying existing ratings where user input isn't required, such as in product cards or review summaries. This separation of display and interactive modes allows the same component to serve multiple purposes throughout your application. For custom web application development projects, this flexibility means your rating component can seamlessly integrate with various UI frameworks and design systems.
Usage Examples and Integration
Integrating the star rating component into your application requires understanding the data flow between parent components and the rating element. The component can operate in two modes: display-only for showing existing ratings, and interactive for capturing user input.
Display Mode
For product cards, review summaries, and other situations where ratings are shown but not collected, use the component in display-only mode:
// Display-only usage
function ProductCard({ product }) {
return (
<div className="product-card">
<h3>{product.name}</h3>
<StarRating rating={product.averageRating} color="#ffd700" />
<span>{product.averageRating.toFixed(1)} out of 5 stars</span>
<span>({product.reviewCount} reviews)</span>
</div>
);
}
Interactive Mode
For forms and feedback collection, connect the component to state management and handle the onChange callback to capture user selections:
// Interactive usage with form submission
function RatingForm() {
const [userRating, setUserRating] = useState(0);
const handleSubmit = async (e) => {
e.preventDefault();
// Send rating to server
await submitRatingToServer(userRating);
alert(`Thank you for your rating of ${userRating} stars!`);
};
return (
<form onSubmit={handleSubmit} className="rating-form">
<label>Rate your experience:</label>
<InteractiveStarRating
value={userRating}
onChange={setUserRating}
maxStars={5}
/>
<button type="submit" disabled={userRating === 0}>
Submit Rating
</button>
</form>
);
}
These patterns ensure proper React data flow between parent and child components while maintaining clean separation of concerns. Building reusable components like this is a hallmark of professional React development services.
Performance Optimization
For applications displaying many star rating components, performance optimization becomes important. Several techniques help maintain smooth rendering even with numerous ratings.
Memoization
Using React.memo prevents unnecessary re-renders of star icons when the rating value hasn't changed. This is particularly beneficial in lists or grids of rating components:
import { memo } from "react";
const StarIcon = memo(({ filled, half, size, color, onClick }) => {
return (
<span onClick={onClick}>
{half ? (
<FaStarHalfAlt size={size} color={color} />
) : (
<FaStar size={size} color={filled ? color : "gray"} />
)}
</span>
);
}, (prevProps, nextProps) => {
return (
prevProps.filled === nextProps.filled &&
prevProps.half === nextProps.half &&
prevProps.color === nextProps.color
);
});
StarIcon.displayName = "StarIcon";
The memoization function compares only the relevant props that affect visual rendering, allowing React to skip re-renders when the visual state hasn't changed.
Unique ID Generation
When using SVG gradients for fractional ratings, each star needs a unique gradient ID to prevent conflicts when multiple rating components appear on the same page:
let componentId = 0;
const createGradientIds = (count) => {
const id = componentId++;
return Array.from({ length: count }, (_, i) => `star-gradient-${id}-${i}`);
};
// Usage in component
const gradientIds = createGradientIds(totalStars);
This approach ensures each rating component has its own set of gradient IDs, preventing visual artifacts when multiple instances exist simultaneously in lists, grids, or dashboards displaying multiple ratings.
Performance optimization techniques like memoization are essential skills for React application development, ensuring your applications remain responsive even as they scale.
Conclusion
Building a half-star rating component from scratch provides valuable insights into React component design, state management, and SVG manipulation. The approach you choose depends on your specific requirements:
- The icon-based method provides simplicity and broad compatibility with any icon library
- The SVG gradient technique enables precision down to quarter stars or finer for specialized applications
Regardless of implementation, prioritizing accessibility ensures your component works for all users. The combination of proper ARIA attributes, keyboard navigation, and clear visual feedback creates an inclusive experience that captures nuanced user feedback effectively.
Start with the basic implementation and incrementally add features as needed. The component's modular design allows you to add interactivity, customization, and fractional precision without rewriting the core logic. This iterative approach results in a robust, reusable component that enhances any application requiring user feedback through visual ratings.
For teams building custom web applications, investing in well-designed reusable components like this star rating system pays dividends in code quality, maintainability, and user experience consistency across your digital products. Whether you're building an e-commerce platform or a SaaS application, mastering component architecture is key to delivering exceptional user experiences.