Star Rating
A component that displays ratings with stars and an optional label. This component builds trust with visual feedback.
components/shadcncraft/pro-marketing/star-rating.tsx
import * as React from "react";
import { IconPlaceholder } from "@/registry/icons/icon-placeholder";
import { cn } from "@/lib/utils";
type StarRatingProps = {
value: number;
max?: number;
label?: string;
size?: "sm" | "md" | "lg";
orientation?: "vertical" | "horizontal";
/**
* Precision for partial stars. If set, the component will fill each star
* proportionally (e.g., 3.2 -> 60% of the 4th star). Defaults to true.
*/
allowPartial?: boolean;
containerClassName?: string;
};
export function StarRating({
value,
max = 5,
label,
size = "lg",
orientation = "vertical",
allowPartial = true,
className,
containerClassName,
...props
}: StarRatingProps & Omit<React.ComponentProps<"div">, "children">) {
const safeMax = Math.max(1, Math.floor(max));
const clampedValue = Math.max(0, Math.min(value, safeMax));
return (
<div
data-size={size}
data-orientation={orientation}
data-slot="star-rating"
className={cn(
"flex flex-col items-center gap-1.5",
"data-[orientation=horizontal]:flex-row",
"data-[size=lg]:[--star-size:calc(--spacing(5))] data-[size=md]:[--star-size:calc(--spacing(4))] data-[size=sm]:[--star-size:calc(--spacing(3))]",
containerClassName
)}
aria-label={`${clampedValue} out of ${safeMax} stars`}
role="img"
{...props}
>
<div className={cn("flex items-center gap-0.5", className)}>
{Array.from({ length: safeMax }).map((_, index) => {
const starIndex = index + 1;
const filledRatio = allowPartial
? Math.max(0, Math.min(1, clampedValue - index))
: clampedValue >= starIndex
? 1
: 0;
return (
<span key={index} className="relative inline-flex" aria-hidden="true">
{/* Base (unfilled) star */}
<IconPlaceholder
lucide="Star"
tabler="IconStar"
hugeicons="StarIcon"
phosphor="StarIcon"
remixicon="RiStarLine"
className="size-(--star-size)"
/>
{/* Filled overlay */}
{filledRatio > 0 ? (
<span
className="absolute inset-0 overflow-hidden"
style={{ width: `${filledRatio * 100}%` }}
>
<IconPlaceholder
lucide="Star"
tabler="IconStar"
hugeicons="StarIcon"
phosphor="StarIcon"
remixicon="RiStarLine"
fill="currentColor"
className="size-(--star-size)"
/>
</span>
) : null}
</span>
);
})}
</div>
{label && (
<span data-slot="star-rating-label" className="text-xs text-muted-foreground">
{label}
</span>
)}
</div>
);
}
No selection tags yet — empty means no evidence, not lack of support.
More from Shadcraft
404 1 – Minimalist error page with navigation recovery actionsblock404 2 – Visual 404 error page with immersive split-screen layoutblock404 3 – Error page with multi-channel support contact cardsblock404 4 – Error page with conceptual plug illustrationblock404 5 – Error page with immersive image and support directoryblock404 6 – Split-screen 404 page with high-contrast editorial visualblockActivity Feed 1 – Filterable timeline with inline contextblockActivity Feed Item 1component
Browse all components in this library →