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:

  1. Semantics – Provides accessibility information for screen readers.
  2. Material – The visual surface that receives elevation shadows and background color.
  3. 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 to const CircleBorder() to force a circular outline.
  • constraints: Override the default BoxConstraints(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:

  1. Import the material library – Ensure you have import 'package:flutter/material.dart'; at the top of your file.

  2. Instantiate RawMaterialButton – Use the constructor with required onPressed and child parameters.

  3. Apply CircleBorder – Set shape: const CircleBorder() to define the circular geometry.

  4. Set custom constraints – Provide constraints: const BoxConstraints(minWidth: 48, minHeight: 48) to create a square hit target that renders as a circle.

  5. Configure visual properties – Add fillColor, elevation, and splashColor as needed.

  6. Add the icon – Pass your Icon widget as the child parameter.

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 RawMaterialButton from packages/flutter/lib/src/material/button.dart when you need full control over a custom circular icon button in Flutter.
  • Set shape: CircleBorder() to enforce circular geometry regardless of constraints.
  • Override constraints with equal minWidth and minHeight (typically 48 dp to match kMinInteractiveDimension) to ensure the button remains circular and accessible.
  • Leverage IconButton.filled with styleFrom(shape: CircleBorder()) for Material 3 apps that require theme integration while maintaining the circular shape.
  • Wrap in Tooltip to match the accessibility standards implemented in packages/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →