| name | wind-ui |
| description | Build Flutter UI with Wind's utility-first className system. W-prefix widgets, design culture, enforcement gates, theming, component-first workflow. |
| when_to_use | TRIGGER: WDiv, WText, WButton, WInput, WSelect, WCheckbox, WIcon, WImage, WSvg, WPopover, WAnchor, WFormInput, WFormSelect, WFormMultiSelect, WFormCheckbox, WDatePicker, WFormDatePicker, WSpacer, WDynamic, className, WindTheme, WindThemeData, wind-ui, Flutter styling, Flutter layout, mobile UI, app design, component design.
DO NOT TRIGGER: general Flutter questions without className usage, pure Dart logic without UI, backend/API code.
|
| version | 1.0.0-alpha.6 |
Wind UI v1
Utility-first styling for Flutter UI. Translate Tailwind-like classes to Flutter render tree semantics via className string parsing.
1. Core Laws
- W-prefix mandate: Use
WDiv (not Container), WText (not Text), WButton (not ElevatedButton/GestureDetector).
- className-first: ALL styling via
className string. Never inline BoxDecoration, TextStyle, or EdgeInsets when a Wind equivalent exists.
- dark: is mandatory: Every
bg-, text-, and border- class MUST have a dark: counterpart.
- Trailing commas: Always on the last constructor parameter and list item.
- Multi-line: Constructor parameters must always be multi-line when 3 or more.
- No theory output: When applying styling, provide the code directly. Do not explain the classes.
2. Flutter Layout Reality
Flutter constraint resolution differs fundamentally from CSS. Wind UI maps className syntax to native Flutter semantics. Follow these rules to avoid overflows and unbounded height errors.
Space-filling
| Goal | Use | Why |
|---|
| Fill remaining space in Row/Column | flex-1 | Maps to Expanded, negotiates available space |
| Full width in Column context | w-full | SizedBox(width: double.infinity), works here |
| Full width in Row context | flex-1 (NOT w-full!) | w-full causes RenderFlex overflow in Rows |
flex-none / shrink-0 | No flex shrink. Unlike CSS, Flutter Row/Column children do not auto-shrink; use flex-1 to allow growth | Row child stays fixed width |
Scrollable layouts
| Goal | className | Required context |
|---|
| Vertical scroll | overflow-y-auto + scrollPrimary: true | Parent MUST have bounded height |
| Bounded height for scroll | flex-1 on the scrollable WDiv | Inside a Column/Flex parent |
| Horizontal scroll | overflow-x-auto | Same rules |
| Flex children inside scrollable main axis | flex-1 on child works (Wind skips Expanded automatically when parent scrolls that axis) | flex flex-row + overflow-x-auto or flex flex-col + overflow-y-auto — no unbounded-constraints assertion |
WDiv Composition Rules
| className contains | Flutter widget | Notes |
|---|
flex flex-col | Column | flex alone defaults to Row |
flex flex-row or flex | Row | Direction default |
flex-row-reverse / flex-col-reverse | Row/Column with flipped main axis | Uses textDirection / verticalDirection so justify-start mirrors (matches CSS) |
order-{0..12} / order-first / order-last / order-[n] | Child reorder inside flex parent | Parent stable-sorts; children without order-* default to 0 |
wrap or grid | Wrap | NOT flex-wrap (no-op!) |
overflow-y-auto | SingleChildScrollView | Needs bounded height |
relative | Stack(clipBehavior: Clip.none) | Children split: normal layout + Positioned |
absolute top-4 right-4 | Positioned(top: 16, right: 16) | Must be inside a relative parent |
absolute inset-0 | Positioned(top: 0, right: 0, bottom: 0, left: 0) | Full overlay pattern |
hidden | SizedBox.shrink() | |
Critical Layout Gotchas:
// ❌ WRONG: overflow in Row
WDiv(
className: 'flex flex-row',
children: [
WDiv(className: 'w-full', child: WText('Long text')), // OVERFLOW
],
)
// ✅ CORRECT
WDiv(
className: 'flex flex-row',
children: [
WDiv(className: 'flex-1', child: WText('Long text')), // Fills remaining space
],
)
// ❌ WRONG: unbounded height scroll
Column(
children: [
WDiv(className: 'overflow-y-auto', children: [...]) // ERROR: infinite height
],
)
// ✅ CORRECT
WDiv(
className: 'flex flex-col h-full', // Bounded
children: [
WDiv(
className: 'flex-1 overflow-y-auto',
scrollPrimary: true, // iOS tap-to-top
children: [...],
),
],
)
// ❌ WRONG: truncate without bounded width
WDiv(
className: 'flex flex-row',
children: [
WText('Very long title', className: 'truncate'), // OVERFLOW
],
)
// ✅ CORRECT
WDiv(
className: 'flex flex-row',
children: [
WDiv(
className: 'flex-1', // Bounds the text
child: WText('Very long title', className: 'truncate'),
),
],
)
// ❌ WRONG: absolute without relative parent
WDiv(
className: 'flex flex-row',
children: [
WDiv(className: 'absolute top-0 right-0', child: badge), // No Stack!
],
)
// ✅ CORRECT: relative parent creates Stack
WDiv(
className: 'relative flex flex-row',
children: [
WDiv(className: 'flex-1', child: content),
WDiv(className: 'absolute top-0 right-0', child: badge), // Positioned overlay
],
)
Note: h-full inside a scrollable parent results in an infinite height error. Use min-h-screen instead. Native Flutter widgets (e.g., ListView.builder, charts) inside a Row or Column MUST be wrapped in Expanded() (allowed exception to Gate 1 for non-Wind widget bounding). absolute children only work inside a relative parent. Only WDiv and WText are detected as absolute; wrap other widgets in a WDiv.
3. Widget Quick Reference
| Widget | Required Props | Key Optional Props | Use For |
|---|
WDiv | - | className, child/children, states, scrollPrimary | Any container, layout |
WText | data | className, selectable, states | All text rendering |
WButton | child | onTap, isLoading, disabled, className | All interactive buttons |
WInput | value, onChanged | type, placeholder, className, placeholderClassName | Standalone inputs |
WSelect | options, value/values, onChange | searchable, isMulti, onCreateOption, className | Dropdowns |
WCheckbox | value, onChanged | className | Toggle checkboxes |
WIcon | icon | className | Icons with styling |
WImage | src | className | Images with object-fit |
WSvg | src | className, preserve-colors | SVG with fill/stroke/preserve |
WPopover | triggerBuilder, contentBuilder | alignment, className | Dropdowns, menus, tooltips |
WAnchor | child | onTap, className | Raw interactive wrapper |
WFormInput | (FormField) | controller, type, placeholder, className, validator | MagicForm-integrated input |
WFormSelect | (FormField) | options, searchable, className | MagicForm-integrated select |
WFormMultiSelect | (FormField) | options, onCreateOption, className | MagicForm multi-select |
WFormCheckbox | (FormField) | label, className | MagicForm-integrated checkbox |
WDatePicker | mode, onDateSelected/onRangeSelected | className, initialDate, firstDate, lastDate | Single date or date range picker |
WFormDatePicker | (FormField) | mode, className, firstDate, lastDate | MagicForm-integrated date picker |
WSpacer | - | className | Semantic spacing between siblings |
WDynamic | json | actions, controller, customIcons, builders, denyWidgets | Server-driven UI from JSON |
WBreakpoint | base (builder) | sm, md, lg, xl, xxl, custom | Render a fully different widget tree per breakpoint — escape hatch when className prefixes + hidden aren't enough |
Rules:
child and children are mutually exclusive on WDiv.
WDiv auto-wraps in WAnchor if hover:, focus:, or active: is present in the className.
WButton with isLoading: true activates loading: prefix classes and disables interaction.
WSelect with isMulti: true must use values + onMultiChange instead of value + onChange.
WSpacer is a const-friendly semantic spacer. Prefer over SizedBox or empty WDiv for gaps between siblings.
WDatePicker mode: DatePickerMode.single or DatePickerMode.range. Range mode uses onRangeSelected callback.
4. Token System
Spacing Formula: n × 4px (e.g., p-4 = 16px, gap-6 = 24px, m-2 = 8px)
Color Syntax: {role}-{color}-{shade} (e.g., bg-blue-500, text-gray-900, border-red-300)
- Opacity:
bg-primary/50, text-gray-500/75
- Arbitrary:
bg-[#FF5733], w-[200px], text-[18px]
Responsive: Prefix with breakpoint (e.g., md:flex-row, lg:p-6, xl:hidden)
- Breakpoints:
sm (640px), md (768px), lg (1024px), xl (1280px), 2xl (1536px)
Typography Sizes: xs (12), sm (14), base (16), lg (18), xl (20), 2xl (24), 3xl (30), 4xl (36)
Border Radius: rounded (4), rounded-md (6), rounded-lg (8), rounded-xl (12), rounded-2xl (16), rounded-full (9999)
Position Types: relative (renders Stack), absolute (renders Positioned inside a Stack)
Offsets (spacing scale): top-{n} right-{n} bottom-{n} left-{n} (e.g., top-4 = 16px)
Insets: inset-{n} (all sides), inset-x-{n} (left+right), inset-y-{n} (top+bottom)
Negative offsets: -top-{n}, -inset-{n} (prefix with -)
Arbitrary offsets: top-[24px], left-[24px] (px only; % is unsupported)
Note: fixed and sticky are not implemented. Absolute children must be inside a relative parent.
5. State & Modifier Prefixes
| Prefix | Trigger | Example |
|---|
dark: | Dark mode (MANDATORY on all colors) | bg-white dark:bg-gray-800 |