What is the difference between Visibility and Opacity?

Medium PriorityAsked in ~50% of Flutter interviews

2 min read

Widgets & UI

Opacity(0)Visibility(visible: false)
Takes layout space❌ (default)
Hit tests / taps
Renders / paints✅ (just transparent)
Keeps child state❌ (default)
Cost when hiddenSame as when visibleNear zero
Good for animation✅ (AnimatedOpacity)❌ (snaps in/out)

Code in action

// Hide via Opacity — child still pays full cost
Opacity(
  opacity: 0.0,
  child: ExpensiveChart(),                 // still built + painted
);

// Hide via Visibility — child is skipped entirely
Visibility(
  visible: showChart,
  child: ExpensiveChart(),
);

// Hide via if — simplest of all
if (showChart) const ExpensiveChart();
// Visibility's knobs for finer control
Visibility(
  visible: false,
  maintainSize: true,            // keep the slot of space
  maintainState: true,           // keep the child State alive
  maintainAnimation: true,       // keep animations running
  maintainInteractivity: false,
  replacement: const SizedBox.shrink(),
  child: ExpensiveWidget(),
);

// Animated fade (the right job for Opacity)
AnimatedOpacity(
  opacity: visible ? 1.0 : 0.0,
  duration: const Duration(milliseconds: 250),
  child: const HelloCard(),
);

When to use which

GoalUse
Just remove the widget from the treeif (cond) Widget()
Hide but keep space reserved (layout stable)Visibility(visible: false, maintainSize: true)
Hide but keep internal state (e.g., scroll position)Visibility(... maintainState: true)
Fade in / fade outAnimatedOpacity (or FadeTransition)
Cross-fade between two widgetsAnimatedCrossFade / AnimatedSwitcher
Disable interaction without changing appearanceIgnorePointer or AbsorbPointer

Common mistakes to avoid

// ❌ Opacity(0) on expensive widgets — still paints fully
Opacity(opacity: 0, child: HeavyChart());
// ✅ Visibility or `if`

// ❌ Opacity for instant show/hide
Opacity(opacity: showChart ? 1 : 0, child: ...);   // wastes work, no animation benefit
// ✅ if / Visibility

// ❌ Visibility(visible: false) when you wanted layout to stay
// Default Visibility removes from layout — set maintainSize: true to keep the gap

// ❌ Opacity wrapping a Stack of many widgets — costly
// ✅ Apply opacity at the layer level via FadeTransition / AnimatedOpacity around the smallest subtree

// ❌ Forgetting that an invisible Opacity child still receives taps
Opacity(opacity: 0, child: GestureDetector(onTap: ...));   // still tappable!
// ✅ Wrap in IgnorePointer or use Visibility

Interview follow-ups

  1. Why is Opacity(0) more expensive than Visibility(false)? Because the child is still built, laid out, and painted — Opacity only multiplies alpha at the compositor. Visibility short-circuits the render: nothing is built or painted when hidden.

  2. When would you set maintainSize: true on a Visibility? When the surrounding layout should not jump when the widget disappears — e.g., reserving space for an error message or a loading indicator.

  3. AnimatedOpacity vs FadeTransition — what's the difference? AnimatedOpacity is an implicit animation — set a new opacity value and Flutter tweens. FadeTransition is explicit — driven by an Animation<double> you control. Use FadeTransition when you need fine-grained control or coordination with other animations.

  4. What's the difference between IgnorePointer and AbsorbPointer? IgnorePointer passes touches through to widgets behind it. AbsorbPointer blocks the touches at this widget — nothing behind receives them either. Both keep the visual appearance unchanged.


How helpful was this content?

Please sign in to rate this article.