Rate
A flexible and intuitive rating component for collecting user feedback through visual ratings. Built on top of rc-rate, the Rate component seamlessly integrates with RizzUI's design system, providing a consistent and accessible way to gather user opinions and ratings.
Features
- ⭐ Visual Rating - Intuitive star-based (or custom character) rating system
- 📏 Multiple Sizes - Three size options (sm, md, lg) to match your design
- 🎨 Custom Characters - Use any icon, emoji, or custom element as rating characters
- 🔢 Half Ratings - Support for half-star ratings for more granular feedback
- ♿ Accessible - Built with accessibility best practices
- 🎯 Type Safe - Full TypeScript support
- 📝 Form Integration - Works seamlessly with form libraries
Installation
Before using the Rate component, you'll need to install the required dependency:
Step 1
Install the rc-rate package.
- npm
- yarn
- pnpm
- bun
npm install rc-rate
yarn add rc-rate
pnpm add rc-rate
bun add rc-rate
Step 2
Create a rate component, components/rate.tsx
import React from 'react';
import RcRate from 'rc-rate';
import type { RateProps as RcRateProps } from 'rc-rate/lib/Rate';
import type { StarProps as RcStarProps } from 'rc-rate/lib/Star';
import { StarIcon } from '@heroicons/react/24/outline';
import { tv, type VariantProps } from 'tailwind-variants';
import { cn } from 'rizzui/cn';
import { FieldErrorText } from 'rizzui/field-error-text';
import { FieldHelperText } from 'rizzui/field-helper-text';
const labelStyles = {
size: {
sm: 'text-xs mb-1',
md: 'text-sm mb-1.5',
lg: 'text-sm mb-1.5',
},
} as const;
const rate = tv({
slots: {
container:
'flex items-center [&>li]:cursor-pointer [&.rc-rate-disabled>li]:cursor-default [&>li]:relative [&>li]:mr-0.5 rtl:[&>li]:ml-0.5 [&>li]:inline-block text-gray-200 [&>.rc-rate-star-half>div>.rc-rate-star-first]:text-orange [&>.rc-rate-star-full>div]:text-orange',
character: '[&>svg]:fill-current',
firstStar:
'[&>li>div>.rc-rate-star-first]:absolute [&>li>div>.rc-rate-star-first]:left-0 rtl:[&>li>div>.rc-rate-star-first]:right-0 [&>li>div>.rc-rate-star-first]:top-0 [&>li>div>.rc-rate-star-first]:w-1/2 [&>li>div>.rc-rate-star-first]:h-full [&>li>div>.rc-rate-star-first]:overflow-hidden',
label: '',
},
variants: {
size: {
sm: {
container: '',
character: 'h-5 w-5',
label: labelStyles.size.sm,
},
md: {
container: '',
character: 'h-6 w-6',
label: labelStyles.size.md,
},
lg: {
container: '',
character: 'h-7 w-7',
label: labelStyles.size.lg,
},
},
disabled: {
true: {
container: '',
},
false: {
container:
'[&>li>div]:transition-all [&>li>div]:duration-300 [&>.rc-rate-star:hover]:scale-110',
},
},
},
defaultVariants: {
size: 'md',
disabled: false,
},
});
export interface RateProps
extends Omit<RcRateProps, 'character' | 'className'> {
label?: React.ReactNode;
size?: VariantProps<typeof rate>['size'];
character?: React.ReactNode | Array<React.ReactNode>;
characterClassName?: string;
helperText?: React.ReactNode;
error?: string;
labelClassName?: string;
rateClassName?: string;
errorClassName?: string;
helperClassName?: string;
className?: string;
ref?: React.Ref<any>;
}
const Rate = ({
size = 'md',
disabled = false,
character = <StarIcon />,
label,
error,
helperText,
labelClassName,
characterClassName,
errorClassName,
helperClassName,
rateClassName,
className,
ref,
...props
}: RateProps) => {
const {
container: containerClass,
character: characterClass,
firstStar: firstStarClass,
label: labelClass,
} = rate({
size,
disabled,
});
const count = props.count ?? 5;
return (
<div className={cn('rizzui-rate', className)}>
{label && (
<label
className={cn('block font-medium', labelClass(), labelClassName)}
>
{label}
</label>
)}
<div role="radiogroup" aria-invalid={error ? 'true' : undefined}>
<RcRate
ref={ref}
disabled={disabled}
character={({ index }: RcStarProps) => (
<div
className={cn(characterClass(), characterClassName)}
aria-label={`Rate ${index + 1} out of ${count}`}
>
{Array.isArray(character)
? character[index as number]
: character}
</div>
)}
className={cn(containerClass(), firstStarClass(), rateClassName)}
aria-label={typeof label === 'string' ? label : 'Rating'}
{...props}
/>
</div>
{!error && helperText && (
<FieldHelperText as="div" size={size} className={helperClassName}>
{helperText}
</FieldHelperText>
)}
{error && (
<FieldErrorText size={size} error={error} className={errorClassName} />
)}
</div>
);
};
Rate.displayName = 'Rate';
export default Rate;
Usage
Basic Example
The simplest way to use the Rate component with default settings:
import Rate from '@components/rate';
export default function App() {
return <Rate />;
}
Controlled Component
For controlled usage, manage the rating value with state:
import React from 'react';
import Rate from '@components/rate';
export default function App() {
const [value, setValue] = React.useState(0);
return <Rate value={value} onChange={(newValue) => setValue(newValue)} />;
}
Sizes
Choose from three size options to match your design requirements:
import Rate from '@components/rate';
export default function App() {
return (
<>
<Rate label="Small" size="sm" />
<Rate label="Medium (Default)" size="md" />
<Rate label="Large" size="lg" />
</>
);
}
Half Ratings
Enable half-star ratings for more granular feedback:
import Rate from '@components/rate';
export default function App() {
return <Rate allowHalf defaultValue={1.5} />;
}
Custom Characters
Replace the default star icon with any custom character or icon:
import Rate from '@components/rate';
import { HeartIcon } from '@heroicons/react/24/solid';
export default function App() {
return <Rate character={<HeartIcon className="w-6" />} defaultValue={1.5} />;
}
Custom Character Array
Use different characters for each rating level:
import Rate from '@components/rate';
const emoji = [
<svg key="1" viewBox="0 0 16 16" fill="currentColor">
{/* Happy emoji SVG */}
</svg>,
<svg key="2" viewBox="0 0 16 16" fill="currentColor">
{/* Neutral emoji SVG */}
</svg>,
// ... more emoji SVGs
];
export default function App() {
return (
<Rate
character={emoji}
defaultValue={2}
characterClassName="[&>svg]:mr-2"
/>
);
}
Disabled State
Disable the rate component to prevent user interaction:
import Rate from '@components/rate';
export default function App() {
return <Rate disabled />;
}
Helper Text
Provide additional context or instructions using the helperText prop:
import Rate from '@components/rate';
export default function App() {
return (
<Rate
defaultValue={2}
helperText="Rate your experience with this product."
/>
);
}
Error State
Display validation errors using the error prop. When an error is present, the helper text is automatically hidden:
import Rate from '@components/rate';
export default function App() {
return <Rate error="Please provide a rating." />;
}
Advanced Usage
Read-Only Display
Display a rating without allowing user interaction:
import Rate from '@components/rate';
export default function App() {
return <Rate value={4.5} allowHalf disabled />;
}
Custom Count
Change the number of rating items:
import Rate from '@components/rate';
export default function App() {
return <Rate count={10} defaultValue={5} />;
}
With Label
Add a descriptive label above the rating:
import Rate from '@components/rate';
export default function App() {
return (
<Rate
label="Product Rating"
defaultValue={4}
helperText="How would you rate this product?"
/>
);
}
API Reference
Rate Props
| Prop | Type | Description | Default |
|---|---|---|---|
| label | ReactNode | The label text displayed above the rating | undefined |
| size | "sm" | "md" | "lg" | Size of the rating component | "md" |
| character | ReactNode | ReactNode[] | Custom character(s) to use instead of default star. Can be a single element or an array for different characters per level | <StarIcon /> |
| characterClassName | string | Additional CSS classes for the character element | undefined |
| helperText | ReactNode | Helper text displayed below the rating (hidden when error is present) | undefined |
| error | string | Error message to display below the rating | undefined |
| disabled | boolean | Disable all rating interactions | false |
| allowHalf | boolean | Allow half-star ratings | false |
| count | number | Number of rating items to display | 5 |
| value | number | Current rating value (controlled) | undefined |
| defaultValue | number | Default rating value (uncontrolled) | undefined |
| labelClassName | string | Additional CSS classes for the label element | undefined |
| rateClassName | string | Additional CSS classes for the rate container | undefined |
| errorClassName | string | Additional CSS classes for the error message | undefined |
| helperClassName | string | Additional CSS classes for the helper text | undefined |
| className | string | Additional CSS classes for the root wrapper element | undefined |
| onChange | (value: number) => void | Callback when rating changes | undefined |
| onHoverChange | (value: number) => void | Callback when hovering over rating items | undefined |
Note: The Rate component extends all props from rc-rate, allowing you to use features like
allowClear,onBlur,onFocus, and more. Refer to the rc-rate documentation for a complete list of available props.
Rate Sizes
type RateSizes = 'sm' | 'md' | 'lg';
- sm - Small size (20px × 20px)
- md - Medium size (24px × 24px) - default
- lg - Large size (28px × 28px)
Best Practices
- Use appropriate labels - Always provide clear labels to indicate what is being rated
- Consider half ratings - Enable
allowHalffor more granular feedback when needed - Handle validation - Use the
errorprop to display validation feedback - Use helper text - Provide context about the rating scale or expectations
- Accessibility - The component includes built-in ARIA attributes for screen readers
- Custom characters - Use meaningful icons or emojis that match your use case
- Form integration - Works seamlessly with form libraries like react-hook-form