Skip to content

Software Development at Program Tom LTD

Place for coding, programming, development and software in general.

Menu
  • Blog
  • PDF Booklets
  • Dev Utils & Content
  • Java Spring Boot Or Web Apps
  • English
    • български
    • English
    • Español
    • Português
    • हिन्दी
    • Русский
    • Deutsch
    • Français
    • Italiano
    • العربية
  • About Us
Menu
In Flutter - How to create a Ripple Effect on Buttons

In Flutter – How to create a Ripple Effect on Buttons?

Posted on September 17, 2025 by Toma Velev

In Flutter, the ripple effect (also known as the ink splash or material ripple) is part of the Material Design guidelines and is usually seen when a user taps on a button, list item, or surface. Flutter provides multiple ways to implement this effect, depending on your use case:

🔹 1. Using InkWell

The most common way. Wrap your widget with InkWell to get a ripple on tap.

InkWell(
  onTap: () {
    print("Tapped!");
  },
  child: Container(
    padding: EdgeInsets.all(20),
    child: Text("Tap Me"),
  ),
)

👉 Notes:

  • InkWell must have a Material ancestor (e.g., inside a MaterialApp, Scaffold, or Material widget).
  • The ripple effect respects the widget’s shape.

🔹 2. Using InkResponse

More configurable than InkWell. Useful if you need control over the ripple shape (circular vs rectangular).

InkResponse(
  onTap: () {
    print("Tapped!");
  },
  radius: 40,
  containedInkWell: true,
  child: Icon(Icons.favorite, size: 50),
)

👉 Difference from InkWell:

  • InkResponse gives more control (like splash radius, custom shapes).
  • InkWell is a simpler wrapper built on top of InkResponse.

🔹 3. Using Material with Ink Features

If you want the ripple behind a custom widget, wrap it with Material and apply an InkWell.

Material(
  color: Colors.transparent,
  child: InkWell(
    splashColor: Colors.blue,
    onTap: () {},
    child: Padding(
      padding: EdgeInsets.all(20),
      child: Text("Ripple over Transparent Material"),
    ),
  ),
)

🔹 4. Using GestureDetector + Custom Ripple

If you don’t want to depend on Material’s ink effects, you can build a custom ripple animation with GestureDetector and AnimatedContainer or CustomPainter.

GestureDetector(
  onTapDown: (_) {
    // trigger custom animation
  },
  child: Container(
    color: Colors.grey[200],
    padding: EdgeInsets.all(20),
    child: Text("Custom Ripple"),
  ),
)

👉 In this case, you’ll need to animate an expanding circle manually (e.g., TweenAnimationBuilder + ClipOval).

🔹 5. Using Ink widget (for painting ripples inside a Material)

If you want a background ripple effect that paints correctly inside a Material widget, use Ink.

Material(
  child: Ink(
    decoration: BoxDecoration(
      color: Colors.grey[200],
      shape: BoxShape.circle,
    ),
    child: InkWell(
      onTap: () {},
      child: Padding(
        padding: EdgeInsets.all(20),
        child: Icon(Icons.touch_app),
      ),
    ),
  ),
)

🔹 6. Third-Party Animation Packages

If you want fancier ripples, you can use packages like:

  • flutter_ink_ripple
  • ripple_animation

These allow animated ripple backgrounds beyond Material guidelines.


✅ Best Practice: Use InkWell for standard Material buttons and InkResponse when you need more control. Go custom (GestureDetector) only if you want non-Material ripple behavior.

Flutter Buttons

Flutter’s button widgets already include the ripple effect out-of-the-box because they are built on top of InkWell / InkResponse internally.

Here’s how ripple behaves across the main button types:


🔹 1. TextButton

Minimal button with ripple on tap.

TextButton(
  onPressed: () {},
  child: Text("Text Button"),
)
  • Transparent background, only text + ripple.
  • Replaces the old FlatButton.

🔹 2. ElevatedButton

Button with elevation + ripple.

ElevatedButton(
  onPressed: () {},
  child: Text("Elevated Button"),
)
  • Background + shadow.
  • Replaces the old RaisedButton.

🔹 3. OutlinedButton

Button with a border + ripple.

OutlinedButton(
  onPressed: () {},
  child: Text("Outlined Button"),
)
  • Ripple shows over the outlined border.
  • Replaces the old OutlineButton.

🔹 4. IconButton

Icon-only button with ripple on tap.

IconButton(
  icon: Icon(Icons.favorite),
  onPressed: () {},
)
  • Uses InkResponse internally for circular ripples.

🔹 5. FloatingActionButton (FAB)

Circular button with ripple.

FloatingActionButton(
  onPressed: () {},
  child: Icon(Icons.add),
)
  • Circular ripple that fits FAB shape.

🔹 6. MaterialButton (base class)

Most Material buttons extend MaterialButton → so ripple is part of its behavior.

MaterialButton(
  color: Colors.blue,
  textColor: Colors.white,
  onPressed: () {},
  child: Text("Material Button"),
)

🔹 Customizing Ripple in Buttons

You can control the ripple color and highlight color using ButtonStyle:

