Neumorphism Animation or Style is a viral trend that has been gaining a lot of attention nowadays. It involves creating user interface elements that appear to be raised from the background and cast a slight shadow, giving them a three-dimensional look. In this article, we will be looking at how to create a Neumorphic animated button in Flutter using the Dart programming language. Even if you’re new to Flutter or haven’t worked with Neumorphism before, don’t worry! this article we will teach you the step-by-step implementation of Neumorphism Animation in a flutter.
in my previous article, we learned about the basic of the Neumorphism Design and how it should look so check that out first. this will give you a more clear vision of the Neumorphism Animation
Implementing a Neumorphic Animation in Flutter
Creating a Neumorphism Button
First, we need to define a custom widget class called as NeumorphismAnimatedButton
that extends StatefulWidget
. This class has a state class _NeumorphismAnimatedButtonState
that extends State
and is responsible for creating the user interface.
class NeumorphismAnimatedButton extends StatefulWidget {
const NeumorphismAnimatedButton({Key? key}) : super(key: key);
@override
State<NeumorphismAnimatedButton> createState() => _NeumorphismAnimatedButtonState();
}
class _NeumorphismAnimatedButtonState extends State<NeumorphismAnimatedButton> {
Widget build(BuildContext context) {
return ///your User Interface
}
}
Inside the _NeumorphismAnimatedButtonState
Class, there are several variables that keep track of the state of the button. These include isClicked
and isDarkMode
, which stores whether the button has been clicked and whether the application is currently in dark mode or light mode. There are also some variables to define the colors and offsets used for the styling of buttons.
class NeumorphismAnimatedButton extends StatefulWidget {
const NeumorphismAnimatedButton({Key? key}) : super(key: key);
@override
State<NeumorphismAnimatedButton> createState() => _NeumorphismAnimatedButtonState();
}
class _NeumorphismAnimatedButtonState extends State<NeumorphismAnimatedButton> {
// state variables
double turns = 0.0;
bool isClicked = false;
bool isDarkMode = true;
Color customWhiteColor = Colors.white60;
Color customBlackColor = Colors.black54;
Widget build(BuildContext context) {
return ///your User Interface
}
}
The _toggleTheme
a function is called when the user toggles the theme switch in the app bar. This function toggles the isDarkMode
variable and updates the UI using the setState
function.
void _toggleTheme() {
setState(() {
isDarkMode = !isDarkMode;
});
}
The build
function returns a Scaffold
widget with an app bar and a body that consists of a centered Stack widget. The Stack widget contains a Padding widget with a AnimatedContainer
widget inside. The AnimatedContainer
widget is wrapped with a GestureDetector
widget that listens for taps on the button.
Scaffold(
backgroundColor: isDarkMode?const Color(0xff2e3239):backgroundColor,
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset("assets/images/sitejuggler.png",
height: 50,width: 50,),
Text("SiteJuggler"),
],
),
actions: [
Switch(
value: isDarkMode,
onChanged: (value) {
_toggleTheme();
},
activeColor: Colors.white,
inactiveThumbColor: Colors.white,
),
],
),
body: Center(
child: Stack(
children:[
Padding(
padding: const EdgeInsets.all(30.0),
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
child: GestureDetector(
onTap: (){
},
child: FittedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: isDarkMode?const Color(0xff2e3239):backgroundColor,
boxShadow:[
BoxShadow(
blurRadius: blur,
offset: Offset(20,-20),
color: isDarkMode?const Color(0xff35393f):Colors.white,
),
BoxShadow(
blurRadius: blur,
offset: Offset(-20,-20),
color: isDarkMode?const Color(0xff23262a):Colors.grey,
)
]
),
child: SizedBox(
height: 100,
width: 100,
),
),
),
),
),
),
],
),
),
);
We can style the button by using an BoxDecoration
in which we are using some properties border-radius, background color, using two BoxShadow
widgets for creating a Neumorphism button. The color and offset of the shadows change depending on whether the app is in dark mode or light mode.
class NeumorphismAnimatedButton extends StatefulWidget {
const NeumorphismAnimatedButton({Key? key}) : super(key: key);
@override
State<NeumorphismAnimatedButton> createState() => _NeumorphismAnimatedButtonState();
}
class _NeumorphismAnimatedButtonState extends State<NeumorphismAnimatedButton> {
// state variables
double turns = 0.0;
bool isClicked = false;
bool isDarkMode = true;
Color customWhiteColor = Colors.white60;
Color customBlackColor = Colors.black54;
// function to toggle theme between light and dark mode
void _toggleTheme() {
setState(() {
isDarkMode = !isDarkMode;
});
}
@override
Widget build(BuildContext context) {
const backgroundColor = Colors.white70; // set a constant background color for light mode
Offset distance = const Offset(30,30);
Offset distance2 = const Offset(-30,30);
double blur = 40.0;
return Scaffold(
backgroundColor: isDarkMode?const Color(0xff2e3239):backgroundColor, // use ternary operator to set background color based on current theme
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset("assets/images/sitejuggler.png", // add an image to the app bar
height: 50,width: 50,),
Text("SiteJuggler"), // add app title
],
),
actions: [
Switch( // add a switch button to toggle theme
value: isDarkMode,
onChanged: (value) {
_toggleTheme();
},
activeColor: Colors.white,
inactiveThumbColor: Colors.white,
),
],
),
body: Center(
child: Stack(
children:[
Padding(
padding: const EdgeInsets.all(30.0),
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
child: GestureDetector(
onTap: (){
},
child: FittedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: isDarkMode?const Color(0xff2e3239):backgroundColor, // use ternary operator to set button color based on current theme
boxShadow:[
BoxShadow(
blurRadius: blur,
offset: Offset(20,-20),
color: isDarkMode?const Color(0xff35393f):Colors.white, // use ternary operator to set shadow color based on current theme
),
BoxShadow(
blurRadius: blur,
offset: Offset(-20,-20),
color: isDarkMode?const Color(0xff23262a):Colors.grey, // use ternary operator to set shadow color based on current theme
)
]
),
child: SizedBox(
height: 100,
width: 100,
),
),
),
),
),
),
],
),
),
);
}
}
congratulations guys, we have set up the basic architecture and styling of a Neumorphic animated button that can be customized further to fit specific design needs.

