Skip to content

fabrik_layout

pub.dev

Responsive Flutter code tends to accumulate MediaQuery.of(context).size.width > 600 checks, each with its own slightly different threshold. fabrik_layout resolves the width once, classifies it, and exposes the result anywhere below:

if (context.layout.isMobile) { … }
final columns = context.layout.value<int>(mobile: 1, tablet: 2, desktop: 3);

Classification is by width, not by platform: a narrow window on a desktop is mobile, which is almost always what layout code means.

dependencies:
fabrik_layout: ^1.2.0
import 'package:fabrik_layout/fabrik_layout.dart';

Wrap your app once, inside MaterialApp.builder:

MaterialApp(
builder: (context, child) => FabrikLayout(child: child!),
home: const HomePage(),
);

Then read layout data anywhere below it:

Widget build(BuildContext context) {
final layout = context.layout;
return Padding(
padding: EdgeInsets.all(layout.isMobile ? 16 : 32),
child: layout.isMobile ? const MobileNav() : const SidebarNav(),
);
}

value<T>() is usually clearer than branching on isMobile. Only mobile is required; wider categories fall back to the next smaller value:

final padding = context.layout.value<double>(
mobile: 16,
tablet: 24,
desktop: 32,
);
final columns = context.layout.value<int>(
mobile: 1,
tablet: 2,
desktop: 3,
largeDesktop: 4,
);
// Falls back to the mobile value on every wider screen
final showLabels = context.layout.value<bool>(mobile: false, desktop: true);

Because the fallback cascades, you can describe only the breakpoints you care about and ignore the rest.

CategoryDefault widthisDesktopOrWider
mobile< 600no
tablet600 – 1023no
desktop1024 – 1439yes
largeDesktop>= 1440yes

largeDesktop is a refinement of desktop, not a replacement. Most apps never mention it — value() reuses the desktop value automatically.

Each value is the upper bound of the category below it:

FabrikLayout(
breakpoints: const FabrikBreakpoints(
mobile: 480, // mobile is < 480
tablet: 768, // tablet is 480 – 767
desktop: 1280, // desktop is 768 – 1279, largeDesktop is >= 1280
),
child: child!,
);

Values must increase; FabrikBreakpoints asserts mobile < tablet < desktop.

FabrikLayout classifies using the width it is given. Inside a bounded box that is the box, which is what you want for a split-pane layout:

Row(
children: [
const SizedBox(width: 280, child: Sidebar()),
Expanded(
// Classifies against the remaining width, not the window
child: FabrikLayout(child: const ContentPane()),
),
],
);

Under a horizontally scrolling ancestor the available width is infinite, so there is nothing meaningful to classify. In that case FabrikLayout falls back to the window width, so a phone is still classified as mobile rather than silently becoming the widest category.

Even so, the clearest placement is above any horizontal scrolling — usually once, in MaterialApp.builder.

final layout = context.layout;
if (layout.isTablet && layout.isLandscape) {
return const TwoPaneLayout();
}
return const SinglePaneLayout();

Text sized for a phone can look small on a desktop monitor. Opt in to a per-device minimum scale:

FabrikLayout(
enableTextScaling: true,
child: child!,
);

Defaults are mobile 1.0×, tablet 1.05×, desktop 1.1×; largeDesktop reuses the desktop floor unless you set it. Override any of them:

FabrikLayout(
enableTextScaling: true,
textScaleConfig: const FabrikTextScaleConfig(
mobile: 1.0,
tablet: 1.08,
desktop: 1.15,
largeDesktop: 1.2,
),
child: child!,
);
ParameterTypeDefaultDescription
childWidgetrequiredThe subtree that receives layout data
breakpointsFabrikBreakpointsFabrikBreakpoints()Width thresholds
enableTextScalingboolfalseApply per-device minimum text scale
textScaleConfigFabrikTextScaleConfigFabrikTextScaleConfig()Scale floors, used when scaling is enabled

Returns FabrikLayoutData. Throws a StateError if no FabrikLayout ancestor is present.

MemberTypeDescription
typeFabrikLayoutTypemobile, tablet, desktop, largeDesktop
isMobile / isTabletboolCategory checks
isDesktopboolThe 1024–1439 band only
isLargeDesktopbool>= 1440 only
isDesktopOrWiderboolDesktop or large desktop
isPortrait / isLandscapeboolOrientation
screenSizeSizeWindow size from MediaQuery
textScalerTextScalerThe resolved scaler
value<T>(…)TPer-category value with fallback
copyWith(…)FabrikLayoutDataCopy with replacements
FieldDefaultMeaning
mobile600Upper bound of mobile
tablet1024Upper bound of tablet
desktop1440Upper bound of desktop
FieldDefaultMeaning
mobile1.0Minimum scale on mobile
tablet1.05Minimum scale on tablet
desktop1.1Minimum scale on desktop
largeDesktopnullFalls back to desktop