Overview
ZIndex
is a widget that controls the layering order (z-index) of its child widget. This is a unique widget specific to Flitter (not available in Flutter itself), allowing you to determine the front-to-back order of widgets similar to CSS z-index.
Use it when you need to display specific widgets on top of others or create complex layer structures. When used together with Stack widget, you can implement more sophisticated layouts.
When to Use?
- When you need to explicitly control widget rendering order
- When displaying popups or overlays on top of other widgets
- When bringing specific widgets to the front in complex layer structures
- When moving selected elements to the top during drag and drop
- When managing element depth in diagrams or games
Basic Usage
import { ZIndex, Container, Stack } from 'flitter';
// Basic z-index application
const LayeredWidget = Stack({
children: [
Container({
width: 200,
height: 200,
color: '#e74c3c'
}),
ZIndex({
zIndex: 10,
child: Container({
width: 100,
height: 100,
color: '#3498db'
})
})
]
});
Props
zIndex (required)
Value: number
A number representing the layer order of the widget. Higher values render in front (on top).
- Positive numbers: Display in front of the default layer
- 0: Default layer (baseline)
- Negative numbers: Display behind the default layer
// Display at the very front
zIndex: 9999
// Default layer
zIndex: 0
// Display at the very back
zIndex: -1
child (optional)
Value: Widget
The child widget to which the z-index will be applied. Can only have one child.
Stacking Context
ZIndex uses a system similar to CSS stacking contexts:
Basic Rules
- Higher z-index displays in front of lower z-index
- When z-index values are equal, document order (creation order) determines precedence
- Nested ZIndex widgets are constrained by their parent’s stacking context
Context Isolation
// If parent zIndex is 1, child zIndex 9999 may still
// appear behind another sibling with zIndex 2
ZIndex({
zIndex: 1,
child: ZIndex({
zIndex: 9999,
child: Container({ color: 'red' })
})
})
Real-World Examples
Example 1: Basic Layering
import { ZIndex, Container, Stack, Positioned } from 'flitter';
const BasicLayering = Stack({
children: [
// Background (no z-index, default 0)
Container({
width: 300,
height: 300,
color: '#ecf0f1'
}),
// Middle layer
Positioned({
top: 50,
left: 50,
child: ZIndex({
zIndex: 1,
child: Container({
width: 200,
height: 200,
color: '#3498db',
child: Center({
child: Text('Middle Layer', {
style: { color: 'white', fontSize: 16 }
})
})
})
})
}),
// Top layer
Positioned({
top: 100,
left: 100,
child: ZIndex({
zIndex: 10,
child: Container({
width: 100,
height: 100,
color: '#e74c3c',
child: Center({
child: Text('Top', {
style: { color: 'white', fontSize: 14 }
})
})
})
})
}),
// Behind background (negative z-index)
Positioned({
top: 25,
left: 25,
child: ZIndex({
zIndex: -1,
child: Container({
width: 50,
height: 50,
color: '#95a5a6',
child: Center({
child: Text('Back', {
style: { color: 'white', fontSize: 12 }
})
})
})
})
})
]
});
Example 2: Dropdown Menu
import { ZIndex, Container, Column, GestureDetector } from 'flitter';
class DropdownMenu extends StatefulWidget {
createState() {
return new DropdownMenuState();
}
}
class DropdownMenuState extends State {
isOpen = false;
toggleDropdown = () => {
this.setState(() => {
this.isOpen = !this.isOpen;
});
};
build() {
return Stack({
children: [
// Main button
GestureDetector({
onTap: this.toggleDropdown,
child: Container({
padding: EdgeInsets.symmetric({ horizontal: 16, vertical: 12 }),
decoration: new BoxDecoration({
color: '#3498db',
borderRadius: BorderRadius.circular(4)
}),
child: Row({
mainAxisSize: MainAxisSize.min,
children: [
Text('Menu', { style: { color: 'white' } }),
SizedBox({ width: 8 }),
Icon(
this.isOpen ? Icons.arrow_drop_up : Icons.arrow_drop_down,
{ color: 'white' }
)
]
})
})
}),
// Dropdown menu (high z-index to display above other elements)
if (this.isOpen)
Positioned({
top: 50,
left: 0,
child: ZIndex({
zIndex: 1000, // High z-index to display above all elements
child: Container({
width: 150,
decoration: new BoxDecoration({
color: 'white',
borderRadius: BorderRadius.circular(4),
boxShadow: [
new BoxShadow({
color: 'rgba(0, 0, 0, 0.1)',
offset: new Offset({ x: 0, y: 2 }),
blurRadius: 8
})
]
}),
child: Column({
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildMenuItem('Option 1'),
_buildMenuItem('Option 2'),
_buildMenuItem('Option 3')
]
})
})
})
})
]
});
}
_buildMenuItem(text: string) {
return GestureDetector({
onTap: () => {
console.log(`${text} selected`);
this.toggleDropdown();
},
child: Container({
padding: EdgeInsets.all(12),
child: Text(text, { style: { fontSize: 14 } })
})
});
}
}
Example 3: Modal Dialog
import { ZIndex, Container, Stack, Positioned, GestureDetector } from 'flitter';
const Modal = ({ isVisible, onClose, children }) => {
if (!isVisible) return null;
return Stack({
children: [
// Background overlay (medium z-index)
Positioned.fill({
child: ZIndex({
zIndex: 100,
child: GestureDetector({
onTap: onClose,
child: Container({
color: 'rgba(0, 0, 0, 0.5)'
})
})
})
}),
// Modal content (highest z-index)
Center({
child: ZIndex({
zIndex: 200,
child: Container({
width: 300,
padding: EdgeInsets.all(24),
decoration: new BoxDecoration({
color: 'white',
borderRadius: BorderRadius.circular(12),
boxShadow: [
new BoxShadow({
color: 'rgba(0, 0, 0, 0.2)',
offset: new Offset({ x: 0, y: 4 }),
blurRadius: 16
})
]
}),
child: children
})
})
})
]
});
};
Example 4: Draggable Card System
import { ZIndex, Container, GestureDetector } from 'flitter';
class DraggableCard extends StatefulWidget {
constructor(
private color: string,
private initialZIndex: number,
private title: string
) {
super();
}
createState() {
return new DraggableCardState();
}
}
class DraggableCardState extends State {
isDragging = false;
position = new Offset({ x: 0, y: 0 });
get currentZIndex() {
// Use highest z-index while dragging
return this.isDragging ? 9999 : this.widget.initialZIndex;
}
build() {
return Positioned({
left: this.position.x,
top: this.position.y,
child: ZIndex({
zIndex: this.currentZIndex,
child: GestureDetector({
onPanStart: (details) => {
this.setState(() => {
this.isDragging = true;
});
},
onPanUpdate: (details) => {
this.setState(() => {
this.position = this.position.plus(details.delta);
});
},
onPanEnd: (details) => {
this.setState(() => {
this.isDragging = false;
});
},
child: Container({
width: 120,
height: 80,
decoration: new BoxDecoration({
color: this.widget.color,
borderRadius: BorderRadius.circular(8),
boxShadow: this.isDragging ? [
new BoxShadow({
color: 'rgba(0, 0, 0, 0.3)',
offset: new Offset({ x: 0, y: 8 }),
blurRadius: 16
})
] : [
new BoxShadow({
color: 'rgba(0, 0, 0, 0.1)',
offset: new Offset({ x: 0, y: 2 }),
blurRadius: 4
})
]
}),
child: Center({
child: Text(this.widget.title, {
style: {
color: 'white',
fontWeight: 'bold',
fontSize: 14
}
})
})
})
})
})
});
}
}
// Usage example
const DraggableCards = Stack({
children: [
DraggableCard('#e74c3c', 1, 'Card 1'),
DraggableCard('#3498db', 2, 'Card 2'),
DraggableCard('#2ecc71', 3, 'Card 3')
]
});
Example 5: Tooltip System
import { ZIndex, Container, Positioned, GestureDetector } from 'flitter';
class TooltipWidget extends StatefulWidget {
constructor(
private tooltip: string,
private child: Widget
) {
super();
}
createState() {
return new TooltipWidgetState();
}
}
class TooltipWidgetState extends State {
showTooltip = false;
build() {
return Stack({
children: [
// Base widget
GestureDetector({
onLongPress: () => this.setState(() => {
this.showTooltip = true;
}),
onTapDown: () => this.setState(() => {
this.showTooltip = false;
}),
child: this.widget.child
}),
// Tooltip (very high z-index)
if (this.showTooltip)
Positioned({
bottom: 0,
left: 0,
child: ZIndex({
zIndex: 9999,
child: Container({
padding: EdgeInsets.symmetric({
horizontal: 8,
vertical: 4
}),
decoration: new BoxDecoration({
color: 'rgba(0, 0, 0, 0.8)',
borderRadius: BorderRadius.circular(4)
}),
child: Text(this.widget.tooltip, {
style: {
color: 'white',
fontSize: 12
}
})
})
})
})
]
});
}
}
Example 6: Game Object Layering
import { ZIndex, Container, Stack } from 'flitter';
const GameScene = Stack({
children: [
// Background layer (farthest back)
ZIndex({
zIndex: -10,
child: Container({
width: 800,
height: 600,
decoration: new BoxDecoration({
gradient: LinearGradient({
colors: ['#87CEEB', '#98FB98']
})
})
})
}),
// Terrain layer
ZIndex({
zIndex: 0,
child: TerrainWidget()
}),
// Game object layer
ZIndex({
zIndex: 10,
child: PlayerWidget()
}),
// Enemy character layer
ZIndex({
zIndex: 15,
child: EnemyWidget()
}),
// Particle effect layer
ZIndex({
zIndex: 20,
child: ParticleEffectWidget()
}),
// UI layer (frontmost)
ZIndex({
zIndex: 100,
child: GameUIWidget()
})
]
});
Best Practices
- Performance: Excessive ZIndex usage can impact rendering performance
- Complexity: Nested stacking contexts can produce unexpected results
- Debugging: When z-index doesn’t work as expected, check the stacking context hierarchy
- Accessibility: Visual order and focus order may differ, so consider accessibility
- Consistency: Maintain a consistent z-index value system throughout your project
Related Widgets
- Stack: When managing spatial positioning of widgets
- Positioned: When specifying absolute positions within Stack
- Overlay: When you need app-wide overlays
- Transform: When expressing depth with 3D transformations