flexiblebox 1.0.3
flexiblebox: ^1.0.3 copied to clipboard
Complete CSS Flexbox implementation for Flutter. Features flex grow/shrink, alignment, wrapping, RTL support, absolute positioning, and scrolling.
FlexibleBox #
A Flutter package that brings CSS Flexbox layout capabilities to your Flutter applications. Create responsive, flexible layouts with ease using familiar flexbox concepts.
Features #
- Complete Flexbox Implementation: Full CSS flexbox specification support
- Flexible Sizing: Support for flex grow, shrink, and basis properties
- Advanced Alignment: Cross-axis and main-axis alignment options
- Wrapping Support: Multi-line flex layouts with wrap and wrap-reverse
- RTL Support: Right-to-left language support
- Absolute Positioning: Position items absolutely within flex containers
- Scrolling: Built-in scrollable flex containers with 2D scrolling support
- Sticky Positioning: Sticky items within flex layouts (edge or padded edge)
- Paint Order Control: Explicit z-order for overlapping elements with shadows
- Layout Data Builders: Build dynamic UI based on scroll position, overflow, and viewport data
- Position Type Control: Relative positioning for flex participation or absolute positioning
- Convenience Widgets: RowBox and ColumnBox for common use cases
- Custom Spacing: Flexible spacing and padding systems
Installation #
Add the package using Flutter CLI:
flutter pub add flexiblebox
Demo #
View the interactive demo: https://sunarya-thito.github.io/flexbox
Quick Start #
Import the package:
import 'package:flexiblebox/flexiblebox_flutter.dart';
import 'package:flexiblebox/flexiblebox_extensions.dart'; // For convenient extension methods
Create a simple flex layout:
FlexBox(
direction: FlexDirection.row,
rowGap: 8.0.spacing,
children: [
FlexItem(
flexGrow: 1.0,
child: Container(
color: Colors.red,
height: 100,
child: Center(child: Text('Flexible Item')),
),
),
FlexItem(
width: 100.0.size,
child: Container(
color: Colors.blue,
height: 100,
child: Center(child: Text('Fixed Width')),
),
),
],
)
Why FlexibleBox? #
FlexibleBox provides a different approach to layout compared to Flutter's built-in widgets, offering several advantages for complex, responsive layouts:
CSS Flexbox Specification Compliance #
FlexibleBox implements the complete CSS Flexbox specification, providing familiar layout behavior for developers coming from web development. This includes:
- True flex-basis calculation: Proper handling of flex grow/shrink with min/max constraints
- Multi-line wrapping: Full control over line breaking and line alignment
- Advanced spacing: Separate row and column gaps with space-between, space-around, and space-evenly
Different Layout Philosophy #
FlexibleBox uses a fundamentally different layout approach compared to Flutter's native constraint-based system:
Flutter's Layout System (BoxConstraints)
Flutter uses a "constraints go down, sizes go up" protocol:
- Parent passes
BoxConstraints(min/max width and height) to children - Each child decides its size within those constraints
- Children return their chosen size to the parent
- Parent uses returned sizes to position children
This is a single-pass, local decision system where:
- Each widget only knows about constraints from its immediate parent
- Children make sizing decisions independently
- Parent positions children after all sizes are known
- No child can know the final sizes of its siblings
Example with Flutter's Row:
Row(
children: [
Expanded(child: Container()), // Gets remaining space
Container(width: 100), // Fixed 100px
],
)
The Expanded child doesn't know the other child's size during layout -
Flutter's Row manages the space distribution after collecting all sizes.
FlexibleBox's Layout System
FlexibleBox implements a multi-pass CSS Flexbox algorithm:
- First Pass - Measurement: Layout all children with loose constraints to determine their intrinsic/preferred sizes (flex-basis)
- Second Pass - Flex Resolution: Calculate available space and distribute it among flex items using flex-grow and flex-shrink factors, respecting min/max constraints
- Third Pass - Cross-Axis Sizing: Determine cross-axis sizes based on alignment and stretch behavior
- Fourth Pass - Positioning: Place all children at their final positions with knowledge of all sizes and spacing
This is a multi-pass, global optimization system where:
- The container knows all children's sizes before making final decisions
- Space is distributed according to flex factors after measuring all children
- Children can be sized relative to viewport, content, or sibling dimensions
- Proper handling of complex scenarios with multiple constraints
- Children receive tight constraints during layout: Because the parent has already calculated the exact size each child should be, it passes tight constraints (min == max) to children during the final layout pass. This is different from Flutter's typical loose constraints and ensures precise control over final dimensions.
- Native 2D scrolling: FlexibleBox supports simultaneous horizontal and
vertical scrolling without nested scroll views. The included
Scrollbarswidget provides unified visual feedback for both scroll directions.
Example with FlexibleBox:
FlexBox(
direction: FlexDirection.row,
children: [
FlexItem(
flexGrow: 1,
minWidth: 100.size,
child: Container(),
),
FlexItem(
width: 200.size,
flexShrink: 2,
child: Container(),
),
],
)
FlexibleBox measures both children first, calculates total space needed (300px), then distributes available space or shrinkage proportionally according to flex factors while respecting the minWidth constraint - just like CSS Flexbox.
Key Technical Differences
| Aspect | Flutter (Row/Column) | FlexibleBox |
|---|---|---|
| Layout passes | Single pass | Multi-pass (measure → flex → align → position) |
| Size constraints | BoxConstraints (min/max) | SizeUnit (fixed, viewport-relative, content-based) |
| Flex algorithm | Simplified flex with MainAxisSize/CrossAxisAlignment | Full CSS Flexbox specification |
| Space distribution | Basic Expanded/Flexible widgets | Precise flex-grow/flex-shrink with proper constraint handling |
| Sibling awareness | No knowledge of sibling sizes | Full knowledge of all children after measurement |
| Child constraints | Typically loose constraints | Tight constraints (min == max) during final layout |
| Wrapping | No native support in Row/Column | Multi-line with line alignment (align-content) |
| 2D Scrolling | Requires nested scroll views | Native support with unified scrollbars |
| Overflow | Handled by parent constraints | Explicit overflow modes with viewport positioning |
This multi-pass approach allows FlexibleBox to handle scenarios that are difficult or impossible with Flutter's single-pass system, such as distributing space proportionally among items with complex size constraints, or positioning elements relative to total content size.
Advanced Features Comparison
FlexibleBox provides sophisticated sizing, positioning, spacing, and alignment options that go far beyond Flutter's native capabilities:
| Feature Category | Flutter (Row/Column/Stack) | FlexibleBox |
|---|---|---|
| Sizing Options | • Fixed sizes (width/height) • BoxConstraints (min/max) • Expanded/Flexible |
• Fixed sizes (SizeUnit.fixed) • Viewport-relative (0.5.relativeSize for 50%) • Content-relative sizing • Min/max per item • Flex grow/shrink with proper constraint handling |
| Positioning | • Stack with Positioned (absolute only) • Sticky via Slivers (complex, limited contexts) • No relative positioning context |
• Absolute positioning (PositionType.none) • Relative positioning context (PositionType.relative) • Sticky positioning (top/left/bottom/right offsets) • Scroll-aware positioning |
| Spacing | • MainAxisAlignment (limited distribution) • SizedBox for gaps • No native gap support |
• Row and column gaps (native) • space-between (even distribution) • space-around (half space at edges) • space-evenly (equal space everywhere) • Flexible padding with viewport units |
| Alignment | • MainAxisAlignment (5 options) • CrossAxisAlignment (5 options) • No multi-line alignment • No align-content |
• justifyContent (main-axis, 7 options) • alignItems (cross-axis, 6 options) • alignSelf (per-item override) • alignContent (multi-line, 7 options) • Stretch alignment • Full CSS Flexbox spec |
| Additional Features | • Fixed paint order (tree order) • No layout data access • Hit testing limited to parent bounds |
• paintOrder (explicit z-ordering) • Layout data builders (scroll, overflow, viewport info) • Hit testing respects actual painted bounds • Overflow bounds detection |
Key Advantages:
- Viewport-Relative Sizing: Use
0.5.relativeSizeor0.3.relativeSizeto size elements relative to the viewport (50% or 30%), eliminating the need forLayoutBuilderandMediaQueryboilerplate - True Sticky Positioning: Elements that scroll normally until reaching an edge, then stick (not possible with Row/Column, requires complex Sliver setup in Flutter)
- Space Distribution Algorithms: Proper
space-between,space-around, andspace-evenlythat work with wrapping layouts (Flutter's MainAxisAlignment is limited) - Per-Item Constraints: Each FlexItem can have its own min/max width/height that integrates with the flex algorithm (Flutter requires manual constraint management)
- Multi-Line Alignment: Control how wrapped lines distribute within the
container with
alignContent(no equivalent in Flutter's Wrap) - Paint Order Control: Override natural paint order for proper shadow and overlay rendering (impossible in Flutter without restructuring the widget tree)
- Layout-Aware Building: Access scroll position, overflow bounds, and viewport data to build dynamic, responsive UI (limited to basic constraints in Flutter's LayoutBuilder)
Flutter Layout Limitations #
Flutter's constraint-based system has several inherent limitations that FlexibleBox addresses:
1. No Sibling Size Awareness
In Flutter's single-pass system, children cannot know their siblings' sizes during layout. This makes certain layouts challenging:
Problem:
// Trying to make items equal to the tallest item
Column(
children: [
Container(height: 100), // Unknown to siblings
Container(height: 150), // Unknown to siblings
Container(height: 120), // Unknown to siblings
],
)
You need workarounds like IntrinsicHeight or manual state management.
FlexibleBox Solution:
FlexBox(
direction: FlexDirection.column,
alignItems: BoxAlignmentGeometry.stretch, // All items match tallest
children: [
FlexItem(child: Container()),
FlexItem(child: Container()),
FlexItem(child: Container()),
],
)
FlexibleBox knows all children's sizes after measurement and can apply stretch alignment.
2. Complex Flex Distribution
Flutter's Expanded and Flexible widgets handle basic flex scenarios, but
struggle with complex constraints:
Problem:
Row(
children: [
Flexible(
flex: 2,
child: Container(
constraints: BoxConstraints(minWidth: 200, maxWidth: 300),
// Flutter may not respect these constraints optimally
),
),
Flexible(flex: 1, child: Container()),
],
)
Flutter's flex resolution doesn't follow CSS Flexbox rules precisely when constraints conflict.
FlexibleBox Solution:
FlexBox(
direction: FlexDirection.row,
children: [
FlexItem(
flexGrow: 2,
flexShrink: 2,
minWidth: 200.size,
maxWidth: 300.size,
child: Container(),
),
FlexItem(flexGrow: 1, flexShrink: 1, child: Container()),
],
)
FlexibleBox implements the full CSS Flexbox algorithm with proper constraint handling and shrink factor calculations.
3. Pointer Events Outside Parent Bounds
This is a critical limitation: Flutter does not deliver pointer events (taps, drags, hovers) to child widgets that are positioned outside their parent's paint bounds.
Problem with Flutter's Stack:
Container(
width: 200,
height: 200,
color: Colors.blue,
child: Stack(
clipBehavior: Clip.none, // Allows visual overflow
children: [
Positioned(
left: -50, // 50px outside parent bounds
top: 100,
child: ElevatedButton(
onPressed: () => print('Clicked!'),
child: Text('Button'),
),
),
],
),
)
The button is visible (with Clip.none), but the left 50px cannot be
clicked because it's outside the parent's 200px width. Flutter's hit testing
only considers the parent's bounds, not the child's actual painted area.
FlexibleBox Solution:
Container(
width: 200,
height: 200,
child: FlexBox(
clipContent: false, // Allows visual and interactive overflow
children: [
AbsoluteItem(
left: (-50).position, // 50px outside parent bounds
top: 100.position,
child: ElevatedButton(
onPressed: () => print('Clicked!'),
child: Text('Button'),
),
),
],
),
)
FlexibleBox respects the child's actual painted bounds for hit testing, meaning:
- If
clipContent: false, the entire button (including the part outside parent bounds) is clickable - If
clipContent: true, hit testing is clipped to parent bounds (like Flutter's default) - You have explicit control over both visual clipping and interaction clipping
Important: Standard Flutter widgets will block pointer events outside their bounds, even when wrapping FlexibleBox. To preserve FlexibleBox's extended hit testing behavior, avoid wrapping it with non-FlexibleBox widgets.
This breaks extended hit testing:
Container(
// this Container blocks pointer events outside its bounds,
// so the left 50px of the button will not be clickable
width: 200,
height: 200,
color: Colors.grey,
child: FlexBox(
clipContent: false,
children: [
AbsoluteItem(
left: (-50).position, // 50px outside Container bounds
top: 100.position,
child: ElevatedButton(
onPressed: () => print('Clicked!'),
child: Text('Button'),
),
),
],
),
)
The Container's 200×200 bounds will block pointer events to the left 50px of the button, even though FlexBox allows the button to render outside.
This preserves extended hit testing:
Container(
// this Container blocks pointer events outside its bounds,
width: 200,
height: 200,
color: Colors.grey,
child: Stack(
// Stack is 200x200, and also blocks pointer events outside its bounds,
// but Stack shrinks the FlexBox so it fits within Container,
// making it able to receive pointer events outside 200x200 area
// although it is now a little bit smaller visually
children: [
Positioned(
top: 0,
left: 50,
right: 0,
bottom: 0,
child: FlexBox(
clipContent: false,
children: [
AbsoluteItem(
left: (-50).position,
top: 100.position,
child: ElevatedButton(
onPressed: () => print('Clicked!'),
child: Text('Button'),
),
),
],
),
),
],
),
)
By using AbsoluteItem for the background instead of wrapping FlexBox with Container, pointer events work correctly for elements outside the 200×200 area.
This is crucial for:
- Dropdown menus that extend beyond their trigger button
- Tooltips that overflow their parent container
- Floating action buttons positioned at container edges
- Context menus and popovers
- Any UI where interactive elements need to overflow their layout bounds
4. Viewport-Relative Sizing
Flutter's constraints are always relative to the immediate parent, making viewport-relative sizing cumbersome:
Problem:
// Need LayoutBuilder and manual calculations
LayoutBuilder(
builder: (context, constraints) {
final screenWidth = MediaQuery.of(context).size.width;
return Container(
width: screenWidth * 0.5, // 50% of screen width
child: Text('Half screen width'),
);
},
)
Note: Using LayoutBuilder prevents Flutter from computing intrinsic sizes
for this widget subtree. Widgets like IntrinsicWidth, IntrinsicHeight, or
any parent that queries child intrinsic dimensions will not work correctly with
LayoutBuilder in the path, because builders cannot provide intrinsic
measurements without actual constraints.
FlexibleBox Solution:
FlexBox(
children: [
FlexItem(
width: 0.5.relativeSize, // 50% of viewport directly
child: Text('Half screen width'),
),
],
)
FlexibleBox has built-in viewport awareness with no extra widgets needed.
5. Two-Dimensional Scrolling
Flutter's scrolling widgets (SingleChildScrollView, ListView,
CustomScrollView) only support single-axis scrolling. For 2D scrolling, you
must nest scroll views, which has several issues:
Problem with Nested ScrollViews:
// Nested scrolling - problematic
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(
width: 2000,
height: 2000,
// Large content
),
),
)
Issues with this approach:
- Ambiguous gesture handling (which scroll view captures the drag?)
- No unified scrollbar for both directions
- Difficult to coordinate scroll positions
- Cannot use diagonal drag gestures effectively
FlexibleBox Solution:
SizedBox(
width: 400,
height: 300,
child: Scrollbars(
// Unified scrollbar widget supporting both directions
child: FlexBox(
horizontalOverflow: LayoutOverflow.hidden, // Enable horizontal scroll with clipping
verticalOverflow: LayoutOverflow.hidden, // Enable vertical scroll with clipping
children: [
FlexItem(
width: 2000.size, // Wide content
height: 2000.size, // Tall content
child: YourLargeWidget(),
),
],
),
),
)
Advantages:
- Single scroll context: No nested scroll views, cleaner gesture handling
- Diagonal scrolling: Users can drag diagonally to scroll in both directions simultaneously
- Unified scrollbars: The
Scrollbarswidget shows both horizontal and vertical scrollbars automatically based on overflow - Coordinated scrolling: Both axes share the same scroll context and can be controlled programmatically together
Scrollbars Widget Features:
- Automatically shows/hides based on content overflow
- Supports both horizontal and vertical scrolling in the same widget
- Customizable appearance (thumb color, thickness, margins)
- Interactive dragging on scrollbar thumbs
- Smooth fade in/out animations
- Works seamlessly with touch, mouse, and trackpad input
Important Note on Child Building:
Unlike Flutter's ListView, GridView, or other lazy-loading scrolling
widgets, FlexibleBox builds all children upfront, even those that are
outside the visible viewport. When clipContent: true, children outside the
viewport are not painted (not visible), but they are still built and exist in
the widget tree.
This is a fundamental difference in approach:
- Flutter's ListView: Builds children lazily as they scroll into view (optimized for many items)
- FlexibleBox: Builds all children immediately (optimized for complex layouts with known items)
Implications:
- ✅ Best for: Known, finite number of items with complex flex layouts
- ✅ Best for: Content where all items need to participate in flex calculations
- ⚠️ Not ideal for: Very long lists with hundreds or thousands of items
- ⚠️ Not ideal for: Infinite scrolling scenarios where lazy loading is essential
If you have a large number of items (e.g., 100+), consider using Flutter's
ListView or GridView instead, or implement custom lazy loading on top of
FlexibleBox.
This makes FlexibleBox ideal for:
- Data tables or grids with a reasonable number of rows/columns
- Image galleries with a known set of images
- Maps or canvas-like interfaces with fixed content
- Spreadsheet-style layouts with defined dimensions
- Dashboard layouts with multiple panels
- Any content that naturally overflows in both dimensions but has a finite, known size
6. Wrap Widget Limitations
Flutter's Wrap widget provides basic wrapping functionality, but lacks many
flexbox features:
Limitations of Flutter's Wrap:
-
No Flex Growing/Shrinking: Items in
Wrapcannot grow or shrink to fill spaceWrap( children: [ Container(width: 100, child: Text('Fixed')), Container(width: 150, child: Text('Also Fixed')), // No equivalent to flex-grow or flex-shrink ], ) -
No Main-Axis Alignment Per Line:
Wraphasalignmentbut it's global, not per-lineWrap( alignment: WrapAlignment.spaceBetween, // Applied uniformly children: [/* items */], )You cannot have different alignment for different lines or
space-betweenthat respects line breaks. -
No Cross-Axis Line Alignment: No equivalent to CSS Flexbox's
align-contentWrap( runAlignment: WrapAlignment.start, // Limited line alignment // No space-between, space-around, space-evenly for lines ) -
No Size Constraints on Items: Cannot set min/max width/height per item within the wrap
Wrap( children: [ Container( constraints: BoxConstraints(minWidth: 100, maxWidth: 200), // Works, but doesn't interact with wrap algorithm ), ], )
FlexibleBox Solution:
FlexBox(
direction: FlexDirection.row,
wrap: FlexWrap.wrap,
justifyContent: BoxAlignmentBase.spaceBetween, // Per-line space distribution
alignContent: BoxAlignmentContent.spaceEvenly, // Line distribution
alignItems: BoxAlignmentGeometry.stretch, // Stretch alignment
rowGap: 8.spacing,
columnGap: 8.spacing,
children: [
FlexItem(
flexGrow: 1, // Grows to fill space
flexShrink: 1, // Shrinks when constrained
minWidth: 100.size, // Minimum width constraint
maxWidth: 300.size, // Maximum width constraint
child: Text('Flexible'),
),
FlexItem(
flexGrow: 2, // Grows twice as fast
minWidth: 150.size,
child: Text('More flexible'),
),
FlexItem(
width: 120.size, // Fixed width
child: Text('Fixed'),
),
],
)
Key Differences:
| Feature | Flutter Wrap | FlexibleBox (with wrap) |
|---|---|---|
| Flex grow/shrink | ❌ No | ✅ Full flex-grow and flex-shrink support |
| Per-line alignment | ❌ Global only | ✅ justify-content applies per line |
| Line distribution | ❌ Basic runAlignment | ✅ align-content with space-between, space-around, space-evenly |
| Size constraints | ❌ No interaction with layout | ✅ min/max width/height integrated with flex algorithm |
| Gap control | ✅ spacing, runSpacing | ✅ rowGap, columnGap (same capability) |
| Item stretching | ❌ No | ✅ Cross-axis stretch alignment |
| Reverse wrap | ❌ No | ✅ FlexWrap.wrapReverse |
| Max items per line | ❌ No | ✅ maxItemsPerLine property |
| Max lines | ❌ No | ✅ maxLines property |
Example: Responsive Tag List
With Wrap, tags are fixed size:
Wrap(
spacing: 8,
children: [
Chip(label: Text('Flutter')),
Chip(label: Text('Dart')),
Chip(label: Text('Mobile Development')),
],
)
With FlexibleBox, tags can grow to fill lines efficiently:
FlexBox(
direction: FlexDirection.row,
wrap: FlexWrap.wrap,
justifyContent: BoxAlignmentBase.spaceBetween,
rowGap: 8.spacing,
columnGap: 8.spacing,
children: [
FlexItem(
flexGrow: 1,
minWidth: 80.size,
child: Chip(label: Text('Flutter')),
),
FlexItem(
flexGrow: 1,
minWidth: 80.size,
child: Chip(label: Text('Dart')),
),
FlexItem(
flexGrow: 1,
minWidth: 80.size,
child: Chip(label: Text('Mobile Development')),
),
],
)
The FlexibleBox version ensures tags grow proportionally to fill each line while respecting minimum sizes, creating a more polished responsive layout.
7. No Control Over Paint Order
Flutter paints children in the order they appear in the widget tree, which can cause visual issues when children overlap:
Problem with Flutter:
Stack(
children: [
Positioned(
left: 50,
top: 50,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.red,
boxShadow: [
BoxShadow(
color: Colors.black45,
blurRadius: 20,
spreadRadius: 5,
),
],
),
),
),
Positioned(
left: 100,
top: 100,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
],
)
In this example, the red container has a shadow. The shadow will paint above the previous sibling (nothing in this case) but will paint below the blue container (next sibling) due to Flutter's natural paint order. This creates an inconsistent visual appearance where the shadow is partially cut off.
FlexibleBox Solution:
FlexBox(
children: [
AbsoluteItem(
left: 50.position,
top: 50.position,
paintOrder: 2, // Paint on top
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.red,
boxShadow: [
BoxShadow(
color: Colors.black45,
blurRadius: 20,
spreadRadius: 5,
),
],
),
),
),
AbsoluteItem(
left: 100.position,
top: 100.position,
paintOrder: 1, // Paint below
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
],
)
The paintOrder property allows explicit control over the drawing order:
- Optional property: If not set, items follow Flutter's natural paint order (tree order)
- Lower values paint first (appear behind)
- Higher values paint later (appear in front)
- Items with the same
paintOrdervalue use tree order as a tiebreaker - This ensures shadows and overlapping elements render correctly regardless of tree position
Use Cases:
- Ensuring shadows always render above or below specific elements
- Creating layered UI with explicit z-order control
- Overlapping cards or panels with consistent stacking
- Tooltips and popovers that should always appear on top
8. Sticky Positioning Not Natively Supported
Flutter has no built-in support for sticky positioning (elements that behave like relative positioning until scrolling, then become fixed within their container).
Note: Flutter does provide sticky-like behavior through SliverAppBar and
SliverPersistentHeader within CustomScrollView, but this requires using the
sliver architecture which is complex and limited to specific scrolling contexts.
It's not a general-purpose sticky positioning solution like CSS's
position: sticky.
FlexibleBox Solution:
FlexBox(
direction: FlexDirection.column,
verticalOverflow: LayoutOverflow.scroll,
children: [
FlexItem(
top: 0.position, // Stick to the top edge
child: Container(
height: 50,
color: Colors.blue,
child: Text('Sticky Header'),
),
),
FlexItem(
height: 1000.size,
child: Text('Long scrollable content...'),
),
],
)
When scrolling, the header will scroll normally until it reaches the top edge, then "stick" to that position while the rest of the content scrolls beneath it.
Sticky Positioning Options:
top: Sticks to the top edge with specified offset (e.g.,10.positionfor 10px padding)left: Sticks to the left edgeright: Sticks to the right edgebottom: Sticks to the bottom edge- Can combine multiple edges (e.g.,
top: 0.position, right: 0.position)
Position Type Control:
FlexibleBox items have a position property that controls positioning behavior:
PositionType.relative(default): Item participates in normal flex layout and can use sticky positioning with top/left/bottom/right offsetsPositionType.none: Item is removed from flex flow and positioned absolutely (equivalent to CSSposition: absolute)
Use Cases:
- Sticky table headers that remain visible while scrolling
- Persistent navigation elements within scrollable sections
- Floating action buttons that stick to container edges
- Sidebar elements that follow scroll until reaching boundaries
9. No Layout Data for Dynamic UI
Flutter's builder widgets (like LayoutBuilder) provide basic constraints, but
don't expose detailed layout information needed for advanced dynamic UIs.
FlexibleBox Solution - Layout Data Builders:
FlexBox(
horizontalOverflow: LayoutOverflow.scroll,
verticalOverflow: LayoutOverflow.scroll,
children: [
FlexItem.builder(
width: 2000.size,
height: 2000.size,
builder: (context, box) {
// Access comprehensive layout data
return Container(
color: box.overflowBounds.left < 0
? Colors.red.withOpacity(0.3)
: Colors.transparent,
child: Column(
children: [
Text('Child Size: ${box.width.toStringAsFixed(0)} x ${box.height.toStringAsFixed(0)}'),
Text('Child Offset: (${box.offsetX.toStringAsFixed(0)}, ${box.offsetY.toStringAsFixed(0)})'),
Text('Scroll Position: (${box.scrollX.toStringAsFixed(0)}, ${box.scrollY.toStringAsFixed(0)})'),
Text('Max Scroll: (${box.maxScrollX.toStringAsFixed(0)}, ${box.maxScrollY.toStringAsFixed(0)})'),
Text('Viewport: ${box.viewportWidth.toStringAsFixed(0)} x ${box.viewportHeight.toStringAsFixed(0)}'),
Text('Content: ${box.contentWidth.toStringAsFixed(0)} x ${box.contentHeight.toStringAsFixed(0)}'),
Text('Overflow Left: ${box.overflowBounds.left.toStringAsFixed(0)}'),
Text('Overflow Right: ${box.overflowBounds.right.toStringAsFixed(0)}'),
Text('Overflow Top: ${box.overflowBounds.top.toStringAsFixed(0)}'),
Text('Overflow Bottom: ${box.overflowBounds.bottom.toStringAsFixed(0)}'),
],
),
);
},
),
],
)
Available Layout Data (LayoutBox):
size/width/height: The child's actual rendered sizeoffset/offsetX/offsetY: The child's position relative to the parentscrollX/scrollY: Current scroll offsetmaxScrollX/maxScrollY: Maximum scrollable distancecontentSize/contentWidth/contentHeight: Total size of scrollable contentviewportSize/viewportWidth/viewportHeight: Visible viewport sizeoverflowBounds: How many pixels the child is outside the parent viewportleft: Pixels overflowing left (negative if visible)right: Pixels overflowing right (positive if outside)top: Pixels overflowing top (negative if visible)bottom: Pixels overflowing bottom (positive if outside)
childBounds: The child's bounding rectangleviewportBounds: The viewport's bounding rectanglecontentBounds: The content's bounding rectangle (adjusted for scroll)horizontalUserScrollDirection: Current horizontal scroll directionverticalUserScrollDirection: Current vertical scroll direction
Builder Variants:
FlexItem.builder: Flex item with layout dataAbsoluteItem.builder: Absolutely positioned item with layout dataLayoutBoxBuilder: Access layout data anywhere in the tree
Use Cases:
- Highlight elements when they're partially scrolled out of view
- Show/hide UI elements based on scroll position
- Display scroll progress indicators
- Fade in/out content based on visibility
- Implement parallax effects based on scroll offset
- Create interactive scroll-based animations
- Build custom scrollbar indicators
- Implement virtual scrolling optimizations based on overflow data
When to Use FlexibleBox vs. Flutter Widgets #
Use FlexibleBox when you need:
- Precise CSS Flexbox behavior for consistency with web layouts
- Complex responsive layouts with viewport-relative sizing
- Multi-line wrapping with sophisticated line alignment
- Absolute or sticky positioning within scrollable containers
- Space distribution algorithms (space-between, space-around, space-evenly)
- Interactive elements that need to overflow parent bounds (dropdowns, tooltips)
- Two-dimensional scrolling (horizontal and vertical simultaneously)
- Large content that overflows in multiple directions (tables, grids, canvases)
- Control over paint order for overlapping elements with shadows or effects
- Sticky headers or footers within scrollable content
- Dynamic UI that adapts based on scroll position or overflow state
- Layout data for scroll-based animations or visibility effects
Use Flutter's Row/Column when:
- You need simple, straightforward layouts
- You're using Flutter's standard constraint system throughout your app
- You don't need CSS Flexbox-specific features
Core Components #
FlexBox #
The main flex container widget that implements the flexbox layout algorithm.
FlexBox(
direction: FlexDirection.row, // Layout direction
wrap: FlexWrap.wrap, // Wrapping behavior
alignItems: BoxAlignmentGeometry.start, // Cross-axis alignment
alignContent: BoxAlignmentContent.start, // Content alignment (for wrapping)
justifyContent: BoxAlignmentBase.start, // Main-axis distribution
rowGap: 8.0.spacing, // Spacing between items horizontally
columnGap: 8.0.spacing, // Spacing between items vertically
children: [ // Flex items
FlexItem(child: Text('Item 1')),
FlexItem(child: Text('Item 2')),
],
)
FlexItem #
Configures individual children within a FlexBox.
FlexItem(
flexGrow: 1.0, // Growth factor
flexShrink: 0.0, // Shrink factor
width: 200.0.size, // Preferred width
height: 100.0.size, // Preferred height
alignSelf: BoxAlignmentGeometry.start, // Individual alignment
paintOrder: 0, // Paint order (lower values paint first)
position: PositionType.relative, // Positioning context (relative or none)
top: 10.0.position, // Sticky positioning offset (for scrollable containers)
child: Container(
color: Colors.blue,
child: Center(child: Text('Flex Item')),
),
)
Key Properties:
flexGrow/flexShrink: Control how the item grows/shrinks to fill spacewidth/height: Preferred size (can use viewport-relative units)minWidth/maxWidth/minHeight/maxHeight: Size constraintsalignSelf: Override container'salignItemsfor this itempaintOrder: Control the drawing order (lower values paint first/behind)position: Set toPositionType.relative(default) to participate in flex layout, orPositionType.noneto become absolutely positionedtop/left/bottom/right: Used for sticky positioning within scrollable containers (whenpositionisrelative)
Builder Variant:
FlexItem.builder provides access to layout data for building dynamic UI:
FlexItem.builder(
width: 200.size,
height: 100.size,
builder: (context, box) {
// Access layout information
final isScrolledOut = box.overflowBounds.top < -50;
return Container(
color: isScrolledOut ? Colors.red : Colors.blue,
child: Text('Scrolled: ${box.scrollY.toInt()}px'),
);
},
)
The LayoutBox provides comprehensive layout data including scroll position,
overflow bounds, viewport size, content size, and more. See "No Layout Data for
Dynamic UI" section above for details.
AbsoluteItem #
For absolutely positioned children within a FlexBox. These items are removed
from the normal flex flow and positioned relative to the viewport (or nearest
ancestor with PositionType.relative).
AbsoluteItem(
left: 10.0.position, // Left offset from viewport edge
top: 20.0.position, // Top offset from viewport edge
right: 10.0.position, // Right offset from viewport edge
bottom: 20.0.position, // Bottom offset from viewport edge
width: 100.0.size, // Fixed width
height: 50.0.size, // Fixed height
paintOrder: 10, // Paint on top of other elements
child: Container(
color: Colors.green,
child: Center(child: Text('Absolute')),
),
)
Key Properties:
left/top/right/bottom: Position offsets (can usePositionUnit.scrollOffsetfor scroll-relative positioning)width/height: Fixed sizepaintOrder: Control the drawing order (useful for overlapping elements with shadows)
Builder Variant:
AbsoluteItem.builder provides access to layout data:
AbsoluteItem.builder(
right: 20.position,
bottom: 20.position,
builder: (context, box) {
// Show/hide based on scroll position
final shouldShow = box.scrollY > 100;
return AnimatedOpacity(
opacity: shouldShow ? 1.0 : 0.0,
duration: Duration(milliseconds: 200),
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.arrow_upward),
),
);
},
)
Sticky FlexItem #
For sticky positioning within scrollable flex containers, use the top, left,
bottom, and right properties on FlexItem. These create sticky elements that
remain fixed relative to the viewport during scrolling. You must also set
verticalOverflow or horizontalOverflow to LayoutOverflow.hidden or
LayoutOverflow.scroll on the FlexBox.
// Wrap FlexBox in a SizedBox to constrain its size and enable scrolling
SizedBox(
height: 300.0, // Fixed height to enable scrolling
child: FlexBox(
direction: FlexDirection.column,
children: [
FlexItem(
height: 100.0.size,
child: Container(
color: Colors.blue,
child: Center(child: Text('Normal Item')),
),
),
// Sticky header that sticks to the top
FlexItem(
top: 0.0.position, // Stick to top edge
left: 0.0.position, // Stick to left edge
right: 0.0.position, // Stick to right edge
height: 50.0.size,
child: Container(
color: Colors.red,
child: Center(child: Text('Sticky Header')),
),
),
FlexItem(
height: 200.0.size,
child: Container(
color: Colors.green,
child: Center(child: Text('Content')),
),
),
FlexItem(
height: 200.0.size,
child: Container(
color: Colors.yellow,
child: Center(child: Text('More Content')),
),
),
],
),
)
Scrollable AbsoluteItem #
AbsoluteItem positions elements relative to the viewport bounds, not content
bounds. This means bottom: PositionUnit.fixed(10.0) positions the element 10
units from the bottom of the viewport, not the content.
For scroll-aware positioning, use PositionUnit.scrollOffset to create elements
that move with scroll:
// Wrap FlexBox in a SizedBox to make it scrollable
SizedBox(
height: 400.0, // Fixed height to enable scrolling
child: FlexBox(
direction: FlexDirection.column,
children: [
// Regular content
FlexItem(
height: 200.0.size,
child: Container(
color: Colors.blue,
child: Center(child: Text('Content 1')),
),
),
FlexItem(
height: 200.0.size,
child: Container(
color: Colors.green,
child: Center(child: Text('Content 2')),
),
),
FlexItem(
height: 200.0.size,
child: Container(
color: Colors.yellow,
child: Center(child: Text('Content 3')),
),
),
// Absolute positioned element that stays fixed in viewport
AbsoluteItem(
right: 20.0.position, // 20px from right viewport edge
bottom: 20.0.position, // 20px from bottom viewport edge
width: 60.0.size,
height: 60.0.size,
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
),
// Element that follows scroll position
AbsoluteItem(
left: 10.0.position,
top: PositionUnit.scrollOffset + 10.0.position, // Moves with scroll
width: 40.0.size,
height: 40.0.size,
child: Container(
color: Colors.red,
child: Center(child: Text('Scroll\nIndicator')),
),
),
],
),
)
Scrollbars #
The Scrollbars widget provides scrollbar UI for scrollable FlexBox containers.
It automatically shows/hides scrollbars based on content overflow and supports
both vertical and horizontal scrolling.
Basic Usage
SizedBox(
width: 400,
height: 300,
child: Scrollbars(
child: FlexBox(
direction: FlexDirection.column,
children: [
for (int i = 0; i < 20; i++)
FlexItem(
height: 80.size,
child: Container(
margin: EdgeInsets.all(8),
color: Colors.primaries[i % Colors.primaries.length],
child: Center(child: Text('Item $i')),
),
),
],
),
),
)
Customizing Scrollbar Appearance
You can customize the scrollbar appearance using DefaultScrollbar:
Scrollbars(
verticalScrollbar: DefaultScrollbar(
thumbDecoration: BoxDecoration(
color: Colors.blue.withOpacity(0.5),
borderRadius: BorderRadius.circular(4),
),
thumbActiveDecoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(4),
),
minThumbLength: 48.0,
margin: EdgeInsets.all(2),
),
horizontalScrollbar: DefaultScrollbar(
thumbDecoration: BoxDecoration(
color: Colors.green.withOpacity(0.5),
borderRadius: BorderRadius.circular(4),
),
),
verticalScrollbarThickness: 12,
horizontalScrollbarThickness: 12,
child: FlexBox(
children: [
// Your content here
],
),
)
Convenience Widgets #
RowBox
Horizontal flex layout (equivalent to FlexBox(direction: FlexDirection.row)):
RowBox(
alignItems: BoxAlignmentGeometry.center,
justifyContent: BoxAlignmentBase.spaceBetween,
rowGap: 8.0.spacing,
children: [
FlexItem(child: Text('Left')),
FlexItem(child: Text('Center')),
FlexItem(child: Text('Right')),
],
)
ColumnBox
Vertical flex layout (equivalent to FlexBox(direction: FlexDirection.column)):
ColumnBox(
alignItems: BoxAlignmentGeometry.stretch,
justifyContent: BoxAlignmentBase.start,
columnGap: 12.0.spacing,
children: [
FlexItem(child: Text('Top')),
FlexItem(child: Text('Middle')),
FlexItem(child: Text('Bottom')),
],
)
Extension Methods #
The package provides convenient extension methods on int and double for
creating units:
import 'package:flexiblebox/flexiblebox_extensions.dart';
// Size units
100.size // SizeUnit.fixed(100.0)
50.0.size // SizeUnit.fixed(50.0)
0.5.relativeSize // SizeUnit.viewportSize * 0.5 (50% of viewport)
// Position units
10.position // PositionUnit.fixed(10.0)
20.0.position // PositionUnit.fixed(20.0)
0.25.relativePosition // PositionUnit.viewportSize * 0.25 (25% of viewport)
// Spacing units
8.spacing // SpacingUnit.fixed(8.0)
16.0.spacing // SpacingUnit.fixed(16.0)
0.1.relativeSpacing // SpacingUnit.viewportSize * 0.1 (10% of viewport)
// Percentage helper
50.percent // 0.5 (useful for calculations)
These extensions make the code more concise and readable:
// Without extensions
FlexItem(
width: SizeUnit.fixed(100.0),
height: SizeUnit.fixed(50.0),
child: MyWidget(),
)
// With extensions
FlexItem(
width: 100.size,
height: 50.size,
child: MyWidget(),
)
Widget Extensions #
The WidgetExtension provides a fluent, chainable API for configuring widget
properties in flexbox layouts. Instead of wrapping widgets in FlexItem or
AbsoluteItem, you can use extension methods directly on any widget.
import 'package:flexiblebox/flexiblebox_extensions.dart';
// Fluent API for widget configuration
Container(color: Colors.blue)
.width(100.size)
.height(50.size)
.flexGrow(1)
.selfAligned(BoxAlignment.center);
// Equivalent to:
FlexItem(
width: 100.size,
height: 50.size,
flexGrow: 1,
alignSelf: BoxAlignment.center,
child: Container(color: Colors.blue),
);
Available Extension Methods
Sizing:
.width(SizeUnit)- Sets widget width.height(SizeUnit)- Sets widget height.sized({SizeUnit? width, SizeUnit? height})- Sets both dimensions.minWidth(SizeUnit)/.maxWidth(SizeUnit)- Width constraints.minHeight(SizeUnit)/.maxHeight(SizeUnit)- Height constraints.minSized({...})/.maxSized({...})- Combined constraints.constrained({...})- Comprehensive size constraints.aspectRatio(double)- Maintains aspect ratio
Flex Behavior:
.flexGrow(double)- Sets flex grow factor.flexShrink(double)- Sets flex shrink factor
Positioning:
.top(PositionUnit)/.left(PositionUnit)- Edge offsets.bottom(PositionUnit)/.right(PositionUnit)- Edge offsets.positioned({...})- Multiple edge offsets.position(PositionType)- Position type (absolute/relative)
Alignment:
.selfAligned(BoxAlignmentGeometry)- Individual alignment
Layout Control:
.paintOrder(int)- Z-index stacking order.key(Key)/.id(Object)- Key assignment.asFlexItem- Explicitly wrap as FlexItem.asAbsoluteItem- Explicitly wrap as AbsoluteItem
Chaining Example
Extension methods can be chained for a clean, declarative syntax:
FlexBox(
direction: FlexDirection.row,
children: [
// Fixed-size sidebar
Container(color: Colors.grey)
.width(200.size)
.height(100.percent.relativeSize),
// Flexible content area
Container(color: Colors.white)
.flexGrow(1)
.minWidth(300.size)
.selfAligned(BoxAlignment.stretch),
// Absolutely positioned overlay
Icon(Icons.close)
.asAbsoluteItem
.positioned(top: 10.position, right: 10.position)
.paintOrder(100),
],
)
Layout Properties #
Direction #
FlexDirection.row: Left to right (default)FlexDirection.rowReverse: Right to leftFlexDirection.column: Top to bottomFlexDirection.columnReverse: Bottom to top
Wrapping #
FlexWrap.none: No wrapping (default)FlexWrap.wrap: Wrap to next lineFlexWrap.wrapReverse: Wrap to previous line
Alignment #
Main Axis (justifyContent)
BoxAlignmentBase.start: Items at the startBoxAlignmentBase.center: Items centeredBoxAlignmentBase.end: Items at the endBoxAlignmentBase.spaceBetween: Space between itemsBoxAlignmentBase.spaceAround: Space around itemsBoxAlignmentBase.spaceEvenly: Equal space distribution
Cross Axis (alignItems)
BoxAlignmentGeometry.start: Items at cross startBoxAlignmentGeometry.center: Items centeredBoxAlignmentGeometry.end: Items at cross endBoxAlignmentGeometry.stretch: Items stretch to fillBoxAlignmentGeometry.baseline: Items aligned by baseline
Content Alignment (alignContent)
BoxAlignmentContent.start: Lines at container startBoxAlignmentContent.center: Lines centered in containerBoxAlignmentContent.end: Lines at container endBoxAlignmentContent.stretch: Lines stretch to fill containerBoxAlignmentContent.spaceBetween: Space between linesBoxAlignmentContent.spaceAround: Space around linesBoxAlignmentContent.spaceEvenly: Equal space distribution
Sizing Units #
SizeUnit
100.sizeorSizeUnit.fixed(double): Fixed size100.0.relativeSizeorSizeUnit.viewportSize * 100.0.size: Viewport-relative sizeSizeUnit.minContent: Minimum content sizeSizeUnit.maxContent: Maximum content sizeSizeUnit.fitContent: Fit content sizeSizeUnit.viewportSize: Viewport size
PositionUnit
10.positionorPositionUnit.fixed(double): Fixed position10.0.relativePositionorPositionUnit.viewportSize * 10.0.position: Viewport-relative positionPositionUnit.zero: Zero position (equivalent to0.position)PositionUnit.viewportSize: Full viewport size along the axisPositionUnit.contentSize: Total content size along the axisPositionUnit.childSize([Object? key]): Size of the positioned child element or another child by its keyPositionUnit.boxOffset: Offset from the box's natural positionPositionUnit.scrollOffset: Current scroll offsetPositionUnit.contentOverflow: Amount content overflows the viewportPositionUnit.contentUnderflow: Amount content underflows the viewportPositionUnit.viewportStartBound: Start boundary of the viewportPositionUnit.viewportEndBound: End boundary of the viewportPositionUnit.cross(PositionUnit): Convert passed PositionUnit to cross axisPositionUnit.constrained({required PositionUnit position, PositionUnit min, PositionUnit max}): Position constrained within min/max bounds
SpacingUnit
8.spacingorSpacingUnit.fixed(double): Fixed spacing8.0.relativeSpacingorSpacingUnit.viewportSize * 8.0.spacing: Viewport-based spacingSpacingUnit.viewportSize: Viewport-based spacing
Math Operations #
All unit types (SizeUnit, SpacingUnit, PositionUnit) support mathematical
operations:
// Basic arithmetic
SizeUnit combinedWidth = 100.size + 50.size;
PositionUnit offset = 200.position - 50.position;
SpacingUnit scaled = 10.spacing * 2.0; // Multiply by scalar
// Complex expressions (equivalent to CSS calc())
SizeUnit halfViewport = SizeUnit.viewportSize * 0.5; // 50% of viewport size
SizeUnit responsiveSize = 100.size + SizeUnit.viewportSize * 0.2; // 100px + 20% viewport
PositionUnit centered = PositionUnit.viewportSize * 0.5 - PositionUnit.childSize * 0.5; // Center child
// Negation
SizeUnit negative = -100.size;
// Constraints
PositionUnit clamped = 150.position.clamp(
min: 0.position,
max: 300.position,
);
API Reference #
For detailed API documentation, visit the API Reference.
Key classes:
Testing #
The package includes comprehensive test coverage. Run tests with:
flutter test
View the interactive demo:
cd demo
flutter run
Contributing #
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License #
This project is licensed under the BSD-3-Clause license - see the LICENSE file for details.
Issues and Feedback #
Found a problem or have a suggestion? We'd love to hear from you!
You can report:
- Bugs: Incorrect behavior, crashes, or unexpected results
- Feature Requests: Missing features or new capabilities you'd like to see
- Documentation Issues: Unclear or incorrect documentation
- Implementation Concerns: Incorrect CSS Flexbox spec implementation
- Limitation Concerns: Performance issues or architectural limitations
- API Improvements: Suggestions for better developer experience
- Questions: Usage questions or clarification requests
When reporting an issue, please include:
- A clear description of the problem or request
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Code samples or screenshots if applicable
- Flutter and package version information
Made with ❤️ for the Flutter community