FlutterFlow's Animations tab in the Properties Panel lets you add fade, slide, scale, and rotate effects to any widget without code. Use On Page Load for entrance animations, On Action for interactive feedback, staggered delays for cascade effects, and loop for continuous motion. Add Lottie files for complex sequences. Animate deliberately — less is more.
The FlutterFlow Animation System — No Code Required
Animation is one of the most visible ways to elevate an app from functional to polished. FlutterFlow's built-in animation system lets you add motion to any widget through the Properties Panel — no Dart code needed for most use cases. The system covers the four animation types that account for 90% of real app animation needs: fade (opacity), slide (position), scale (size), and rotate. Each animation has a trigger (when does it run?), a direction, a duration, and a delay. Understanding how these four controls combine gives you the ability to create everything from a simple page entrance to a complex staggered list reveal. This tutorial walks through each type with concrete UI examples you can apply immediately.
Prerequisites
- A FlutterFlow project on any plan
- A page with at least a few widgets to animate (buttons, cards, text)
- Basic familiarity with the FlutterFlow Properties Panel
- For Lottie: a .json Lottie file (download free ones from lottiefiles.com)
Step-by-step guide
Add your first On Page Load fade-in animation
Add your first On Page Load fade-in animation
Select any widget on your canvas — a Card or a Column works well for a first experiment. Open the Properties Panel on the right and scroll to the Animations section. Click the + button to add a new animation. Choose Fade from the animation type dropdown. Set the trigger to On Page Load. Set Duration to 600ms. Set Delay to 0ms. Set the Start value to 0.0 (fully transparent) and the End value to 1.0 (fully visible). Click the play button in the Animations panel to preview the animation in FlutterFlow's canvas — you will see the widget fade in from invisible to visible. Switch to Run Mode on a connected device to see it in the actual app. Every widget you give an On Page Load animation will run that animation each time its parent page is navigated to.
Expected result: The selected widget fades in from transparent to fully visible every time the page loads. The animation is visible in both FlutterFlow's preview and Run Mode.
Add a slide-in animation to create entrance motion
Add a slide-in animation to create entrance motion
Slide animations move a widget from an off-screen or offset position into its final layout position. Select your widget in the Properties Panel. In Animations, add a new animation and choose Slide. Set the trigger to On Page Load. Set Direction to Bottom (the widget slides up from below — the most common and natural-feeling entrance). Set Duration to 500ms. Set Offset to 30 pixels — this is the starting distance from the final position. You can combine a Fade and a Slide on the same widget: FlutterFlow runs both simultaneously. The combination of fading in while sliding up is the same pattern used by Material Design for list item entrances and feels premium without being distracting. Try Direction: Right for a side-drawer-style reveal.
Expected result: The widget slides up from 30px below its final position while simultaneously fading in. The combined effect is a smooth, polished entrance animation.
Create staggered cascade animations across a list
Create staggered cascade animations across a list
A staggered animation reveals list items or cards one after another with a small delay between each — instead of all appearing simultaneously. In your ListView or Column with multiple children, select the first child widget and add a Fade + Slide animation with Delay: 0ms. Select the second child and add the same animation with Delay: 100ms. Select the third with Delay: 200ms. Continue the pattern (each item adds 100ms). This cascade creates the effect of items flowing in sequentially from top to bottom. Keep the total cascade time under 600ms for a list of 6 items — longer cascades make users wait before the content is fully visible and actionable. For dynamically generated lists, use a Custom Widget with an AnimationController that uses AnimationStagger to apply delays programmatically.
Expected result: List items appear one after another in sequence, each fading and sliding in 100ms after the previous one. The page feels alive and intentional rather than static.
Add On Action animations for interactive feedback
Add On Action animations for interactive feedback
On Action animations run when triggered by a user interaction — typically a button tap. They are perfect for feedback animations that confirm the user's action. Select a button widget and add a Scale animation. Set the trigger to On Action. Set the action to On Tap. Set the scale start to 1.0 (normal size), end to 0.95, and back to 1.0 using a curve (EaseInOut). Set Duration to 200ms. This creates a subtle press animation — the button shrinks slightly when tapped, providing tactile-style feedback. Another powerful use: add a Fade animation to a success message widget with trigger On Action tied to a form submission button — the success message fades in only after the submit action completes. Wire this in the Action Flow editor by calling the animation trigger at the end of your submit action chain.
Expected result: Tapping the button causes it to briefly scale down to 95% and back to 100%. Users feel the tap is being acknowledged. Success messages fade in after the submit action completes.
Add a Lottie animation for complex sequences
Add a Lottie animation for complex sequences
For animations that go beyond what FlutterFlow's built-in fade/slide/scale/rotate can achieve — loading spinners, success checkmarks, empty state illustrations, confetti — use a Lottie Animation widget. In FlutterFlow's widget panel, search for Lottie Animation and drag it onto your canvas. In the Properties Panel, set the Source to either a local asset (upload a .json Lottie file) or a URL (e.g., from LottieFiles CDN). Set Repeat to true for continuous animations (loaders) or false for one-shot animations (success checkmarks that play once). Use the Loop Mode dropdown to choose between repeating, once, or bounce. Control when the Lottie plays using an Action that calls Play/Pause Lottie from the Action Flow editor. A common pattern: show a Lottie loading animation while waiting for an API call, then hide it and show the success Lottie when the call completes.
Expected result: The Lottie animation plays on screen. For a looping loader, it repeats continuously. For a success checkmark, it plays once and stops at the final frame.
Use loop animations for continuous background motion
Use loop animations for continuous background motion
Some UI elements benefit from continuous, gentle motion — a floating action button that pulses to draw attention, a background gradient that slowly shifts colors, or a loading skeleton that shimmers. For a pulsing effect, select your widget, add a Scale animation, enable the Loop toggle in FlutterFlow's animation settings, set scale from 1.0 to 1.05 and back, with Duration 1200ms and curve Ease In Out. The loop will repeat indefinitely while the widget is visible. For more complex continuous animations (rotating loading icon, animated progress bar, shimmer effect), use a Custom Widget with a Dart AnimationController that uses RepeatBehavior.loop. Connect the animation value to a Transform.rotate or a Custom Painter. Always use a dispose() call to cancel AnimationControllers when the widget is removed.
1// Custom Widget with looping AnimationController2class PulsingWidget extends StatefulWidget {3 final Widget child;4 const PulsingWidget({Key? key, required this.child}) : super(key: key);5 @override6 State<PulsingWidget> createState() => _PulsingWidgetState();7}89class _PulsingWidgetState extends State<PulsingWidget>10 with SingleTickerProviderStateMixin {11 late final AnimationController _controller;12 late final Animation<double> _scaleAnimation;1314 @override15 void initState() {16 super.initState();17 _controller = AnimationController(18 duration: const Duration(milliseconds: 1200),19 vsync: this,20 )..repeat(reverse: true);21 _scaleAnimation = Tween<double>(begin: 1.0, end: 1.06).animate(22 CurvedAnimation(parent: _controller, curve: Curves.easeInOut),23 );24 }2526 @override27 void dispose() {28 _controller.dispose(); // ALWAYS dispose to prevent memory leaks29 super.dispose();30 }3132 @override33 Widget build(BuildContext context) {34 return ScaleTransition(scale: _scaleAnimation, child: widget.child);35 }36}Expected result: The widget continuously and gently pulses between its normal size and 106% scale in a smooth, breathable rhythm that draws attention without being distracting.
Complete working example
1import 'package:flutter/material.dart';23// A Custom Widget that staggers entrance animations across a list of children4class StaggeredList extends StatefulWidget {5 final List<Widget> children;6 final Duration itemDelay;7 final Duration itemDuration;8 final Offset slideOffset;910 const StaggeredList({11 Key? key,12 required this.children,13 this.itemDelay = const Duration(milliseconds: 80),14 this.itemDuration = const Duration(milliseconds: 500),15 this.slideOffset = const Offset(0, 30),16 }) : super(key: key);1718 @override19 State<StaggeredList> createState() => _StaggeredListState();20}2122class _StaggeredListState extends State<StaggeredList>23 with TickerProviderStateMixin {24 late final List<AnimationController> _controllers;25 late final List<Animation<double>> _fadeAnims;26 late final List<Animation<Offset>> _slideAnims;2728 @override29 void initState() {30 super.initState();31 final count = widget.children.length;32 _controllers = List.generate(33 count,34 (i) => AnimationController(35 duration: widget.itemDuration,36 vsync: this,37 ),38 );3940 _fadeAnims = _controllers41 .map((c) => Tween<double>(begin: 0, end: 1).animate(42 CurvedAnimation(parent: c, curve: Curves.easeOut),43 ))44 .toList();4546 _slideAnims = _controllers47 .map((c) => Tween<Offset>(48 begin: widget.slideOffset,49 end: Offset.zero,50 ).animate(CurvedAnimation(parent: c, curve: Curves.easeOut)))51 .toList();5253 // Start each animation with a staggered delay54 for (int i = 0; i < count; i++) {55 Future.delayed(widget.itemDelay * i, () {56 if (mounted) _controllers[i].forward();57 });58 }59 }6061 @override62 void dispose() {63 for (final c in _controllers) {64 c.dispose();65 }66 super.dispose();67 }6869 @override70 Widget build(BuildContext context) {71 return Column(72 children: List.generate(widget.children.length, (i) {73 return AnimatedBuilder(74 animation: _controllers[i],75 builder: (context, child) {76 return FadeTransition(77 opacity: _fadeAnims[i],78 child: Transform.translate(79 offset: _slideAnims[i].value,80 child: child,81 ),82 );83 },84 child: widget.children[i],85 );86 }),87 );88 }89}Common mistakes
Why it's a problem: Adding animations to every widget on a page
How to avoid: Animate only the most important 2-3 elements per page. Use animations to guide the user's eye to the primary action or the most important content. Everything else should load instantly without motion.
Why it's a problem: Using animation durations over 1000ms for UI transitions
How to avoid: Keep entrance animations between 400-700ms. Interactive feedback animations (button press, toggle) should be 150-250ms. Only use durations over 700ms for deliberate celebration animations (success screens, achievement unlocks) where the wait is the point.
Why it's a problem: Creating looping AnimationControllers in Custom Widgets without disposing them
How to avoid: Always override dispose() in your StatefulWidget and call _controller.dispose() on every AnimationController you create. This is mandatory, not optional.
Best practices
- Animate a maximum of 2-3 elements per page — motion should guide attention, not compete for it
- Use 400-700ms for entrance animations and 150-250ms for interactive feedback animations
- Combine Fade and Slide on the same widget for a polished entrance — the combination feels more natural than either alone
- Always dispose AnimationControllers in the dispose() method of StatefulWidgets to prevent memory leaks
- Use staggered delays of 60-100ms between cascading list items — longer gaps make the cascade feel slow
- Test animations on a mid-range Android device, not just a high-end iPhone — performance gaps are significant
- Prefer Lottie for complex frame-by-frame sequences like success checkmarks, loading states, and empty state illustrations
- Turn off animations for users who have enabled 'Reduce Motion' in accessibility settings — check MediaQuery.disableAnimations
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a FlutterFlow app and want to add entrance animations to a list of cards that appear sequentially when the page loads. Write a Flutter Custom Widget called StaggeredList that accepts a list of child widgets and animates each one with a 80ms staggered delay, combining a fade from 0 to 1 opacity and a 30px upward slide. Ensure AnimationControllers are properly disposed.
In my FlutterFlow project, I have 6 widgets in a Column on a page. Walk me through using the Properties Panel Animations tab to add a staggered entrance animation where each widget fades in and slides up, with an 80ms delay between each widget. What Delay values should I set for each of the 6 widgets?
Frequently asked questions
Can I add animations in FlutterFlow without writing any code?
Yes, for the four core animation types — fade, slide, scale, and rotate. All four are available directly in the Properties Panel Animations tab and require no Dart code. Lottie animations also require no code, just a .json file. Custom AnimationController patterns for complex or looping animations do require Custom Widget code.
What is the difference between On Page Load and On Action animation triggers?
On Page Load triggers the animation automatically whenever the page is navigated to. This is for entrance animations — things that should appear as the page opens. On Action triggers the animation manually via an action in the Action Flow editor, typically tied to a button tap, form submission, or gesture. This is for interactive feedback and response animations.
How do I make an animation repeat only once (not loop) when triggered by a button?
In the Animations panel, leave the Loop toggle off. When configured without loop, the animation plays once forward and stops at its end value. If you want the widget to return to its original state after the animation, set the Reset to Initial State toggle on.
My animations look smooth in FlutterFlow's preview but lag on Android. Why?
FlutterFlow's canvas preview runs at full desktop GPU performance. Many mid-range Android devices have significantly weaker GPUs. Common causes of animation lag on Android: too many simultaneous animations, large images animated with scale, or complex shadow/blur effects on animated widgets. Test on a physical mid-range Android device during development, not just the preview.
Can I animate between two completely different pages in FlutterFlow?
Yes, via page transition animations. In the Navigate action, click the Transition Type dropdown and choose Fade, Slide (Left, Right, Up, Down), or Scale. The transition duration is also configurable. For custom page transitions that are not in the built-in list, you need to use a Custom Navigator configuration, which requires code export.
What is a Lottie animation and where do I get them?
Lottie is a JSON-based animation format originally created by Airbnb. Lottie files contain vector animation data that Flutter renders at any size without quality loss. You can find thousands of free Lottie animations at lottiefiles.com. Download the .json file and upload it to your FlutterFlow project as an asset, then add a Lottie Animation widget and point it to that asset file.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation