tailwind_ui_flutter
Utility-first widgets for Flutter. Stop nesting six widgets to style one card.
Tailwind CSS v4 design tokens + chainable widget extensions for Flutter.
// Before β 18 lines of nested widgets
Padding(
padding: const EdgeInsets.all(16),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: DecoratedBox(
decoration: const BoxDecoration(
color: Colors.white,
boxShadow: [...],
border: Border(...),
),
child: Text(
'Hello',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
),
),
)
// After β 1 readable chain
Text('Hello')
.bold()
.fontSize(TwFontSizes.lg)
.p(TwSpacing.s4)
.bg(TwColors.white)
.rounded(TwRadii.lg.value)
.shadow(TwShadows.md)
Features
| Feature | Details |
|---|---|
| π¨ 242 colors | All 22 Tailwind v4 families Γ 11 shades (50 β 950) |
| π 35 spacing values | s0 β s96 on the 4 px scale, with EdgeInsets getters |
| π€ 13 font sizes | Paired with correct Tailwind line-heights |
| π 10 border radii | none β full, with per-corner BorderRadius getters |
| π 7 shadow presets | sm, base, md, lg, xl, 2xl, none |
| π§ 21 opacity steps | o0 β o100 |
| π± 5 breakpoints | sm, md, lg, xl, 2xl with responsive helpers |
| β‘ Widget extensions | 70+ chainable methods on any Widget |
| βοΈ Text extensions | 50+ chainable methods on Text |
| π§© TwStyle | Reusable styles with merge(), dark variants, apply() |
| π Dark mode | TwVariant.dark + TwTheme adaptive presets |
| π ThemeData integration | 4 ThemeExtension classes |
| Zero dependencies | Flutter SDK only |
Installation
dependencies:
tailwind_ui_flutter: ^0.2.0
flutter pub get
import 'package:tailwind_ui_flutter/tailwind_ui_flutter.dart';
Quick Start
import 'package:flutter/material.dart';
import 'package:tailwind_ui_flutter/tailwind_ui_flutter.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TwTheme(
data: TwThemeData.light(),
child: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Hello, Tailwind Flutter!')
.bold()
.fontSize(TwFontSizes.xl2)
.textColor(TwColors.blue.shade600)
.p(TwSpacing.s6)
.bg(TwColors.blue.shade50)
.rounded(TwRadii.xl.value)
.shadow(TwShadows.lg),
),
);
}
}
Design Tokens
Colors β TwColors
TwColors.blue.shade500 // #3B82F6
TwColors.slate.shade900 // #0F172A
TwColors.emerald.shade500 // #10B981
TwColors.white // #FFFFFF
TwColors.black // #000000
TwColors.transparent // #00000000
All 22 families: slate, gray, zinc, neutral, stone, red, orange,
amber, yellow, lime, green, emerald, teal, cyan, sky, blue,
indigo, violet, purple, fuchsia, pink, rose.
Spacing β TwSpacing
TwSpacing.s4.value // 16.0 px (space-4)
TwSpacing.s4.all // EdgeInsets.all(16)
TwSpacing.s6.x // EdgeInsets.symmetric(horizontal: 24)
TwSpacing.s4.top // EdgeInsets.only(top: 16)
Font Sizes β TwFontSizes
TwFontSizes.lg.size // 18.0 px
TwFontSizes.lg.lineHeight // 28.0 px
TwFontSizes.lg.textStyle // TextStyle(fontSize: 18, height: 1.556)
Border Radius β TwRadii
TwRadii.lg.value // 8.0 px
TwRadii.lg.all // BorderRadius.circular(8)
TwRadii.lg.top // BorderRadius.vertical(top: Radius.circular(8))
TwRadii.full.value // 9999.0 px
Shadows β TwShadows
TwShadows.sm // subtle 1-layer
TwShadows.md // medium elevation (cards)
TwShadows.lg // large (modals)
TwShadows.xl2 // maximum elevation
TwShadows.none // []
Opacity β TwOpacity
TwOpacity.o50 // 0.50
TwOpacity.o75 // 0.75
TwOpacity.o0 // 0.00 (fully transparent)
Widget Extensions
β οΈ Order rule: Apply Text extensions before widget extensions. Once you call
.p(),.bg(), etc. the type becomesWidgetand text-specific methods are no longer available.
Padding
widget.p(TwSpacing.s4) // p-4 β EdgeInsets.all(16)
widget.px(TwSpacing.s6) // px-6 β horizontal 24
widget.py(TwSpacing.s2) // py-2 β vertical 8
widget.pt(TwSpacing.s4) // pt-4 β top only
widget.pb(TwSpacing.s4) // pb-4 β bottom only
widget.pl(TwSpacing.s4) // pl-4 β left only
widget.pr(TwSpacing.s4) // pr-4 β right only
widget.padding(const EdgeInsets.fromLTRB(4, 8, 4, 12)) // custom
Margin
widget.m(TwSpacing.s4) // m-4
widget.mx(TwSpacing.s8) // mx-8
widget.my(TwSpacing.s4) // my-4
widget.mt / mb / ml / mr // individual sides
Background
widget.bg(TwColors.blue.shade500) // solid colour
widget.gradient(LinearGradient(...)) // gradient
Border Radius
widget.rounded() // rounded (4 px)
widget.rounded(TwRadii.lg.value) // rounded-lg (8 px)
widget.rounded(TwRadii.full.value) // rounded-full (pill)
widget.roundedWith(TwRadii.xl.top) // top corners only
widget.clipOval() // circular clip
Shadows
widget.shadow() // default shadow
widget.shadow(TwShadows.lg) // shadow-lg
widget.shadow(TwShadows.none) // remove shadow
Borders
widget.border() // 1px gray-200
widget.border(color: TwColors.blue.shade500, width: 2) // custom
widget.borderTop() // border-t
widget.borderBottom() // border-b
widget.borderLeft() // border-l
widget.borderRight() // border-r
widget.borderX() // border-x
widget.borderY() // border-y
Opacity
widget.opacity(TwOpacity.o50) // opacity-50
widget.opacity(0.75) // any value (auto-clamped)
Sizing
widget.width(256) // w-64
widget.height(128) // h-32
widget.fullWidth() // w-full
widget.fullHeight() // h-full
widget.square(48) // w-12 h-12
widget.size(200, 100) // w + h together
widget.aspectRatio(16/9) // aspect-video
Visibility
widget.invisible() // visibility: hidden (keeps space)
widget.visible(isVisible: false) // hide but keep space
widget.hidden(show: false) // display: none
Layout
widget.center() // mx-auto equivalent
widget.align(Alignment.topRight)
widget.flexible() // flex-1
widget.expanded() // flex-grow fill
Interaction
widget.onTap(() {})
widget.onLongPress(() {})
widget.tooltip('This is a tooltip')
Transform
widget.scale(1.1)
widget.rotate(0.1) // radians
widget.translate(dx: 10, dy: 0)
Text Extensions
Text('Hello')
// Weight
.bold() // font-bold
.semibold() // font-semibold
.medium() // font-medium
.light() // font-light
.thin() // font-thin
// Size (with paired line-height)
.fontSize(TwFontSizes.xl3) // text-3xl
// Color
.textColor(TwColors.blue.shade600)
// Style
.italic()
.underline() // text-decoration: underline
.lineThrough() // text-decoration: line-through
// Transform
.uppercase() // text-transform: uppercase
.lowercase()
.capitalize() // first letter of each word
// Alignment
.center() // text-center
.start() // text-left (RTL-aware)
.end() // text-right (RTL-aware)
.justify()
// Overflow
.ellipsis() // truncate (1 line)
.ellipsis(maxLines: 2)
.maxLines(3)
.noWrap() // whitespace-nowrap
// Spacing
.letterSpacing(0.05)
.lineHeight(1.75)
TwStyle β Reusable Styles
// Define once (like a CSS class)
const cardStyle = TwStyle(
padding: EdgeInsets.all(16), // p-4
backgroundColor: Color(0xFFFFFFFF), // bg-white
borderRadius: BorderRadius.all(Radius.circular(8)), // rounded-lg
shadows: TwShadows.md,
border: Border.fromBorderSide(BorderSide(color: Color(0xFFE5E7EB))),
);
// Apply anywhere
cardStyle.apply(child: myWidget)
// Merge (right wins)
final primaryCard = cardStyle.merge(TwStyle(
backgroundColor: TwColors.blue.shade50,
shadows: TwShadows.lg,
));
// Dark mode
const themedCard = TwStyle(
backgroundColor: Color(0xFFFFFFFF),
variants: {
TwVariant.dark: TwStyle(
backgroundColor: Color(0xFF1E293B),
),
},
);
// Resolve in build()
themedCard.resolve(context).apply(child: myWidget)
TwStyleMerger
// Merge a list of styles (last wins)
final result = TwStyleMerger.mergeAll([base, hover, active]);
// Conditional merge
final style = TwStyleMerger.when(
base: cardStyle,
condition: isSelected,
override: selectedStyle,
);
// Extension on List<TwStyle>
final merged = [s1, s2, s3].merge();
Built-in presets
TwStyle.card // white card, rounded-lg, shadow-md, border
TwStyle.ghost // transparent, rounded-lg, border-only
TwStyle.badge // pill shape, no shadow, tight padding
Theme System
// Wrap app
TwTheme(
data: TwThemeData.light(),
child: MyApp(),
)
// Adaptive (follows system)
TwTheme(
data: TwThemeData.fromBrightness(
MediaQuery.platformBrightnessOf(context),
),
child: MyApp(),
)
// Read in build()
final tw = context.tw; // TwThemeData accessor
final blue = tw.colors.blue.shade500;
final gap = tw.spacing.s4.px;
final br = tw.radii.lg.all;
// Responsive helpers
context.isMobile // < 640 px
context.isTablet // β₯ 768 px
context.isDesktop // β₯ 1024 px
context.isDarkMode // true when Brightness.dark
context.breakpoint // TwBreakpointLabel enum
Integration with Material ThemeData
MaterialApp(
theme: ThemeData(
extensions: TwThemeExtensions.all(data: TwThemeData.light()),
),
darkTheme: ThemeData(
extensions: TwThemeExtensions.all(data: TwThemeData.dark()),
),
)
// Read
final colors = Theme.of(context).extension<TwColorExtension>()!;
final primary = colors.primary.shade500;
Tailwind CSS β tailwind_ui_flutter Cheatsheet
| Tailwind CSS | tailwind_ui_flutter |
|---|---|
bg-blue-500 |
.bg(TwColors.blue.shade500) |
p-4 |
.p(TwSpacing.s4) |
px-6 py-3 |
.px(TwSpacing.s6).py(TwSpacing.s3) |
rounded-lg |
.rounded(TwRadii.lg.value) |
rounded-full |
.rounded(TwRadii.full.value) |
shadow-md |
.shadow(TwShadows.md) |
border |
.border() |
border-b |
.borderBottom() |
opacity-50 |
.opacity(TwOpacity.o50) |
w-full |
.fullWidth() |
text-lg |
.fontSize(TwFontSizes.lg) |
font-bold |
.bold() |
font-semibold |
.semibold() |
text-slate-700 |
.textColor(TwColors.slate.shade700) |
text-center |
.center() |
uppercase |
.uppercase() |
line-through |
.lineThrough() |
invisible |
.invisible() |
aspect-video |
.aspectRatio(16/9) |
dark:bg-slate-800 |
TwVariant.dark in TwStyle.variants |
Architecture
tailwind_ui_flutter/
βββ lib/
β βββ tailwind_ui_flutter.dart β Single import barrel
β βββ src/
β βββ tokens/ β Design tokens (all const)
β β βββ tw_colors.dart β 242 colors
β β βββ tw_spacing.dart β 35 spacing values
β β βββ tw_font_sizes.dart β 13 sizes + line-heights
β β βββ tw_font_weights.dart β 9 weights
β β βββ tw_radii.dart β 10 radii
β β βββ tw_shadows.dart β 7 shadow presets
β β βββ tw_opacity.dart β 21 opacity steps
β β βββ tw_breakpoints.dart β 5 breakpoints
β βββ extensions/ β Extension methods
β β βββ widget_extensions.dart β 70+ methods on Widget
β β βββ text_extensions.dart β 50+ methods on Text
β βββ styles/ β TwStyle system
β β βββ tw_style.dart β Composable style objects
β β βββ tw_variant.dart β light / dark enum
β β βββ tw_style_merger.dart β mergeAll / when helpers
β βββ theme/ β Theme layer
β βββ tw_theme_data.dart β 7 token sub-classes
β βββ tw_theme.dart β InheritedWidget
β βββ tw_theme_extension.dartβ 4 ThemeExtension classes
β βββ tw_context_extension.dart β context.tw accessor
βββ test/ β 90%+ coverage test suite
Performance
Every extension method returns the same Flutter widget you would have written manually β no intermediate objects, no Builder allocations, no runtime parsing.
TwStyle.apply() goes further: it combines backgroundColor, borderRadius,
shadows, and border into a single DecoratedBox, producing a shallower
widget tree than chaining individual extensions.
Roadmap
Responsive extensions (.sm(),.md(),.lg()variants per breakpoint)Interactive variants (TwVariant.hover,.focus,.disabled)Animation tokens (.transition(),.animate())Full typography system (font-family tokens, tracking scale)Directional support improvements (RTL-aware margins)
License
MIT β see LICENSE.
Made for Flutter developers who love Tailwind CSS. β‘
Libraries
- tailwind_ui_flutter
- tailwind_ui_flutter β Utility-first widgets for Flutter.