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

example/lib/main.dart

import 'package:flutter/widgets.dart';
import 'package:px_responsive/src/px_responsive_config.dart';
import 'package:px_responsive/src/px_responsive_core.dart';
import 'package:px_responsive/px_responsive.dart';

// ============================================================================
// RESPONSIVE BUILDER - Build different widgets per device type
// ============================================================================

/// A builder widget that builds different layouts based on device type.
///
/// Use this widget when you need completely different widget trees
/// for different device types (mobile, tablet, desktop).
///
/// ## When to Use
///
/// - Different page layouts per device (e.g., single column vs multi-column)
/// - Different navigation patterns (bottom nav vs side nav)
/// - Completely different UI structures that can't be achieved with just scaling
///
/// ## Basic Example
///
/// ```dart
/// PxResponsiveBuilder(
///   mobile: (context) => const MobileLayout(),
///   tablet: (context) => const TabletLayout(),
///   desktop: (context) => const DesktopLayout(),
/// )
/// ```
///
/// ## With Fallback
///
/// If [tablet] or [desktop] are not provided, they fall back to smaller sizes:
///
/// ```dart
/// PxResponsiveBuilder(
///   mobile: (context) => const MobileLayout(),
///   desktop: (context) => const DesktopLayout(),
///   // Tablet will use MobileLayout
/// )
///
/// PxResponsiveBuilder(
///   mobile: (context) => const MobileLayout(),
///   // Both tablet and desktop will use MobileLayout
/// )
/// ```
///
/// ## Real-World Example
///
/// ```dart
/// PxResponsiveBuilder(
///   mobile: (context) => Scaffold(
///     appBar: AppBar(title: const Text('My App')),
///     drawer: const NavigationDrawer(),
///     body: const ContentList(),
///     bottomNavigationBar: const BottomNavBar(),
///   ),
///   tablet: (context) => Scaffold(
///     body: Row(
///       children: [
///         const NavigationRail(),
///         const Expanded(child: ContentList()),
///       ],
///     ),
///   ),
///   desktop: (context) => Scaffold(
///     body: Row(
///       children: [
///         const SideNavigation(expanded: true),
///         const Expanded(child: ContentList()),
///         const DetailPanel(),
///       ],
///     ),
///   ),
/// )
/// ```
///
/// ## Comparison with responsiveValue
///
/// Use [PxResponsiveBuilder] when you need different widget trees.
/// Use [responsiveValue] when you need different values for the same widget.
///
/// ```dart
/// // Use PxResponsiveBuilder for different structures
/// PxResponsiveBuilder(
///   mobile: (context) => ListView(...),
///   desktop: (context) => GridView(...),
/// )
///
/// // Use responsiveValue for different values
/// Container(
///   width: responsiveValue(mobile: 100.w, desktop: 200.w),
/// )
/// ```
///
/// See also:
/// - [PxResponsiveValue] for providing different values (not widgets)
/// - [PxResponsiveVisibility] for showing/hiding widgets
/// - [responsiveValue] for inline value selection
class PxResponsiveBuilder extends StatelessWidget {
  /// Builder function for mobile layout.
  ///
  /// Called when screen width < [PxResponsiveConfig.mobileBreakpoint].
  ///
  /// This is required and serves as the fallback for [tablet] and [desktop]
  /// if they are not provided.
  final WidgetBuilder mobile;

  /// Builder function for tablet layout.
  ///
  /// Called when [PxResponsiveConfig.mobileBreakpoint] <= width < [PxResponsiveConfig.tabletBreakpoint].
  ///
  /// If null, falls back to [mobile].
  final WidgetBuilder? tablet;

  /// Builder function for desktop layout.
  ///
  /// Called when screen width >= [PxResponsiveConfig.tabletBreakpoint].
  ///
  /// If null, falls back to [tablet]. If [tablet] is also null, falls back to [mobile].
  final WidgetBuilder? desktop;

  /// Creates a responsive builder widget.
  ///
  /// [mobile] is required and serves as the fallback for other device types.
  ///
  /// ## Parameters
  ///
  /// - [mobile]: Required. Builder for mobile layout.
  /// - [tablet]: Optional. Builder for tablet layout. Falls back to [mobile].
  /// - [desktop]: Optional. Builder for desktop layout. Falls back to [tablet] or [mobile].
  const PxResponsiveBuilder({
    super.key,
    required this.mobile,
    this.tablet,
    this.desktop,
  });

  @override
  Widget build(BuildContext context) {
    final responsive = PxResponsive();

    if (responsive.isDesktop) {
      return (desktop ?? tablet ?? mobile)(context);
    }
    if (responsive.isTablet) {
      return (tablet ?? mobile)(context);
    }
    return mobile(context);
  }
}

