image

07 Dec 2025

9K

35K

Building a Star Rating Widget in Flutter

Star rating widgets are a common and essential UI component for applications that involve user feedback, reviews, or product evaluation. They provide an intuitive visual representation of quality or satisfaction, allowing users to quickly rate items with a simple tap. In Flutter, creating such a widget is straightforward, leveraging its powerful declarative UI framework and state management capabilities.

This article will guide you through the process of building a reusable and customizable star rating widget in Flutter. We'll cover the core concepts, implementation details, and how to make it interactive and configurable.

Core Concepts

At its heart, a star rating widget consists of a row of star icons. The number of filled stars typically reflects the current rating. To make it interactive, we need to handle user taps on individual stars to update the rating. This requires managing the widget's internal state.

We'll use a StatefulWidget for this purpose, as the rating changes dynamically based on user interaction. Key elements will include:

  • A Row widget to arrange the stars horizontally.
  • Icon widgets to represent individual stars.
  • GestureDetector or InkWell widgets to detect taps on each star.
  • State variables to store the current rating.
  • Callbacks to inform parent widgets when the rating changes.

1. Widget Skeleton

First, let's set up the basic structure of our StarRating widget as a StatefulWidget. This will include the constructor for initial parameters and the state class.


import 'package:flutter/material.dart';

class StarRating extends StatefulWidget {
  final int starCount;
  final double initialRating;
  final double starSize;
  final Color color;
  final Color? borderColor;
  final void Function(double rating)? onRatingUpdate;

  const StarRating({
    Key? key,
    this.starCount = 5,
    this.initialRating = 0.0,
    this.starSize = 24.0,
    this.color = Colors.amber,
    this.borderColor,
    this.onRatingUpdate,
  }) : assert(initialRating >= 0 && initialRating <= starCount),
       super(key: key);

  @override
  State<StarRating> createState() => _StarRatingState();
}

class _StarRatingState extends State<StarRating> {
  late double _currentRating;

  @override
  void initState() {
    super.initState();
    _currentRating = widget.initialRating;
  }

  @override
  void didUpdateWidget(covariant StarRating oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.initialRating != widget.initialRating) {
      _currentRating = widget.initialRating;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: List.generate(widget.starCount, (index) {
        return _buildStar(context, index);
      }),
    );
  }

  Widget _buildStar(BuildContext context, int index) {
    // Implementation for building individual stars will go here
    return Container(); // Placeholder
  }
}

In this initial setup:

  • starCount: The total number of stars to display (e.g., 5).
  • initialRating: The starting rating value.
  • starSize: The size of each star icon.
  • color: The color of the filled stars.
  • borderColor: Optional color for outlined stars when not filled.
  • onRatingUpdate: A callback function that gets triggered when the user changes the rating.
  • _currentRating: An internal state variable to hold the currently selected rating.
  • initState and didUpdateWidget: To initialize and update _currentRating based on initialRating.

2. Displaying Stars and Handling Interaction

Now, let's implement the _buildStar method to render each star and make it interactive. We'll use GestureDetector to capture taps on individual stars. The logic will determine whether a star should be filled, partially filled, or empty based on the _currentRating.


// ... (inside _StarRatingState class)

  Widget _buildStar(BuildContext context, int index) {
    IconData iconData;
    Color iconColor;

    if (index >= _currentRating) {
      // Empty star
      iconData = Icons.star_border;
      iconColor = widget.borderColor ?? Colors.grey;
    } else if (index + 1 > _currentRating && index < _currentRating) {
      // Half-filled star
      iconData = Icons.star_half;
      iconColor = widget.color;
    } else {
      // Filled star
      iconData = Icons.star;
      iconColor = widget.color;
    }

    return GestureDetector(
      onTap: () {
        setState(() {
          _currentRating = index + 1.0;
        });
        widget.onRatingUpdate?.call(_currentRating);
      },
      child: Icon(
        iconData,
        color: iconColor,
        size: widget.starSize,
      ),
    );
  }

Explanation:

  • The index parameter helps us determine the position of the star.
  • We use conditional logic to decide whether to display a star_border (empty), star_half (half-filled), or star (filled) icon.
  • The GestureDetector wraps each Icon, making it tappable.
  • When a star is tapped, setState is called to update _currentRating to the index of the tapped star plus one (since indices are 0-based).
  • Finally, widget.onRatingUpdate?.call(_currentRating) invokes the callback, notifying the parent widget about the rating change.

