px_responsive 0.0.4 copy "px_responsive: ^0.0.4" to clipboard
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.

pub package License: MIT


Table of Contents #


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, isDesktop getters
  • ๐Ÿ–ฅ๏ธ Ultra-Wide Support โ€” Optional maxWidth cap 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.wf width, 80.hf height
  • Hero sections: 100.wf width, 60.hf height

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 tablet is null โ†’ uses mobile
  • If desktop is null โ†’ uses tablet (or mobile if 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 #

Ibrahim El Mourchidi

Ibrahim El Mourchidi

Flutter & Firebase Developer โ€ข Cairo, Egypt

GitHub Follow Email LinkedIn Profile


๐Ÿ‘ฅ 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.

1
likes
160
points
222
downloads

Publisher

verified publisherutanium.org

Weekly Downloads

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.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on px_responsive