// ============================================================================
// RESPONSIVE VALUE - Provide different values per device type
// ============================================================================

/// A widget that provides different values based on device type.
///
/// Similar to [PxResponsiveBuilder] but instead of building different widgets,
/// it provides a value to a builder function. This is useful when the widget
/// structure is the same but configuration differs.
///
/// ## When to Use
///
/// - Same widget structure with different configuration per device
/// - Grid column counts
/// - Axis counts for lists
/// - Spacing values
/// - Any typed value that varies by device
///
/// ## Basic Example
///
/// ```dart
/// PxResponsiveValue<int>(
///   mobile: 2,
///   tablet: 3,
///   desktop: 4,
///   builder: (context, columnCount) {
///     return GridView.count(
///       crossAxisCount: columnCount,
///       children: items,
///     );
///   },
/// )
/// ```
///
/// ## With Complex Types
///
/// ```dart
/// PxResponsiveValue<EdgeInsets>(
///   mobile: const EdgeInsets.all(8),
///   tablet: const EdgeInsets.all(16),
///   desktop: const EdgeInsets.all(24),
///   builder: (context, padding) {
///     return Padding(
///       padding: padding,
///       child: const Content(),
///     );
///   },
/// )
/// ```
///
/// ## With Custom Objects
///
/// ```dart
/// class LayoutConfig {
///   final int columns;
///   final double spacing;
///   final bool showSidebar;
///
///   const LayoutConfig({
///     required this.columns,
///     required this.spacing,
///     required this.showSidebar,
///   });
/// }
///
/// PxResponsiveValue<LayoutConfig>(
///   mobile: const LayoutConfig(columns: 1, spacing: 8, showSidebar: false),
///   tablet: const LayoutConfig(columns: 2, spacing: 12, showSidebar: false),
///   desktop: const LayoutConfig(columns: 3, spacing: 16, showSidebar: true),
///   builder: (context, config) {
///     return Row(
///       children: [
///         if (config.showSidebar) const Sidebar(),
///         Expanded(
///           child: GridView.count(
///             crossAxisCount: config.columns,
///             mainAxisSpacing: config.spacing,
///             crossAxisSpacing: config.spacing,
///             children: items,
///           ),
///         ),
///       ],
///     );
///   },
/// )
/// ```
///
/// ## Fallback Behavior
///
/// Like [PxResponsiveBuilder], values fall back to smaller sizes:
/// - Desktop uses [desktop] ?? [tablet] ?? [mobile]
/// - Tablet uses [tablet] ?? [mobile]
/// - Mobile always uses [mobile]
///
/// ## Comparison with responsiveValue Function
///
/// [PxResponsiveValue] is a widget, [responsiveValue] is a function.
/// Use the function for inline values, use the widget when you need
/// the value in a builder context.
///
/// ```dart
/// // Function (inline)
/// Container(width: responsiveValue(mobile: 100, desktop: 200))
///
/// // Widget (builder context)
/// PxResponsiveValue<int>(
///   mobile: 2,
///   desktop: 4,
///   builder: (context, count) => GridView.count(crossAxisCount: count),
/// )
/// ```
class PxResponsiveValue<T> extends StatelessWidget {
  /// Value for mobile layout.
  ///
  /// This is required and serves as the fallback for [tablet] and [desktop].
  final T mobile;

  /// Value for tablet layout.
  ///
  /// If null, falls back to [mobile].
  final T? tablet;

  /// Value for desktop layout.
  ///
  /// If null, falls back to [tablet]. If [tablet] is also null, falls back to [mobile].
  final T? desktop;

  /// Builder function that receives the appropriate value for current device.
  ///
  /// This is called with the selected value based on device type.
  final Widget Function(BuildContext context, T value) builder;

  /// Creates a responsive value widget.
  ///
  /// ## Parameters
  ///
  /// - [mobile]: Required. Value for mobile devices.
  /// - [tablet]: Optional. Value for tablets. Falls back to [mobile].
  /// - [desktop]: Optional. Value for desktop. Falls back to [tablet] or [mobile].
  /// - [builder]: Required. Builder that receives the selected value.
  const PxResponsiveValue({
    super.key,
    required this.mobile,
    this.tablet,
    this.desktop,
    required this.builder,
  });

  @override
  Widget build(BuildContext context) {
    final responsive = PxResponsive();
    T value;

    if (responsive.isDesktop) {
      value = desktop ?? tablet ?? mobile;
    } else if (responsive.isTablet) {
      value = tablet ?? mobile;
    } else {
      value = mobile;
    }

    return builder(context, value);
  }
}

// ============================================================================
// RESPONSIVE VISIBILITY - Show/hide widgets per device type
// ============================================================================

