Q&A
How to show and hide widgets in Flutter
Overview
"Hiding" a widget can mean three different things in a layout: remove it completely (no space taken), keep its space but make it invisible, or keep it built but not painted. Flutter offers a tool for each, plus the simplest approach - conditional construction - where you just don't put the widget in the tree at all. State drives it: flip a bool and call setState to rebuild.
graph TD
A[Want to hide a widget] --> B{Should it keep layout space?}
B -->|no, remove entirely| C[Conditional: if / ternary]
B -->|no, but stay in tree| D[Offstage]
B -->|yes, keep space| E[Visibility maintainSize / Opacity]
style C fill:#1e1b4b,color:#fff
Approach 1: Conditional Rendering (most common)
Just include the widget only when needed. With setState toggling a flag:
bool _showDetails = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () => setState(() => _showDetails = !_showDetails),
child: Text(_showDetails ? "Hide" : "Show"),
),
if (_showDetails) Text("Here are the details!"), // collection-if
],
);
}
The if inside a children list (collection-if) cleanly adds/removes the widget - when hidden, it's not in the tree at all and takes no space. A ternary works too:
_showDetails ? DetailsWidget() : const SizedBox.shrink(),
SizedBox.shrink() is a zero-size placeholder for the "nothing" branch.
Approach 2: Visibility Widget
Visibility toggles without restructuring your tree, and can optionally preserve layout space:
Visibility(
visible: _showDetails,
child: Text("Details"),
// optional:
// maintainSize: true, maintainAnimation: true, maintainState: true,
)
- Default: when
visible: false, the child is removed from layout (no space). - With
maintainSize: true(requiresmaintainAnimationandmaintainState), it keeps its space but is invisible - useful to avoid layout jumps. replacement:lets you show a different widget when hidden.
Approach 3: Offstage
Offstage keeps the widget in the tree and builds it, but doesn't paint it or give it size:
Offstage(
offstage: !_showDetails,
child: ExpensiveWidget(),
)
Useful when you want to keep the widget's state alive and ready (e.g. measure it, or toggle quickly) without showing it.
Approach 4: Opacity (visible space, transparent)
Opacity(opacity: 0.0, child: ...) renders the widget and reserves its space but makes it transparent. It still receives layout and (unless wrapped) can still be interactive, so it's the right tool only when you specifically want "invisible but present." Note it's relatively expensive to animate.
Choosing the Right One
| Need | Use |
|---|---|
| Remove completely, free its space | if / ternary, or Visibility (default) |
| Keep its space (avoid layout shift) | Visibility(maintainSize: true) or Opacity |
| Keep built & stateful but not shown | Offstage |
| Animate the appearance | AnimatedOpacity / AnimatedSwitcher |
Animated Show/Hide
For smooth transitions:
AnimatedOpacity(
opacity: _showDetails ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: Text("Details"),
)
AnimatedSwitcher cross-fades between two widgets when the child changes.
Why This Matters in Interviews
Flutter interviews use this to test understanding of the widget tree and the layout-vs-paint distinction. The key insight: hiding isn't one thing - if/ternary removes the widget entirely, Visibility can keep or drop its space, Offstage keeps it built but unpainted, Opacity keeps space but transparent. Knowing that conditional construction is cheapest (the widget simply isn't there) and that setState drives the toggle is the core.
Common Pitfalls
- Using
Opacity(0)to "hide" but the widget still occupies space and may stay tappable. - Expecting
Visibility(visible:false)to preserve space by default (it doesn't unlessmaintainSize). - Rebuilding expensive widgets repeatedly when
Offstagecould keep them ready. - Forgetting
setState, so the boolean changes but the UI doesn't rebuild.
References
- Flutter
Visibility: https://api.flutter.dev/flutter/widgets/Visibility-class.html - Flutter
Offstage: https://api.flutter.dev/flutter/widgets/Offstage-class.html