Let’s Begin With The Animation
Creating Animated Icon
first of all, We need to add the Center widget to position the AnimatedIcon widget at the center of the screen.
after that we need to add the AnimatedIcon widget :
Center(
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _controller,
size: 20,
color: isDarkMode? Colors.white:Colors.black,
)
)
for controlling animation, we have to use an AnimationController.
late AnimationController _controller;
Now we are initializing the _controller
variable in the initState
using the vsync
argument, for ensuring the sync of animation with the device
@override
void initState() {
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync:this
);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}

Implementing Animated Rotation
AnimatedRotation(
curve: Curves.easeOutExpo,
turns: turns,
duration: const Duration(seconds: 1),
child: GestureDetector(
onTap: (){
if(isClicked){
setState(() {
turns -= 1/4;
_controller.reverse();
});
}
else{
setState(() {
turns += 1/4;
_controller.forward();
});
}
isClicked = !isClicked;
},
child: FittedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: isDarkMode ? const Color(0xff2e3239) : backgroundColor,
boxShadow:[
BoxShadow(
blurRadius: blur,
offset: -distance,
color: isDarkMode ? const Color(0xff35393f) : Colors.white,
),
BoxShadow(
blurRadius: blur,
offset: distance,
color: isDarkMode ? const Color(0xff23262a) : Colors.grey,
)
]
),
child: SizedBox(
height: 100,
width: 100,
child: Center(
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _controller,
size: 20,
color: isDarkMode ? Colors.white : Colors.black,
)
),
),
),
),
),
),
Creating Neumorphism Animation Page
We are now adding another AnimatedContainer that will be shown when the button is clicked. This container will cover the whole screen with a background color and will be hidden when the button is not clicked.
// AnimatedContainer to show/hide the full screen background when button is clicked
AnimatedContainer(
height: isClicked ? MediaQuery.of(context).size.height : 0,
width: isClicked ? MediaQuery.of(context).size.width : 0,
duration: Duration(milliseconds: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: isDarkMode ? const Color(0xff2e3239) : backgroundColor,
boxShadow:[
BoxShadow(
blurRadius: blur,
offset: -distance,
color: isDarkMode ? const Color(0xff35393f) : Colors.white,
),
BoxShadow(
blurRadius: blur,
offset: distance,
color: isDarkMode ? const Color(0xff23262a) : Colors.grey,
)
]
),
),
Let’s Fix the Shadow Problem
for having the shadow property we are using the two box-shadow. But when we rotate the button, the shadow also rotates, we need a consistent shadow. So, to solve this problem, we need to use different shadow offsets for when the button is clicked or not.
We have created two different shadow offsets: distance
and distance2
. distance
is the shadow offset when the button is not clicked, and distance2
is the shadow offset when the button is clicked. We use a ternary operator to determine which shadow offset to use based on whether the button is clicked or not.
Overall, this will give us a nice shadow effect that will remain consistent even when the button is rotated.
boxShadow:[
BoxShadow(
blurRadius: blur,
offset: isClicked ? distance2 : -distance,
color: isDarkMode ? const Color(0xff35393f) : Colors.white,
),
BoxShadow(
blurRadius: blur,
offset: isClicked ? -distance2 : distance,
color: isDarkMode ? const Color(0xff23262a) : Colors.grey,
)
]
Here, the blurRadius
is the amount of blur applied to the shadow, offset
is the distance the shadow is offset from the button, and color
is the color of the shadow.
We use the same isClicked
ternary operator to determine which color to use for the shadows based on whether the app is in dark mode or not.
Output
As you have implemented the code and run it, you should see a button with the animated menu icon. When you click on the button, the icon rotates and expands the container, which covers the whole screen, displaying some dummy text. When you click the button again, the container shrinks and the icon rotates back to its original position. The button also changes color to reflect the dark or light mode based on the device setting. Overall, the output should show a simple but effective implementation of the Neumorphism style in a Flutter app.
Full Source Code
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
title: 'Stepper Widget- SiteJuggler',
home: const NeumorphismAnimatedButton(),
);
}
}
class NeumorphismAnimatedButton extends StatefulWidget {
const NeumorphismAnimatedButton({Key? key}) : super(key: key);
@override
State<NeumorphismAnimatedButton> createState() => _NeumorphismAnimatedButtonState();
}
class _NeumorphismAnimatedButtonState extends State<NeumorphismAnimatedButton>
with TickerProviderStateMixin { ///new
late AnimationController _controller;///new
double turns = 0.0;
bool isClicked = false;
bool isDarkMode = true;
Color customWhiteColor = Colors.white60;
Color customBlackColor = Colors.black54;
void _toggleTheme() {
setState(() {
isDarkMode = !isDarkMode;
});
}
@override
void initState() {
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync:this
);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
const backgroundColor = Colors.white70;
Offset distance = const Offset(30,30);
Offset distance2 = const Offset(-30,30);
double blur = 40.0;
return Scaffold(
backgroundColor: isDarkMode?const Color(0xff2e3239):backgroundColor,
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset("assets/images/sitejuggler.png",
height: 50,width: 50,),
Text("SiteJuggler"),
],
),
actions: [
Switch(
value: isDarkMode,
onChanged: (value) {
_toggleTheme();
},
activeColor: Colors.white,
inactiveThumbColor: Colors.white,
),
],
),
body: Center(
child: Stack(
children:[
Padding(
padding: const EdgeInsets.all(90.0),
child: AnimatedContainer(
height: isClicked?MediaQuery.of(context).size.height:0,
width: isClicked?MediaQuery.of(context).size.width:0,
//color: customBlackColor,
duration: Duration(milliseconds: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: isDarkMode?const Color(0xff2e3239):backgroundColor,
boxShadow:[
BoxShadow(
blurRadius: blur,
offset: -distance,
color: isDarkMode?const Color(0xff35393f):Colors.white,
),
BoxShadow(
blurRadius: blur,
offset: distance,
color: isDarkMode?const Color(0xff23262a):Colors.grey,
)
]
),
),
),
Padding(
padding: const EdgeInsets.all(30.0),
child: AnimatedContainer(
height: isClicked?50:100,
width: isClicked?50:100,
duration: Duration(milliseconds: 200),
child: AnimatedRotation(
curve: Curves.easeOutExpo, //starts fast and then get slow at end
turns: turns,
duration: const Duration(seconds: 1),
child: GestureDetector(
onTap: (){
if(isClicked){
setState(() {
turns -= 1/4;
// turns = turns - 1/4;
_controller.reverse(); ///new
});
}
else{
setState(() {
turns += 1/4;
// turns = turns + 1/4;
_controller.forward(); ///new
});
}
isClicked = !isClicked;
},
child: FittedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: isDarkMode?const Color(0xff2e3239):backgroundColor,
boxShadow:[
BoxShadow(
blurRadius: blur,
offset: isClicked?distance2:-distance,
color: isDarkMode?const Color(0xff35393f):Colors.white,
),
BoxShadow(
blurRadius: blur,
offset: isClicked?-distance2:distance,
color: isDarkMode?const Color(0xff23262a):Colors.grey,
)
]
),
child: SizedBox(
height: 100,
width: 100,
child: Center(
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _controller,
size: 20,
color: isDarkMode? Colors.white:Colors.black,
)
),
),
),
),
),
),
),
),
],
),
),
);
}
}
Conclusion
In this article, we learned how to create a beautiful Neumorphic Animation menu button in Flutter. We started by explaining what Neumorphism is and how it can be used in UI design. Then, we walked through the code step by step, adding a menu icon with animation and implementing a Neumorphic effect. We also added an expandable container that appears when the button is clicked. Finally, we solved the issue of the shadow turning along with the button. By following this tutorial, you should now have a good understanding of how to create Neumorphic UI elements in Flutter.