/// A widget that conditionally shows or hides its child based on device type.
///
/// Use this for showing different UI elements on different devices,
/// such as navigation patterns, optional panels, or device-specific features.
///
/// ## Basic Example
///
/// ```dart
/// // Only show sidebar on tablet and desktop
/// PxResponsiveVisibility(
///   visibleOnMobile: false,
///   visibleOnTablet: true,
///   visibleOnDesktop: true,
///   child: const Sidebar(),
/// )
/// ```
///
/// ## Named Constructors
///
/// For common patterns, use the named constructors:
///
/// ```dart
/// // Only on mobile
/// PxResponsiveVisibility.mobile(child: const BottomNavBar())
///
/// // Only on tablet
/// PxResponsiveVisibility.tablet(child: const TabletMenu())
///
/// // Only on desktop
/// PxResponsiveVisibility.desktop(child: const DesktopToolbar())
///
/// // Tablet and desktop (not mobile)
/// PxResponsiveVisibility.tabletUp(child: const SideNavigation())
///
/// // Mobile and tablet (not desktop)
/// PxResponsiveVisibility.tabletDown(child: const CompactHeader())
/// ```
///
/// ## With Replacement Widget
///
/// Show an alternative widget when hidden:
///
/// ```dart
/// PxResponsiveVisibility(
///   visibleOnMobile: false,
///   visibleOnTablet: true,
///   visibleOnDesktop: true,
///   replacement: const MobileDrawerButton(),
///   child: const SideNavigation(),
/// )
/// ```
///
/// ## Maintaining State
///
/// Use [maintainState] to keep the widget in the tree but hidden:
///
/// ```dart
/// PxResponsiveVisibility.desktop(
///   maintainState: true, // Widget stays in tree, just hidden
///   child: const ExpensiveWidget(),
/// )
/// ```
///
/// This is useful when:
/// - The child has state you want to preserve across device changes
/// - The child is expensive to rebuild
/// - You're animating visibility changes
///
/// ## Real-World Example
///
/// ```dart
/// Scaffold(
///   body: Row(
///     children: [
///       // Side navigation only on tablet and desktop
///       PxResponsiveVisibility.tabletUp(
///         child: const SideNavigation(),
///       ),
///       // Main content
///       const Expanded(child: MainContent()),
///       // Detail panel only on desktop
///       PxResponsiveVisibility.desktop(
///         child: const DetailPanel(),
///       ),
///     ],
///   ),
///   // Bottom navigation only on mobile
///   bottomNavigationBar: PxResponsiveVisibility.mobile(
///     child: const BottomNavBar(),
///   ),
/// )
/// ```
///
/// ## Visibility Summary
///
/// | Constructor | Mobile | Tablet | Desktop |
/// |-------------|--------|--------|---------|
/// | `.mobile()` | ✅ | ❌ | ❌ |
/// | `.tablet()` | ❌ | ✅ | ❌ |
/// | `.desktop()` | ❌ | ❌ | ✅ |
/// | `.tabletUp()` | ❌ | ✅ | ✅ |
/// | `.tabletDown()` | ✅ | ✅ | ❌ |
///
/// See also:
/// - [PxResponsiveBuilder] for completely different layouts
/// - [PxResponsiveValue] for different values with same structure
class PxResponsiveVisibility extends StatelessWidget {
  /// The child widget to conditionally show.
  final Widget child;

  /// Whether to show the child on mobile devices.
  ///
  /// Default: `true`
  final bool visibleOnMobile;

  /// Whether to show the child on tablet devices.
  ///
  /// Default: `true`
  final bool visibleOnTablet;

  /// Whether to show the child on desktop devices.
  ///
  /// Default: `true`
  final bool visibleOnDesktop;

  /// Optional replacement widget when child is hidden.
  ///
  /// If null, uses `SizedBox.shrink()` (takes no space).
  ///
  /// ## Example
  ///
  /// ```dart
  /// PxResponsiveVisibility.tabletUp(
  ///   replacement: const IconButton(
  ///     icon: Icon(Icons.menu),
  ///     onPressed: openDrawer,
  ///   ),
  ///   child: const SideNavigation(),
  /// )
  /// ```
  final Widget? replacement;

  /// Whether to maintain the child's state when hidden.
  ///
  /// If `true`, wraps child in [Offstage] instead of replacing it.
  /// The child remains in the widget tree but is not painted or
  /// interactive.
  ///
  /// Default: `false`
  ///
  /// ## When to Use
  ///
  /// - Child has state you want to preserve
  /// - Child is expensive to rebuild
  /// - You need to animate visibility transitions
  ///
  /// ## Example
  ///
  /// ```dart
  /// PxResponsiveVisibility.desktop(
  ///   maintainState: true,
  ///   child: const VideoPlayer(), // Keeps playing when hidden
  /// )
  /// ```
  final bool maintainState;

