Tutorial · 15 min · beginner
Layout that thinks in widgets
Build a profile card from the core layout vocabulary — Row, Column, Stack, Expanded — and learn how DartNative makes flexbox behave exactly like Flutter.
What you'll build — running on device.
Layout is where a “Flutter-compatible” claim gets tested hardest, because
Flutter’s sizing rules are subtle: children fill a Column’s width, a
Stack sizes to its first in-flow child, Expanded divides leftovers.
DartNative runs layout on Yoga (flexbox) but enforces Flutter’s rules on
top of it — so the code below is plain Flutter thinking, rendering as real
native views. You’ll build a profile card that uses the whole core
vocabulary.
What you need
- A project from Your first DartNative app
(or
dn create layout_basics)
Step 1 — The screen skeleton
A white screen with a scrolling body — every section is just a ListView
child, spaced with SizedBox:
Scaffold(
backgroundColor: const Color(0xFFFFFFFF),
brightness: Brightness.light, // declare the light trait
appBar: AppBar(title: const Text('Profile', style: ...)),
body: ListView(
padding: const EdgeInsets.all(16),
children: const [
_Banner(),
SizedBox(height: 56), // clears the avatar overhang
_NameBlock(),
SizedBox(height: 20),
_StatsRow(),
// ...
],
),
)
A childless SizedBox is a spacer — that ports straight from Flutter. (One
DartNative difference to remember: SizedBox(width:, child:) does not
constrain its child — use Container(width:) for that.)
Step 2 — The banner, with Stack
The classic profile header: a gradient banner with the avatar overhanging its bottom edge.
Stack(
clipBehavior: Clip.none, // let the avatar overhang
children: [
// In-flow child: defines the Stack's size.
Container(
height: 140,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFFA60A6), Color(0xFFB5C75E)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
),
),
// Overlay: positioned against the Stack's box, hanging below it.
Positioned(
left: 20,
bottom: -44,
child: Container(
width: 88, height: 88,
decoration: BoxDecoration(
color: const Color(0xFF7C6BAE),
borderRadius: BorderRadius.circular(44),
border: Border.all(color: const Color(0xFFFFFFFF), width: 4),
),
child: const Center(child: Text('L', style: ...)),
),
),
],
)
This is the Flutter Stack contract, reproduced deliberately: the first
in-flow child sizes the Stack, Positioned children float against that
box. (Under the hood, positioned children become absolute-positioned Yoga
nodes — but you never think about that.)
Step 3 — Name row: grow vs hug
Two intents in one Row: the name block should take all available width,
the PRO badge should hug its content.
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Lisa Taylor', style: ...),
SizedBox(height: 2),
Text('@lisa — building native things', style: ...),
],
),
),
Container( // the badge: no Expanded → intrinsic width
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: const Color(0xFFEDF7DD),
borderRadius: BorderRadius.circular(999),
),
child: const Text('PRO', style: ...),
),
],
)
Expanded = grow, plain child = hug. And note BorderRadius.circular(999)
— the Flutter capsule idiom works (it’s clamped internally so it can’t
degenerate).
Step 4 — The stats strip: equal thirds
Row(
children: [
stat('128', 'Posts'), // each is Expanded(child: Column(...))
stat('2.4k', 'Followers'),
stat('310', 'Following'),
],
)
Three Expanded children with no flex argument split the row equally —
the flexbox way to build any N-up strip, no width math.
Step 5 — Proportional buttons
Expanded(flex:) divides space by ratio — Follow gets two thirds, Message
one:
Row(
children: [
Expanded(flex: 2, child: /* Follow button */),
const SizedBox(width: 10),
Expanded(child: /* Message button */),
],
)
Run it and rotate the device: everything re-flows proportionally, because nothing above uses a hard-coded width except the avatar.
Why this is native
The layout math is flexbox, but everything it positions is a real platform view: the texts are UILabels, the scroll is a real scroll view, the gradient is a native layer. DartNative’s reconciler enforces Flutter’s sizing contract on top of Yoga — children fill a Column’s width, Stacks size to their first in-flow child,
double.infinitymeans fill — so layouts port from Flutter without relearning anything, and render without a canvas imitating anything.
The finished code
One file, in the public repo — dn create ., dn run.