Skip to content

Conversation

@solidusite
Copy link

@solidusite solidusite commented Dec 27, 2025

add star rating component with customizable props

πŸ”— Linked issue

Resolves #505

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

A component to display and collect star ratings from users.
image

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@solidusite
Copy link
Author

Idk if change the name of component. Simply "rating" instead of "star-rating"?

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 27, 2025

npm i https://pkg.pr.new/@nuxt/ui@5757

commit: b89d07b

@caiotarifa
Copy link

Thanks, @solidusite.

About the component name, I’d prefer InputRating over StarRating or Rating.

Why:

  • Functionally, this is a form control: it’s driven by v-model and supports default-value, exactly like other Input* components in Nuxt UI. οΏΌ
  • The component isn’t truly β€œstar” specific: the highlight icon customization (e.g. hearts), so the current name is semantically narrow. οΏΌ
  • Keeping the Input* prefix improves discoverability and sets expectations: "this returns a value (number) through v-model".

Given it’s a new component, I think this is the best moment to align naming before release.

@solidusite
Copy link
Author

Thanks, @solidusite.

About the component name, I’d prefer InputRating over StarRating or Rating.

Why:

  • Functionally, this is a form control: it’s driven by v-model and supports default-value, exactly like other Input* components in Nuxt UI. οΏΌ

  • The component isn’t truly β€œstar” specific: the highlight icon customization (e.g. hearts), so the current name is semantically narrow. οΏΌ

  • Keeping the Input* prefix improves discoverability and sets expectations: "this returns a value (number) through v-model".

Given it’s a new component, I think this is the best moment to align naming before release.

Totally agree

@benjamincanac
Copy link
Member

We were waiting for this to be implemented in Reka UI to handle all complex logic such as form validation: unovue/reka-ui#1968

@J-Michalek What do you think?

Copy link
Contributor

@J-Michalek J-Michalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benjamincanac I would like to get this component done one day because there definitely are some users that want this, but I think we either need to wait for the RekaUI solution or create it ourselves based on the RadioGroup component.

The current implementation shown in this PR is not accessible at all and it seems there is no input so there are most likely issues with native form behavior.

@solidusite
Copy link
Author

I completely understand, thanks for the clarification.
I wasn’t aware of the existing RekaUI PR β€” if the plan is to wait for that solution, I’m totally fine with closing this PR for now.

That said, if you’d prefer to move forward sooner and have some guidance on the expected direction (for example, reworking this on top of RadioGroup to ensure proper accessibility and native form behavior), I’d be happy to iterate on the component accordingly.

Copy link
Member

I think it's worth iterating on your PR since you've done most of the job already to use the Reka UI RadioGroup component, it comes down to the same thing as using Rating primitives that use RadioGroup underneath.

@solidusite
Copy link
Author

solidusite commented Jan 6, 2026

I made some improvements @benjamincanac

Refactoring with RadioGroupItem

  • Refactored the component to use RadioGroupItem from reka-ui for better consistency with other form components
  • Improved internal structure and maintainability

Disabled vs Readonly States

Refactored the disabled/readonly logic for better clarity and visual distinction:

  • readonly: Maintains normal appearance (full opacity, default cursor). Prevents user interaction while keeping the component visually unchanged. Ideal for displaying existing ratings that cannot be modified.

  • disabled: Shows reduced opacity (75%) and a not-allowed cursor. Clearly indicates the component is temporarily unavailable or inactive. Both states prevent interaction, but disabled provides a stronger visual indication of unavailability.

The component now properly separates functional disabled state (which includes readonly for interaction blocking) from visual disabled state (only when explicitly disabled).

Accessibility Improvements

  • Enhanced keyboard navigation support through RadioGroupItem integration
  • Improved aria-label attributes with descriptive text: "Rate X stars out of Y" instead of just numbers
  • Better focus management and visual feedback for keyboard users
  • Improved form validation integration with proper required field handling
  • Focus ring is now present by default for accessibility (can be disabled via ui prop if needed)

Additional Changes

  • Fixed icon sizing to properly respond to size prop changes
  • Removed duplicate CSS classes on icons
  • Improved hover state handling when disabled
  • Better visual feedback for half-star ratings
  • Enhanced form integration example with validation schema
Registrazione.schermo.2026-01-06.alle.15.48.20.mov

@solidusite solidusite requested a review from J-Michalek January 7, 2026 19:46
@solidusite solidusite force-pushed the feat/star-rating-component branch from c0fa99d to 30293ab Compare January 7, 2026 19:48
…e component with readonly and disabled states, add focus ring customization, and improve documentation
…timize stars state computation and props handling

- Remove separate computed for stars and inline logic into `starsWithState` using Array.from
- Precompute steps inside `starsWithState` and remove `getStepsForStar`
- Use `star.steps` in the template instead of calling a function per star
- Remove redundant fallback in `currentValue` (useVModel already handles `defaultValue`)
- Add optional `orientation` prop with default `horizontal` and use it in the template
>
<UIcon
:name="starIcon"
:class="[ui.icon({ class: props.ui?.icon }), ui.starHalf({ class: props.ui?.starHalf })]"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:class="[ui.icon({ class: props.ui?.icon }), ui.starHalf({ class: props.ui?.starHalf })]"
:class="ui.icon({ class: props.ui?.icon })"

The icon inside the half-star overlay div is incorrectly applying both the icon and starHalf UI classes, causing duplicate clip-path styling.

View Details

Analysis

Duplicate UI class styling on half-star icon in InputRating component

What fails: The UIcon component inside the starHalf overlay div (line 241) incorrectly applies both ui.icon() and ui.starHalf() classes, causing redundant and semantically incorrect styling to be applied to the icon element.

How to reproduce:

<!-- Before fix - line 241 in src/runtime/components/InputRating.vue -->
<UIcon
  :name="starIcon"
  :class="[ui.icon({ class: props.ui?.icon }), ui.starHalf({ class: props.ui?.starHalf })]"
/>

Render the InputRating component with allowHalf: true and a half-star rating (e.g., 3.5).

Result: The icon element receives the ui.starHalf() class styling which includes:

  • absolute inset-0 pointer-events-none overflow-hidden [clip-path:polygon(...)] [-webkit-clip-path:polygon(...)]

These are container-level styles already applied to the parent div.

Expected: The icon should only receive ui.icon() styling (w-full h-full), matching the pattern used for:

  1. The filled star icon (line 229)
  2. The empty star icon (line 221)

The parent container provides the clip-path clipping - the child icon element should not reapply container styles.

Fix applied: Changed line 241 to only apply ui.icon():

:class="ui.icon({ class: props.ui?.icon })"

This restores consistency across all star variants and removes unnecessary stacking context creation from the redundant clip-path declaration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Rating

4 participants