πŸ“± simple_responsive_layout

A minimal, powerful Flutter package for building responsive layouts, widgets, values, and visibility without the bloat.

✨ Features

  • 🧩 ResponsiveChild – Switch a single widget's layout based on device type
  • πŸ—οΈ ResponsiveLayout – Switch container layouts (Row ↔ Column ↔ Grid) based on device
  • πŸ”’ ResponsiveValue – Dynamically select values based on device type
  • πŸ«₯ ResponsiveVisibility – Show/hide widgets on specific device types
  • πŸ“ ResponsiveSizedBox – Flexible sizing based on device type
  • πŸ›£οΈ ResponsivePadding – Adaptive padding based on device type
  • 🧰 ResponsiveBuilder – Full control with custom builder and responsive info
  • βš™οΈ ResponsiveSettings – Customize breakpoints
  • πŸ”„ ResponsiveInfo – Complete device information with helpful properties
  • ❌ Zero dependencies β€” pure Flutter implementation
  • ⚑ Extremely lightweight and fast

πŸš€ Installation

Add this to your pubspec.yaml:

dependencies:
  simple_responsive_layout: ^1.0.0

Then run:

flutter pub get

πŸ› οΈ Usage

Basic Setup (Optional)

While the package works with default settings, you can customize breakpoints globally:

void main() {
  // Optional: Configure global responsive settings
  ResponsiveSettings.defaultSettings = ResponsiveSettings(
    mobileWidth: 600,     // breakpoint between mobile and tablet
    tabletWidth: 1024,    // breakpoint between tablet and desktop
  );
  
  runApp(const MyApp());
}

Provider Setup (Optional)

For more advanced projects, you can use the provider to manage settings:

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ResponsiveSettingsProvider(
      settings: ResponsiveSettings(
        mobileWidth: 550,
        tabletWidth: 1200,
      ),
      child: MaterialApp(
        title: 'My Responsive App',
        home: HomePage(),
      ),
    );
  }
}

ResponsiveChild

Switch a single widget's layout based on device:

ResponsiveChild(
  child: Text('Hello World'),
  mobileChild: (context, child) => Center(
    child: child,
  ),
  tabletChild: (context, child) => Align(
    alignment: Alignment.centerLeft,
    child: child,
  ),
  desktopChild: (context, child) => Container(
    padding: EdgeInsets.all(16),
    child: child,
  ),
)

Or build the child dynamically:

ResponsiveChild(
  childBuilder: (context, info) => Text(
    info.isMobileOnly 
      ? 'Mobile View' 
      : info.isTabletOnly 
          ? 'Tablet View' 
          : 'Desktop View',
    style: TextStyle(
      fontSize: info.isMobile ? 16 : info.isTablet ? 20 : 24,
    ),
  ),
)

ResponsiveLayout

Switch how a group of widgets are arranged:

ResponsiveLayout(
  children: [
    Card(child: Text('Item 1')),
    Card(child: Text('Item 2')),
    Card(child: Text('Item 3')),
  ],
  mobileChild: (context, children) => Column(
    children: children,
  ),
  tabletChild: (context, children) => Row(
    children: children,
  ),
  desktopChild: (context, children) => Wrap(
    spacing: 16,
    runSpacing: 16,
    children: children,
  ),
)

Or dynamically generate different children based on device:

ResponsiveLayout(
  childrenBuilder: (context, info) => [
    Text('Header'),
    if (info.isMobile) 
      const SizedBox(height: 20),
    if (!info.isMobile) 
      const SizedBox(width: 20),
    Text('Content'),
    if (info.isDesktopOnly) 
      Text('Desktop-only content'),
  ],
  mobileChild: (context, children) => Column(children: children),
  tabletChild: (context, children) => Row(children: children),
)

ResponsiveValue

Choose dynamic values based on device type:

// In a widget:
final fontSize = ResponsiveValue<double>(
  defaultValue: 16.0,
  mobileValue: 14.0,
  tabletValue: 16.0,
  desktopValue: 18.0,
).value(context);

Text(
  'Hello World',
  style: TextStyle(fontSize: fontSize),
)

Or with custom logic:

