DEV Community

Cover image for Angular Signals Tutorial: Crafting a Custom Star Rating Component with Accessibility
Rajat
Rajat

Posted on

Angular Signals Tutorial: Crafting a Custom Star Rating Component with Accessibility

🟒 Opening With a Question

Ever wondered how to build a sleek, accessible star rating component in Angular that users and screen readers will love?

Whether you're designing a review system or collecting user feedback, a star rating component is a UI staple. But creating one that’s reusable, accessible, responsive, and powered by Angular’s latest features like Signals takes your development skills to the next level.

By the end of this tutorial, you’ll learn:

  • βœ… How to create a reusable Angular component with Signals
  • 🏷️ Best practices for accessibility (ARIA roles, keyboard navigation)
  • πŸ’‘ Styling tips to make your rating UI shine
  • πŸ“¦ How to use the component across apps and modules

πŸ”§ 1. Project Setup (Angular 18 + Signals)

Make sure you're on Angular 18+. If not, upgrade:

ng update @angular/cli @angular/core

Enter fullscreen mode Exit fullscreen mode

Generate your component:

ng generate component shared/star-rating

Enter fullscreen mode Exit fullscreen mode

We'll use @angular/core’s new Signals API to handle the reactive logic cleanly and declaratively.


⭐ 2. Component Structure

Here's what the star-rating component structure looks like:

@Component({
  selector: 'app-star-rating',
  templateUrl: './star-rating.component.html',
  styleUrls: ['./star-rating.component.scss'],
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class StarRatingComponent {
  readonly stars = 5;
  readonly rating = signal(0);
  readonly hovered = signal<number | null>(null);

  setRating = (value: number) => this.rating.set(value);
  setHover = (value: number | null) => this.hovered.set(value);
}

Enter fullscreen mode Exit fullscreen mode

Use signals for both rating state and hover effectsβ€”clean and reactive with no RxJS needed.


🎨 3. HTML Template with Accessibility (ARIA)

Here’s the accessible and interactive template:

<div
  role="radiogroup"
  aria-label="Star rating"
  class="star-rating"
>
  <ng-container *ngFor="let star of [].constructor(stars); let i = index">
    <span
      role="radio"
      tabindex="0"
      [attr.aria-checked]="rating() === i + 1"
      (click)="setRating(i + 1)"
      (keydown.enter)="setRating(i + 1)"
      (mouseenter)="setHover(i + 1)"
      (mouseleave)="setHover(null)"
      [class.filled]="i < (hovered() ?? rating())"
    >
      β˜…
    </span>
  </ng-container>
</div>

Enter fullscreen mode Exit fullscreen mode

πŸ’¬ Pro Tip: Always use role="radiogroup" for screen reader clarity and tabindex for keyboard focus.


πŸ§ͺ 4. Styling (SCSS)

Give your stars a professional look:

.star-rating {
  display: flex;
  gap: 0.5rem;
  span {
    font-size: 2rem;
    cursor: pointer;
    color: #ccc;
    transition: color 0.2s;
    &.filled {
      color: #f5b301;
    }
    &:focus {
      outline: 2px solid #007acc;
      outline-offset: 2px;
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

β™Ώ 5. Accessibility Checklist

  • βœ… role="radiogroup" on container
  • βœ… role="radio" on stars
  • βœ… aria-checked state
  • βœ… Keyboard navigation (Enter key)
  • βœ… Focus outline for better visibility

🧩 6. Making It Reusable

Allow @Input()s for max stars and readonly mode:

@Input({ required: true }) stars = 5;
@Input() readonly = false;
@Output() ratingChange = new EventEmitter<number>();

Enter fullscreen mode Exit fullscreen mode

Update the template logic for readonly:

(click)="!readonly && setRating(i + 1)"

Enter fullscreen mode Exit fullscreen mode

🌐 7. Bonus: Add This to a Shared Library

If you're using Nx or a mono-repo setup, move this to a ui-components library to share it across multiple Angular apps.


βœ… What You’ve Learned

By now, you’ve built a signal-powered, accessible, and reusable star rating component that is:

  • πŸ”„ Reactive with Angular Signals
  • β™Ώ Fully accessible with ARIA roles
  • πŸ’Ž Reusable across components/modules
  • πŸ“± Stylish and responsive

🎯 Your Turn, Devs!

πŸ‘€ Did this article spark new ideas or help solve a real problem?

πŸ’¬ I'd love to hear about it!

βœ… Are you already using this technique in your Angular or frontend project?

🧠 Got questions, doubts, or your own twist on the approach?

Drop them in the comments below β€” let’s learn together!


πŸ™Œ Let’s Grow Together!

If this article added value to your dev journey:

πŸ” Share it with your team, tech friends, or community β€” you never know who might need it right now.

πŸ“Œ Save it for later and revisit as a quick reference.


πŸš€ Follow Me for More Angular & Frontend Goodness:

I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.

  • πŸ’Ό LinkedIn β€” Let’s connect professionally
  • πŸŽ₯ Threads β€” Short-form frontend insights
  • 🐦 X (Twitter) β€” Developer banter + code snippets
  • πŸ‘₯ BlueSky β€” Stay up to date on frontend trends
  • 🌟 GitHub Projects β€” Explore code in action
  • 🌐 Website β€” Everything in one place
  • πŸ“š Medium Blog β€” Long-form content and deep-dives
  • πŸ’¬ Dev Blog β€” Free Long-form content and deep-dives
  • βœ‰οΈ Substack β€” Weekly frontend stories & curated resources
  • 🧩 Portfolio β€” Projects, talks, and recognitions

πŸŽ‰ If you found this article valuable:

  • Leave a πŸ‘ Clap
  • Drop a πŸ’¬ Comment
  • Hit πŸ”” Follow for more weekly frontend insights

Let’s build cleaner, faster, and smarter web apps β€” together.

Stay tuned for more Angular tips, patterns, and performance tricks! πŸ§ͺπŸ§ πŸš€

** ✨ Share Your Thoughts To πŸ“£ Set Your Notification Preference **

Top comments (0)