flutter_screenutil & designSize
— The Thing Nobody Actually Explains
You've probably seen it in someone's main.dart and copy-pasted it without really understanding what's going on. This post fixes that — with visual examples, real comparisons, and the exact reasoning behind every decision.
01 Why Hardcoded Sizes Fail — With Visuals
Every Flutter developer has been here. You spend an afternoon building a screen. You run it on your emulator and it looks perfect — every card the right size, the typography breathing, the spacing generous but not wasteful. You're proud of it.
Then you run it on a colleague's phone — a slightly smaller Android — and the text overflows a button. On a tablet it looks like three business cards lost in a desert. You feel that specific frustration that only UI developers know.
This isn't a Flutter bug. It's a structural problem: when you write width: 320, Flutter renders exactly 320 logical pixels — always, regardless of the screen it's running on. The issue is that 320 logical pixels is a completely different visual proportion on a 360dp-wide phone versus a 430dp-wide phone, let alone a 768dp tablet.
width: 300 hardcoded value — perfect on the developer's 390dp phone, overflowing on a small phone, and completely lost on a tablet.The frustrating thing is this isn't even a mistake you're making. You were handed Figma numbers and you typed them. The problem is that Flutter has no idea those numbers were meant to be proportional to a specific canvas. That context is lost the moment you write a bare number. flutter_screenutil is how you give Flutter that context back.
02 What flutter_screenutil Actually Does
flutter_screenutil is an open-source package maintained by OpenFlutter. It solves one problem with singular focus: it gives you a way to write layout values in the same units your designer used in Figma, and automatically scales them to match any screen at runtime.
The mechanism is simpler than you might think. You tell it what screen size the design was built for. It computes a scale factor by comparing that design size to the actual device screen. Every value you annotate with .w, .h, .sp, or .r gets multiplied by that scale factor before being handed to Flutter's layout engine.
That's it. No magic. No complex algorithm. Just a scale factor and some extension methods.
Think of it like a projector. You design a slide for a 390×844 screen. When the projector (real device) is larger, everything scales up. When it's smaller, everything scales down. The designSize is the original slide dimensions you're telling the projector about.
Despite being one of Flutter's most downloaded packages (over 5,000 GitHub stars), a surprising number of developers treat it as boilerplate — they copy the initialisation code from a tutorial, slap .w everywhere, and move on without understanding why. This leads to subtle bugs that are hard to diagnose because things look almost right, but never quite correct across all devices.
03 The Figma Connection — designSize Explained
When a designer opens Figma and creates a new file, the first thing they do is choose a frame. That frame has a specific width and height. Every element they place — the button, the card, the padding, the headline — is positioned and sized relative to that frame.
The most common Figma frame sizes in Flutter projects are shown below:
designSize. Always ask — don't guess. For new projects without a designer, 390×844 is a solid default.Now here's the key thing that most tutorials skip over. When a designer places a login card that is 320px wide inside a 390px-wide Figma frame, they're making a proportional decision — that card should be about 82% of the screen width. But Flutter doesn't know any of this. Flutter just sees the number 320 and renders 320 logical pixels, regardless of the screen.
designSize is how you communicate that missing proportional intent. You're essentially saying to ScreenUtil: "This layout was designed for a 390dp canvas. Please scale all the values I annotate so that the proportions are preserved on whatever screen the user actually has."
This is the moment it clicks. The proportion is preserved. A button that took up 82% of the Figma frame still takes up 82% of any device's screen. That's what "pixel-perfect" actually means in a responsive context — not that every pixel is the same size, but that every element holds the same visual weight.
04 Side-by-Side Comparison: With vs Without designSize
The fastest way to understand why designSize matters is to see what breaks when you get it wrong. Below is the same app — same Flutter code, same Figma-derived values — shown across three scenarios on two different screen sizes.
Pay particular attention to that middle column — the "wrong designSize" scenario. The values still scale. It doesn't obviously break. But things feel slightly cramped or slightly oversized in ways you can't quite name. This is the most insidious bug because you pass a visual review and it only reveals itself when users from different regions with different devices start reporting it.
Getting the designSize right is not just a technical correctness thing — it's about preserving the designer's visual intent across all devices. That's what separates a good Flutter developer from a great one on UI tasks.
05 Complete Setup — From Zero to Working
This is everything you need to do. You do this once per project and then never touch it again (unless you change your Figma frame size).
Add the package to pubspec.yaml
Then run flutter pub get in your terminal.
dependencies:
flutter:
sdk: flutter
flutter_screenutil: ^5.9.3 # always check pub.dev for the latest version
Note your Figma frame dimensions
In Figma: click the top-level frame (not a child element) → read W and H in the right inspect panel. These are your designSize numbers. Write them down.
Wrap MaterialApp in main.dart
This is the one file change that affects the entire app. ScreenUtilInit must be the ancestor of your MaterialApp.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
// ← YOUR FIGMA FRAME SIZE. This is the most important line in the file.
// Do NOT use your device's screen size here — use the Figma frame.
designSize: const Size(390, 844),
// Prevents fonts from scaling too large on wide tablets.
// Uses min(scaleW, scaleH) for .sp instead of just scaleW.
minTextAdapt: true,
// Supports split-screen mode on Android and foldable phones.
splitScreenMode: true,
builder: (_, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'My App',
home: child,
);
},
child: const HomePage(),
);
}
}
From version 5.9.0+, the builder callback receives two parameters (context, child). If you copied code from an older tutorial that shows a zero-parameter or single-parameter builder, update it to match the pattern above or you'll get a compile error.
Import and use extensions in widget files
In every widget file that needs responsive values, import the package. Then append extensions to your numeric literals. That's the entire workflow.
// Required in every file that uses .w .h .sp .r
import 'package:flutter_screenutil/flutter_screenutil.dart';
// Then just use the extensions directly on your numbers:
Container(
width: 320.w,
height: 56.h,
padding: EdgeInsets.all(16.r),
child: Text('Hello', style: TextStyle(fontSize: 18.sp)),
)
06 Every Extension Explained — .w .h .sp .r .sm
Most tutorials list these and move on. I want to actually explain what each one calculates and when to use which — because using the wrong one is a common source of subtle layout bugs.
.w for both widths and heights of UI elements.Why .w for Heights?
This is the part that surprises most people. Intuitively you'd think .w for widths and .h for heights. But in practice, using .w for element heights produces much better results.
Here's the reason. If a card is 200px tall on a 390dp-wide, 844dp-tall phone, it represents about 24% of screen height. Now open that on a tablet in landscape: the screen might be 1024dp wide but only 600dp tall. If you scaled by height, your card shrinks dramatically. If you scale by width, it grows proportionally with the wider screen and looks natural.
On landscape tablets, card heights will shrink because screen height is smaller even though there's more horizontal space. Cards feel squished and unusable.
Cards scale proportionally with screen width in all orientations. The visual proportion the designer intended is preserved everywhere.
07 Real Login Screen — Figma Values Copied Directly
Let me walk you through the complete Figma-to-Flutter workflow. Your designer hands you a file. You open Figma, click the top-level frame, and it shows 390×844. You click elements and note their values from the inspect panel.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF0F0FF),
body: SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: 19.w, // Figma: 19px screen margin
vertical: 40.h,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Welcome Back 👋',
style: TextStyle(
fontSize: 34.sp, // Figma: 34sp headline
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 8.h),
Text(
'Sign in to your account',
style: TextStyle(
fontSize: 15.sp, // Figma: 15sp subtitle
color: const Color(0xFF6B6860),
),
),
SizedBox(height: 40.h),
// Login Card — 352px wide in Figma → 352.w
Container(
width: 352.w,
padding: EdgeInsets.all(20.r), // Figma: 20px padding
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r), // 16r
boxShadow: [BoxShadow(blurRadius: 24.r,
color: Colors.black.withOpacity(0.06))],
),
child: Column(children: [
_buildInput(label: 'Email address', icon: Icons.email_outlined),
SizedBox(height: 16.h),
_buildInput(label: 'Password', icon: Icons.lock_outline, obscure: true),
SizedBox(height: 28.h),
SizedBox(
width: double.infinity,
height: 56.h, // Figma: button 56px tall
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF5046E4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
),
onPressed: () {},
child: Text('Log In',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w700),
),
),
),
]),
),
],
),
),
),
);
}
Widget _buildInput({
required String label,
required IconData icon,
bool obscure = false,
}) => TextField(
obscureText: obscure,
style: TextStyle(fontSize: 14.sp),
decoration: InputDecoration(
labelText: label,
labelStyle: TextStyle(fontSize: 13.sp),
prefixIcon: Icon(icon, size: 20.r), // .r for icons
contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.r),
),
),
);
}
Look at what happened there. Every single number in that code was copied directly from the Figma inspect panel. Not one value was calculated, estimated, or guessed. The developer's contribution was: read the Figma number, type it, add the right extension. The designer and developer now speak exactly the same coordinate language.
08 Mistakes That Will Bite You
I'm going to be direct about these because they're all things I've seen cause real bugs on real projects — including my own.
Mistake 1 — Setting designSize to your own device screen
This is by far the most common mistake. A developer looks at their emulator (say, 393×852) and sets designSize: const Size(393, 852). Everything looks perfect on their phone. On everyone else's phone it's slightly off. This is because they got lucky — their device matched the design by coincidence, not by correctness.
designSize: const Size(393, 852) — correct only if your Figma designer actually used a 393×852 frame, which you should verify, not assume. Always ask your designer.
Mistake 2 — Missing the import in widget files
The extensions require an explicit import in every file that uses them. If you forget, you'll get a confusing error saying .w is not defined on num. Add import 'package:flutter_screenutil/flutter_screenutil.dart'; to every widget file using extensions.
Mistake 3 — Using ScreenUtil values before initialization
If you define ThemeData or constants outside the ScreenUtilInit builder function, ScreenUtil hasn't run yet and will throw. Keep your theme definitions inside the builder, or use the late-initialization pattern.
Mistake 4 — Not setting minTextAdapt: true
On a 768dp tablet with a 390dp design size, the scale factor is about 1.97. Without minTextAdapt, a 16sp font becomes ~31sp. Your UI turns into a poster. Always include minTextAdapt: true.
minTextAdapt: true — prevents font size explosion on wide screens.
splitScreenMode: true — handles foldable phones and Android split-screen correctly. Both should be in every Flutter project using ScreenUtil.
Mistake 5 — Applying .sp to things that aren't fonts
Some developers use .sp for icons or container sizes thinking it's "the font-aware" extension. It respects accessibility text scaling — which is fine for text but unexpected for layout elements. Use .r for icons and .w for containers.
09 Cheat Sheet & Quick Reference
Save this. These are the things you'll look up most often on a real project.
| Extension | Calculates | Best for | Watch out |
|---|---|---|---|
| 200.w | 200 × (screenW ÷ designW) | Widths, h-padding, gaps, also widget heights | Use for heights of cards/elements too |
| 100.h | 100 × (screenH ÷ designH) | Scroll containers, full-screen height areas | Don't use for widget/card heights |
| 16.sp | 16 × scaleW (+ minTextAdapt) | ALL font sizes, TextStyle.fontSize | Never use .w or .r for text |
| 12.r | 12 × min(scaleW, scaleH) | Border radius, icon sizes, EdgeInsets.all() | Great for uniform padding/radius |
| 14.sm | min(14, 14.sp) | Captions and labels on tablets | Never grows beyond design size |
| 0.5.sw | 0.5 × actual screen width | Half-screen widths, fractional layouts | Screen-relative, ignores designSize |
| 0.3.sh | 0.3 × actual screen height | Hero image heights, fractional screen areas | Screen-relative, ignores designSize |
Rule 1: Set designSize to your Figma frame, not your phone screen.
Rule 2: Copy pixel values from Figma inspect, add .w / .sp / .r — no math needed.
Rule 3: Use .w for widget heights; reserve .h for scroll containers and screen-level spacing.
Once these three rules click, using flutter_screenutil becomes second nature. Your Figma-to-Flutter handoffs will be faster, your layouts will be consistent across all the devices your QA team throws at you, and your designer will stop sending you correction screenshots.
The deeper thing worth appreciating here is that flutter_screenutil isn't solving a Flutter problem — Flutter itself is perfectly capable of handling different screen sizes. It's solving a communication problem between the design world and the engineering world. The designSize is the shared coordinate system, the Rosetta Stone between Figma pixels and Flutter pixels. Once you see it that way, everything else follows naturally.