final padding = ResponsiveValue<EdgeInsets>(
  defaultValue: EdgeInsets.all(16),
  valueBuilder: (context, info) {
    // Full custom logic for any scenario
    if (info.isMobileOnly) {
      return EdgeInsets.all(8);
    } else if (info.isTabletOnly) {
      return EdgeInsets.symmetric(horizontal: 24, vertical: 16);
    } else {
      return EdgeInsets.symmetric(horizontal: 32, vertical: 20);
    }
  },
).value(context);

ResponsiveVisibility

Show or hide widgets for different device types:

// Only visible on mobile and tablet
ResponsiveVisibility(
  deviceTypes: [ResponsiveDeviceType.mobile, ResponsiveDeviceType.tablet],
  child: FloatingActionButton(
    onPressed: () {},
    child: Icon(Icons.add),
  ),
)

// Only visible on desktop
ResponsiveVisibility(
  deviceTypes: [ResponsiveDeviceType.desktop],
  child: Row(
    children: [
      Icon(Icons.settings),
      Text('Advanced Settings'),
    ],
  ),
)

ResponsiveSizedBox

Different sizes for spacing based on device:

// Vertical gap that changes based on device
ResponsiveSizedBox(
  defaultSize: const Size(double.infinity, 16),
  mobileSize: const Size(double.infinity, 8),
  tabletSize: const Size(double.infinity, 16),
  desktopSize: const Size(double.infinity, 24),
)

// Or with child
ResponsiveSizedBox(
  mobileSize: const Size(double.infinity, 100),
  tabletSize: const Size(300, double.infinity),
  desktopSize: const Size(500, double.infinity),
  child: Text('Sized content'),
)

ResponsivePadding

Adaptive padding based on device:

ResponsivePadding(
  defaultSize: EdgeInsets.all(16),
  mobileSize: EdgeInsets.all(8),
  tabletSize: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
  desktopSize: EdgeInsets.symmetric(horizontal: 32, vertical: 20),
  child: Card(
    child: Text('Content with device-specific padding'),
  ),
)

ResponsiveBuilder

For complete custom control:

ResponsiveBuilder(
  builder: (context, info) {
    // Full access to device info for custom layouts
    if (info.isMobileOnly) {
      return ListView(
        children: [
          Text('Mobile Layout'),
          // Mobile-specific widgets
        ],
      );
    } else if (info.isTabletOnly) {
      return Row(
        children: [
          Expanded(
            flex: 1,
            child: Text('Sidebar'),
          ),
          Expanded(
            flex: 3,
            child: Text('Main Content'),
          ),
        ],
      );
    } else {
      return Row(
        children: [
          Expanded(
            flex: 1,
            child: Text('Sidebar'),
          ),
          Expanded(
            flex: 4,
            child: Text('Main Content'),
          ),
          Expanded(
            flex: 1,
            child: Text('Additional Sidebar'),
          ),
        ],
      );
    }
  },
)

🧠 Understanding ResponsiveInfo

The ResponsiveInfo class gives you complete information about the current device:

ResponsiveBuilder(
  builder: (context, info) {
    // Direct device type
    print('Device type: ${info.deviceType}');
    
    // Equality checks
    if (info.deviceType == ResponsiveDeviceType.mobile) {
      // Do something
    }
    
    // Properties
    print('Is mobile: ${info.isMobile}');
    print('Is tablet: ${info.isTablet}');
    print('Is desktop: ${info.isDesktop}');

    return Text('Device type: ${info.deviceType}');
  },
)

βš™οΈ Customizing Settings

Global Settings

// Change the global defaults
ResponsiveSettings.defaultSettings = ResponsiveSettings(
  mobileWidth: 480,    // Smaller mobile breakpoint
  tabletWidth: 1024,   // Standard tablet breakpoint
);

Local Settings Override

You can override settings for specific widgets:

final myCustomSettings = ResponsiveSettings(
  mobileWidth: 400,
  tabletWidth: 800,
);

// Apply to a specific widget
ResponsiveChild(
  settings: myCustomSettings,
  child: Text('Custom breakpoints apply here'),
  // ...
)

πŸ“ How Device Detection Works

Simple MediaQuery width-based detection:

