Flutter Scalify ๐
The Intelligent Scaling Engine for Flutter.
A complete, high-performance responsive system โ not just a sizing tool. Scale your entire UI across Mobile, Web, and Desktop with simple extensions, smart container queries, and zero overhead.
๐ Table of Contents
| Section | Description |
|---|---|
| โก๏ธ Why Scalify? | Full feature checklist |
| ๐ฆ Installation | Setup guide |
| ๐ Quick Start | First integration |
| โ๏ธ ScalifyConfig | Foundation โ design dimensions, breakpoints, performance |
| ๐ API Cheat Sheet | All extensions at a glance |
| ๐ป Complete Example | Full working code |
| ๐ Best Practices | Which extension for which element |
| ๐จ Theme Auto-Scaling | One-line theme scaling |
| ๐ฑ Screen Breakpoints | 6-tier breakpoint reference |
| โ Widgets โ | |
| ๐ Core Layout Widgets | Grid, Flex, Layout, Visibility, Builder |
| ๐ฆ Container Queries | ContainerQuery, AdaptiveContainer |
| ๐งฑ ScalifyBox | Local container scaling |
| ๐งฉ ScalifySection | Split-screen scaling |
| ๐ก๏ธ AppWidthLimiter | Ultra-wide protection |
| ๐ค ResponsiveText | Auto-resize text |
| ๐ ResponsiveSpacing | Design token spacing |
| ๐ ScalifyDebugOverlay | Debug metrics panel |
| ๐ ResponsiveNavigation | Bottom/Rail/Sidebar |
| ๐ ResponsiveWrap | Auto line-wrapping |
| ๐ผ๏ธ ResponsiveImage | Per-screen images |
| ๐ญ AnimatedResponsiveTransition | Smooth layout switching |
| ๐ ResponsiveTable | DataTable โ Cards |
| ๐งฎ ResponsiveConstraints | Per-screen BoxConstraints |
| ๐๏ธ ScalifySliver | SliverAppBar/Header/Persistent |
| โ Reference โ | |
| ๐ Live Resizing | Desktop/Web resize handling |
| ๐ง Engine Internals | Performance deep-dive |
| ๐ Migration Guide | v2.x โ v3.0.0 |
| ๐งช Testing | 312 tests |
โก๏ธ Why Scalify?
| Feature | Scalify |
|---|---|
| O(1) Inline Math (vm:prefer-inline) | โ |
| Container Queries (Rebuild by parent size) | โ |
| 4K/Ultra-Wide Smart Dampening | โ |
| Responsive Grid System (6-Tier) | โ |
| Builder Pattern (Above MaterialApp) | โ |
| InheritedModel (Granular Rebuild) | โ |
| Debounce on Resize (Desktop/Web) | โ |
| Section Scaling (ScalifySection) | โ |
| Local Scaler (ScalifyBox) | โ |
| 6 Screen Types (Watch โ Large Desktop) | โ |
| Theme Auto-Scaling (One line) | โ |
| Percentage Scaling (.pw .hp) | โ |
| Responsive Text (Auto-resize + ShortText) | โ |
| Design Token Spacing (xs โ xxl) | โ |
| Debug Overlay (Draggable, Live Metrics) | โ |
| Adaptive Navigation (Bottom/Rail/Sidebar) | โ |
| Responsive Wrap (Auto line-wrapping) | โ |
| Responsive Image (Per-screen sources) | โ |
| Animated Transitions (Smooth layout switch) | โ |
| Responsive Table (DataTable โ Cards) | โ |
| Responsive Constraints (Per-screen BoxConstraints) | โ |
| Sliver Widgets (AppBar/Header/Persistent) | โ |
Simple .w .h .sp Extensions |
โ |
| Zero External Dependencies | โ |
| 312 Tests Passing | โ |
๐ผ๏ธ Responsive Preview

