px_responsive 0.0.4
px_responsive: ^0.0.4 copied to clipboard
A powerful tri-tier responsive design system for Flutter. Automatically scales UI elements across mobile, tablet, and desktop based on your Figma/XD design specifications.
px_responsive #
A powerful tri-tier responsive design system for Flutter that automatically scales your UI across mobile, tablet, and desktop platforms based on your Figma/XD design specifications.
Table of Contents #
- Features
- Installation
- Quick Start
- How It Works
- API Reference
- Responsive Widgets
- Configuration Options
- Best Practices
- Ultra-Wide Screen Support
- Complete Example
- WASM Support
- License
Features #
- ๐ฏ Tri-Tier Scaling โ Automatically switches between mobile, tablet, and desktop base designs
- ๐ Design-to-Code Mapping โ Use exact values from your Figma/XD designs
- ๐ Safe Scaling โ Built-in min/max constraints prevent layout breaking
- ๐ฑ Device Detection โ Simple
isMobile,isTablet,isDesktopgetters - ๐ฅ๏ธ Ultra-Wide Support โ Optional
maxWidthcap for large displays - ๐งฉ Rich Widget Library โ Responsive builders, visibility controls, and value providers
- โจ Intuitive API โ Clean extension syntax (
.w,.h,.sp,.r) - ๐ WASM Compatible โ Pure Dart implementation, works everywhere Flutter runs
Installation #
Add px_responsive to your pubspec.yaml:
dependencies:
px_responsive: ^0.0.1
Then run:
flutter pub get
Quick Start #
1. Wrap Your App #
Wrap your root widget with PxResponsiveWrapper and provide your design specifications:
import 'package:flutter/material.dart';
import 'package:px_responsive/px_responsive.dart';
void main() {
runApp(
PxResponsiveWrapper(
config: const PxResponsiveConfig(
// Your design tool's artboard sizes
desktop: Size(1920, 1080),
tablet: Size(834, 1194),
mobile: Size(375, 812),
// Breakpoints (when to switch layouts)
mobileBreakpoint: 600,
tabletBreakpoint: 1200,
),
child: const MyApp(),
),
);
}
2. Use Extensions in Your Widgets #
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 200.w, // Scaled width
height: 150.h, // Scaled height
padding: EdgeInsets.all(16.r), // Scaled padding
child: Text(
'Hello World',
style: TextStyle(fontSize: 18.sp), // Scaled font
),
);
}
}
3. Adapt to Device Types #
Widget build(BuildContext context) {
if (isMobile) {
return MobileLayout();
} else if (isTablet) {
return TabletLayout();
} else {
return DesktopLayout();
}
}
How It Works #
The Scaling Formula #
When you use 200.w, the package calculates:
result = 200 ร (currentScreenWidth รท activeBaseDesignWidth)
Automatic Base Switching #
The package automatically selects the appropriate base design based on screen width:
| Screen Width | Active Base | Example |
|---|---|---|
| < 600px | Mobile (375ร812) | Phones |
| 600px โ 1199px | Tablet (834ร1194) | Tablets, small laptops |
| โฅ 1200px | Desktop (1920ร1080) | Desktops, large screens |
Visual Example #
Mobile Design (375px wide) Your Phone (390px wide)
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Button: 100px โ โ โ Button: 104px โ
โ Font: 16px โ Scaling โ Font: 16.6px โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
Scale Factor: 390 รท 375 = 1.04
API Reference #
Core Extensions #
These are the primary extensions you'll use most often.
| Extension | Description | Use Case |
|---|---|---|
.w |
Width scaling | Container widths, horizontal padding/margins |
.h |
Height scaling | Container heights, vertical padding/margins |
.sp |
Font scaling | Text sizes (has tighter max constraint) |
.r |
Radius scaling | Border radius, circular elements |
Container(
width: 300.w, // Scales with screen width
height: 200.h, // Scales with screen height
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r), // Uniform scaling
),
child: Text(
'Responsive Text',
style: TextStyle(fontSize: 16.sp), // Won't get too large
),
)
When to Use Each
| Scenario | Recommended Extension |
|---|---|
| Container/Card width | .w |
| Container/Card height | .h |
| Horizontal padding | .w |
| Vertical padding | .h |
| Symmetric padding | .r or .w |
| Font sizes | .sp |
| Icon sizes | .sp or .r |
| Border radius | .r |
| Avatar/Circle dimensions | .r |
| Spacing between items | .w (horizontal) or .h (vertical) |
Screen Percentage Extensions #
Use these when you want a percentage of the full screen.
| Extension | Description | Example |
|---|---|---|
.wf |
Percentage of screen width | 50.wf = 50% of width |
.hf |
Percentage of screen height | 25.hf = 25% of height |
Container(
width: 80.wf, // 80% of screen width
height: 50.hf, // 50% of screen height
child: Text('Full-width card'),
)
Best Use Cases
- Full-width containers:
100.wf - Half-screen layouts:
50.wf - Modal dialogs:
90.wfwidth,80.hfheight - Hero sections:
100.wfwidth,60.hfheight
Parent-Relative Extensions #
Use these when you need sizing relative to a parent widget, not the screen.
| Extension | Description |
|---|---|
.wr(context) |
Percentage of parent width |
.hr(context) |
Percentage of parent height |
Setup Required: Wrap the parent with PxRelativeSizeProvider:
PxRelativeSizeProvider(
child: Row(
children: [
Container(
width: 30.wr(context), // 30% of Row's width
child: Sidebar(),
),
Container(
width: 70.wr(context), // 70% of Row's width
child: MainContent(),
),
],
),
)
Best Use Cases
- Split layouts within a container
- Proportional grid items
- Nested responsive layouts
Clamping Methods #
Prevent values from going too small or too large.
| Method | Description | Example |
|---|---|---|
.wMin(min) |
Width with minimum | 200.wMin(150) โ at least 150 |
.wMax(max) |
Width with maximum | 200.wMax(300) โ at most 300 |
.wClamp(min, max) |
Width within range | 200.wClamp(150, 300) |
.hMin(min) |
Height with minimum | 100.hMin(80) |
.hMax(max) |
Height with maximum | 100.hMax(120) |
.hClamp(min, max) |
Height within range | 100.hClamp(80, 120) |
.spMin(min) |
Font with minimum | 14.spMin(12) |
.spMax(max) |
Font with maximum | 24.spMax(32) |
.spClamp(min, max) |
Font within range | 16.spClamp(14, 20) |
Container(
// Width scales but never below 200 or above 500
width: 300.wClamp(200, 500),
child: Text(
'Readable Text',
// Font scales but stays between 14 and 22
style: TextStyle(fontSize: 18.spClamp(14, 22)),
),
)
Best Use Cases
- Buttons that shouldn't be too small on tiny screens
- Text that must remain readable
- Images that shouldn't exceed a certain size
- Cards with minimum touch targets
Object Extensions #
Scale entire objects at once.
EdgeInsets Extensions
| Extension | Description |
|---|---|
.w |
All sides scaled by width factor |
.scaled |
Horizontal by width, vertical by height |
.r |
All sides scaled by radius factor |
// All sides scale uniformly
padding: EdgeInsets.all(16).w
// Horizontal and vertical scale independently
padding: EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
).scaled
// Uniform scaling for symmetric padding
margin: EdgeInsets.all(12).r
Size Extensions
| Extension | Description |
|---|---|
.scaled |
Width by scaleW, height by scaleH |
.w |
Both dimensions by scaleW |
.r |
Both dimensions by scaleR (uniform) |
// Aspect-ratio aware scaling
Size imageSize = Size(400, 300).scaled;
// Square that stays square
Size avatarSize = Size(80, 80).r;
BorderRadius Extensions
| Extension | Description |
|---|---|
.r |
All corners scaled by radius factor |
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16).r,
)
// Or with different corners
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
).r
Global Getters #
Quick access to device information without calling PxResponsive().
| Getter | Type | Description |
|---|---|---|
isMobile |
bool |
True if width < mobileBreakpoint |
isTablet |
bool |
True if between breakpoints |
isDesktop |
bool |
True if width โฅ tabletBreakpoint |
deviceType |
PxDeviceType |
Enum: .mobile, .tablet, .desktop |
screenWidth |
double |
Actual screen width |
screenHeight |
double |
Actual screen height |
effectiveWidth |
double |
Width used for scaling (respects maxWidth) |
// Simple boolean checks
if (isMobile) {
return CompactView();
}
// Switch on device type
switch (deviceType) {
case PxDeviceType.mobile:
return MobileLayout();
case PxDeviceType.tablet:
return TabletLayout();
case PxDeviceType.desktop:
return DesktopLayout();
}
// Use dimensions directly
final isLandscape = screenWidth > screenHeight;
Global Functions #
responsiveValue
Returns different values based on device type.
T responsiveValue<T>({
required T mobile,
T? tablet,
T? desktop,
})
// Different column counts
int columns = responsiveValue(
mobile: 1,
tablet: 2,
desktop: 4,
);
// Different padding
double padding = responsiveValue(
mobile: 16.0,
tablet: 24.0,
desktop: 32.0,
);
// Different widgets
Widget icon = responsiveValue(
mobile: Icon(Icons.menu),
desktop: Icon(Icons.dashboard),
);
Fallback Behavior:
- If
tabletis null โ usesmobile - If
desktopis null โ usestablet(ormobileif tablet is also null)
Responsive Widgets #
PxResponsiveBuilder #
Build completely different widget trees for each device type.
PxResponsiveBuilder(
mobile: (context) => MobileLayout(),
tablet: (context) => TabletLayout(),
desktop: (context) => DesktopLayout(),
)
When to Use:
- Completely different layouts per device
- Different navigation patterns (drawer vs sidebar)
- Different component hierarchies
// Navigation example
PxResponsiveBuilder(
mobile: (context) => Scaffold(
drawer: NavigationDrawer(),
body: Content(),
),
desktop: (context) => Row(
children: [
SideNavigation(),
Expanded(child: Content()),
],
),
)
PxResponsiveValue #
Provide different values and build with them.
PxResponsiveValue<int>(
mobile: 1,
tablet: 2,
desktop: 4,
builder: (context, columnCount) {
return GridView.count(
crossAxisCount: columnCount,
children: items,
);
},
)
When to Use:
- Same widget structure, different parameters
- Grid layouts with varying columns
- Dynamic spacing or sizing
// Dynamic grid
PxResponsiveValue<int>(
mobile: 2,
tablet: 3,
desktop: 5,
builder: (context, columns) => GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
),
itemBuilder: (context, index) => ProductCard(products[index]),
),
)
PxResponsiveVisibility #
Show or hide widgets based on device type.
Default Constructor
PxResponsiveVisibility(
visibleOnMobile: false,
visibleOnTablet: true,
visibleOnDesktop: true,
child: Sidebar(),
)
Named Constructors
| Constructor | Visible On |
|---|---|
.mobile() |
Mobile only |
.tablet() |
Tablet only |
.desktop() |
Desktop only |
.tabletUp() |
Tablet + Desktop |
.tabletDown() |
Mobile + Tablet |
// Sidebar only on desktop
PxResponsiveVisibility.desktop(
child: Sidebar(),
)
// Mobile menu button
PxResponsiveVisibility.mobile(
child: IconButton(
icon: Icon(Icons.menu),
onPressed: openDrawer,
),
)
// Show on tablet and up
PxResponsiveVisibility.tabletUp(
child: ExtendedNavigation(),
replacement: CompactNavigation(), // Shown when hidden
)
Options
| Property | Description |
|---|---|
replacement |
Widget shown when child is hidden |
maintainState |
Keep child's state when hidden (uses Offstage) |
PxResponsiveVisibility.desktop(
maintainState: true, // Keep sidebar state when switching to tablet
child: Sidebar(),
replacement: CollapsedSidebar(),
)
When to Use:
- Hiding navigation elements on mobile
- Showing/hiding sidebars
- Conditional feature visibility
- Progressive disclosure of UI elements
Configuration Options #
PxResponsiveConfig #
const PxResponsiveConfig({
// Base design sizes from your design tool
Size desktop = const Size(1920, 1080),
Size tablet = const Size(834, 1194),
Size mobile = const Size(375, 812),
// Breakpoints
double mobileBreakpoint = 600, // Below this = mobile
double tabletBreakpoint = 1200, // Above this = desktop
// Scaling constraints
double? maxWidth, // Cap width for ultra-wide screens
double? minScaleFactor = 0.5, // Elements won't shrink below 50%
double? maxScaleFactor = 2.0, // Elements won't grow beyond 200%
double? maxTextScaleFactor = 1.5, // Text won't grow beyond 150%
})
Common Design Sizes #
| Platform | Common Sizes |
|---|---|
| Mobile | 375ร812 (iPhone X), 360ร640 (Android), 414ร896 (iPhone Plus) |
| Tablet | 834ร1194 (iPad Pro 11"), 768ร1024 (iPad), 1024ร768 (Landscape) |
| Desktop | 1920ร1080 (Full HD), 1440ร900 (MacBook), 1366ร768 (Laptop) |
Configuration Examples #
Standard Setup
PxResponsiveConfig(
desktop: Size(1440, 900),
tablet: Size(768, 1024),
mobile: Size(375, 812),
)
With Ultra-Wide Support
PxResponsiveConfig(
desktop: Size(1920, 1080),
tablet: Size(834, 1194),
mobile: Size(375, 812),
maxWidth: 1920, // Content won't stretch beyond 1920px
)
Conservative Scaling
PxResponsiveConfig(
desktop: Size(1920, 1080),
tablet: Size(834, 1194),
mobile: Size(375, 812),
minScaleFactor: 0.8, // Don't shrink too much
maxScaleFactor: 1.5, // Don't grow too much
maxTextScaleFactor: 1.2, // Keep text readable
)
Best Practices #
1. Match Your Design Tool #
Always use the exact artboard sizes from your design tool:
// If your Figma mobile frame is 390ร844
mobile: Size(390, 844),
// If your XD desktop artboard is 1440ร900
desktop: Size(1440, 900),
2. Use Appropriate Extensions #
// โ
Good
width: 200.w, // Horizontal โ use .w
height: 100.h, // Vertical โ use .h
fontSize: 16.sp, // Text โ use .sp
borderRadius: 12.r, // Radius โ use .r
// โ Avoid
width: 200.h, // Don't use .h for width
fontSize: 16.w, // Don't use .w for fonts
3. Clamp Critical Values #
// Ensure buttons are always tappable (min 44px)
height: 48.hMin(44),
// Ensure text is always readable
fontSize: 14.spMin(12),
// Prevent images from getting too large
width: 400.wMax(500),
4. Use Device-Specific Values #
// Different spacing per device
padding: EdgeInsets.all(
responsiveValue(mobile: 12, tablet: 16, desktop: 24).w
),
// Different layouts
crossAxisCount: responsiveValue(mobile: 2, tablet: 3, desktop: 4),
5. Combine Extensions Thoughtfully #
// Responsive padding with scaling
padding: EdgeInsets.symmetric(
horizontal: responsiveValue(mobile: 16, tablet: 24, desktop: 32).w,
vertical: responsiveValue(mobile: 12, tablet: 16, desktop: 20).h,
),
Ultra-Wide Screen Support #
On ultra-wide monitors (3840px+), UI elements can become excessively large. Use maxWidth to cap scaling:
Without maxWidth #
Screen: 3840px โ Scale: 3840/1920 = 2.0ร
A 200px button becomes 400px (too large!)
With maxWidth: 1920 #
Screen: 3840px โ Effective: 1920px โ Scale: 1.0ร
A 200px button stays 200px (centered on screen)
Implementation #
PxResponsiveWrapper(
config: PxResponsiveConfig(
desktop: Size(1920, 1080),
maxWidth: 1920, // โ Add this
),
child: MyApp(),
)
Centering Content #
When using maxWidth, center your content for the best appearance:
Scaffold(
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 1920),
child: YourContent(),
),
),
)
Complete Example #
import 'package:flutter/material.dart';
import 'package:px_responsive/px_responsive.dart';
void main() {
runApp(
PxResponsiveWrapper(
config: const PxResponsiveConfig(
desktop: Size(1920, 1080),
tablet: Size(834, 1194),
mobile: Size(375, 812),
maxWidth: 1920,
),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'px_responsive Demo',
style: TextStyle(fontSize: 20.sp),
),
// Show menu button only on mobile
leading: PxResponsiveVisibility.mobile(
child: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
),
),
body: Row(
children: [
// Sidebar only on desktop
PxResponsiveVisibility.desktop(
child: Container(
width: 250.w,
color: Colors.grey[200],
child: const Center(child: Text('Sidebar')),
),
),
// Main content
Expanded(
child: Padding(
padding: EdgeInsets.all(
responsiveValue(mobile: 16, tablet: 24, desktop: 32).w,
),
child: PxResponsiveValue<int>(
mobile: 1,
tablet: 2,
desktop: 3,
builder: (context, columns) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
spacing: 16.w,
childAspectRatio: 1.2,
),
itemCount: 9,
itemBuilder: (context, index) => Card(
child: Padding(
padding: EdgeInsets.all(16.r),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star, size: 40.sp),
SizedBox(height: 8.h),
Text(
'Item $index',
style: TextStyle(fontSize: 16.sp),
),
],
),
),
),
);
},
),
),
),
],
),
// Bottom navigation only on mobile
bottomNavigationBar: PxResponsiveVisibility.mobile(
child: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
),
);
}
}
WASM Support #
This package is fully compatible with Flutter's WebAssembly (WASM) compilation target. It uses only:
- Pure Dart code
- Standard Flutter widgets
- No platform-specific plugins
Simply compile your Flutter web app to WASM as usual:
flutter build web --wasm
License #
This project is licensed under the MIT License - see the LICENSE file for details.
๐ค About the Author #
๐ฅ Contributors #
We appreciate all contributions to this project!
Support #
If you find this package helpful, please give it a โญ on GitHub!
For bugs or feature requests, please open an issue.