3. Full Code Example

Here's the complete, runnable code for the StarRating widget and an example of how to use it in a simple Flutter application.


import 'package:flutter/material.dart';

class StarRating extends StatefulWidget {
  final int starCount;
  final double initialRating;
  final double starSize;
  final Color color;
  final Color? borderColor;
  final void Function(double rating)? onRatingUpdate;

  const StarRating({
    Key? key,
    this.starCount = 5,
    this.initialRating = 0.0,
    this.starSize = 24.0,
    this.color = Colors.amber,
    this.borderColor,
    this.onRatingUpdate,
  }) : assert(initialRating >= 0 && initialRating <= starCount),
       super(key: key);

  @override
  State<StarRating> createState() => _StarRatingState();
}

class _StarRatingState extends State<StarRating> {
  late double _currentRating;

  @override
  void initState() {
    super.initState();
    _currentRating = widget.initialRating;
  }

  @override
  void didUpdateWidget(covariant StarRating oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.initialRating != widget.initialRating) {
      _currentRating = widget.initialRating;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: List.generate(widget.starCount, (index) {
        return _buildStar(context, index);
      }),
    );
  }

  Widget _buildStar(BuildContext context, int index) {
    IconData iconData;
    Color iconColor;

    if (index >= _currentRating) {
      // Empty star
      iconData = Icons.star_border;
      iconColor = widget.borderColor ?? Colors.grey;
    } else if (index + 1 > _currentRating && index < _currentRating) {
      // Half-filled star
      iconData = Icons.star_half;
      iconColor = widget.color;
    } else {
      // Filled star
      iconData = Icons.star;
      iconColor = widget.color;
    }

    return GestureDetector(
      onTap: () {
        setState(() {
          _currentRating = index + 1.0;
        });
        widget.onRatingUpdate?.call(_currentRating);
      },
      child: Icon(
        iconData,
        color: iconColor,
        size: widget.starSize,
      ),
    );
  }
}


// --- Example Usage ---

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  double _userRating = 3.5; // Example initial rating

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Star Rating Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Star Rating Widget'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'Rate this item:',
                style: TextStyle(fontSize: 20),
              ),
              const SizedBox(height: 20),
              StarRating(
                initialRating: _userRating,
                starCount: 5,
                starSize: 40.0,
                color: Colors.deepOrange,
                borderColor: Colors.deepOrange.withOpacity(0.5),
                onRatingUpdate: (rating) {
                  setState(() {
                    _userRating = rating;
                  });
                  print('New rating: $rating');
                },
              ),
              const SizedBox(height: 20),
              Text(
                'Your current rating: ${_userRating.toStringAsFixed(1)}',
                style: const TextStyle(fontSize: 18),
              ),
              const SizedBox(height: 40),
              const Text(
                'Another example (read-only):',
                style: TextStyle(fontSize: 20),
              ),
              const SizedBox(height: 20),
              StarRating(
                initialRating: 4.2,
                starCount: 5,
                starSize: 30.0,
                color: Colors.green,
                // No onRatingUpdate means it's read-only
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Conclusion

You have now successfully created a customizable and interactive star rating widget in Flutter! This widget can be easily integrated into various parts of your application, from product review screens to feedback forms. By understanding the core principles of StatefulWidget, GestureDetector, and conditional UI rendering, you can extend this widget further to include features like half-star selection on drag, different icon sets, or more complex animations.

Related Articles

Dec 19, 2025

Building a Widget List with Sticky

Building a Widget List with Sticky Header in Flutter Creating dynamic and engaging user interfaces is crucial for modern applications. One common UI pattern th

Dec 19, 2025

Mastering Transform Scale & Rotate Animations in Flutter

Mastering Transform Scale & Rotate Animations in Flutter Flutter's powerful animation framework allows developers to create visually stunning and highly intera

Dec 19, 2025

Building a Countdown Timer Widget in Flutter

Building a Countdown Timer Widget in Flutter Countdown timers are a fundamental component in many modern applications, ranging from e-commerce platforms indica