๐ฆ Installation
dependencies:
flutter_scalify: ^3.1.0
flutter pub get
๐ Quick Start
โ Recommended: Builder Pattern (ScalifyProvider wraps MaterialApp)
This is the best practice for maximum performance. Placing ScalifyProvider above MaterialApp prevents cascading rebuilds when screen size changes.
import 'package:flutter/material.dart';
import 'package:flutter_scalify/flutter_scalify.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ScalifyProvider(
config: const ScalifyConfig(
designWidth: 375, // Your Figma/XD design width
designHeight: 812, // Your Figma/XD design height
),
builder: (context, child) => MaterialApp(
theme: ThemeData.light().scale(context), // ๐จ Auto-scale theme
home: child,
),
child: const HomeScreen(),
);
}
}
Alternative: Child Pattern (ScalifyProvider inside MaterialApp)
MaterialApp(
builder: (context, child) {
return ScalifyProvider(
config: const ScalifyConfig(designWidth: 375, designHeight: 812),
child: child ?? const SizedBox(),
);
},
home: const HomeScreen(),
);
๐ก Tip: The builder pattern is recommended because it puts
ScalifyProvideraboveMaterialApp, so changing window size doesn't rebuildMaterialAppitself.
โ๏ธ ScalifyConfig โ Full Reference
This is the foundation of Scalify. Every developer should understand
ScalifyConfigfirst โ it controls design dimensions, breakpoints, font clamping, scaling bounds, 4K protection, and performance tuning. All other features depend on these settings.
ScalifyConfig(
// โโโ ๐ Design Baseline โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// These values should match your UI design file (e.g., Figma, XD).
// Scalify calculates scale factors by comparing actual screen size
// to these reference dimensions.
designWidth: 375.0, // Your design's reference width in pixels
designHeight: 812.0, // Your design's reference height in pixels
// โโโ ๐ฑ Breakpoints โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Define exact pixel boundaries for each screen type.
// These control when ResponsiveGrid, ResponsiveVisibility,
// ResponsiveNavigation, etc. switch their layout.
watchBreakpoint: 300.0, // < 300px = Watch
mobileBreakpoint: 600.0, // 300โ600px = Mobile
tabletBreakpoint: 900.0, // 600โ900px = Tablet
smallDesktopBreakpoint: 1200.0, // 900โ1200px = Small Desktop
desktopBreakpoint: 1800.0, // 1200โ1800px = Desktop
// // > 1800px = Large Desktop
// โโโ ๐ก Font Control โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// These ensure font sizes remain readable on all devices.
// .fz extension uses these bounds for clamping.
minFontSize: 6.0, // Minimum allowed font size (floor)
maxFontSize: 256.0, // Maximum allowed font size (ceiling)
respectTextScaleFactor: true, // Respect system accessibility text scaling
// โโโ ๐ Scale Bounds โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Prevent extreme scaling that would distort the UI.
// E.g., on a 4K monitor, the scale might be 5.0x โ maxScale caps it.
minScale: 0.5, // UI never scales below 50%
maxScale: 4.0, // UI never scales above 400%
// โโโ ๐ก๏ธ 4K / Ultra-Wide Protection โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// On very wide screens (e.g., 3840px), raw scaling would make text
// enormous. Smart Dampening kicks in above the threshold width.
memoryProtectionThreshold: 1920.0, // Width where dampening starts
highResScaleFactor: 0.65, // Dampening strength (0.0โ1.0)
// 0.0 = full dampening (scale stops growing)
// 1.0 = no dampening (linear scaling continues)
// 0.65 = balanced (recommended)
// โโโ โก Performance Tuning โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// These prevent excessive widget rebuilds during window resizing
// (Desktop/Web). Most apps can use defaults.
debounceWindowMillis: 120, // Wait 120ms after last resize event
rebuildScaleThreshold: 0.01, // Ignore scale changes < 1%
rebuildWidthPxThreshold: 4.0, // Ignore width changes < 4px
enableGranularNotifications: false, // Enable InheritedModel aspect filtering
// โโโ ๐ Orientation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// If true, swaps designWidth/designHeight in landscape orientation.
// Useful for apps that have separate landscape designs.
autoSwapDimensions: false,
// โโโ ๐ง Minimum Window Width โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// If > 0, enables horizontal scrolling when the window is narrower
// than this value. Prevents content from being crushed on tiny windows.
minWidth: 0.0,
// โโโ ๐ท๏ธ Legacy Compatibility โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Only needed when migrating from v1.x with ContainerQuery tier system.
legacyContainerTierMapping: false,
showDeprecationBanner: true, // Shows debug banner when legacy = true
)
๐ What Each Setting Affects
| Setting | What it controls | Default |
|---|---|---|
designWidth / designHeight |
Base reference for .w, .h, .s, .fz etc. |
375 / 812 |
watchBreakpoint โ desktopBreakpoint |
ScreenType classification for all responsive widgets |
See above |
minFontSize / maxFontSize |
.fz clamping range |
6 / 256 |
minScale / maxScale |
Scale factor bounds for all extensions | 0.5 / 4.0 |
memoryProtectionThreshold |
4K dampening activation width | 1920 |
highResScaleFactor |
Dampening strength on ultra-wide | 0.65 |
debounceWindowMillis |
Resize debounce for Desktop/Web | 120 |
rebuildScaleThreshold |
Minimum scale change to trigger rebuild | 0.01 |
rebuildWidthPxThreshold |
Minimum px change to trigger rebuild | 4.0 |
๐ก Most apps only need to set
designWidthanddesignHeight. All other values have smart defaults.
๐ API Cheat Sheet
1. Size & Font
| Extension | Example | Description |
|---|---|---|
.w |
100.w |
Width โ Scales based on screen width ratio |
.h |
50.h |
Height โ Scales based on screen height ratio |
.s |
20.s |
Size โ General scaling (min of width/height) |
.fz |
16.fz |
Font Size โ Scaled + clamped + accessibility |
.iz |
24.iz |
Icon Size โ Proportional icon scaling |
.r |
12.r |
Radius โ Based on min(scaleWidth, scaleHeight) |
.si |
10.si |
Scaled Int โ Rounded integer for native APIs |
.sc |
16.sc |
Scale โ Alias for .s |
.ui |
16.ui |
UI โ Alias for .s |
2. Percentage Scaling
| Extension | Example | Description |
|---|---|---|
.pw |
50.pw |
50% of screen width |
.hp |
25.hp |
25% of screen height |
3. Spacing (SizedBox)
| Extension | Example | Output |
|---|---|---|
.sbh |
20.sbh |
SizedBox(height: 20.h) |
.sbw |
10.sbw |
SizedBox(width: 10.w) |
.sbhw() |
20.sbhw(width: 10) |
SizedBox(height: 20.h, width: 10.w) |
.sbwh() |
10.sbwh(height: 20) |
SizedBox(width: 10.w, height: 20.h) |
4. Padding (EdgeInsets)
| Extension | Example | Output |
|---|---|---|
.p |
16.p |
EdgeInsets.all(16.s) |
.ph |
16.ph |
EdgeInsets.symmetric(horizontal: 16.w) |
.pv |
16.pv |
EdgeInsets.symmetric(vertical: 16.h) |
.pt |
16.pt |
EdgeInsets.only(top: 16.h) |
.pb |
16.pb |
EdgeInsets.only(bottom: 16.h) |
.pl |
16.pl |
EdgeInsets.only(left: 16.w) |
.pr |
16.pr |
EdgeInsets.only(right: 16.w) |
List Shorthand:
[20, 10].p // EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h)
[10, 5, 10, 5].p // EdgeInsets.fromLTRB(10.w, 5.h, 10.w, 5.h)
5. Border Radius
| Extension | Example | Output |
|---|---|---|
.br |
12.br |
BorderRadius.circular(12.r) |
.brt |
12.brt |
Top corners only |
.brb |
12.brb |
Bottom corners only |
.brl |
12.brl |
Left corners only |
.brr |
12.brr |
Right corners only |
6. Context API (for const widgets)
When widgets are inside a const tree and can't use global extensions, use context:
context.w(100) // Width scaling
context.h(50) // Height scaling
context.r(12) // Radius (min of scaleWidth/scaleHeight)
context.sp(16) // Scale factor
context.fz(18) // Font size (clamped + accessibility)
context.iz(24) // Icon size
context.s(10) // General scale
context.pw(50) // 50% of screen width
context.hp(25) // 25% of screen height
๐ป Complete Example
Container(
padding: [20, 10].p, // Symmetric padding
width: 300.w, // Responsive width
height: 200.h, // Responsive height
decoration: BoxDecoration(
color: Colors.white,
borderRadius: 20.brt, // Top border radius
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(0, 4.s), // Scaled offset
blurRadius: 10.s,
)
],
),
child: Column(
children: [
Icon(Icons.star, size: 32.iz), // Scaled icon
16.sbh, // Spacing
Text("Scalify", style: TextStyle(fontSize: 24.fz)), // Scaled font
8.sbh,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Start", style: TextStyle(fontSize: 14.fz)),
8.sbw, // Width spacing
Icon(Icons.arrow_forward, size: 16.iz),
],
)
],
),
)
๐ Best Practices โ Consistent UI
Use the right extension for each element to maintain a consistent UI across all screen sizes:
| Element | Use | Why |
|---|---|---|
| Text / Fonts | .fz |
Scaled + clamped + accessibility |
| Icons | .iz / .s |
Proportional to screen |
| Button height | .s |
โ Never .h โ distorts on wide screens |
| Input field height | .s |
โ Never .h โ text overflows |
| Container width | .w |
Follows screen width |
| Container height | .s |
Stays proportional |
| Horizontal padding | .w |
Follows width |
| Vertical spacing | .h |
Adapts to screen height |
| General spacing | .s |
Balanced proportional |
| Border radius | .r / .br |
Uses min(scaleW, scaleH) |
โ ๏ธ Common Mistake: Using
.hfor button/input heights causes them to shrink on wide screens (where height < width), making text overflow.โ Fix: Use
.sโ it usesmin(scaleWidth, scaleHeight)which stays balanced.
// โ Wrong โ height shrinks on wide screens
SizedBox(height: 48.h, child: ElevatedButton(...))
// โ
Correct โ stays proportional everywhere
SizedBox(height: 48.s, child: ElevatedButton(...))
๐จ Theme Auto-Scaling
Scale your entire app theme with one line โ no need to add .fz to every text widget.
ScalifyProvider(
builder: (context, child) => MaterialApp(
theme: ThemeData.light().scale(context), // โจ One line!
home: child,
),
child: const HomeScreen(),
)
๐ก Automatically skips scaling when
scaleFactor == 1.0for zero overhead.
๐ฑ Screen Breakpoints
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Screen Type โ Width Range โ Enum Value โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Watch โ < 300px โ ScreenType.watch โ
โ Mobile โ 300px - 600px โ ScreenType.mobile โ
โ Tablet โ 600px - 900px โ ScreenType.tablet โ
โ Small DT โ 900px - 1200px โ ScreenType.smallDesktop โ
โ Desktop โ 1200px - 1800px โ ScreenType.desktop โ
โ Large DT โ > 1800px โ ScreenType.largeDesktop โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Responsive Widgets
ResponsiveGrid โ The Ultimate Grid
Supports Manual Columns (per screen type) and Auto-Fit (dynamic data).
Manual Mode:
ResponsiveGrid(
spacing: 16,
runSpacing: 16,
watch: 1,
mobile: 2,
tablet: 3,
smallDesktop: 3,
desktop: 4,
largeDesktop: 5,
children: [/* widgets */],
)
Auto-Fit Mode (API data):
ResponsiveGrid(
minItemWidth: 150,
itemCount: products.length,
itemBuilder: (context, index) => ProductCard(data: products[index]),
)
Sliver Mode (infinite scroll):
CustomScrollView(
slivers: [
ResponsiveGrid(
useSliver: true,
mobile: 2,
desktop: 4,
itemCount: 100,
itemBuilder: (context, i) => ItemCard(i),
),
],
)
ResponsiveFlex โ Row โ Column
Automatically switches between Row and Column based on screen width.
ResponsiveFlex(
switchOn: ScreenType.mobile, // Column on mobile, Row on larger
spacing: 20,
flipOnRtl: true, // RTL support
children: [
UserAvatar(),
UserInfo(),
],
)
Custom breakpoint:
ResponsiveFlex(
breakpoint: 600, // Column when width < 600
children: [Widget1(), Widget2()],
)
ResponsiveLayout โ Orientation Switch
ResponsiveLayout(
portrait: Column(children: [Image(), Bio()]),
landscape: Row(children: [Image(), Bio()]),
)
ResponsiveVisibility โ Show/Hide by Screen Type
// Whitelist: Show ONLY on mobile
ResponsiveVisibility(
visibleOn: [ScreenType.mobile],
child: MobileNavBar(),
)
// Blacklist: Hide on desktop
ResponsiveVisibility(
hiddenOn: [ScreenType.desktop, ScreenType.largeDesktop],
replacement: DesktopSidebar(), // Optional replacement widget
child: MobileDrawer(),
)
ResponsiveBuilder โ Direct Data Access
ResponsiveBuilder(
builder: (context, data) {
return Text("Screen: ${data.screenType} โ ${data.width.toInt()}px");
},
)
Conditional Logic โ valueByScreen
final columns = context.valueByScreen<int>(
mobile: 2,
tablet: 3,
desktop: 4,
largeDesktop: 6,
);
๐ฆ Container Queries
ContainerQuery โ Rebuild by Parent Size
Unlike MediaQuery which reads the screen size, ContainerQuery reads the parent widget's size. Perfect for reusable components.
ContainerQuery(
breakpoints: [200, 400, 800],
onChanged: (prev, current) => print("Tier: ${current.tier}"),
builder: (context, query) {
if (query.isMobile) return CompactCard();
if (query.isTablet) return MediumCard();
return FullCard();
},
)
QueryTier values: xs, sm, md, lg, xl, xxl
AdaptiveContainer โ Tier-Based Widgets
AdaptiveContainer(
breakpoints: [200, 500],
xs: Icon(Icons.home), // < 200px
sm: Column(children: [Icon(), Text()]), // 200-500px
lg: Row(children: [Icon(), Text(), Button()]), // > 500px
)
๐งฑ ScalifyBox โ Local Scaling
Scale elements relative to a specific container instead of the screen. Perfect for credit cards, game UIs, and embedded components.
ScalifyBox(
referenceWidth: 320,
referenceHeight: 200,
fit: ScalifyFit.contain, // width | height | contain | cover
builder: (context, ls) {
return Container(
width: ls.w(300), // Local width scaling
height: ls.h(180), // Local height scaling
padding: ls.p(20), // Local padding
decoration: BoxDecoration(
borderRadius: ls.br(12), // Local border radius
),
child: Column(
children: [
Text("VISA", style: TextStyle(fontSize: ls.fz(18))),
ls.sbh(10), // Local spacing
],
),
);
},
)
LocalScaler API:
| Method | Description |
|---|---|
ls.w(value) |
Local width scaling |
ls.h(value) |
Local height scaling |
ls.s(value) |
Local general scaling |
ls.fz(value) |
Local font size (clamped) |
ls.iz(value) |
Local icon size |
ls.si(value) |
Rounded int |
ls.p(value) |
EdgeInsets.all |
ls.ph(value) |
Horizontal padding |
ls.pv(value) |
Vertical padding |
ls.br(value) |
BorderRadius.circular |
ls.r(value) |
Radius.circular |
ls.sbh(value) |
SizedBox(height:) |
ls.sbw(value) |
SizedBox(width:) |
ls.scaleFactor |
Current scale value |
๐งฉ ScalifySection โ Independent Section Scaling
Creates an independent scaling context for any part of your UI. Essential for split-screen / master-detail layouts where each section should scale based on its own width.
Row(
children: [
// Sidebar: scales based on 300px width
SizedBox(
width: 300,
child: ScalifySection(child: Sidebar()),
),
// Main content: scales based on remaining width
Expanded(
child: ScalifySection(child: MainContent()),
),
],
)
๐ก Tip: Inside a
ScalifySection, use context-based extensions (context.fz(16),context.w(100)) instead of global extensions (16.fz,100.w) for accurate section-local scaling.
Full Master-Detail Example:
class MasterDetailLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
// Mobile: bottom navigation
if (width < 900) {
return Scaffold(
body: MainPages(),
bottomNavigationBar: BottomNav(),
);
}
// Desktop: sidebar + content โ each scales independently
return Row(
children: [
SizedBox(
width: 300,
child: ScalifySection(child: Sidebar()),
),
Expanded(
child: ScalifySection(child: MainPages()),
),
],
);
}
}
๐ก๏ธ AppWidthLimiter โ Ultra-Wide Protection
Centers and constrains your app on ultra-wide monitors.
AppWidthLimiter(
maxWidth: 1400,
minWidth: 360, // Enables horizontal scroll below 360px
horizontalPadding: 16,
backgroundColor: Color(0xFFE2E8F0),
child: YourApp(),
)
๐ค ResponsiveText โ Smart Auto-Resizing Text
A text widget that automatically shrinks font size to fit available space, and optionally shows shorter text on small screens.
Basic Usage
ResponsiveText(
'Welcome to our Amazing Application',
style: TextStyle(fontSize: 18.fz),
maxLines: 2,
)
Auto-Resize (Shrink to Fit)
ResponsiveText(
'This long heading will shrink to fit any container width',
style: TextStyle(fontSize: 24.fz, fontWeight: FontWeight.bold),
autoResize: true, // Enables auto-shrinking
minFontSize: 12, // Never shrinks below 12px
maxLines: 1,
)
Short Text for Small Screens
ResponsiveText(
'Welcome to our Premium Shopping Experience',
shortText: 'Welcome', // Shows on mobile/watch
style: TextStyle(fontSize: 20.fz),
)
API Reference
| Parameter | Type | Default | Description |
|---|---|---|---|
text |
String |
required | The full text to display |
shortText |
String? |
null |
Abbreviated text for small screens |
autoResize |
bool |
false |
Enable auto font-size shrinking |
minFontSize |
double |
8.0 |
Floor for auto-resize |
stepGranularity |
double |
0.5 |
Precision of resize steps |
maxLines |
int? |
null |
Maximum number of lines |
overflow |
TextOverflow |
ellipsis |
Overflow behavior |
๐ก Performance: When
autoResizeisfalse, noLayoutBuilderis used โ zero overhead.
๐ ResponsiveSpacing โ Design Token System
A unified spacing system with predefined, scaled values โ like design tokens in Figma.
Setup (Optional)
// Customize spacing values (or use defaults: 4, 8, 16, 24, 32, 48)
ScalifySpacing.init(const SpacingScale(
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
xxl: 48,
));
Usage
Column(
children: [
Text('Title', style: TextStyle(fontSize: 24.fz)),
Spacing.sm.gap, // SizedBox(height: 8 * scaleFactor)
Text('Subtitle'),
Spacing.md.gap, // SizedBox(height: 16 * scaleFactor)
Text('Body text'),
],
)
Container(
padding: Spacing.lg.insets, // EdgeInsets.all(24 * scaleFactor)
margin: Spacing.sm.insetsH, // EdgeInsets.symmetric(horizontal: 8.s)
child: Content(),
)
Spacing API
| Extension | Output | Example |
|---|---|---|
.gap |
SizedBox(height: scaled) |
Spacing.md.gap |
.gapW |
SizedBox(width: scaled) |
Spacing.sm.gapW |
.gapAll |
SizedBox(width: s, height: s) |
Spacing.lg.gapAll |
.value |
double (raw scaled value) |
Spacing.xl.value |
.insets |
EdgeInsets.all(scaled) |
Spacing.md.insets |
.insetsH |
EdgeInsets.symmetric(horizontal:) |
Spacing.lg.insetsH |
.insetsV |
EdgeInsets.symmetric(vertical:) |
Spacing.sm.insetsV |
.insetsT |
EdgeInsets.only(top:) |
Spacing.md.insetsT |
.insetsB |
EdgeInsets.only(bottom:) |
Spacing.md.insetsB |
.insetsL |
EdgeInsets.only(left:) |
Spacing.sm.insetsL |
.insetsR |
EdgeInsets.only(right:) |
Spacing.sm.insetsR |
Default Scale Values
| Tier | Value | Use Case |
|---|---|---|
xs |
4 | Tight spacing, chip gaps |
sm |
8 | List item padding, small gaps |
md |
16 | Card padding, section gaps |
lg |
24 | Section spacing, content margins |
xl |
32 | Large section gaps |
xxl |
48 | Page-level spacing |
๐ก All spacing values are automatically scaled via
GlobalResponsive.scaleFactor.
๐ ScalifyDebugOverlay โ Developer Tools
A draggable, collapsible debug panel showing live responsive metrics. Zero overhead in release builds.
Usage
ScalifyProvider(
builder: (context, child) => MaterialApp(
home: ScalifyDebugOverlay(
child: child!,
),
),
child: const HomeScreen(),
)
What It Shows
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ฑ MOBILE โ
โ ๐ Size 375 ร 812 โ
โ โ๏ธ Scale 1.000 โ
โ โ๏ธ ScaleW 1.000 โ
โ โ๏ธ ScaleH 1.000 โ
โ ๐ค TextSF 1.00 โ
โ ๐ Rebuilds 3 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Features
- ๐จ Color-coded screen type indicator
- ๐ฑ๏ธ Draggable โ move anywhere on screen
- ๐ Collapsible โ tap header to toggle
- ๐ก๏ธ Zero production cost โ uses
kDebugModeguard - ๐ฏ RepaintBoundary โ isolated repaints
API
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
true |
Enable/disable (still debug-only) |
initialTop |
double |
50.0 |
Initial Y position |
initialRight |
double |
16.0 |
Initial X position |
๐ ResponsiveNavigation โ Adaptive Navigation
Automatically switches between BottomNavigationBar, NavigationRail, and a sidebar drawer based on screen size.
Usage
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _selectedIndex = 0;
final _pages = [
const HomePage(),
const SearchPage(),
const ProfilePage(),
];
@override
Widget build(BuildContext context) {
return ResponsiveNavigation(
destinations: const [
NavDestination(icon: Icons.home, label: 'Home'),
NavDestination(icon: Icons.search, label: 'Search'),
NavDestination(
icon: Icons.person,
label: 'Profile',
badge: 3, // Badge support!
),
],
selectedIndex: _selectedIndex,
onChanged: (i) => setState(() => _selectedIndex = i),
body: _pages[_selectedIndex],
);
}
}
Navigation Modes
โโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ Screen โ Navigation โ Breakpoint โ
โโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโค
โ Mobile โ BottomNavBar โ โค railBreakpoint โ
โ Tablet โ NavigationRail โ โค sidebarBreakpoint โ
โ Desktop โ Sidebar Drawer โ > sidebarBreakpoint โ
โโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโ
Custom Bottom Navigation
Use bottomNavBuilder for full UI control over the bottom navigation bar:
ResponsiveNavigation(
destinations: destinations,
selectedIndex: _index,
onChanged: (i) => setState(() => _index = i),
body: pages[_index],
bottomNavBuilder: (context, destinations, selected, onChanged) {
return BottomNavigationBar(
currentIndex: selected,
onTap: onChanged,
items: destinations.map((d) => BottomNavigationBarItem(
icon: Icon(d.icon),
label: d.label,
)).toList(),
);
},
)
Sidebar Footer, Header & Hidden Items
Add custom widgets to the sidebar and hide specific destinations from it:
ResponsiveNavigation(
destinations: [
NavDestination(icon: Icons.home, label: 'Home'),
NavDestination(icon: Icons.search, label: 'Search'),
NavDestination(
icon: Icons.person,
label: 'Profile',
showInSidebar: false, // โ
Hidden from sidebar, visible in bottom nav
),
],
selectedIndex: _index,
onChanged: (i) => setState(() => _index = i),
body: pages[_index],
sidebarHeader: Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [Icon(Icons.apps), SizedBox(width: 8), Text('My App')],
),
),
sidebarFooter: ListTile(
leading: CircleAvatar(child: Text('A')),
title: Text('Alaa Hassan'),
subtitle: Text('Admin'),
),
)
Nested Navigation
ResponsiveNavigation supports nested navigation โ the sidebar stays fixed while tab content navigates independently:
ResponsiveNavigation(
destinations: destinations,
selectedIndex: _index,
onChanged: (i) => setState(() => _index = i),
body: IndexedStack(
index: _index,
children: [
Navigator( // Each tab has its own navigation stack
key: _navKeys[0],
onGenerateRoute: (_) => MaterialPageRoute(
builder: (_) => ListPage(navKey: _navKeys[0]),
),
),
Navigator(key: _navKeys[1], /* ... */),
],
),
)
๐ก Key: Use
IndexedStack+ per-tabNavigatorwithGlobalKey<NavigatorState>to preserve tab state and enable push/pop within each tab while the sidebar remains visible.
API Reference
| Parameter | Type | Default | Description |
|---|---|---|---|
destinations |
List<NavDestination> |
required | Nav items |
selectedIndex |
int |
required | Current selection |
onChanged |
ValueChanged<int> |
required | Selection callback |
body |
Widget |
required | Main content |
bottomNavBuilder |
BottomNavBuilder? |
null |
Full custom bottom nav UI |
sidebarHeader |
Widget? |
null |
Widget above sidebar items |
sidebarFooter |
Widget? |
null |
Widget below sidebar items |
drawerWidth |
double |
280.0 |
Sidebar width |
railExtended |
bool |
false |
Rail shows labels inline |
showLabels |
bool |
true |
Show rail labels |
elevation |
double |
0.0 |
Surface elevation |
railBreakpoint |
ScreenType |
mobile |
BottomโRail switch |
sidebarBreakpoint |
ScreenType |
smallDesktop |
RailโSidebar switch |
backgroundColor |
Color? |
theme | Background color |
selectedColor |
Color? |
primary | Selected item color |
NavDestination Properties
| Property | Type | Default | Description |
|---|---|---|---|
icon |
IconData |
required | Icon for the destination |
label |
String |
required | Label text |
selectedIcon |
IconData? |
null |
Icon when selected |
badge |
int? |
null |
Badge count (shows as chip) |
showInSidebar |
bool |
true |
Whether to show in sidebar mode |
๐ ResponsiveWrap โ Smart Auto-Wrapping
A widget that lays out children horizontally and wraps to the next line automatically when space runs out โ perfect for chips, tags, and button groups.
Usage
ResponsiveWrap(
spacing: 12,
runSpacing: 8,
children: [
FilterChip(label: Text('All')),
FilterChip(label: Text('Clothes')),
FilterChip(label: Text('Electronics')),
FilterChip(label: Text('Shoes')),
FilterChip(label: Text('Books')),
],
)
API
| Parameter | Type | Default | Description |
|---|---|---|---|
children |
List<Widget> |
required | Widgets to layout |
spacing |
double |
8.0 |
Horizontal gap (auto-scaled) |
runSpacing |
double |
8.0 |
Vertical gap between lines |
alignment |
WrapAlignment |
start |
Main axis alignment |
crossAxisAlignment |
WrapCrossAlignment |
center |
Cross axis alignment |
scaleSpacing |
bool |
true |
Scale spacing via .s |
padding |
EdgeInsetsGeometry? |
null |
Outer padding |
๐ก Unlike
ResponsiveFlex(RowโColumn),ResponsiveWrapwraps items line by line without switching direction.
๐ผ๏ธ ResponsiveImage โ Screen-Adaptive Images
Displays different image sources based on screen type, optimizing memory and visual quality.
Usage
ResponsiveImage(
mobile: AssetImage('assets/banner_sm.webp'),
tablet: AssetImage('assets/banner_md.webp'),
desktop: AssetImage('assets/banner_lg.webp'),
fit: BoxFit.cover,
height: 200.s,
borderRadius: BorderRadius.circular(12),
)
With Placeholder & Auto-Optimize
ResponsiveImage(
mobile: NetworkImage('https://example.com/sm.jpg'),
desktop: NetworkImage('https://example.com/lg.jpg'),
autoOptimize: true, // Decodes at display size to save memory
width: 300.w,
height: 200.s,
placeholder: Center(child: CircularProgressIndicator()),
errorWidget: Icon(Icons.broken_image),
)
API
| Parameter | Type | Default | Description |
|---|---|---|---|
mobile |
ImageProvider |
required | Image for mobile (fallback) |
tablet |
ImageProvider? |
null |
Image for tablet |
desktop |
ImageProvider? |
null |
Image for desktop |
fit |
BoxFit |
cover |
How to fit the image |
autoOptimize |
bool |
false |
Decode at display size |
placeholder |
Widget? |
null |
Loading placeholder |
errorWidget |
Widget? |
null |
Error fallback |
borderRadius |
BorderRadius? |
null |
Rounded corners |
๐ก Fallback chain:
desktop โ tablet โ mobileโ always displays something.
๐ญ AnimatedResponsiveTransition โ Smooth Layout Switching
Animates smoothly between different responsive layouts when the screen type changes. Essential for polished Desktop/Web experiences.
Usage
AnimatedResponsiveTransition(
duration: Duration(milliseconds: 300),
transition: ResponsiveTransitionType.fadeSlide,
mobile: CompactCard(),
tablet: MediumCard(),
desktop: ExpandedCard(),
)
Available Transitions
| Type | Visual Effect |
|---|---|
ResponsiveTransitionType.fade |
Simple fade between layouts |
ResponsiveTransitionType.fadeSlide |
Fade + horizontal slide |
ResponsiveTransitionType.scale |
Scale up/down transition |
ResponsiveTransitionType.fadeScale |
Fade + subtle scale |
Custom Transition
AnimatedResponsiveTransition(
mobile: MobileLayout(),
desktop: DesktopLayout(),
customTransitionBuilder: (child, animation) {
return RotationTransition(turns: animation, child: child);
},
)
API
| Parameter | Type | Default | Description |
|---|---|---|---|
mobile |
Widget |
required | Mobile layout (fallback) |
tablet |
Widget? |
null |
Tablet layout |
desktop |
Widget? |
null |
Desktop layout |
duration |
Duration |
300ms |
Animation duration |
curve |
Curve |
easeInOut |
Animation curve |
transition |
ResponsiveTransitionType |
fade |
Transition type |
customTransitionBuilder |
Function? |
null |
Custom override |
๐ก Animation only triggers on ScreenType change โ not on sub-pixel resize events.
๐ ResponsiveTable โ Adaptive Data Display
A table that automatically switches between a full DataTable on desktop and a card list on mobile. Supports sorting, column hiding, and custom card layouts.
Basic Usage
ResponsiveTable(
columns: ['Name', 'Price', 'Status', 'Date'],
rows: [
['iPhone 15', '\$999', 'Available', '2024-01-15'],
['MacBook Pro', '\$1999', 'Sold Out', '2024-02-20'],
['AirPods', '\$249', 'Available', '2024-03-10'],
],
hiddenColumnsOnMobile: [3], // Hide 'Date' on mobile
)
Custom Mobile Cards
ResponsiveTable(
columns: ['Name', 'Price', 'Status'],
rows: products.map((p) => [p.name, p.price, p.status]).toList(),
mobileCardBuilder: (context, row, headers) => Card(
child: ListTile(
leading: Icon(Icons.shopping_bag),
title: Text(row[0].toString()),
subtitle: Text(row[1].toString()),
trailing: Chip(label: Text(row[2].toString())),
),
),
onRowTap: (index, row) => print('Tapped: $row'),
)
With Sorting
ResponsiveTable(
columns: ['Name', 'Price'],
rows: data,
showSortIndicator: true,
sortColumnIndex: _sortIndex,
sortAscending: _ascending,
onSort: (column, ascending) {
setState(() {
_sortIndex = column;
_ascending = ascending;
_sortData();
});
},
)
Modes
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Screen โ Display Mode โ
โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Mobile โ Card list (lazy loaded) โ
โ Tablet โ Full DataTable โ
โ Desktop โ Full DataTable โ
โโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
API
| Parameter | Type | Default | Description |
|---|---|---|---|
columns |
List<String> |
required | Column headers |
rows |
List<List<dynamic>> |
required | Row data |
mobileCardBuilder |
Function? |
null |
Custom card builder |
hiddenColumnsOnMobile |
List<int>? |
null |
Columns to hide |
onRowTap |
Function? |
null |
Row tap callback |
tableBreakpoint |
ScreenType |
mobile |
CardโTable switch |
showSortIndicator |
bool |
false |
Show sort arrows |
onSort |
Function? |
null |
Sort callback |
horizontalScroll |
bool |
true |
Scroll on overflow |
cardSpacing |
double |
8.0 |
Gap between cards |
๐ก Mobile cards use
ListView.builderfor lazy rendering โ O(visible) performance even with 1000+ rows.
๐งฎ ResponsiveConstraints โ Screen-Adaptive Constraints
Applies different BoxConstraints based on screen type โ precise control over min/max dimensions per device.
Usage
ResponsiveConstraints(
mobile: BoxConstraints(maxWidth: 350, minHeight: 100),
tablet: BoxConstraints(maxWidth: 500, minHeight: 120),
desktop: BoxConstraints(maxWidth: 800, minHeight: 150),
child: ProductCard(),
)
Centered with Alignment
ResponsiveConstraints(
alignment: Alignment.center,
mobile: BoxConstraints(maxWidth: 350),
desktop: BoxConstraints(maxWidth: 600),
child: LoginForm(),
)
With Scaled Constraints
ResponsiveConstraints(
scaleConstraints: true, // Values multiply by scaleFactor
mobile: BoxConstraints(maxWidth: 300),
desktop: BoxConstraints(maxWidth: 700),
child: ContentArea(),
)
API
| Parameter | Type | Default | Description |
|---|---|---|---|
mobile |
BoxConstraints |
required | Constraints for mobile |
tablet |
BoxConstraints? |
null |
Constraints for tablet |
desktop |
BoxConstraints? |
null |
Constraints for desktop |
alignment |
AlignmentGeometry? |
null |
Align constrained child |
scaleConstraints |
bool |
false |
Scale values by scaleFactor |
๐๏ธ ScalifySliver โ Responsive Sliver Widgets
A complete set of responsive Sliver widgets for CustomScrollView. Includes 3 widgets:
ScalifySliverAppBar
A responsive SliverAppBar with per-screen expanded heights.
CustomScrollView(
slivers: [
ScalifySliverAppBar(
title: 'My Store',
mobileExpandedHeight: 200,
desktopExpandedHeight: 350,
flexibleBackground: Image.asset('assets/banner.jpg', fit: BoxFit.cover),
pinned: true,
),
// ... more slivers
],
)
ScalifySliverHeader
Displays different sliver widgets based on screen type.
ScalifySliverHeader(
mobile: SliverToBoxAdapter(child: CompactSearchBar()),
desktop: SliverToBoxAdapter(child: ExpandedSearchBar()),
)
ScalifySliverPersistentHeader
A responsive sticky header with adjustable max/min heights.
ScalifySliverPersistentHeader(
mobileMaxHeight: 60,
desktopMaxHeight: 100,
pinned: true,
builder: (context, shrinkOffset, overlapsContent) {
final progress = shrinkOffset / 40;
return Container(
color: Colors.blue.withAlpha((200 * (1 - progress)).toInt()),
child: Center(child: Text('Sticky Header')),
);
},
)
Full Example
CustomScrollView(
slivers: [
ScalifySliverAppBar(
title: 'Products',
mobileExpandedHeight: 180,
desktopExpandedHeight: 300,
flexibleBackground: Image.network(bannerUrl, fit: BoxFit.cover),
),
ScalifySliverPersistentHeader(
mobileMaxHeight: 50,
desktopMaxHeight: 80,
builder: (_, __, ___) => FilterBar(),
),
ResponsiveGrid(
useSliver: true,
mobile: 2,
desktop: 4,
itemCount: products.length,
itemBuilder: (ctx, i) => ProductCard(products[i]),
),
],
)
ScalifySliverAppBar API
| Parameter | Type | Default | Description |
|---|---|---|---|
title |
String |
required | Title text |
mobileExpandedHeight |
double |
200.0 |
Height on mobile |
tabletExpandedHeight |
double? |
null |
Height on tablet |
desktopExpandedHeight |
double? |
null |
Height on desktop |
flexibleBackground |
Widget? |
null |
Background widget |
pinned |
bool |
true |
Stay visible on scroll |
floating |
bool |
false |
Show on scroll up |
stretch |
bool |
true |
Stretch on over-scroll |
๐ Live Resizing (Desktop & Web)
For instant UI updates while dragging the window:
Option 1: ResponsiveBuilder (Recommended)
ResponsiveBuilder(
builder: (context, data) {
return Scaffold(
body: Center(
child: Text("${data.width.toInt()}px", style: TextStyle(fontSize: 20.fz)),
),
);
},
)
Option 2: Direct Subscription
@override
Widget build(BuildContext context) {
context.responsiveData; // ๐ Subscribe to resize events
return Scaffold(/* ... */);
}
๐ง Advanced: How the Engine Works
Configurable Rebuild Tolerance
Scalify uses a dual-tolerance system to prevent unnecessary rebuilds:
rebuildWidthPxThreshold(default 4.0px) โ Ignores sub-pixel size changesrebuildScaleThreshold(default 0.01) โ Ignores scale changes < 1%
Screen: 375px โ 377px (2px diff < 4px threshold)
Scale: 1.000 โ 1.005 (0.005 diff < 0.01 threshold)
โ No rebuild! โ
Internally, Quantized IDs (ร1000) are still used for InheritedModel aspect-based comparisons to ensure fast integer equality checks.
InheritedModel Aspects
Enable granular notifications to rebuild widgets only when specific data changes:
// Only rebuilds when screen TYPE changes (not on every pixel resize)
ScalifyProvider.of(context, aspect: ScalifyAspect.type);
// Only rebuilds when scale FACTOR changes
ScalifyProvider.of(context, aspect: ScalifyAspect.scale);
// Only rebuilds when text scale changes (accessibility)
ScalifyProvider.of(context, aspect: ScalifyAspect.text);
Enable in config:
ScalifyConfig(enableGranularNotifications: true)
Debounce on Resize
On Desktop/Web, window resizing fires hundreds of events per second. Scalify debounces platform-driven resize events with a configurable window (debounceWindowMillis, default 120ms), calculating the layout once after the user stops dragging. Parent-driven updates (e.g., didChangeDependencies) remain synchronous for instant response.
// Disable debounce for instant updates:
ScalifyConfig(debounceWindowMillis: 0)
// Increase debounce for weaker devices:
ScalifyConfig(debounceWindowMillis: 200)
Nested Providers & Performance (observeMetrics)
When nesting ScalifyProvider (e.g., for a split-screen section), the inner provider should not listen to window resize events directly, as this creates a "double debounce" race condition with the parent provider, causing UI lag.
To fix this, set observeMetrics: false on the nested provider:
ScalifyProvider(
config: sectionConfig,
observeMetrics: false, // โก๏ธ Disables internal resize listener
child: SectionContent(),
)
This ensures the inner provider updates synchronously when its parent rebuilds, resulting in 60fps performance during window resizing. ScalifySection handles this automatically.
4K Smart Dampening
For screens wider than memoryProtectionThreshold (default 1920px):
Normal: scale = screenWidth / designWidth
4K: scale = thresholdScale + (excessWidth / designWidth ร dampFactor)
This prevents text from becoming 5ร the intended size on ultra-wide monitors.
๐ Migration from v2.x โ v3.0.0
1. Builder Pattern (Recommended)
- MaterialApp(
- builder: (context, child) => ScalifyProvider(child: child),
- home: HomeScreen(),
- )
+ ScalifyProvider(
+ builder: (context, child) => MaterialApp(home: child),
+ child: const HomeScreen(),
+ )
2. Context API for const Widgets
- // Won't update in const trees
- Text("Hi", style: TextStyle(fontSize: 16.fz))
+ // Always updates via context
+ Builder(
+ builder: (context) => Text(
+ "Hi",
+ style: TextStyle(fontSize: context.sp(16)),
+ ),
+ )
3. Percentage Scaling (New)
SizedBox(width: 50.pw) // 50% of screen width
SizedBox(height: 25.hp) // 25% of screen height
4. Theme Scaling (New)
MaterialApp(
theme: ThemeData.light().scale(context),
)
๐งช Testing
The package includes 312 comprehensive tests covering all widgets, extensions, and edge cases:
flutter test --reporter compact
# 00:04 +312: All tests passed!
Test Coverage by Feature
| Feature | Tests |
|---|---|
Core Extensions (.w, .h, .s, .fz, etc.) |
50 |
| ResponsiveGrid | 30+ |
| ResponsiveVisibility | 15+ |
| ResponsiveFlex | 15+ |
| ResponsiveText | 8 |
| ResponsiveSpacing | 12 |
| ScalifyDebugOverlay | 4 |
| ResponsiveNavigation | 11 |
| ResponsiveWrap | 10 |
| ResponsiveImage | 11 |
| AnimatedResponsiveTransition | 12 |
| ResponsiveTable | 9 |
| ResponsiveConstraints | 9 |
| ScalifySliver (AppBar/Header/Persistent) | 10 |
| Container Queries, ScalifyBox, etc. | 30+ |
Author
Alaa Hassan Damad
- Email: alaahassanak772@gmail.com
- Country: Iraq
License
MIT License โ see LICENSE for details.
Libraries
- flutter_scalify
- The Intelligent Scaling Engine for Flutter. Scalify provides a high-performance engine to scale UI elements across Mobile, Web, and Desktop with zero overhead.
- responsive_scale/adaptive_container
- responsive_scale/animated_responsive_transition
- responsive_scale/app_width_limiter
- responsive_scale/container_query
- responsive_scale/global_responsive
- responsive_scale/responsive_builder
- responsive_scale/responsive_constraints
- responsive_scale/responsive_data
- responsive_scale/responsive_extensions
- responsive_scale/responsive_flex
- responsive_scale/responsive_grid
- responsive_scale/responsive_helper
- responsive_scale/responsive_image
- responsive_scale/responsive_layout
- responsive_scale/responsive_spacing
- responsive_scale/responsive_table
- responsive_scale/responsive_text
- responsive_scale/responsive_visibility
- responsive_scale/responsive_wrap
- responsive_scale/scalify_box
- responsive_scale/scalify_builder
- responsive_scale/scalify_config
- responsive_scale/scalify_debug_overlay
- responsive_scale/scalify_provider
- responsive_scale/scalify_section
- responsive_scale/scalify_sliver
- responsive_scale/theme_extension