Styling with .tw
The accumulator model — chain typed utilities onto any widget; they collect into one immutable style with last-wins conflict resolution.
.tw begins a style chain on any widget. Each call adds a typed utility; the chain
accumulates into one immutable FwStyle and renders as a single styled node.
final t = context.fw;
Text('Save').tw.px(4).py(2).bg(t.colors.primary).text(t.colors.primaryForeground).rounded(t.radii.md);Declarative styling
In plain Flutter, styling is imperative — you assemble the widget tree by hand, choosing each wrapper and its nesting order:
// Imperative — you build the structure.
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: DecoratedBox(
decoration: BoxDecoration(color: t.colors.primary, borderRadius: BorderRadius.circular(6)),
child: child,
),
);.tw makes styling declarative: you declare what you want and the engine resolves how —
which primitives to emit, in what order, and how to handle conflicts. The chain above is just:
// Declarative — you declare intent.
child.tw.px(4).py(2).bg(t.colors.primary).rounded(6);The chain is only syntax; semantically it's an unordered set of declarations. Order doesn't change the structure — only conflicts resolve by order (last-wins, below). That's the same shift Tailwind made for the web: stop writing the how (hand-built rules/wrappers), declare the what (utilities).
The accumulator model
A .tw chain isn't a stack of wrappers — the utilities merge into one style that resolves lazily.
Two things follow from that:
- Last-wins conflict resolution. If you set the same property twice, the last call wins.
.px(4).px(2)resolves to horizontal padding of 2 units (8 px), not 6 — and emits no duplicate wrapper. - Lazy resolution. The style resolves against the active interaction states and the
viewport/container size at build time, which is what makes states
(
hover:/focus:/…) and breakpoints (sm:/md:/…) first-class rather than bolted on.
Units
Spacing and sizing are in utility units of 4 logical pixels, exactly like Tailwind's default
scale. p(4) → p-4 → 16 px. w(10) → 40 px.
The utility families
Every single-box utility lives on .tw. They're documented in full in the Utility reference;
here's the map (multi-child layout — direction, gap, positioning — is not .tw; see
Layout):
Spacing & sizing
p px m · w h size min/max · wFraction · aspect
Backgrounds, borders & radius
bg · gradients · bgImage · border borderS · rounded* · clip
Typography
text textSize weight leading tracking · font* · lineClamp truncate
Effects & filters
shadow* ring opacity blur blendMode · color filters · fit
Transforms & interactivity
scale rotate skew translate · 3D · cursor · visibility
Composing it
final t = context.fw;
Text('Card title')
.tw
.textSize(FwFontSize.lg.px)
.weight(600)
.text(t.colors.cardForeground); // a styled Text
const SizedBox()
.tw
.w(64)
.h(40)
.bg(t.colors.muted)
.rounded(t.radii.lg)
.shadow(t.shadows.sm); // a styled box