ElevatedButton(
  style: ElevatedButton.styleFrom(
    splashFactory: InkRipple.splashFactory, // default ripple
    foregroundColor: Colors.white,
    backgroundColor: Colors.blue,
    overlayColor: MaterialStateProperty.all(Colors.red.withOpacity(0.3)), // ripple color
  ),
  onPressed: () {},
  child: Text("Custom Ripple"),
)
  • overlayColor → sets ripple & pressed state color.
  • splashFactory → can swap ripple type (InkRipple, InkSparkle, NoSplash).

✨ So basically:

  • All Material buttons already come with ripple (no need to wrap with InkWell).
  • You just tweak them with ButtonStyle if you want to change ripple color/behavior.

Customzing Ripple Effect

You can absolutely parametrize a ripple color by making it lighter or darker than the base color. Flutter has a handy method for this:

Option 1: Use withOpacity

splashColor: backgroundColor.withOpacity(0.3),  // 30% opacity
highlightColor: backgroundColor.withOpacity(0.2),

That way, even a dark background gets a visible translucent splash.


Option 2: Darken/lighten the base color

You can programmatically make a color darker or lighter by converting it to HSL (hue-saturation-lightness):

Color darken(Color color, [double amount = .1]) {
  final hsl = HSLColor.fromColor(color);
  final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
  return hslDark.toColor();
}

Color lighten(Color color, [double amount = .1]) {
  final hsl = HSLColor.fromColor(color);
  final hslLight = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0));
  return hslLight.toColor();
}

Then in your InkWell:

InkWell(
  splashColor: lighten(backgroundColor, 0.2),
  highlightColor: darken(backgroundColor, 0.2),
  onTap: ...
)

Option 3: Use Material defaults

If you skip setting splashColor and highlightColor, Flutter will pick a default based on the ThemeData. Sometimes letting the theme handle contrast gives better results.


👉 I’d recommend Option 2 if you want full control and a parameterized way to always ensure the ripple is visible regardless of background.

Do you want me to make you a small reusable helper (e.g. rippleColorFrom(backgroundColor)) that automatically decides whether to darken or lighten depending on how bright the base is?

Good question 👍

By default, the ListTile ripple is the theme’s splash color, coming from the InkWell inside it.
You can override it using the properties:

  • tileColor → background when not pressed
  • selectedTileColor → background when selected
  • hoverColor → hover effect (desktop/web)
  • focusColor → when focused
  • splashColor → the ripple color (this is what you want!)

Example:

ListTile(
  title: Text("Custom Ripple"),
  onTap: () {
    print("Tapped");
  },
  splashColor: Colors.purple.withOpacity(0.3), // ripple color
  tileColor: Colors.grey[200],                 // background
  selectedTileColor: Colors.blue[100],         // when selected
)

If you want to set it globally for all ListTiles in your app, use ThemeData:

MaterialApp(
  theme: ThemeData(
    splashColor: Colors.red.withOpacity(0.2), // global ripple
    hoverColor: Colors.red.withOpacity(0.1),
  ),
  home: Scaffold(
    body: ListView(
      children: [
        ListTile(title: Text("One"), onTap: () {}),
        ListTile(title: Text("Two"), onTap: () {}),
      ],
    ),
  ),
);

Note: If you are not seeing Ripple Effect, it may be that the color you’ve set to the Pressed State is lighter than the  Non-Pressed State. It happened to me – so – play around with toggling your clickable items (without actually triggering the taps). This way you will improve the UX and make your app act responsively to the user interfactions.

  • Prompt-to-Production: How AI is Forcing Us to Build Higher Quality Software
  • Debug Web View Flutter App
  • Skipping AI? You’re a Relic – Time to Evolve or Perish!
  • 2026 Flutter Launch Blueprint: Your 10-Step Checklist to App Store Domination
  • Product Requirements Document – for different software development levels

Categories

  • Apps (25)
  • ChatGPT (27)
  • Choosing a Framework (38)
  • Flutter (281)
  • Graphical User Interface (14)
  • Marketing (119)
  • Software Development (292)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (14)
  • Uncategorized (4)
  • Vaadin (16)

Tags

Algorithms (9) crypto (29) flutterdev (39) General (86) Java (7) QR & Bar Codes (3) Software Dev Choices (33) Spring Boot (1) standards (1) Theme (3) User Authentication & Authorization (9) User Experience (10) Utilities (19) WordPress (11)

Product categories

  • All Technologies (87)
    • Flutter Apps (26)
    • GPT (4)
    • Java (39)
    • Native Android (3)
    • PHP (9)
    • Spring (Boot) / Quarkus (36)
    • Utils (15)
    • Vaadin 24+ (28)
    • Vaadin 8 (1)
  • Apps (18)
    • Employees DB (1)
    • Notes (6)
    • Personal Budget (1)
    • Recipes Book (1)
    • Stuff Organizer (1)
    • To-Do (2)
  • PDF Books (3)
  • Source Code Generators (8)

Recent Posts

  • Prompt-to-Production: How AI is Forcing Us to Build Higher Quality Software
  • Debug Web View Flutter App
  • Skipping AI? You’re a Relic – Time to Evolve or Perish!
  • 2026 Flutter Launch Blueprint: Your 10-Step Checklist to App Store Domination
  • Product Requirements Document – for different software development levels

Post Categories

  • Apps (25)
  • ChatGPT (27)
  • Choosing a Framework (38)
  • Flutter (281)
  • Graphical User Interface (14)
  • Marketing (119)
  • Software Development (292)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (14)
  • Uncategorized (4)
  • Vaadin (16)