Skip to main content

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 install 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:

Rate your experience with this product.
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

PropTypeDescriptionDefault
labelReactNodeThe label text displayed above the ratingundefined
size"sm" | "md" | "lg"Size of the rating component"md"
characterReactNode | ReactNode[]Custom character(s) to use instead of default star. Can be a single element or an array for different characters per level<StarIcon />
characterClassNamestringAdditional CSS classes for the character elementundefined
helperTextReactNodeHelper text displayed below the rating (hidden when error is present)undefined
errorstringError message to display below the ratingundefined
disabledbooleanDisable all rating interactionsfalse
allowHalfbooleanAllow half-star ratingsfalse
countnumberNumber of rating items to display5
valuenumberCurrent rating value (controlled)undefined
defaultValuenumberDefault rating value (uncontrolled)undefined
labelClassNamestringAdditional CSS classes for the label elementundefined
rateClassNamestringAdditional CSS classes for the rate containerundefined
errorClassNamestringAdditional CSS classes for the error messageundefined
helperClassNamestringAdditional CSS classes for the helper textundefined
classNamestringAdditional CSS classes for the root wrapper elementundefined
onChange(value: number) => voidCallback when rating changesundefined
onHoverChange(value: number) => voidCallback when hovering over rating itemsundefined

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 allowHalf for more granular feedback when needed
  • Handle validation - Use the error prop 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