Device Type Width Range
Mobile < mobileWidth (default: 600px)
Tablet >= mobileWidth && < tabletWidth
Desktop >= tabletWidth (default: 1024px)

πŸ† Complete Example

import 'package:flutter/material.dart';
import 'package:simple_responsive_layout/simple_responsive_layout.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Simple Responsive Layout Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: ResponsiveSettingsProvider(
        // Optional - Use ResponsiveSettingsProvider to provide the breakpoints to the widget tree
        settings: ResponsiveSettings(mobileWidth: 600, tabletWidth: 1200),
        child: const ResponsiveHomePage(),
      ),
    );
  }
}

class ResponsiveHomePage extends StatelessWidget {
  const ResponsiveHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        width: double.infinity,
        // Use ResponsiveValue to set the background color based on device type
        color: ResponsiveValue<Color>(
          defaultValue: Colors.grey,
          mobileValue: Colors.blue.shade300,
          tabletValue: Colors.green.shade300,
          desktopValue: Colors.purple.shade300,
        ).value(context),
        child: SafeArea(
          child: Column(
            children: [
              const SizedBox(height: 20),
              // Use ResponsiveBuilder to get the device type and display it
              ResponsiveBuilder(
                builder: (context, info) {
                  return Text(
                    'Device Type: ${info.deviceType.name.toUpperCase()}',
                    style: const TextStyle(
                      fontSize: 24,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  );
                },
              ),
              const SizedBox(height: 20),
              ResponsiveLayout(
                defaultChild: (context, children) => Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: children,
                  ),
                ),
                tabletChild: (context, children) =>
                    Center(child: Wrap(children: children)),
                desktopChild: (context, children) => Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: children,
                ),
                children: const [
                  BoxWidget(label: 'Box 1'),
                  BoxWidget(label: 'Box 2'),
                  BoxWidget(label: 'Box 3'),
                ],
              ),
              //Mobile only widget
              const ResponsiveVisibility(
                deviceTypes: [ResponsiveDeviceType.mobile],
                child: Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(
                    "This text is only visible on mobile",
                    style: TextStyle(fontSize: 20, color: Colors.white),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class BoxWidget extends StatelessWidget {
  final String label;
  const BoxWidget({super.key, required this.label});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
      ),
      child: ResponsiveSizedBox(
        mobileSize: const Size(120, 120),
        tabletSize: const Size(170, 170),
        desktopSize: const Size(220, 220),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text(
              label,
              // Use ResponsiveValue to set the font size in a text style based on device type
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: const ResponsiveValue<double>(
                  defaultValue: 12,
                  mobileValue: 40,
                  tabletValue: 18,
                  desktopValue: 25,
                ).value(context),
              ),
            ),
            ResponsiveVisibility(
              // Use ResponsiveVisibility to show/hide widgets based on device type
              deviceTypes: const [
                ResponsiveDeviceType.tablet,
                ResponsiveDeviceType.desktop,
              ],
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  "This is only visible on desktop and tablet - larger text on desktop.",
                  textAlign: TextAlign.center,
                  // Use ResponsiveValue to set an entire text style based on device type
                  style: const ResponsiveValue(
                    defaultValue: TextStyle(fontSize: 14),
                    desktopValue: TextStyle(fontSize: 20),
                  ).value(context),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

πŸ“„ License

MIT License.
Free for personal and commercial use.

❀️ Why simple_responsive_layout?

  • No unnecessary complexity - Lightweight yet powerful
  • Full control when you need it - Override anything
  • Tiny package size - No dependency bloat
  • Pure Flutter philosophy - No extras required
  • Perfect for any project size - From small apps to enterprise projects

🎯 Conclusion

Simple. Clean. Powerful Flutter responsiveness.

πŸ“¦ Coming soon

  • Better orientation support (portrait/landscape)
  • AnimatedResponsive widgets for smooth transitions
  • Additional responsive helpers (ResponsiveGridView, etc.)
  • Media query shortcuts for more device metrics
  • Orientation-specific overrides (landscape/portrait)

πŸ›‘οΈ Status

βœ… Production ready
βœ… No known issues
βœ… Backed by real-world Flutter projects

πŸ™Œ Contributing

If you like this package, ⭐️ star it on GitHub, share it, or contribute!