  /// Creates a responsive visibility widget.
  ///
  /// By default, the child is visible on all device types.
  /// Set [visibleOnMobile], [visibleOnTablet], or [visibleOnDesktop]
  /// to `false` to hide on specific devices.
  ///
  /// ## Parameters
  ///
  /// - [child]: Required. The widget to conditionally show.
  /// - [visibleOnMobile]: Show on mobile. Default: `true`
  /// - [visibleOnTablet]: Show on tablet. Default: `true`
  /// - [visibleOnDesktop]: Show on desktop. Default: `true`
  /// - [replacement]: Widget to show when hidden. Default: `SizedBox.shrink()`
  /// - [maintainState]: Keep child in tree when hidden. Default: `false`
  const PxResponsiveVisibility({
    super.key,
    required this.child,
    this.visibleOnMobile = true,
    this.visibleOnTablet = true,
    this.visibleOnDesktop = true,
    this.replacement,
    this.maintainState = false,
  });

  /// Creates a visibility widget that only shows on mobile.
  ///
  /// Visible: Mobile ✅ | Tablet ❌ | Desktop ❌
  ///
  /// ## Example
  ///
  /// ```dart
  /// PxResponsiveVisibility.mobile(
  ///   child: const BottomNavigationBar(),
  /// )
  /// ```
  const PxResponsiveVisibility.mobile({
    super.key,
    required this.child,
    this.replacement,
    this.maintainState = false,
  })  : visibleOnMobile = true,
        visibleOnTablet = false,
        visibleOnDesktop = false;

  /// Creates a visibility widget that only shows on tablet.
  ///
  /// Visible: Mobile ❌ | Tablet ✅ | Desktop ❌
  ///
  /// ## Example
  ///
  /// ```dart
  /// PxResponsiveVisibility.tablet(
  ///   child: const TabletSpecificWidget(),
  /// )
  /// ```
  const PxResponsiveVisibility.tablet({
    super.key,
    required this.child,
    this.replacement,
    this.maintainState = false,
  })  : visibleOnMobile = false,
        visibleOnTablet = true,
        visibleOnDesktop = false;

  /// Creates a visibility widget that only shows on desktop.
  ///
  /// Visible: Mobile ❌ | Tablet ❌ | Desktop ✅
  ///
  /// ## Example
  ///
  /// ```dart
  /// PxResponsiveVisibility.desktop(
  ///   child: const DesktopToolbar(),
  /// )
  /// ```
  const PxResponsiveVisibility.desktop({
    super.key,
    required this.child,
    this.replacement,
    this.maintainState = false,
  })  : visibleOnMobile = false,
        visibleOnTablet = false,
        visibleOnDesktop = true;

  /// Creates a visibility widget that shows on tablet and desktop (not mobile).
  ///
  /// Visible: Mobile ❌ | Tablet ✅ | Desktop ✅
  ///
  /// Use for elements that need more screen space, like side navigation.
  ///
  /// ## Example
  ///
  /// ```dart
  /// PxResponsiveVisibility.tabletUp(
  ///   child: const SideNavigation(),
  /// )
  /// ```
  const PxResponsiveVisibility.tabletUp({
    super.key,
    required this.child,
    this.replacement,
    this.maintainState = false,
  })  : visibleOnMobile = false,
        visibleOnTablet = true,
        visibleOnDesktop = true;

  /// Creates a visibility widget that shows on mobile and tablet (not desktop).
  ///
  /// Visible: Mobile ✅ | Tablet ✅ | Desktop ❌
  ///
  /// Use for mobile-oriented elements that aren't needed on desktop.
  ///
  /// ## Example
  ///
  /// ```dart
  /// PxResponsiveVisibility.tabletDown(
  ///   child: const TouchOptimizedControls(),
  /// )
  /// ```
  const PxResponsiveVisibility.tabletDown({
    super.key,
    required this.child,
    this.replacement,
    this.maintainState = false,
  })  : visibleOnMobile = true,
        visibleOnTablet = true,
        visibleOnDesktop = false;

  @override
  Widget build(BuildContext context) {
    final responsive = PxResponsive();
    bool isVisible;

    if (responsive.isDesktop) {
      isVisible = visibleOnDesktop;
    } else if (responsive.isTablet) {
      isVisible = visibleOnTablet;
    } else {
      isVisible = visibleOnMobile;
    }

    // If maintainState is true, use Offstage to keep widget in tree
    if (maintainState) {
      return Offstage(
        offstage: !isVisible,
        child: child,
      );
    }

    // Otherwise, swap between child and replacement
    return isVisible ? child : (replacement ?? const SizedBox.shrink());
  }
}
1
likes
140
points
22
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