How to Create a Custom Circular Icon Button in Flutter: A Complete Guide
Use RawMaterialButton with shape: CircleBorder() and custom BoxConstraints to build a custom circular icon button in Flutter that supports background colors, elevation, and ink splashes.
Flutter’s material library provides powerful primitives for building interactive UI elements. When you need a custom circular icon button in Flutter that goes beyond the standard IconButton styling, you can leverage lower-level widgets directly from the flutter/flutter repository to achieve precise control over shape, size, and visual effects.
Why Use RawMaterialButton for a Custom Circular Icon Button in Flutter?
The RawMaterialButton widget, defined in packages/flutter/lib/src/material/button.dart, serves as the foundational building block for all material buttons. Unlike the higher-level IconButton, which wraps RawMaterialButton with opinionated defaults, using RawMaterialButton directly gives you explicit control over the button’s geometry and constraints.
The Three Essential Components
According to the source code in button.dart, RawMaterialButton composes three critical elements:
- Semantics – Provides accessibility information for screen readers.
- Material – The visual surface that receives elevation shadows and background color.
- InkWell / InkResponse – Supplies the ink splash effect when the button is pressed.
These pieces are wired together internally, exposing a clean API for customization while handling the complex interaction logic for you.
Shape and Constraints Control
The key to creating a perfect circle lies in two specific parameters:
shape: Set this toconst CircleBorder()to force a circular outline.constraints: Override the defaultBoxConstraints(minWidth: 88, minHeight: 36)with equal width and height values (e.g.,minWidth: 48, minHeight: 48) to ensure the button is circular rather than oval.
The constant kMinInteractiveDimension = 48.0 defined in packages/flutter/lib/src/material/constants.dart represents the material design minimum touch target size, which you should respect for accessibility.
Step-by-Step Implementation Guide
Follow these steps to construct a custom circular icon button in Flutter using the raw material API:
-
Import the material library – Ensure you have
import 'package:flutter/material.dart';at the top of your file. -
Instantiate RawMaterialButton – Use the constructor with required
onPressedandchildparameters. -
Apply CircleBorder – Set
shape: const CircleBorder()to define the circular geometry. -
Set custom constraints – Provide
constraints: const BoxConstraints(minWidth: 48, minHeight: 48)to create a square hit target that renders as a circle. -
Configure visual properties – Add
fillColor,elevation, andsplashColoras needed. -
Add the icon – Pass your
Iconwidget as thechildparameter.
Complete Code Examples
Basic Circular Icon Button
This minimal example creates a flat circular button with just an icon and splash effect:
RawMaterialButton(
onPressed: () => debugPrint('Tapped'),
shape: const CircleBorder(),
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
padding: EdgeInsets.zero,
child: const Icon(Icons.search, size: 20),
);
The result is a 40 × 40 dp hit target with a transparent background; the ink splash renders on the material surface when pressed.
Styled Button with Background and Elevation
For a raised circular button with background color and shadow:
RawMaterialButton(
onPressed: () => debugPrint('Pressed'),
shape: const CircleBorder(),
constraints: const BoxConstraints(minWidth: 56, minHeight: 56),
fillColor: Colors.green,
elevation: 4,
splashColor: Colors.white24,
child: const Icon(Icons.check, color: Colors.white, size: 28),
);
This creates a 56 dp green circle raised 4 dp above the surface with a white splash overlay.
Material 3 IconButton.filled Variant
If your app uses Material 3, you can achieve the same result using the higher-level API with explicit shape override:
IconButton.filled(
onPressed: () => debugPrint('M3 button'),
icon: const Icon(Icons.thumb_up),
style: IconButton.styleFrom(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
shape: const CircleBorder(),
minimumSize: const Size(48, 48),
elevation: 2,
shadowColor: Colors.black45,
),
);
The IconButton.filled implementation in packages/flutter/lib/src/material/icon_button.dart resolves the shape property from the button’s style. While the default Material 3 shape is a StadiumBorder, supplying CircleBorder() replaces it with a perfect circle.
Accessible Button with Tooltip
For production apps, wrap the button in a Tooltip to match the accessibility pattern used by IconButton:
Tooltip(
message: 'Add to favourites',
child: RawMaterialButton(
onPressed: () => debugPrint('Fav'),
shape: const CircleBorder(),
constraints: const BoxConstraints(minWidth: 48, minHeight: 48),
fillColor: Colors.redAccent,
child: const Icon(Icons.favorite, color: Colors.white),
),
);
Key Source Files in the Flutter Repository
Understanding the underlying implementation helps you customize effectively. These files from the flutter/flutter repository define the components discussed:
| File | Purpose | Key Elements |
|---|---|---|
packages/flutter/lib/src/material/icon_button.dart |
Modern IconButton and IconButton.styleFrom implementation |
InkResponse usage (line 809), styleFrom helper (line 462), Tooltip integration |
packages/flutter/lib/src/material/button.dart |
RawMaterialButton definition |
shape parameter, constraints handling, Material and InkWell composition |
packages/flutter/lib/src/material/ink_well.dart |
Ripple effect implementation | InkResponse and InkWell widgets that provide splash animations |
packages/flutter/lib/src/material/constants.dart |
Material design constants | kMinInteractiveDimension = 48.0 (minimum touch target size) |
packages/flutter/lib/src/material/theme_data.dart |
Theme defaults | Default ButtonStyle values that IconButton.styleFrom merges with |
Summary
- Use
RawMaterialButtonfrompackages/flutter/lib/src/material/button.dartwhen you need full control over a custom circular icon button in Flutter. - Set
shape: CircleBorder()to enforce circular geometry regardless of constraints. - Override
constraintswith equalminWidthandminHeight(typically 48 dp to matchkMinInteractiveDimension) to ensure the button remains circular and accessible. - Leverage
IconButton.filledwithstyleFrom(shape: CircleBorder())for Material 3 apps that require theme integration while maintaining the circular shape. - Wrap in
Tooltipto match the accessibility standards implemented inpackages/flutter/lib/src/material/icon_button.dart.
Frequently Asked Questions
What is the difference between IconButton and RawMaterialButton?
IconButton is a higher-level widget defined in packages/flutter/lib/src/material/icon_button.dart that wraps RawMaterialButton with preset defaults for icon sizing, padding, and visual density. RawMaterialButton (from button.dart) exposes the underlying Material, InkResponse, and Semantics components directly, giving you explicit control over shape, constraints, elevation, and fillColor that IconButton manages internally.
How do I change the size of a circular icon button in Flutter?
Adjust the constraints parameter of RawMaterialButton to specify the desired minWidth and minHeight. For a perfect circle, use equal values such as const BoxConstraints(minWidth: 56, minHeight: 56). Ensure the value is at least 48.0 (the kMinInteractiveDimension constant from constants.dart) to maintain accessibility compliance. When using IconButton.styleFrom, set the minimumSize property to a Size with equal width and height.
Can I use IconButton.filled to create a circular button?
Yes. Although IconButton.filled (defined in icon_button.dart) defaults to a StadiumBorder in Material 3, you can override the shape by providing style: IconButton.styleFrom(shape: const CircleBorder()). This merges your circular shape with the filled button’s background color and elevation system, producing a circular button that integrates automatically with the app’s ThemeData.
How do I add a ripple effect to a custom circular button?
The ripple (ink splash) is provided automatically when you use RawMaterialButton or IconButton because both wrap an InkResponse (from ink_well.dart). In RawMaterialButton, the InkResponse is constructed internally with the splashColor and highlightColor you provide. To customize the ripple appearance, set the splashColor parameter to your desired color (e.g., Colors.white24). If building a completely custom button from scratch, you would need to wrap your widget in Material and InkWell manually, but RawMaterialButton already handles this composition.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →