# Wind documentation > Wind brings Tailwind utility classes to Flutter: one className string becomes an optimised widget tree, with responsive, dark: and state variants built in. # Installation Add wind_kit to a Flutter app, wrap it in `WindTheme` and style your first widget with a className. - [Requirements](#requirements) - [Installation](#installation-step) - [Basic Setup](#basic-setup) - [Alternative Setup](#alternative-setup) - [Verify Installation](#verify-installation) Getting started with Wind requires a few configuration steps to ensure utility classes resolve correctly across your application. ## Requirements Before adding Wind to your project, ensure your environment meets these minimum version requirements. We recommend staying on the latest stable versions for the best experience. | Dependency | Minimum Version | Recommended | |:-----------|:----------------|:------------| | Flutter | `>= 3.47.0` | `3.50.0+` | | Dart | `>= 3.13.0` | `3.14.0+` | ## Installation Add `wind_kit` and `material_ui` to your `pubspec.yaml` using the Flutter CLI: ```bash flutter pub add wind_kit material_ui ``` Your app imports `material_ui` for its `MaterialApp`, so it declares the package itself rather than relying on `wind_kit` to bring it in. Existing apps that still import `package:flutter/material.dart` need extra steps. See the [upgrade guide](https://anilcancakir.com/wind_kit/docs/getting-started/upgrade-guide.md#moving-to-material-ui) for the import change and for `MaterialUiCompatibilityBridge`, which covers legacy packages and subtrees. Alternatively, add both manually to your dependencies: ```yaml dependencies: wind_kit: any # or pin to latest stable from pub.dev/packages/wind_kit material_ui: any # or pin to latest stable from pub.dev/packages/material_ui ``` > [!NOTE] > An app on Flutter 3.47 to 3.49 can use the same imports. Flutter 3.50 deprecates `package:flutter/material.dart`, and `package:material_ui/material_ui.dart` is its replacement. ## Basic Setup To use Wind utilities, you must wrap your application with the `WindTheme` widget. This provides the context needed for responsive breakpoints, dark mode, and state resolution. ### The Builder Pattern (Recommended) The `builder` pattern is the preferred way to initialise Wind. It provides a `controller` that synchronises Wind's theme state (like dark mode) directly with `material_ui`'s `MaterialApp`. Let's look at a typical implementation: ```dart import 'package:material_ui/material_ui.dart'; import 'package:wind_kit/wind_kit.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return WindTheme( // Always use the 'data' parameter for configuration data: WindThemeData(), builder: (context, controller) { return MaterialApp( title: 'Wind App', // Automatically syncs Material theme with Wind's state theme: controller.toThemeData(), home: const HomePage(), ); }, ); } } ``` By using the `builder` pattern, any theme changes triggered via `context.windTheme.toggleTheme()` will instantly propagate to both Wind widgets and native Material components. > [!NOTE] > Ensure you use the `data:` parameter. `WindTheme` has no `theme:` parameter, so an argument of that name does not compile. ## Alternative Setup If you do not need reactive synchronisation with `MaterialApp` or are only using Wind in a specific subtree, you can use the `child` pattern: ```dart WindTheme( data: WindThemeData(), child: MaterialApp( home: const HomePage(), ), ) ``` While this setup works for Wind widgets like `WDiv` or `WText`, native Material widgets will not automatically react to Wind theme changes unless handled manually. ## Verify Installation To verify that Wind is correctly configured, add a `WDiv` with a few utility classes to your project: ```dart class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: WDiv( className: 'bg-blue-500 p-6 rounded-xl shadow-lg', child: WText( 'Wind is working!', className: 'text-white font-bold text-xl', ), ), ), ); } } ``` If you see a blue card with white text, the installation is successful. --- # Upgrade Guide What changed between wind_kit versions and what to edit in your app when you upgrade. - [Overview](#overview) - [Moving to wind_kit](#moving-to-wind-kit) - [Moving to material_ui](#moving-to-material-ui) - [1.8.x to 1.9.0](#one-eight-to-one-nine) - [1.3.x to 1.4.0](#one-three-to-one-four) - [1.0.x to 1.1.0](#one-zero-to-one-one) - [New, opt-in](#new-opt-in) - [Behavior changes to review](#behavior-changes) - [Action required](#action-required) - [Fixes you get for free](#free-fixes) ## Overview Wind follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). A project pinned to `^1.0.0` resolves to the latest `1.x` automatically. Within a major version no public API is removed; a minor release can still change behaviour, flagged BREAKING (behaviour) in the changelog. This guide lists the per-version changes worth knowing about, newest first. If a section has no "Action required" entry, the upgrade is drop-in. ## Moving to wind_kit The package was renamed to `wind_kit`. To move an app over, replace the previous dependency name in `pubspec.yaml` with `wind_kit`, and replace the previous import with the new barrel: ```dart import 'package:wind_kit/wind_kit.dart'; ``` No `W*` or `Wind*` class changes, and every className token behaves as before. `WInput`'s selection handles and toolbar are now drawn by the package: lollipop handles, and a toolbar that scrolls sideways when its actions do not fit and has no arrow. Do not depend on both packages in one app: each has its own `WindTheme` and `WindDebugRegistry`, so a `WDiv` from one finds no theme from the other. ### Moving to material_ui Flutter 3.50 deprecates `package:flutter/material.dart` and `package:flutter/cupertino.dart`, and wind_kit builds on `material_ui`, the package that replaces the Material barrel. The floor is now Dart 3.13 and Flutter 3.47, and an app on Flutter 3.47 to 3.49 can already make the move. **Action required for an app that imports `package:flutter/material.dart`.** Add `material_ui` to the dependencies and replace the import: ```bash flutter pub add material_ui ``` ```dart // Before import 'package:flutter/material.dart'; // After import 'package:material_ui/material_ui.dart'; ``` `dart fix --apply --code=migrate_design_widgets` performs the import change for you, as the `material_ui` README describes. It also rewrites `package:flutter/cupertino.dart` to `package:cupertino_ui/cupertino_ui.dart`, so an app that uses Cupertino types directly adds `cupertino_ui` too (`flutter pub add cupertino_ui`); `cupertino_ui` types and the framework's Cupertino types are distinct to Dart, so do not mix the two. `material_ui` defines its own `MaterialApp`, `ThemeData` and localisations, so an app that keeps importing `package:flutter/material.dart` for its `MaterialApp` hits two symptoms: - Wind input widgets throw "No MaterialLocalizations found". - `WindThemeData.toThemeData()` returns a `ThemeData` that does not match the `ThemeData` type the old `MaterialApp` expects. The fix leaves `export` directives alone in the reported Dart 3.13.5 cases ([dart-lang/sdk#64560](https://github.com/dart-lang/sdk/issues/64560), still open when this guide was written), so a barrel file that re-exports `package:flutter/material.dart` or `package:flutter/cupertino.dart` needs its directive replaced by hand with the `material_ui` or `cupertino_ui` barrel. Search for `export 'package:flutter/material.dart'` after running it. **Action required for an app that uses the `flutter_localizations` delegates.** Replace `GlobalMaterialLocalizations` and `GlobalCupertinoLocalizations` from `flutter_localizations` with the delegates from `material_ui`, and remove the `package:flutter_localizations/flutter_localizations.dart` import from the same file: both libraries export a `GlobalMaterialLocalizations`, so keeping both makes the name ambiguous and the build fails. `GlobalMaterialLocalizations.delegates` already holds the Cupertino, Material and Widgets delegates, so delete separate `GlobalWidgetsLocalizations.delegate`, `GlobalMaterialLocalizations.delegate` and `GlobalCupertinoLocalizations.delegate` entries instead of listing them again. Keep your app's own delegates in the list: ```dart // Before: the delegates of GlobalMaterialLocalizations and GlobalCupertinoLocalizations // from package:flutter_localizations // After: the delegates from package:material_ui, next to your own MaterialApp( localizationsDelegates: [ AppLocalizations.delegate, // Keep your app's own delegates. ...GlobalMaterialLocalizations.delegates, ], ) ``` If a third-party package or a subtree still imports `package:flutter/material.dart`, `material_ui` ships `MaterialUiCompatibilityBridge`, which bridges `ThemeData` and `MaterialLocalizations` for that subtree; see Step 3 of the `material_ui` README. `material_ui` marks it deprecated as a temporary migration utility that a future release removes, so treat it as a stopgap until that subtree moves to `material_ui` too. ## 1.8.x to 1.9.0 A minor release that changes behaviour, flagged BREAKING (behaviour) in the changelog, so a `^1.8.0` constraint picks it up on the next `pub get`. **`tracking-*` is now em, as in Tailwind.** Letter spacing scales with the font size, so every `tracking-*` class renders differently. **Action required for a custom `WindThemeData.tracking` map.** It is now read as em multipliers: a value written as pixels now means that many em, so convert it. ```dart // Before: logical pixels WindThemeData(tracking: {'tight': -1.0}) // After: a multiple of the font size (-1px on 14px text is about -0.07) WindThemeData(tracking: {'tight': -0.07}) ``` The built-in scale already holds Tailwind's values, so an app that uses only the default `tracking-*` classes needs no edit; check the few places where the tighter or wider spacing shows. An arbitrary `tracking-[Nem]` is now em too (it was read as N pixels), and a negative arbitrary value now applies instead of being dropped. ## 1.3.x to 1.4.0 A minor release that changes behaviour, flagged BREAKING (behaviour) in the changelog. **A single-line `WInput` defaults its Return key to `TextInputAction.done`** instead of `.next`. Multiline still defaults to `.newline`, and an explicit `textInputAction` still wins. Flutter implements `.next` as a move to the next focusable widget, not the next text field, so the old default could land on a button or a swatch. A form that relied on the old default gets it back per field: ```dart WInput( textInputAction: TextInputAction.next, ) ``` ## 1.0.x to 1.1.0 A backward-compatible minor release. The headline additions are opt-in, so existing apps compile and render unchanged. One debug-only assertion and a few input behaviour changes are worth a quick scan. ### New, opt-in These features do nothing until you use them. **className aliases** (`WindThemeData.aliases`). Map a bare token to a full utility string and Wind expands it (recursively) before parsing, in every widget: ```dart WindTheme( data: WindThemeData( aliases: { 'row': 'flex flex-row', 'col': 'flex flex-col', 'center': 'items-center justify-center', 'card': 'p-4 rounded-lg bg-white dark:bg-gray-800', }, ), child: const MyApp(), ) // then anywhere: WDiv(className: 'row center', children: [...]) ``` See [Theming: Aliases](https://anilcancakir.com/wind_kit/docs/core-concepts/theming.md#aliases) for the full semantics (bare-token keys, recursion, shadowing, the empty default). **`WIcon.foregroundColor`.** Pass a runtime `Color` directly instead of interpolating it into a `text-[#hex]` className, which would bloat the parser cache: ```dart // category.color is a runtime value (for example from the database) WIcon(Icons.star, foregroundColor: category.color, className: 'text-lg') ``` It overrides any `text-*` / `dark:text-*` from the className and stays out of the parser cache key, matching `WText.foregroundColor`. **`WInput` read-only state.** `readOnly: true` now activates a `readonly` state, so `readonly:` prefixed classes style a read-only field the way `disabled:` styles a disabled one: ```dart WInput( readOnly: true, value: 'Locked', className: 'p-3 rounded-lg bg-white dark:bg-gray-800 readonly:bg-gray-100 dark:readonly:bg-gray-700', ) ``` ### Behavior changes to review No code change is required, but the rendered result differs in these cases. - **`WInput` renders Material-free.** It now builds on `EditableText` inside a decorated `Container` instead of a Material `TextField`, so it no longer requires a `MaterialApp` ancestor and works under Cupertino, a custom root, or a bare `WidgetsApp`. If you relied on ambient Material `InputDecoration` theming bleeding into `WInput`, style it with `className` instead. - **Selection handles are Cupertino-style on every platform.** Previously Material-style on Android and web. The copy / cut / paste menu reads `WidgetsLocalizations`, so it works under any ancestor. Behaviour is identical; only the handle and toolbar appearance changes on Android and web. - **`InputType.number` restricts input to a signed decimal on every platform.** A default formatter now allows digits, one decimal point, and an optional leading minus, so the field is numeric on web too (where the keyboard type alone enforces nothing). Supply your own `inputFormatters` to override the default. ### Action required - **Do not pass both `value` and `controller` to `WInput`.** Supplying both was always a logic error (the controller silently won); it now throws an `AssertionError` in debug builds. Pick one: use `value` + `onChanged` for React-style binding, or a `controller` for imperative control. ```dart // Before (silently used the controller, ignored value) WInput(value: _text, controller: _controller) // After: choose one WInput(value: _text, onChanged: (v) => setState(() => _text = v)) // or WInput(controller: _controller) ``` ### Fixes you get for free No action needed; these land automatically. - Form widget labels, hints, and errors (`WFormInput`, `WFormSelect`, `WFormCheckbox`, `WFormDatePicker`) carry `dark:` pairs, so they stay legible in dark mode. - A conditional `prefix` / `suffix` (for example a clear button that appears once the field has text) no longer drops focus on the first keystroke, and an appearing suffix no longer changes the field height. - A disabled `WInput` (`enabled: false`) is fully non-interactive again and reports `isEnabled: false` to assistive technology. - `WInput` emits a single clean `textField` semantics node instead of the previous double node. --- # Utility-First Fundamentals Utility-first styling is the core philosophy of Wind. Instead of building complex, nested widget trees for every design detail, you apply pre-defined utility classes directly to your widgets to compose styles. - [Introduction](#introduction) - [The Shift in Thinking](#the-shift-in-thinking) - [Why Utility-First?](#why-utility-first) - [How it Works](#how-it-works) - [Quick Reference](#quick-reference) - [Syntax Guide](#syntax-guide) ## Introduction Wind translates utility strings into high-performance Flutter widget trees. This approach moves styling from the "implementation" phase into the "composition" phase, allowing you to build UIs by combining functional building blocks. ```dart WDiv( className: 'flex flex-col gap-4 p-6 bg-white rounded-xl shadow-lg border border-gray-100', children: [ WText('Utility-First', className: 'text-2xl font-bold text-blue-600'), WText('Style your Flutter apps with ease.', className: 'text-gray-500'), WButton( className: 'bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg', child: Text('Get Started'), ), ], ) ``` ## The Shift in Thinking Traditionally, styling in Flutter involves deep nesting. To create a simple card with padding and a shadow, you typically wrap widgets inside multiple containers and decorators. Let's compare the two approaches: ### Traditional Flutter ```dart // Standard Flutter approach Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: const Text( "Hello World", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ) ``` ### Wind ```dart // Wind approach WDiv( className: "p-4 bg-white rounded-xl shadow-lg", child: WText( "Hello World", className: "text-lg font-bold", ), ) ``` By flattening the widget tree, Wind makes the UI structure more readable and significantly reduces the amount of code required to build complex interfaces. ## Why Utility-First? Adopting a utility-first workflow provides several architectural advantages: - **Development Velocity**: Stay within your widget tree. There is no need to jump between separate files or long constructor lists to adjust styling. - **Design Consistency**: Instead of using magic numbers, you work with a standardised scale (e.g., `p-4` for 16px). This ensures your UI remains visually aligned. - **Improved Maintainability**: Styles are local to the widget. When you modify a `WDiv`, you see exactly what it affects without worrying about global CSS-like side effects. - **Declarative Modifiers**: Handling responsiveness (`md:`), dark mode (`dark:`), and states (`hover:`) is handled via simple prefixes rather than conditional logic in your build methods. ## How it Works Wind uses a high-performance pipeline to transform strings into native Flutter styles. This process is optimised through caching to ensure runtime overhead is minimal. 1. **Context Initialisation**: `WindContext` captures the current environment, including screen size (breakpoints), brightness, and theme scales. 2. **Parsing**: The `WindParser` tokenises the `className` string and delegates to specialised parsers for colours, spacing, borders, and more. 3. **Style Composition**: Parsers generate a `WindStyle` object, an immutable, typed representation of the requested styles. 4. **Widget Application**: `W-prefixed` widgets consume this `WindStyle` to dynamically build the optimal Flutter widget hierarchy (e.g., injecting `Padding` or `DecoratedBox` only when needed). > [!NOTE] > Wind caches parsed styles in an unbounded map, keyed per context. Once a class string is parsed, subsequent renders retrieve the pre-computed style almost instantly. `WindParser.clearCache()` empties it. ## Quick Reference Common utility categories and their primary classes: | Category | Primary Classes | Description | |:---------|:----------------|:------------| | **Layout** | `flex`, `grid`, `block`, `hidden` | Controls positioning and visibility. | | **Spacing** | `p-4`, `m-2`, `gap-4`, `px-6` | Manages padding, margins, and spacing between children. | | **Sizing** | `w-full`, `h-64`, `max-w-md` | Defines explicit widths and heights. | | **Colours** | `bg-blue-500`, `text-white` | Applies theme colours to backgrounds and text. | | **Borders** | `border`, `rounded-lg`, `ring-2` | Configures borders, radii, and focus rings. | ## Syntax Guide Wind uses a syntax that matches Tailwind CSS, supporting standard values, arbitrary inputs, and modifiers. | Pattern | Example | Description | |:--------|:--------|:------------| | **Standard** | `p-4` | A predefined value from your theme scale. | | **Negative** | `-top-2` | Negative offset (positioning only; negative margins are not supported). | | **Arbitrary** | `w-[350px]` | Uses exact values inside square brackets for one-off styles. | | **Opacity** | `bg-blue-500/50` | Appends a percentage to a colour to set alpha transparency. | | **Modifiers** | `md:flex-row` | Conditionally applies styles based on screen size or state. | --- # Responsive Design Building interfaces that adapt to any screen size is a core requirement for modern applications. Wind makes this process declarative using a mobile-first, utility-based approach. - [Mobile-First Approach](#mobile-first-approach) - [Breakpoint Reference](#breakpoint-reference) - [Platform Prefixes](#platform-prefixes) - [Combining Modifiers](#combining-modifiers) - [Customizing Breakpoints](#customizing-breakpoints) - [Related Documentation](#related-documentation) ```dart // Stacked on mobile (default), horizontal on medium screens and up WDiv( className: 'flex flex-col md:flex-row gap-4 p-4 bg-gray-100 md:bg-blue-50', children: [ WDiv(className: 'w-full md:w-1/2 h-24 bg-blue-500'), WDiv(className: 'w-full md:w-1/2 h-24 bg-red-500'), ], ) ``` ## Mobile-First Approach Wind follows a mobile-first philosophy. This means that utility classes without a prefix apply to all screen sizes by default. Responsive modifiers like `md:` or `lg:` only apply when the screen width meets or exceeds the specified breakpoint. Consider this example: ```dart // Mobile: blue background, Desktop: green background WDiv(className: 'bg-blue-500 lg:bg-green-500') ``` In this case, the `bg-blue-500` class is the base style. The `lg:bg-green-500` class only activates when the screen width is at least 1024px, overriding the base background colour. ## Breakpoint Reference Wind includes five default breakpoints inspired by the standard Tailwind CSS scale. | Prefix | Value | Description | |:-------|:------|:------------| | `sm` | 640px | Large phones / Small tablets | | `md` | 768px | Tablets (Portrait) | | `lg` | 1024px | Laptops / Tablets (Landscape) | | `xl` | 1280px | Desktops | | `2xl` | 1536px | Large Desktops | > [!NOTE] > These values represent the **minimum width** required for the modifier to take effect. ## Platform Prefixes In addition to screen sizes, Wind supports platform-specific modifiers. These allow you to apply styles based on the operating system or environment. | Prefix | Applies To | |:-------|:-----------| | `ios:` | iOS devices | | `android:` | Android devices | | `macos:` | macOS | | `web:` | Web browsers | | `mobile:` | Both iOS and Android | | `windows:` | Windows | | `linux:` | Linux | Example usage: ```dart WDiv( className: 'p-4 ios:p-6 android:p-2 web:p-8', child: WText('Platform-specific padding'), ) ``` ## Combining Modifiers You can chain multiple modifiers to create highly specific styling rules. For example, you might want a hover effect that only applies in dark mode on large screens. ```dart WButton( className: 'bg-blue-500 lg:dark:hover:bg-indigo-600', child: WText('Submit'), ) ``` ## Customizing Breakpoints If the default breakpoints do not match your project requirements, you can define custom screens in `WindThemeData`. ```dart WindTheme( data: WindThemeData( screens: { 'tablet': 600, 'laptop': 900, 'desktop': 1200, }, ), child: MyApp(), ) ``` Once defined, these custom keys can be used as prefixes: `tablet:p-4`, `laptop:flex-row`, etc. ## Related Documentation - [Utility-First Fundamentals](https://anilcancakir.com/wind_kit/docs/core-concepts/utility-first.md) - [Theming](https://anilcancakir.com/wind_kit/docs/core-concepts/theming.md) - [Dark Mode](https://anilcancakir.com/wind_kit/docs/core-concepts/dark-mode.md) - [State Management](https://anilcancakir.com/wind_kit/docs/core-concepts/state-management.md) --- # Dark Mode Wind makes it incredibly easy to support dark mode using the `dark:` variant modifier. Instead of managing complex theme objects or multiple style sheets, you simply prefix your utility classes to apply styles only when dark mode is active. - [Basic Usage](#basic-usage) - [System vs Manual Mode](#system-vs-manual-mode) - [Responsive Dark Mode](#responsive-dark-mode) - [Best Practices](#best-practices) ```dart WDiv( className: "p-6 bg-white dark:bg-slate-900 rounded-xl shadow-lg dark:shadow-none border border-gray-200 dark:border-slate-800", child: WText( "Dark Mode Support", className: "text-gray-900 dark:text-white font-bold", ), ) ``` ## Basic Usage To style an element for dark mode, add the `dark:` prefix to any utility class. This works for colours, borders, shadows, and even opacity. Let's look at a practical example: ```dart WDiv( className: "bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-100", child: WText("Adaptive text colors"), ) ``` In this example, the background will be light gray in light mode and shift to a dark gray when dark mode is enabled. The text colour behaves similarly, maintaining readability across themes. ## System vs Manual Mode By default, Wind follows the system's brightness settings via `PlatformDispatcher.platformBrightness` while `syncWithSystem` is true (the default). However, you can manually override this behaviour to allow users to toggle themes within your app. ### Manual Toggling You can use the `WindTheme` controller to toggle the theme or set a specific brightness programmatically: ```dart // Toggle between light and dark context.windTheme.toggleTheme(); // Force dark mode context.windTheme.setTheme( context.windThemeData.copyWith(brightness: Brightness.dark, syncWithSystem: false), ); ``` ### Material Sync If you want your Material `ThemeData` to stay in sync with Wind's dark mode, use the `toThemeData()` helper in your `MaterialApp`. This is particularly useful for styling native Material components that aren't yet wrapped in Wind widgets. ```dart WindTheme( data: WindThemeData(), builder: (context, controller) => MaterialApp( theme: controller.toThemeData(), home: const HomePage(), ), ) ``` ## Responsive Dark Mode You can combine the `dark:` modifier with responsive breakpoints to create highly specific layouts. The order of modifiers doesn't strictly matter, but following a consistent pattern like `{breakpoint}:{state}:{utility}` is recommended for readability. ```dart WDiv( // Blue background on mobile, but red on large screens ONLY in dark mode className: "bg-blue-500 lg:dark:bg-red-500", ) ``` ## Best Practices ### Avoid Pure Black Using pure black (`#000000`) for backgrounds can often lead to high contrast that causes eye strain. We recommend using deep grays or slates like `slate-900` or `gray-900` for a more polished dark mode experience. ```dart // Recommended className: "dark:bg-slate-900" // Use sparingly className: "dark:bg-black" ``` ### Soften Your Borders Borders that look great in light mode might appear too harsh in dark mode. Consider reducing the opacity or choosing a darker shade for borders to keep the design soft. ```dart WDiv(className: "border border-gray-200 dark:border-gray-700") ``` --- # State Management & Interactive Styles Style hover, focus, disabled, loading and custom states with prefixed classes and the `states` prop. - [Introduction](#introduction) - [How It Works](#how-it-works) - [Built-in State Prefixes](#built-in-state-prefixes) - [Quick Reference](#quick-reference) - [Interactive Widgets](#interactive-widgets) - [WAnchor: The Core Wrapper](#wanchor-the-core-wrapper) - [WButton: The High-Level Action](#wbutton-the-high-level-action) - [WInput: Focus States](#winput-focus-states) - [Custom States](#custom-states) - [State Propagation](#state-propagation) - [Related Documentation](#related-documentation) ## Introduction Interactive styles allow you to define how your UI reacts to user input without managing complex state variables. Instead of nesting multiple `MouseRegion`, `GestureDetector`, and `Focus` widgets, Wind provides a declarative way to handle states directly in your `className` string. By using prefixes like `hover:`, `focus:` and `disabled:`, you can build responsive, tactile interfaces that feel native to both web and mobile platforms. ## How It Works Wind's parsing engine identifies state prefixes in your class string and stores them in a separate style map. When the widget's internal state changes (e.g., a user hovers over it), the widget rebuilds and applies the corresponding styles. Consider this basic button example: ```dart WButton( onTap: () => print('Action triggered'), className: 'bg-blue-500 dark:bg-blue-600 hover:bg-blue-600 dark:hover:bg-blue-700 p-4 rounded-lg duration-200', child: WText('Save Changes', className: 'text-white dark:text-white'), ) ``` In this case, the background shifts between shades based on the interaction state. A `WButton` applies the new colour at once and `duration-200` only fades its opacity; to animate the colour itself, put the same classes on a `WDiv`, which wraps its decoration in an `AnimatedContainer` when a duration is set. > [!NOTE] > For state prefixes to work, the widget must be wrapped in an interactive component like `WAnchor`, `WButton`, or `WInput`. `WDiv` wraps itself in a `WAnchor` when its className has `hover:`, `focus:` or `active:`; hover is per anchor and is not inherited by a nested `WDiv`. ## Built-in State Prefixes Wind supports several interactive states out of the box. These are automatically managed when using interactive widgets: - **hover**: Triggered when a pointer enters the widget bounds. - **focus**: Triggered when the widget receives keyboard or input focus. - **active**: Reserved. It is not wired to the pointer: it applies only when you pass `states: {'active'}` to the widget that carries the `active:` classes (`WText`, `WDiv`, `WButton`). Passing it to a `WAnchor` does not reach a descendant `WText`, because the anchor's custom states are not part of the context a descendant resolves its styles from. - **disabled**: Active when `WButton(disabled: true)` or `WAnchor(isDisabled: true)`; a null `onTap` does not disable. `WButton(isLoading: true)` activates `loading:` on the button's own `className`, not `disabled:`, so a class meant to cover both needs both prefixes (`disabled:opacity-50 loading:opacity-50`). A loading button's anchor is disabled, though, so a `WText` or `WIcon` inside it does resolve `disabled:`. ## Quick Reference | Prefix | Trigger | Typical Use Case | |:-------|:--------|:-----------------| | `hover:` | Pointer entry | Changing background or elevation on desktop. | | `focus:` | Keyboard focus | Showing a ring or border on input fields. | | `active:` | `states: {'active'}` only | Press styling you drive yourself: track the press in your own state and pass `states: {'active'}` to the styled widget itself. | | `disabled:` | `disabled: true` / `isDisabled: true` | Reducing opacity or greying out actions. `WButton(isLoading: true)` applies `loading:` to the button itself and `disabled:` only to descendants such as `WText`. | ## Interactive Widgets Wind provides specific widgets designed to manage and propagate these states. ### WAnchor: The Core Wrapper `WAnchor` is the low-level engine behind Wind's interactivity. It provides no visual styling itself; its sole purpose is to detect gestures and share that state with its child tree. This is useful for making entire cards or complex layouts interactive. ```dart WAnchor( onTap: () {}, child: WDiv( className: 'bg-white dark:bg-gray-800 hover:bg-slate-50 dark:hover:bg-gray-700 border p-6 rounded-xl duration-300', children: [ WText('Interactive Card', className: 'text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400'), ], ), ) ``` ### WButton: The High-Level Action While `WAnchor` is for generic wrappers, `WButton` is optimised for actions. It includes built-in support for loading states, padding defaults, and button-specific accessibility. ```dart WButton( onTap: _submit, className: 'bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 text-white px-8 py-3 rounded', child: Text('Submit'), ) ``` ### WInput: Focus States `WInput` manages its own focus state. It is common to use `focus:` prefixes here to highlight the active field or show a focus ring. ```dart WInput( placeholder: 'Search...', className: 'border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 p-3 rounded-lg', ) ``` ## Custom States Sometimes you need to style a widget based on a state that isn't built-in, such as whether an item is "selected" or "loading". You can pass custom strings to the `states` parameter. ```dart WDiv( className: 'border-2 border-gray-200 selected:border-blue-500 bg-white selected:bg-blue-50 p-4', states: isSelected ? {'selected'} : {}, child: WText('Selection Item'), ) ``` If the `states` set contains "selected", any class prefixed with `selected:` will be applied. ## State Propagation Interactivity in Wind is shared through `WindContext`. When an ancestor widget like `WAnchor` changes state, it propagates that state to all children in its subtree. Let's look at a nested example: ```dart WAnchor( onTap: () {}, child: WDiv( className: 'p-4 bg-white hover:bg-gray-50', children: [ WText('Title', className: 'font-bold hover:text-blue-600'), WText('Description', className: 'text-gray-500'), ], ), ) ``` Even if the mouse is only hovering over the padding of the `WDiv`, the `WText` widget will receive the "hover" state and update its colour accordingly. ## Related Documentation - [Responsive Design](https://anilcancakir.com/wind_kit/docs/core-concepts/responsive-design.md) - Combining states with breakpoints. - [Dark Mode](https://anilcancakir.com/wind_kit/docs/core-concepts/dark-mode.md) - Styling for night owls. - [WAnchor Widget](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md) - Technical reference for the anchor engine. --- # Theme Configuration Configure colours, spacing, typography, breakpoints and animations through `WindThemeData`, and wrap the app in `WindTheme`. - [Introduction](#introduction) - [The WindTheme Widget](#the-windtheme-widget) - [Theme Change Callbacks](#theme-change-callbacks) - [Resetting to System Theme](#resetting-to-system-theme) - [Colors](#colors) - [Typography](#typography) - [Spacing and Sizing](#spacing-and-sizing) - [Borders and Shadows](#borders-and-shadows) - [Customizing Defaults](#customizing-defaults) - [Aliases](#aliases) - [Quick Reference](#quick-reference) ## Introduction Wind is designed to be fully customisable from the ground up. If you've ever used Tailwind CSS, you'll feel right at home with how we handle "design tokens." Instead of hunting through nested widget properties to change a colour or a font size, you define your design system once in your theme configuration. Everything in Wind (from the colours in `bg-blue-500` to the spacing in `p-4`) is driven by `WindThemeData`. This ensures that your UI remains consistent across the entire application while giving you the freedom to break away from defaults whenever you need to. ## The WindTheme Widget To start customising your design system, you need to wrap your application with the `WindTheme` widget. This widget acts as a provider, making your theme configuration available to all `W` widgets in the tree. > [!IMPORTANT] > Always use the `data:` parameter when providing your theme configuration. Let's look at a basic setup: ```dart import 'package:wind_kit/wind_kit.dart'; void main() { runApp( WindTheme( data: WindThemeData( // We'll customize this in the next sections ), onThemeChanged: (brightness) { // Optional: persist user's theme preference }, child: MaterialApp( home: MyHome(), ), ), ); } ``` ## Theme Change Callbacks When a user manually toggles the theme via `toggleTheme()`, you may want to persist their preference. The `onThemeChanged` callback fires on every brightness change that does not come from the OS sync; it does **not** fire when the system brightness changes automatically. ```dart WindTheme( onThemeChanged: (brightness) { // Persist the manual choice with your own storage (savePreference is the host's). savePreference('theme_mode', brightness == Brightness.dark ? 'dark' : 'light'); }, data: WindThemeData(), child: MaterialApp( home: MyHome(), ), ) ``` The callback receives only the brightness, so a stored `light` or `dark` means "the user chose this mode", not "the OS currently reports it". Restore it as a manual choice with `syncWithSystem: false`, and store nothing while the user follows the system (see [Resetting to System Theme](#resetting-to-system-theme)). > [!IMPORTANT] > `onThemeChanged` fires on every brightness change that does not come from the OS sync: `toggleTheme()`, `setTheme()`, `updateTheme(brightness:)`, `resetToSystem()`, and a new `data:` from a parent rebuild. Automatic system brightness sync does not trigger this callback. ## Resetting to System Theme After a user manually toggles the theme, the automatic system sync is disabled to respect their choice. To re-enable automatic system brightness sync, call `resetToSystem()`: ```dart // Re-enable system brightness sync WindTheme.of(context).resetToSystem(); // Or via extension context.windTheme.resetToSystem(); ``` This immediately syncs the theme with the current platform brightness and re-enables the `syncWithSystem` flag so future OS changes are reflected automatically. If you persist the preference, clear it after you reset. `onThemeChanged` fires after `resetToSystem()` only when the brightness value actually changes (and then it saves the platform brightness as if it were a manual choice), and never for later OS changes, so a stored `light` or `dark` cannot tell a restart that the user went back to following the system. Delete the stored value yourself once the reset has run, and restore from it on startup: ```dart // Going back to the system: re-enable sync, then forget the manual choice // (after the reset, because the reset itself may fire onThemeChanged). context.windTheme.resetToSystem(); await removePreference('theme_mode'); // On startup: no stored value keeps the default syncWithSystem: true. final String? saved = await readPreference('theme_mode'); WindTheme( data: saved == null ? WindThemeData() : WindThemeData( brightness: saved == 'dark' ? Brightness.dark : Brightness.light, syncWithSystem: false, ), onThemeChanged: (brightness) { savePreference('theme_mode', brightness == Brightness.dark ? 'dark' : 'light'); }, child: MaterialApp( home: MyHome(), ), ) ``` `removePreference`, `readPreference` and `savePreference` stand for the host's own storage. ## Colors Colours in Wind are defined as palettes. When you add a colour to your theme, Wind expects a `MaterialColor` so it can resolve utilities like `bg-brand-50` through `bg-brand-900`. Here's how to define custom colours: ```dart WindThemeData( colors: { // A MaterialColor swatch: shades 50-900 (Flutter swatches have no 950) 'brand': Colors.indigo, // Explicit shades for fine-tuned control 'accent': MaterialColor(0xFF3B82F6, { 50: Color(0xFFEFF6FF), 500: Color(0xFF3B82F6), 900: Color(0xFF1E3A8A), }), }, ) ``` Once defined, these become available as utility classes immediately: `text-brand-600`, `bg-accent-500/50`, or `border-brand-900`. ## Typography You can take full control of your app's typography by overriding font families, sizes, and weights. Wind applies a "sans" font family globally by default, mimicking Tailwind's behaviour. ```dart WindThemeData( fontFamilies: { 'sans': 'Inter', // The default global font 'display': 'Oswald', // Accessible via font-display }, fontSizes: { 'xs': 12.0, 'base': 16.0, 'xl': 20.0, '4xl': 36.0, }, fontWeights: { 'thin': FontWeight.w100, 'bold': FontWeight.w700, }, ) ``` But what if you don't want Wind to inject a global `DefaultTextStyle`? You can simply set `applyDefaultFontFamily: false` in your configuration. ## Spacing and Sizing Wind uses a numeric scale for spacing (`p-4`, `m-2`, `gap-6`). By default, each unit is equal to `4.0` logical pixels. This means `p-4` translates to `16.0` pixels. If your design system is built on a different grid (like a 5px or 8px grid), you can adjust the `baseSpacingUnit`: ```dart WindThemeData( baseSpacingUnit: 5.0, // Now p-4 = 20px ) ``` You can also customise the "screen" breakpoints if the default Tailwind values don't fit your needs: ```dart WindThemeData( screens: { 'sm': 600, 'md': 900, 'lg': 1200, }, ) ``` ## Borders and Shadows The "feel" of your app often comes down to its borders and shadows. Wind allows you to define these scales explicitly so your `rounded-lg` or `shadow-xl` classes are always consistent. ```dart WindThemeData( borderRadius: { 'none': 0, 'sm': 2, 'DEFAULT': 4, // Applied by 'rounded' class 'lg': 8, 'full': 9999, }, shadows: { 'sm': [BoxShadow(color: Colors.black12, blurRadius: 2)], 'md': [BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 2))], }, ) ``` ## Customizing Defaults Wind provides a comprehensive default theme that matches Tailwind CSS v3. When you provide your own `WindThemeData`, your values are **merged** with the defaults. If you want to tweak just one or two things from an existing configuration, use `copyWith`: ```dart final darkTheme = myDefaultTheme.copyWith( brightness: Brightness.dark, ringColor: Colors.amber, ); ``` ## Aliases `WindThemeData(aliases: {...})` lets you define short, bare-token names that expand to full utility strings before parsing. Every `W` widget resolves aliases centrally, so a class name like `row` becomes `flex flex-row` everywhere without any per-widget wiring. ```dart WindThemeData( aliases: { 'row': 'flex flex-row', 'col': 'flex flex-col', 'center': 'items-center justify-center', 'row-c': 'row center', // recursive: expands 'row' and 'center' first }, ) ``` With this configuration, the following `className` values are equivalent: ```dart // Before aliases WDiv(className: 'flex flex-row items-center justify-center bg-white dark:bg-gray-800') // After aliases WDiv(className: 'row-c bg-white dark:bg-gray-800') ``` **How expansion works:** - Alias keys are always **bare** (no prefix). A prefixed token still resolves: its prefix is peeled off, its bare body is matched against the map, and the prefix is re-applied to every produced token. `md:row` becomes `md:flex md:flex-row`, and `hover:bg-surface` carries `hover:` onto each token the `bg-surface` alias produces (including its `dark:` peer, yielding `hover:dark:...`, which the parser resolves regardless of prefix order). Define keys bare (`row`, `bg-surface`), never `md:row`. - Expansion is recursive: an alias value may reference other aliases. The expander resolves all aliases before handing the result to the parser. - Aliases are empty by default (`{}`). The feature is purely opt-in: a `WindThemeData` without an `aliases` key behaves identically to today. - If an alias key shadows a built-in token (for example, `'flex': 'flex flex-row'`), the alias wins and Wind emits a debug-mode warning so you can rename before shipping. - Expansion is bounded: a cyclic alias (`'a': 'a'`) and a deep or wide fan-out map terminate safely (the offending token is left unexpanded or the output is capped), so a misconfigured map never hangs rendering. Aliases are developer configuration, not a place to interpolate untrusted runtime strings. > [!NOTE] > Alias keys must be plain strings with no colons or slashes. A prefixed token still resolves: its prefix is peeled off, the bare body is matched against the map, and the prefix is re-applied to each produced token (`md:col` -> `md:flex md:flex-col`, `hover:row` -> `hover:flex hover:flex-row`). ## Quick Reference `WindThemeData` exposes 24 configurable fields. The table below groups them by concern. ### Mode and Behavior | Property | Type | Default | Description | |:---------|:-----|:--------|:------------| | `brightness` | `Brightness` | `light` | Initial mode (`light` or `dark`). Overridden on mount by the OS brightness while `syncWithSystem` is `true` (see note below) | | `syncWithSystem` | `bool` | `true` | Auto-follow OS brightness until the user calls `toggleTheme()` | | `applyDefaultFontFamily` | `bool` | `true` | Inject Wind's default font family as a global `DefaultTextStyle` | | `baseSpacingUnit` | `double` | `4.0` | Multiplier for numeric spacing (`p-4` → `4 * 4 = 16px`) | > Setting `brightness: Brightness.dark` alone has no effect while `syncWithSystem` is `true` (the default): the controller reads the OS brightness on mount and overrides it, so `dark:` classes stay inactive on a light OS. To pin a fixed mode, pass `WindThemeData(brightness: Brightness.dark, syncWithSystem: false)`, or switch at runtime with `controller.toggleTheme()` / `setTheme(...)`. ### Tokens | Property | Type | Description | |:---------|:-----|:------------| | `colors` | `Map` | Custom colour palettes (`bg-brand-500`, etc.) | | `screens` | `Map` | Breakpoint min-widths (`sm`, `md`, `lg`, `xl`, `2xl`, custom) | | `containers` | `Map` | Named spacing: `p-md`, `gap-xs` resolve to `containers[key] * baseSpacingUnit`. There is no `container` utility, and `max-w-*` uses a fixed scale. | | `fontFamilies` | `Map` | Font aliases (`sans`, `serif`, `mono`, custom) | | `fontSizes` | `Map` | Text size scale (`text-xs` through `text-6xl`) | | `fontWeights` | `Map` | Weight scale (`font-thin` through `font-black`) | | `tracking` | `Map` | Letter spacing in em, a multiple of the font size (`tracking-tight`, etc.) | | `leading` | `Map` | Line height (`leading-tight`, etc.) | | `borderWidths` | `Map` | Border width scale (`border-2`, etc.) | | `borderRadius` | `Map` | Corner radius scale (`rounded-lg`, etc.) | | `shadows` | `Map>` | Shadow definitions (`shadow-md`, etc.) | | `opacities` | `Map` | Opacity scale (`opacity-50`, etc.) | | `zIndices` | `Map` | Z-index scale (`z-10`, etc.) | | `transitionDurations` | `Map` | Duration scale (`duration-200`, etc.) | | `transitionCurves` | `Map` | Easing scale (`ease-in-out`, etc.) | | `animations` | `Map` | Animation types (`animate-spin`, etc.) | | `aliases` | `Map` | Bare-token shorthands that expand (recursively) before parsing. Default `{}`. See [Aliases](#aliases). | ### Ring (Focus) Effects | Property | Type | Default | Description | |:---------|:-----|:--------|:------------| | `ringColor` | `Color` | Tailwind blue-500 | Default ring colour when not overridden by `ring-{color}` | | `ringWidths` | `Map` | `0,1,2,DEFAULT=3,4,8` | Ring width scale (`ring`, `ring-2`, etc.) | | `ringOffsets` | `Map` | `0,1,2,4,8` | Ring offset scale (`ring-offset-2`, etc.) | ### Controller methods `WindTheme.of(context)` returns a `WindThemeController` exposing these mutators: | Method | Signature | Description | |:-------|:----------|:------------| | `toggleTheme()` | `void toggleTheme()` | Flip between light and dark, pinning `syncWithSystem` to `false`. | | `setTheme()` | `void setTheme(WindThemeData newData)` | Replace the active theme data wholesale. | | `updateTheme()` | `void updateTheme({Brightness? brightness, Map? colors, Map? screens, Map? fontSizes, Map? fontWeights, Map? fontFamilies, Map? borderWidths, Map? borderRadius, double? baseSpacingUnit})` | Partial update via `copyWith`; only the passed fields change. | | `resetToSystem()` | `void resetToSystem()` | Re-enable automatic OS brightness sync. | ### Equality `WindThemeData` implements value-based `==` and `hashCode` (the `hashCode` covers scalar fields only), so constructing a fresh default `WindThemeData()` during a parent rebuild does not clobber a `toggleTheme()` choice already held by the controller. ### Widget-level callback `onThemeChanged` is a `WindTheme` widget parameter, not a `WindThemeData` field. It fires on every brightness change that does not come from the OS sync (`toggleTheme()`, `setTheme()`, `updateTheme(brightness:)`, `resetToSystem()`, and a new `data:` from a parent rebuild) and is documented in the [Theme Change Callbacks](#theme-change-callbacks) section above. For more details on how to sync these values with Flutter's standard Material components, check out the [Theme Binding](https://anilcancakir.com/wind_kit/docs/core-concepts/theme-binding.md) guide. --- # Theme Binding Theme binding allows you to sync your Wind configuration with Flutter's native `ThemeData` system. This ensures that standard Material widgets automatically reflect your Wind tokens, creating a cohesive visual experience across your entire application. - [Why Bind Themes?](#why-bind-themes) - [Reactive Binding](#reactive-binding) - [Toggling Themes](#toggling-themes) - [Static Binding](#static-binding) - [Mapping Reference](#mapping-reference) ## Why Bind Themes? By default, Wind manages its own styling tokens (colours, spacing, typography). However, Flutter's built-in widgets (like `AppBar`, `FloatingActionButton`, or `TextField`) rely on the standard `Theme.of(context)` to determine their appearance. Binding the two systems ensures: - **Consistency:** A button using `bg-primary-500` matches a standard `ElevatedButton`. - **Inheritance:** Standard Flutter widgets automatically pick up your Wind font families and colours. - **Automation:** Toggling dark mode in Wind automatically updates the `brightness` of your standard Material theme. ## Reactive Binding To ensure your entire application rebuilds when the theme changes (e.g., switching to dark mode), use the `builder` pattern. This provides a `WindThemeController` that dynamically generates a new `ThemeData` whenever the state updates. ```dart class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final windTheme = WindThemeData( colors: { 'primary': Colors.indigo, 'secondary': Colors.teal, }, ); return WindTheme( data: windTheme, builder: (context, controller) { // controller.toThemeData() produces a standard Flutter ThemeData return MaterialApp( theme: controller.toThemeData(), home: const HomePage(), ); }, ); } } ``` ## Toggling Themes When using the reactive `builder` pattern, you can trigger a global theme update from anywhere in the widget tree. This will automatically update both Wind utilities and standard Material widgets. ```dart // Toggle between Light and Dark mode context.windTheme.toggleTheme(); ``` ## Static Binding If you do not need dynamic theme switching at runtime, you can bind the theme once during initialisation. This is useful for apps with a fixed brand identity or forced dark/light modes. ```dart final windTheme = WindThemeData( brightness: Brightness.dark, syncWithSystem: false, colors: {'primary': Colors.purple}, ); return WindTheme( data: windTheme, child: MaterialApp( theme: windTheme.toThemeData(), home: const HomePage(), ), ); ``` ## Mapping Reference The `toThemeData()` method intelligently maps Wind tokens to their standard Flutter counterparts: | Wind Token | Flutter Theme Property | |:-----------|:-----------------------| | `colors['primary']` | `colorScheme.primary` | | `colors['secondary']` | `colorScheme.secondary` | | `colors['error']` | `colorScheme.error` | | `colors['background']` | `scaffoldBackgroundColor` | | `brightness` | `brightness` | | `fontFamilies['sans']` | `textTheme.fontFamily` | Three rows have a fallback. `colors['primary']` only repaints Material widgets when it differs from Wind's seeded default (the `blue` swatch); otherwise the Material layer keeps its indigo baseline. `colors['secondary']` defaults to teal. Without `colors['background']`, `scaffoldBackgroundColor` reads the `bg-[#hex]` in your `bg-surface` alias (`dark:bg-[#hex]` in dark mode) and then falls back to white in light mode and `gray-900` in dark mode. This mapping ensures that your utility-first styles and native widgets stay perfectly in sync. --- # Dynamic Rendering Build Flutter UIs from JSON at runtime. WDynamic turns a Map structure into a live widget tree with action handling and form state management. - [Introduction](#introduction) - [How It Works](#how-it-works) - [JSON Schema](#json-schema) - [Action System](#action-system) - [Form State Management](#form-state-management) - [Security & Whitelisting](#security-whitelisting) - [Custom Widget Builders](#custom-widget-builders) - [Custom Icons](#custom-icons) - [Error Handling](#error-handling) - [Related Documentation](#related-documentation) ## Introduction Dynamic rendering, also known as Server-Driven UI, allows your backend to control the structure and behaviour of your Flutter interface at runtime. Instead of hard-coding widget trees in Dart, you define them in JSON and let WDynamic convert them into live widgets. This approach unlocks powerful capabilities: - **CMS-driven layouts** - Non-technical teams can author UI configurations through admin panels. - **A/B testing** - Experiment with different layouts without rebuilding your app. - **Remote configuration** - Update UI elements instantly via API responses. - **White-label apps** - Customise entire interfaces per client from a single codebase. **Traditional approach:** ```dart // Hard-coded widget tree WDiv( className: 'flex gap-4 p-6', children: [ WButton(onTap: handleSubmit, child: WText('Submit')), ], ) ``` **Dynamic approach:** ```dart // JSON-driven widget tree WDynamic( json: { 'type': 'WDiv', 'props': {'className': 'flex gap-4 p-6'}, 'children': [ { 'type': 'WButton', 'props': { 'onTap': {'action': 'handleSubmit'}, }, 'children': [ {'type': 'WText', 'props': {'text': 'Submit'}} ], }, ], }, actions: {'handleSubmit': (args, state) => print('Form: ${state.getAll()}')}, ) ``` ## How It Works WDynamic follows a simple pipeline: ``` JSON Map → WDynamicRenderer → Widget Tree ``` The renderer parses your JSON configuration, validates widget types against a security whitelist, instantiates the corresponding Flutter widgets, and wires up action callbacks. All Wind utilities (className, responsive prefixes, state modifiers) work exactly as they do in statically-defined widgets. Here's a basic example that renders a styled container with text: ```dart WDynamic( json: { 'type': 'WDiv', 'props': { 'className': 'p-6 bg-white dark:bg-slate-800 rounded-xl shadow-sm', }, 'children': [ { 'type': 'WText', 'props': { 'text': 'Hello from JSON!', 'className': 'text-xl font-bold text-gray-800 dark:text-white', }, }, ], }, ) ``` The output is identical to writing the equivalent `WDiv` and `WText` widgets directly in Dart. ## JSON Schema Every widget definition follows a consistent three-key structure: ```json { "type": "WidgetType", "props": { /* widget properties */ }, "children": [ /* nested widgets */ ] } ``` ### type The widget class name as a string. Must be present in the default whitelist or registered via custom builders. ```dart {'type': 'WDiv'} {'type': 'WButton'} {'type': 'Column'} // Flutter core widgets also supported ``` ### props A map of properties specific to the widget type. Common properties include: | Property | Description | Example | |:---------|:------------|:--------| | `className` | Wind utility classes | `"flex gap-4 p-6"` | | `text` | Text content (WText) | `"Submit Form"` | | `placeholder` | Input placeholder (WInput) | `"Enter email..."` | | `id` | State tracking identifier | `"username"` | | `onTap`, `onChange`, etc. | Action bindings | `{"action": "submit", "args": {...}}` | ```dart { 'type': 'WInput', 'props': { 'id': 'email', 'placeholder': 'user@example.com', 'className': 'border border-gray-300 rounded-lg p-3', 'onChange': {'action': 'validateEmail'}, }, } ``` ### children An array of nested widget definitions. Optional for widgets like `WText` or `WIcon`. ```dart { 'type': 'WDiv', 'props': {'className': 'flex flex-col gap-2'}, 'children': [ {'type': 'WText', 'props': {'text': 'First'}}, {'type': 'WText', 'props': {'text': 'Second'}}, ], } ``` ## Action System Interactive widgets (buttons, inputs, checkboxes) trigger actions via the `onTap` and `onChange` props. Each action prop references a named handler registered in the `actions` map. **Supported action props:** - `onTap` - Single tap/click (WButton, WAnchor) - `onChange` - Value change (WInput, WCheckbox, WSelect, WDatePicker) **JSON syntax:** ```dart { 'onTap': { 'action': 'actionName', 'args': {'key': 'value'} } } ``` **Handler signature:** Action handlers support two signatures. Wind automatically detects which one you provide: ```dart // Simple signature (args only) (Map args) { print('Action triggered with ${args['key']}'); } // Stateful signature (args + state) (Map args, WDynamicState state) { final username = state.get('username'); print('User $username clicked ${args['button']}'); } ``` **Complete example:** ```dart WDynamic( json: { 'type': 'WButton', 'props': { 'className': 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded', 'onTap': {'action': 'submitForm', 'args': {'formId': 'login'}}, }, 'children': [ {'type': 'WText', 'props': {'text': 'Login'}} ], }, actions: { 'submitForm': (Map args, WDynamicState state) { final formId = args['formId']; final email = state.get('email'); final password = state.get('password'); print('Submitting $formId: email=$email'); }, }, ) ``` > [!NOTE] > If an action name is not found in the `actions` map, WDynamic logs a warning and continues silently. This prevents crashes from misconfigured JSON. ## Form State Management Widgets with an `id` prop automatically register their values in a shared state store. This eliminates manual state wiring for form fields. **Auto-tracked widgets:** - `WInput` - Tracks text value - `WCheckbox` - Tracks boolean checked state - `WSelect` - Tracks selected value - `WDatePicker` - Tracks DateTime value The `id` is all that is required. A widget carrying one writes every change into the store by itself, so `onChange` is needed only when a change must also dispatch an action. When both are present the store is written first and the action receives the new value in its args as `_value`. A widget with neither an `id` nor an `onChange` has nowhere to put a value, so it stays display-only. > [!NOTE] > `WDatePicker` delivers its value differently. It writes the date to the store like the others, but its action dispatches with no injected `_value`, so a handler reads the selected date from `state.get(id)`. **Accessing state from actions:** ```dart WDynamic( json: { 'type': 'WDiv', 'props': {'className': 'flex flex-col gap-4'}, 'children': [ { 'type': 'WInput', 'props': { 'id': 'username', // Auto-tracked 'placeholder': 'Enter name...', }, }, { 'type': 'WButton', 'props': { 'onTap': {'action': 'greet'}, }, 'children': [ {'type': 'WText', 'props': {'text': 'Greet'}} ], }, ], }, actions: { 'greet': (args, state) { final name = state.get('username') ?? 'Guest'; print('Hello, $name!'); }, }, ) ``` **External state access with WDynamicController:** The controller API allows you to read/write form values from outside the WDynamic widget: ```dart final controller = WDynamicController(); // Pre-fill values controller.setValue('email', 'user@example.com'); controller.setValue('newsletter', true); // Pass to WDynamic WDynamic( json: myFormJson, controller: controller, actions: myActions, ) // Read values externally final email = controller.getValue('email'); final allValues = controller.getAll(); // {'email': '...', 'newsletter': true} // Listen for changes final dispose = controller.addListener('email', (value) { print('Email changed: $value'); }); // Clean up controller.dispose(); ``` **State API reference:** | Method | Description | |:-------|:------------| | `getValue(id)` | Get a single value by id | | `setValue(id, value)` | Set a value (triggers listeners) | | `getAll()` | Get all values as a map | | `reset()` | Clear all values | | `addListener(id, callback)` | Listen for changes to a specific id | ## Security & Whitelisting Because JSON can come from untrusted sources (user input, third-party APIs), WDynamic uses a **whitelist-only** security model. Only explicitly allowed widget types can render. **Default allowed widgets:** Wind widgets: - `WDiv`, `WText`, `WButton`, `WImage`, `WIcon`, `WAnchor`, `WInput`, `WCheckbox`, `WSvg`, `WSelect`, `WPopover`, `WDatePicker`, `WSpacer` Flutter core widgets: - `Column`, `Row`, `Center`, `SizedBox`, `Expanded`, `Container`, `Wrap`, `Stack`, `Positioned`, `Padding`, `Align`, `Opacity`, `AspectRatio`, `FittedBox`, `ClipRRect`, `Spacer` **Restricting widgets:** Use the `denyWidgets` parameter to remove specific types from the whitelist: ```dart WDynamic( json: untrustedJson, denyWidgets: {'WButton', 'Stack'}, // Disable buttons and stacks ) ``` Any attempt to render a denied widget will trigger the `onUnknownWidget` callback, or render a red error box naming the denied type when no callback is set. **Why whitelist-only?** Arbitrary widget instantiation could expose dangerous APIs (file system access, network calls, etc.). The whitelist ensures that only safe, layout-focused widgets can be dynamically rendered. ## Custom Widget Builders Extend WDynamic with your own widget types using the `builders` parameter: ```dart WDynamic( json: { 'type': 'ProfileCard', 'props': { 'name': 'Alice', 'role': 'Engineer', 'avatar': 'https://example.com/avatar.png', }, }, builders: { 'ProfileCard': (Map props, List children) { return WDiv( className: 'flex items-center gap-4 p-4 bg-white rounded-lg shadow', children: [ WImage(src: props['avatar'], className: 'w-12 h-12 rounded-full'), WDiv( className: 'flex flex-col', children: [ WText(props['name'] ?? '', className: 'font-bold'), WText(props['role'] ?? '', className: 'text-sm text-gray-600'), ], ), ], ); }, }, ) ``` **Builder signature:** ```dart typedef WWidgetBuilder = Widget Function( Map props, List children, ); ``` Custom builders receive the parsed `props` map and any resolved `children` widgets. You can extract typed values, apply conditional logic, or compose complex layouts. > [!WARNING] > Custom builders bypass the security whitelist. Only register builders for widgets you trust. Never pass user-provided builder functions. ## Custom Icons For `WIcon` widgets in your JSON, you can provide custom icon mappings without building a full custom widget builder. Use the `customIcons` prop to map string names to `IconData`. Names are matched in lowercase, so write every key in lowercase: ```dart WDynamic( json: { 'type': 'WIcon', 'props': {'icon': 'mycustomicon'}, }, customIcons: { 'mycustomicon': Icons.star, 'myothericon': Icons.favorite, }, ) ``` This is a lightweight alternative to custom builders when you only need to extend the icon library. See [WDynamic: Custom Icons](https://anilcancakir.com/wind_kit/docs/widgets/w-dynamic.md#custom-icons) for complete configuration details. ## Error Handling WDynamic provides two callbacks for handling rendering failures: **onUnknownWidget** - Called when a widget type is not in the whitelist: ```dart WDynamic( json: {'type': 'UnsafeWidget', 'props': {}}, onUnknownWidget: (String type, Map props) { return WText( 'Widget "$type" not allowed', className: 'text-red-500 p-2 bg-red-50 rounded', ); }, ) ``` **onError** - Called when a widget build throws an exception: ```dart WDynamic( json: myJson, onError: (String type, Object error) { return WDiv( className: 'p-4 bg-red-100 border border-red-300 rounded', child: WText('Error rendering $type: $error'), ); }, ) ``` Both callbacks are optional. If one is not provided, WDynamic renders a red error box instead: it names the denied type for `onUnknownWidget`, and shows the exception text for `onError`. **Maximum depth protection:** To prevent infinite recursion or stack overflows from deeply nested JSON, WDynamic enforces a maximum depth limit (default: 50 levels): ```dart WDynamic( json: deeplyNestedJson, maxDepth: 100, // Increase if needed ) ``` ## Related Documentation - [WDynamic Widget](https://anilcancakir.com/wind_kit/docs/widgets/w-dynamic.md) - Full API reference - [State Management](https://anilcancakir.com/wind_kit/docs/core-concepts/state-management.md) - Interactive states and WAnchor - [Utility-First Fundamentals](https://anilcancakir.com/wind_kit/docs/core-concepts/utility-first.md) - Wind className syntax - [WButton](https://anilcancakir.com/wind_kit/docs/widgets/w-button.md) - Action-driven button widget - [WInput](https://anilcancakir.com/wind_kit/docs/widgets/w-input.md) - Form input with state tracking --- # Debugging Print a widget's composition tree with the `debug` token, catch unknown className tokens and expose wind_kit state to tapir. - [Introduction](#introduction) - [Enabling Debug Mode](#enabling-debug-mode) - [Understanding the Output](#understanding-the-output) - [Composition Tree](#composition-tree) - [Final Styles](#final-styles) - [Build Time](#build-time) - [How WindLogger Works](#how-windlogger-works) - [Quick Reference](#quick-reference) - [Common Scenarios](#common-scenarios) - [Unknown className Warnings](#unknown-classname-warnings) - [External Tooling Integration](#external-tooling-integration) - [Aggregate Performance Counters](#aggregate-performance-counters) ## Introduction Wind includes a specialised debugging system designed to peel back the abstraction of the utility-first engine. Since Wind translates a single `className` string into a complex hierarchy of Flutter widgets, the debugging tools allow you to verify the exact widget composition, resolved styles, and build performance. The system is built directly into the core engine, requiring no external configuration or devtools extensions to inspect the "parsing → styling → building" pipeline. ```dart WDiv( className: 'debug flex p-4 bg-blue-500 rounded-lg', child: WText('Debugging active'), ) ``` ## Enabling Debug Mode To enable debugging for a specific widget, add the `debug` utility class to its `className` string. This approach allows for targeted inspection without flooding the console with logs from unrelated widgets. ```dart // Enabling debug for a button WButton( className: 'debug bg-red-500 hover:bg-red-600 px-4 py-2 rounded', onTap: () => print('Action'), child: WText('Delete'), ) ``` When the `WindParser` encounters the `debug` class, it triggers a detailed report to the console during the widget's build phase. > [!NOTE] > Debug mode is scoped to the specific widget instance. It does not affect parent or child widgets unless they also include the `debug` class. ## Understanding the Output The debug report is divided into three primary sections that represent the lifecycle of a Wind widget. ### Composition Tree The Composition Tree visualises the "Atomic Widget" stack. Because Wind often wraps your content in multiple Flutter widgets (such as `Padding`, `DecoratedBox`, or `ConstrainedBox`) to satisfy the `className` requirements, this tree shows the final structure. ```text --- [WIND DEBUG] Composition Tree: --- Padding( padding: EdgeInsets.all(16.0), child: DecoratedBox( decoration: BoxDecoration( color: Color(0xFF3B82F6), borderRadius: BorderRadius.circular(8.0) ), child: ... ) ) ``` ### Final Styles This section displays the fully resolved `WindStyle` object. This is the final state after all utility classes, responsive modifiers (e.g., `md:`), and state variants (e.g., `hover:`) have been merged. ```text --- [WIND DEBUG] Final Styles: WindStyle( padding: EdgeInsets.all(16.0), backgroundColor: Color(0xFF3B82F6), borderRadius: BorderRadius.circular(8.0), isFlex: true, ) --- ``` ### Build Time Wind measures the total time spent in the style resolution and widget building process. Metrics are provided in microseconds (µs) to ensure high-precision performance monitoring. ```text --- [WIND DEBUG] Build Time: 142µs --- ``` Significant spikes in build time (typically >1ms) may indicate excessive class complexity or inefficient custom state resolutions. ## How WindLogger Works Internally, Wind leverages a `WindLogger` service. When the `WindParser` resolves a `className`, it checks for the presence of the `debug` flag in the resulting style object. If active, the parser passes the resolved properties and the generated widget hierarchy to the logger. The logger then formats this data into a human-readable "pseudo-Dart" format. The timing is handled by a `Stopwatch` that starts when the parsing begins and stops when the widget tree is returned. ## Quick Reference | Field | Purpose | Output Format | |:------|:--------|:--------------| | `debug` | Utility class | N/A | | `Composition Tree` | Widget hierarchy | Pseudo-Dart code | | `Final Styles` | Style verification | `WindStyle` properties | | `Build Time` | Performance audit | Microseconds (µs) | ## Common Scenarios Let's look at common debugging workflows using the `debug` class: - **Inspecting Responsive Styles**: Resize the viewport while `debug` is active. The logger will re-trigger and show the new `Final Styles` for the active breakpoint. - **Verifying State Variants**: Hover over or focus a widget with state-based classes (like `hover:bg-blue-600`). The log will update to show the merged style. - **Analysing Layout Layers**: Use the `Composition Tree` to determine which utility class is responsible for a specific Flutter wrapper, helping identify unexpected padding or alignment issues. ## Unknown className Warnings Wind drops any token no parser recognises: an unknown token never throws and never renders. That keeps a typo from crashing the UI, but a silent drop also hides the typo. To surface it, the parser prints a one-time hint in `kDebugMode` whenever it meets a className it cannot resolve: ```text Wind: unknown className 'ps-4' was ignored. ``` The hint fires once per unique token per session (deduped like the alias shadow and cycle warnings), so a token repeated across many widgets logs a single line. Release builds print nothing: the check sits behind `kDebugMode` and is tree-shaken out. > [!NOTE] > The warning only covers a token that no parser claims. A parser claims a token by its prefix and does not validate the value behind it, so `p-not-a-spacing` is claimed by the padding parser: it produces no padding and no warning. A silent result for a token you expect to work still means checking the spelling of its value. > > The warning targets genuine typos. Valid Wind tokens that a widget consumes outside the parser map are exempt and never warn: the `object-*` fit family (read by `WImage`), the `inline-flex` / `inline-block` / `inline` display keywords (emitted by `WBadge`, inert in Flutter), and deliberately inert compatibility tokens (`transition` / `transition-colors`, `antialiased`, `sr-only`, and the `*-nums` font-variant family such as `tabular-nums`). If a token you expect to work triggers the warning, check the [Token catalogue](https://anilcancakir.com/wind_kit/docs/layout) for the canonical spelling. A common case is a CSS name that Wind aliases: `flex-wrap` prints the hint and points you at the canonical `wrap`. ## External Tooling Integration Beyond the `debug` className for in-console inspection, Wind exposes a contracts-based bridge so external debug tools (E2E drivers, runtime inspectors, IDE plugins) can read live Wind state per widget. The bridge ships in the production dependency `wind_kit_diagnostics_contracts` and is installed via a single facade call. ### When you need this You need to call `Wind.installDebugResolver()` when one of these is true: - You are using `tapir` to drive E2E tests against your app. tapir reads Wind state (the active className, breakpoint, brightness, platform, dynamic states, resolved background colour, resolved text colour) via the contracts package during snapshot capture. - You are writing an inspector or IDE tool that needs the same per-widget Wind state at runtime. If neither applies, you can skip this step. Wind itself does not depend on the resolver being installed; widgets render the same with or without it. ### Wiring it up Call `Wind.installDebugResolver()` once during app startup, inside a `!kReleaseMode` guard (so a profile build still carries it). The call is idempotent (safe to call multiple times) and is automatically a no-op in release builds, so the bridge is tree-shaken out of production binaries. ```dart import 'package:flutter/foundation.dart'; import 'package:wind_kit/wind_kit.dart'; void main() { if (!kReleaseMode) { Wind.installDebugResolver(); } runApp(const MyApp()); } ``` After the call, any consumer of `WindDebugRegistry.current` (from the contracts package) can call `resolver.resolve(element)` on a Wind widget's `Element` to receive a map of debug fields: `className`, `breakpoint`, `brightness`, `platform`, `states`, `bgColor`, `textColor`. Tools read this without depending on Wind directly. ### What the contract emits `WindDebugResolverImpl.resolve(element)` returns at most seven fields per widget element: | Field | Type | Notes | |:------|:-----|:------| | `className` | `String` | The raw className string as written, before breakpoints, dark mode and state prefixes are applied | | `breakpoint` | `String` | The currently-active named breakpoint (`sm` / `md` / `lg` / `xl` / `2xl` / `base`) | | `brightness` | `String` | `'light'` or `'dark'` | | `platform` | `String` | `'web'` / `'android'` / `'ios'` / etc. | | `states` | `List` | Active dynamic states (`hover`, `focus`, `active`, custom keys) | | `bgColor` | `String?` | Resolved background colour as `#RRGGBB` (6 digits, alpha dropped), read from the className only, so an inline prop is not reflected | | `textColor` | `String?` | Resolved foreground colour as `#RRGGBB` (alpha dropped), read from the className only | Fields whose value is null are omitted; the map only carries what the widget actually resolved. ### Release-build safety `Wind.installDebugResolver()` and `Wind.installPerfResolver()` are no-ops in release builds (`!kReleaseMode`, not `kDebugMode`, so a profile build still carries them for a profiling session). The guard in user code further ensures dart2js / dart2native tree-shakes the entire branch in release. There is no production cost from leaving the call in place. ## Aggregate Performance Counters `WindDebugResolver` answers per-`Element` questions, so it has nowhere to put a number that belongs to no single element: how often the style cache was hit, how many `WDiv`s were built. `WindPerfCounters` and a second contract, `WindPerfResolver`, cover exactly those. ### The three cache outcomes, and why they are three `WindParser.parse` runs on every build of every W-widget, and its cache has three outcomes rather than the usual two: | Outcome | When | Cost | |---|---|---| | **Hit** | no `baseStyle`, and the key is present | a map lookup | | **Miss** | no `baseStyle`, key absent | parses once, then caches | | **Bypass** | a `baseStyle` was supplied | parses on every rebuild, never caches | A bypass never consults and never writes the cache, because the cache key is composed from the context and the className only. Folding it into "miss" would report a cache-miss rate that looks explainable while saying nothing about work that never amortises. Worth reading the call sites precisely: `WDiv` and `WText` both call the parser as `baseStyle: style`, and `style` is the widget's own nullable property. **A bypass is a property of a caller writing `style:`, not of using these widgets.** Driven against a real app, bypasses measured zero across 1613 W-widget builds while the hit rate stayed at 99% or better. ### Turning them on Counting is off by default and every increment sits behind one static bool check, so a disabled counter costs a load and a branch that tree-shakes: ```dart WindPerfCounters.enabled = true; // ... drive the interaction you want to measure ... print(WindPerfCounters.cacheHits); // and cacheMisses, cacheBypasses print(WindPerfCounters.wDivBuilds); // and wTextBuilds print(WindPerfCounters.widgetBuilds); // {'WButton': 3, 'WDiv': 12, ...} print(WindPerfCounters.wrapperEmissions); // {'DecoratedBox': 5, 'MouseRegion': 2, ...} print(WindPerfCounters.inheritedReads); // mediaQuerySize, mediaQueryBrightness, windTheme, defaultTextStyle ``` `widgetBuilds` counts every W-widget with a build method, by its type name (`'WButton'`, `'WDiv'`, ...); `wDivBuilds`/`wTextBuilds` stay as dedicated counters alongside it rather than being folded in, since they are the pinned six-key contract's own fields. `wrapperEmissions` counts the Flutter (or Wind-authored render-layer) wrapper widget Wind inserts at each composition-pipeline branch (`DecoratedBox`, `ConstrainedBox`, `Padding`, `MouseRegion`, `Semantics`, `DefaultTextStyle`, ...), one inline call beside the construction it never changes. `inheritedReads` is fixed to four keys: `mediaQuerySize` (a `MediaQuery.sizeOf(context)` read, which depends on the size aspect only), `mediaQueryBrightness` (`MediaQuery.maybePlatformBrightnessOf`), `windTheme` (`WindTheme.maybeDataOf`), `defaultTextStyle` (`DefaultTextStyle.of`). `WindParser.clearCache()` resets the counters as well as the cache, so a hit rate is always reported against the cache it was measured on. A brightness toggle does not clear the cache, so the counters continue across it; they zero when the host calls `clearCache()` (for example after changing the colour maps) or `WindPerfCounters.reset()`, which zeroes them without touching the cache. ### Reading them from outside `Wind.installPerfResolver()` publishes the totals through the diagnostics contracts package, the same bridge `installDebugResolver()` uses and a separate slot on the same registry. It is idempotent, `!kReleaseMode`-gated (so a profile build still carries it), and installing it costs nothing on its own since counting stays off until the flag is set: ```dart if (!kReleaseMode) { Wind.installDebugResolver(); // per-Element state Wind.installPerfResolver(); // aggregate counters } ``` A tool then reads `WindDebugRegistry.currentPerf?.stats()` and gets exactly nine keys: `cacheHits`, `cacheMisses`, `cacheBypasses`, `cacheSize`, `wDivBuilds`, `wTextBuilds` (all `int`), plus `widgetBuilds`, `wrapperEmissions`, `inheritedReads` (all `Map`). The `null` when no resolver is registered is meaningful and worth passing through rather than flattening to zeros: it is what lets a caller tell "wind never installed one" from "the counters really are zero". `tapir` reads this during a `tapir:perf_begin` / `tapir:perf_end` session. --- # Display Utilities for controlling the layout mode of an element. - [Basic Usage](#basic-usage) - [Block](#block) - [Flex](#flex) - [Grid](#grid) - [Wrap](#wrap) - [Hidden](#hidden) - [Quick Reference](#quick-reference) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [State Variants](#state-variants) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Flex container WDiv(className: 'flex gap-4', children: [...]) // Grid container WDiv(className: 'grid grid-cols-3', children: [...]) // Hidden element WDiv(className: 'hidden', child: ...) ``` ## Basic Usage ### Block Use `block` to create a standard block-level element. In Wind, this is the default behaviour of `WDiv` (rendering a `Container` or `SizedBox`). ```dart WDiv( className: 'block p-4 bg-white', child: WText('This is a block element'), ) ``` ### Flex Use `flex` to create a flex container. This enables Flexbox layout properties (like `flex-row`, `items-center`, `justify-between`). > [!NOTE] > `flex` creates a `Row` or `Column` depending on `flex-row` (default) or `flex-col`. ```dart WDiv( className: 'flex items-center space-x-4', children: [ WDiv(className: 'w-10 h-10 bg-blue-500'), WText('Flex item'), ], ) ``` ### Grid Use `grid` to create a grid container. This enables Grid layout properties (like `grid-cols-3`, `gap-4`). ```dart WDiv( className: 'grid grid-cols-3 gap-4', children: [ WDiv(className: 'h-20 bg-blue-100'), WDiv(className: 'h-20 bg-blue-200'), WDiv(className: 'h-20 bg-blue-300'), ], ) ``` ### Wrap Use `wrap` to create a wrapping container. This renders a Flutter `Wrap` widget. > [!NOTE] > In Flutter, `Row`/`Column` cannot wrap, so `wrap` renders a dedicated `Wrap` widget rather than composing with `flex`. Prefer `wrap` directly; `flex-wrap` is aliased to it for callers coming from CSS (emits a one-time debug hint suggesting `wrap`). ```dart WDiv( className: 'wrap gap-2', children: [ WBadge('React'), WBadge('Flutter'), WBadge('Vue'), ], ) ``` ### Hidden Use `hidden` to remove an element from the visual tree. This renders a `SizedBox.shrink()` (effectively 0x0 size). ```dart WDiv( className: 'hidden', child: WText('This will not appear'), ) ``` ## Quick Reference | Class | Description | | :--- | :--- | | `block` | Standard box model layout (Default). | | `flex` | Creates a Flex container (Row/Column). | | `grid` | Creates a Grid container. | | `wrap` | Creates a Wrap container. | | `hidden` | Removes the element from the layout. | ## Responsive Design Prefix any display utility with a breakpoint variant like `md:` to apply it only at specific screen sizes. ```dart // Hidden on mobile, Flex on medium screens and up WDiv( className: 'hidden md:flex gap-4', children: [...], ) // Block on mobile, Hidden on large screens WDiv( className: 'block lg:hidden', child: ..., ) ``` ## Dark Mode Prefix any display utility with `dark:` to apply it only when dark mode is active. ```dart // Visible only in dark mode WDiv( className: 'hidden dark:block', child: WText('Dark Mode Active'), ) ``` ## State Variants Prefix utilities with state variants like `hover:` or `focus:` to conditionally apply display modes. `active:` is not wired to the pointer; it applies only when the widget is given `states: {'active'}`. `group-hover:` is not supported. ```dart // Show element on hover WAnchor( onTap: () {}, child: WDiv( className: 'p-4', children: [ WText('Hover the card'), WText('Revealed on hover', className: 'hidden hover:block'), ], ), ) ``` A `WDiv` with `hover:` tracks its own hover; reveal through a `WText`. ## Arbitrary Values The display utility does not support arbitrary values. You must use one of the predefined keywords (`block`, `flex`, `grid`, `wrap`, `hidden`). ## Customizing Theme Display utilities are structural and cannot be customised via `WindThemeData`. They map directly to Flutter widgets (`Row`, `Column`, `Wrap`, `GridView`). ## Related Documentation - [Flexbox](https://anilcancakir.com/wind_kit/docs/layout/flexbox.md) - Layout direction, alignment, and sizing for flex containers. - [Grid](https://anilcancakir.com/wind_kit/docs/layout/grid.md) - Column definitions and gaps for grid containers. --- # Flexbox & Layout Utilities for controlling flex containers, direction, alignment, wrapping, and spacing. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Flex Direction](#flex-direction) - [Reversing Direction](#reversing-direction) - [Child Order](#child-order) - [Wrapping](#wrapping) - [Justify Content](#justify-content) - [Align Items](#align-items) - [Align Content](#align-content) - [Align Self](#align-self) - [Flex, Grow & Shrink](#flex-grow--shrink) - [Gap & Spacing](#gap--spacing) - [Responsive Design](#responsive-design) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Flex Children Inside a Scrollable Axis](#flex-and-overflow) - [Related Documentation](#related-documentation) ```dart // Basic flex row WDiv(className: 'flex gap-4 items-center') // Vertical column WDiv(className: 'flex flex-col gap-2') ``` ## Basic Usage Flexbox in Wind mirrors CSS Flexbox behaviour but maps to Flutter's `Row` and `Column` widgets. Use `flex` to initialise a flex container, then apply utilities to control direction, alignment, and spacing. ```dart WDiv( className: 'flex flex-col md:flex-row justify-between items-center gap-4 p-6 bg-white', children: [ WText('Logo'), WDiv( className: 'flex gap-2', children: [ WText('Home'), WText('About'), ], ), ], ) ``` ## Quick Reference | Class | CSS Equivalent | Flutter Equivalent | |:------|:---------------|:-------------------| | `flex` | `display: flex` | `Row` / `Column` | | `wrap` | `flex-wrap: wrap` | `Wrap` | | `flex-row` | `flex-direction: row` | `Row()` | | `flex-col` | `flex-direction: column` | `Column()` | | `flex-row-reverse` | `flex-direction: row-reverse` | `Row` with the text direction opposite to the ambient one | | `flex-col-reverse` | `flex-direction: column-reverse` | `Column(verticalDirection: up)` | | `order-{n}` | `order: {n}` | Stable-sort children before layout | | `justify-{alignment}` | `justify-content: ...` | `MainAxisAlignment` | | `items-{alignment}` | `align-items: ...` | `CrossAxisAlignment` | | `gap-{n}` | `gap: {n}` | `Flex.spacing` (render layer, no widget); a `SizedBox` spacer under `justify-around` / `justify-evenly` only | | `flex-1` | `flex: 1` | `Expanded()` | | `shrink-0` | `flex-shrink: 0` | No wrapper: preserves intrinsic size | ## Flex Direction Control the axis of your layout. ```dart // Row (Horizontal) - Default for 'flex' WDiv(className: 'flex flex-row') // Column (Vertical) WDiv(className: 'flex flex-col') ``` ## Reversing Direction Use `flex-row-reverse` or `flex-col-reverse` to reverse the main-axis direction. Rather than reversing the children list, Wind flips the axis itself (via `Row.textDirection` / `Column.verticalDirection`), so alignment tokens mirror correctly: `justify-start` anchors to what is now the visual end, matching CSS's `flex-direction: *-reverse`. Cross-axis alignment is unaffected. ```dart WDiv( className: 'flex flex-row-reverse gap-2', children: [ WText('First in code'), WText('Last in code'), ], ) // Renders as: [Last in code] [First in code] ``` Responsive prefixes work as expected: ```dart // Column on mobile, row-reverse on md+ WDiv(className: 'flex flex-col md:flex-row-reverse gap-4') ``` ## Child Order Use `order-*` on individual flex children to override their paint order without changing the source order. The parent flex container stable-sorts children by their resolved `order` value before laying them out; children without `order-*` default to `0`. | Class | Behaviour | |:------|:---------| | `order-0` .. `order-12` | Explicit index (integers only) | | `order-first` | Placed before everything else (sentinel `-9999`) | | `order-last` | Placed after everything else (sentinel `9999`) | | `order-none` | Resets to `0` | | `order-[n]` | Arbitrary integer (supports negative, e.g. `order-[-3]`) | ```dart WDiv( className: 'flex', children: [ WDiv(className: 'order-3', child: WText('A')), WDiv(className: 'order-1', child: WText('B')), WDiv(className: 'order-2', child: WText('C')), ], ) // Visual order: B, C, A ``` Responsive reordering is a common use case: swap the layout per breakpoint without duplicating children: ```dart WDiv( className: 'flex flex-col md:flex-row gap-4', children: [ WDiv(className: 'order-2 md:order-1', child: WText('Sidebar')), WDiv(className: 'order-1 md:order-2', child: WText('Main content')), ], ) ``` When combined with `flex-*-reverse`, children are sorted by `order-*` first; the container then flips the main-axis direction so `justify-*` still applies against the (now reversed) axis. ## Wrapping Use `wrap` to create a wrapping layout (Flutter's `Wrap` widget). > [!WARNING] > Flutter's `Row` and `Column` do **not** wrap. You must use the `wrap` class instead of `flex` if you want wrapping behaviour. ```dart // Items wrap to the next line when they run out of space WDiv( className: 'wrap gap-2', children: [ WDiv(className: 'px-2 py-1 bg-gray-200 rounded', child: WText('Tag 1')), WDiv(className: 'px-2 py-1 bg-gray-200 rounded', child: WText('Tag 2')), // ... ], ) ``` ## Justify Content Controls how children are distributed along the **main axis** (Horizontal for `row`, Vertical for `col`). | Class | Flutter `MainAxisAlignment` | |:------|:----------------------------| | `justify-start` | `start` | | `justify-end` | `end` | | `justify-center` | `center` | | `justify-between` | `spaceBetween` | | `justify-around` | `spaceAround` | | `justify-evenly` | `spaceEvenly` | ```dart WDiv( className: 'flex justify-between', children: [ WDiv(className: 'w-10 h-10 bg-red-500'), WDiv(className: 'w-10 h-10 bg-blue-500'), ], ) ``` > **A child that grows turns the automatic `Flexible` wrap off.** Space distribution wraps each row child in a `Flexible` so it can shrink, mirroring the CSS `flex: 0 1 auto` default. Flutter shares free space equally between flex children, though, so that wrap also hands a share to a sibling that never asked for one: a `justify-between` row holding a `flex-1` title and a 24 px icon split 400 px down the middle, and the title rendered at 200 px with 176 px sitting blank beside the icon. So when any child claims a grow share (`flex-1`, `flex-{n}`, `grow`, `flex-grow`, `flex-auto`, a bare `w-full`, or a raw `Expanded` / `Flexible`), the wrap is skipped and the growing child takes the whole remainder while its siblings keep their content width, which is what CSS `justify-content: space-between` does. There is nothing left to distribute once a child grows, so the wrap was only ever about shrinking. Three exceptions: `overflow-hidden` keeps the wrap unconditionally because that token asks for shrinking on purpose, the shrink-only tokens (`shrink`, `flex-shrink`, `flex-initial`) are not a grow claim, and a PREFIXED grow token (`hover:flex-1`, `md:grow`) is not one either. A prefixed token is conditional, and the row cannot tell from the class string whether the variant is active, so treating it as a claim would strip the shrink wrap off every sibling at a breakpoint where nothing grows. Use an unprefixed `flex-1` when you want the child to own the remainder. ## Align Items Controls how children are distributed along the **cross axis** (Vertical for `row`, Horizontal for `col`). | Class | Flutter `CrossAxisAlignment` | |:------|:-----------------------------| | `items-start` | `start` | | `items-end` | `end` | | `items-center` | `center` | | `items-baseline` | `baseline` | | `items-stretch` | `stretch` | ```dart // Vertically center items in a row WDiv(className: 'flex items-center h-20') ``` > **Column cross-axis stretch (default and explicit `items-stretch`).** A `flex flex-col` with no explicit `items-*` token, or with an explicit `items-stretch`, stretches each `WDiv`, `WAnchor` (any child), and `WButton` child that does not control its own width to the column width, matching CSS `align-items: stretch`. Explicit `items-stretch` therefore equalises child widths (every eligible child fills the column), closing the asymmetry with `grid ... items-stretch`. For `WAnchor`: when the anchor wraps a `WDiv`, the inner `WDiv`'s className decides (so `WAnchor > WDiv(w-32)` keeps 128 px; a `WAnchor > WDiv` with a self-flex token is excluded just as a direct self-flexing `WDiv` is); when the anchor wraps a `WText` or raw widget, the anchor stretches by policy so its tap surface fills the column. Left untouched: children with an explicit width (`w-*` / `min-w-*` / `max-w-*` / `w-full`, in any state/breakpoint variant), children that self-wrap in `Expanded`/`Flexible` (`grow`, `flex-grow`, `flex-auto`, `flex-initial`, `shrink`, `flex-shrink`, `flex-N`), `basis-*` children, absolute children, bare `WText` leaves, and raw Flutter widgets. `shrink-0` / `flex-none` children still stretch on the cross axis (`flex-shrink` is main-axis only, matching CSS). Add `items-start` / `items-center` / `items-end` to turn stretch off and let children size to content. Rows are never auto-stretched on the cross axis. When the column itself sits in an unbounded-width context (a bare `Row` slot, `UnconstrainedBox`, horizontal scroll), the stretch safely falls back to content-sized children instead of forcing an infinite width. > **Layout stability: wind's flex is intrinsic-safe.** Flutter's `IntrinsicHeight` and `IntrinsicWidth` perform an intrinsic-dimension pass that reads child sizes mid-layout, and a `LayoutBuilder` on that path asserts `LayoutBuilder does not support returning intrinsic dimensions`. Wind's flex uses no `LayoutBuilder`, and neither does `h-full` since it became the `WindFullHeightBox` render object: column cross-axis stretch is a real render object (`WindCrossStretch`), and `basis-*` resolves against the flex's own extent via a `WindMainExtentProvider` (see `lib/src/widgets/w_div.dart` and `wind_equal_height_row.dart`). So a `flex flex-col` (with or without `basis-*`) renders correctly inside an `items-stretch` grid cell, under an `IntrinsicHeight`/`IntrinsicWidth`, or in a `Table` cell without asserting. For a connector, rail, or divider that must fill the cross axis to match the tallest sibling, prefer a `Stack` with a `Positioned(top: 0, bottom: 0)` line, or use wind's own `items-stretch` column (also intrinsic-free and animation-safe). > > ```dart > // Safe connector pattern: Stack + Positioned, no IntrinsicHeight > Stack( > children: [ > Row(children: [leftContent, rightContent]), > Positioned( > top: 0, > bottom: 0, > left: connectorX, > width: 2, > child: ColoredBox(color: railColor), > ), > ], > ) > > // Or let wind do the stretch safely: > WDiv(className: 'flex flex-col items-stretch gap-4') > ``` ## Align Content Only applicable when using `wrap`. Controls how lines of wrapped content are aligned. | Class | Flutter `WrapAlignment` | |:------|:------------------------| | `align-content-start` | `start` | | `align-content-end` | `end` | | `align-content-center` | `center` | | `align-content-between` | `spaceBetween` | | `align-content-around` | `spaceAround` | | `align-content-evenly` | `spaceEvenly` | | `align-content-stretch` | `start` (Flutter limitation) | ## Align Self Control alignment of an individual flex item, overriding the container's `items-*` setting. | Class | Flutter `Alignment` | |:------|:--------------------| | `align-self-start` | `topCenter` | | `align-self-end` | `bottomCenter` | | `align-self-center` | `center` | | `align-self-stretch` | `center` | | `align-self-auto` | `center` | Tailwind's shorthand `self-start` / `self-end` / `self-center` / `self-stretch` / `self-auto` is accepted as an alias for the matching `align-self-*` class, so either form works. ```dart WDiv( className: 'flex items-start h-20', children: [ WDiv(className: '...'), // This item centers itself (self-center is shorthand for align-self-center) WDiv(className: 'self-center ...'), ], ) ``` ## Flex, Grow & Shrink Control how individual children resize to fill available space. | Class | Description | |:------|:------------| | `flex-1` | Allow child to grow and fill available space (`Expanded`). | | `flex-grow` / `grow` | Alias for `flex-1` (Tailwind v3 and v4 names). | | `grow-0` | Do not grow: child keeps its intrinsic main-axis size. | | `flex-{n}` | Specific flex factor (e.g., `flex-2`). | | `flex-auto` / `flex-initial` | Both wrap the child in a loose `Flexible`; only `flex-auto` counts as a grow claim. | | `shrink` | Allow child to shrink if needed (`FlexFit.loose`). | | `shrink-0` | Preserve intrinsic size: no Flexible wrapper, child keeps its natural dimensions. | | `flex-none` | CSS `flex: 0 0 auto`: do not grow AND do not shrink. Keeps intrinsic size (same no-shrink path as `shrink-0`). | | `flex-shrink-0` / `flex-grow-0` | Not wired: dropped silently. Write `shrink-0` or `grow-0`. | | `shrink-{n}` / `grow-{n}` with `n` above 0 | Not wired: dropped silently. `shrink-1` and `grow-1` are written `shrink` and `grow`; no other factor has an equivalent. | | `basis-1/2` / `basis-1/3` / `basis-1/4` / `basis-full` | Fractional flex-basis: initial main-axis size (width in a row, height in a column). Approximates CSS `flex-basis`; ignores grow/shrink interplay. | | `basis-[Npx]` | Fixed flex-basis: a fixed main-axis size in logical pixels (e.g., `basis-[120px]`). | > **`w-full` on a Row child is treated as `flex-1`.** A `flex flex-row` hands non-flex children an unbounded main-axis width, so a bare `w-full` child would otherwise assert `RenderBox was not laid out`. Wind now wraps such a child in `Expanded`, so `w-full` fills the row exactly like `flex-1`. Prefer `flex-1` for clarity; reach for `w-full` only when sharing a className across row and non-row contexts. A prefixed `md:w-full` is not auto-expanded (use `md:flex-1`). ```dart WDiv( className: 'flex', children: [ // Sidebar: Fixed width, won't shrink WDiv(className: 'w-16 shrink-0 bg-gray-200'), // Content: Fills remaining space WDiv( className: 'flex-1 bg-white p-4', child: WText('Main Content'), ), ], ) ``` ## Gap & Spacing Wind's `gap` utilities add space between flex or grid items without using margin on the children themselves. | Class | Value (Default) | Description | |:------|:----------------|:------------| | `gap-0` | 0px | No gap | | `gap-1` | 4px | Small gap | | `gap-2` | 8px | Medium gap | | `gap-4` | 16px | Large gap | | `gap-x-{n}` | - | Horizontal gap only | | `gap-y-{n}` | - | Vertical gap only | > [!NOTE] > `space-x-{n}` and `space-y-{n}` are supported as aliases for `gap`, but `gap` is preferred for consistency with Grid. ```dart // 16px gap horizontally and vertically WDiv(className: 'flex gap-4') // 8px horizontal, 16px vertical WDiv(className: 'flex flex-col gap-x-2 gap-y-4') ``` ## Responsive Design Change layout direction or spacing based on screen size. This is extremely powerful for mobile-first designs. ```dart // Column on mobile, Row on tablet+ WDiv(className: 'flex flex-col md:flex-row gap-4 md:gap-8') ``` ## Arbitrary Values Need a specific gap or flex value not in the theme? Use bracket notation. ```dart // Specific 23px gap WDiv(className: 'flex gap-[23px]') // Specific pixel gap (rem is not supported, so write px) WDiv(className: 'flex gap-[24px]') ``` ## Customizing Theme To change the default spacing scale used by `gap`, modify `baseSpacingUnit` or the `containers` map in `WindThemeData`. ```dart WindThemeData( baseSpacingUnit: 8.0, // Now gap-1 = 8px, gap-2 = 16px ) ``` ## Flex Children Inside a Scrollable Axis When a flex container's own main axis is scrollable (`flex-row` with `overflow-x-auto|scroll`, or `flex-col` with `overflow-y-auto|scroll`), Flutter's `Expanded`/`Flexible` cannot resolve: the incoming constraint is unbounded. Wind detects this at resolution time and skips the `Expanded`/`Flexible` wrap for direct flex children in that pass, so `flex-N` becomes a no-op while the axis is scrollable. This enables the common responsive pattern of "scroll on narrow, distribute on wide": ```dart WDiv( className: 'flex flex-row gap-1 overflow-x-auto sm:overflow-visible', children: [ for (final tab in tabs) WDiv(className: 'flex-1', child: _tabBtn(tab)), ], ) ``` At `base` the row scrolls horizontally and children keep their intrinsic width. At `sm+` the overflow is removed and `flex-1` distributes width equally. Nested non-scrolling flex subtrees under a scrolling ancestor behave normally. ## Related Documentation - [Grid Layout](https://anilcancakir.com/wind_kit/docs/layout/grid.md) - [Display Modes](https://anilcancakir.com/wind_kit/docs/layout/display.md) - [Sizing](https://anilcancakir.com/wind_kit/docs/layout/sizing.md) --- # Grid Template Columns Utilities for specifying the columns in a grid layout. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Equal-Height Rows](#equal-height-rows) - [Responsive](#responsive) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Related Documentation](#related-documentation) ```dart // Basic grid with 3 columns WDiv( className: 'grid grid-cols-3 gap-4', children: [ WDiv(className: 'bg-blue-500 h-12'), WDiv(className: 'bg-blue-500 h-12'), WDiv(className: 'bg-blue-500 h-12'), ], ) ``` ## Basic Usage Use `grid-cols-{n}` to create a grid with `n` equally sized columns. This utility must be used alongside the `grid` class to activate the grid layout model. ```dart WDiv( className: 'grid grid-cols-4 gap-4', children: [ // ... 4 items per row ], ) ``` ## Quick Reference | Class | Properties | |:--- |:--- | | `grid-cols-1` | `crossAxisCount: 1` | | `grid-cols-2` | `crossAxisCount: 2` | | `grid-cols-3` | `crossAxisCount: 3` | | `grid-cols-4` | `crossAxisCount: 4` | | `grid-cols-5` | `crossAxisCount: 5` | | `grid-cols-6` | `crossAxisCount: 6` | | `grid-cols-12` | `crossAxisCount: 12` | > [!NOTE] > Wind's parser supports **any integer** value for `grid-cols-{n}`, not just the standard Tailwind scale (1-12). ## Equal-Height Rows By default a Wind grid sizes each cell to its own content (it renders as a `Wrap`), so a row where one card is taller leaves the others short. Add `items-stretch` to make every cell in a row match the tallest, mirroring CSS Grid's default `align-items: stretch`. With `items-stretch` the grid renders as a column of equal-height rows instead of a `Wrap`. ```dart // KPI stat cards that all share the tallest card's height per row WDiv( className: 'grid grid-cols-3 gap-4 items-stretch', children: [ WDiv( className: 'p-4 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700', children: [ WText('Revenue', className: 'text-sm text-gray-500 dark:text-gray-400'), WText('\$12,480', className: 'text-2xl font-bold text-gray-900 dark:text-white'), WText('+8% vs last week', className: 'text-xs text-green-600 dark:text-green-400'), ], ), // ...sibling cards stretch to match the tallest ], ) ``` The equal-height rows are laid out for real (each cell is measured with a loose height, then re-laid to at least the row's tallest via a **min** height, never a tight squeeze), NOT via `IntrinsicHeight`, so cells whose content is itself a `flex flex-col`, or that use `h-full` / `basis-*` (neither of which carries a `LayoutBuilder` any more), stretch correctly instead of asserting `LayoutBuilder does not support returning intrinsic dimensions`. Because a cell is never forced below its own content height, a stretched cell also produces no residual `RenderFlex overflowed` warning (#141). ## Responsive Prefix grid utilities with breakpoint variants like `md:` or `lg:` to change the column count at different screen sizes. ```dart WDiv( className: 'grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-4', children: [ // 1 col on mobile, 3 on tablet, 6 on desktop ], ) ``` ## Dark Mode While column counts rarely change based on theme, you can use `dark:` if needed. ```dart WDiv( className: 'grid grid-cols-2 dark:grid-cols-4', children: [...], ) ``` ## Arbitrary Values Wind supports any integer for grid columns directly in the utility class. You do not need square brackets `[]` for this property. ```dart // Create a grid with exactly 16 columns WDiv( className: 'grid grid-cols-16 gap-1', children: [...], ) ``` ## Related Documentation - [Flexbox & Gap](https://anilcancakir.com/wind_kit/docs/layout/flexbox.md) - For `gap-{n}`, `gap-x-{n}`, and `gap-y-{n}` utilities. - [Display](https://anilcancakir.com/wind_kit/docs/layout/display.md) - For the `grid` class. --- # Sizing Utilities for setting the width and height of elements. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Width](#width) - [Height](#height) - [Size (Both Axes)](#size-both-axes) - [Min & Max Dimensions](#min--max-dimensions) - [Intrinsic Sizing Limitation](#intrinsic-sizing-limitation) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Fixed width and height WDiv(className: 'w-64 h-32 bg-blue-500') // Percentage based WDiv(className: 'w-1/2 h-full bg-green-500') // Viewport relative WDiv(className: 'w-screen h-screen bg-gray-900') ``` ## Basic Usage Use `w-{size}` and `h-{size}` to set fixed dimensions based on the global spacing scale, or use fraction/percentage utilities like `w-1/2` or `w-full`. ```dart WDiv( className: 'w-full max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg', child: WText('Centered Content'), ) ``` ## Quick Reference ### Fixed Sizes Values follow the global spacing scale (1 unit = 4px). | Class | Value | Description | |:------|:------|:------------| | `w-0` / `h-0` | 0px | Zero width/height | | `w-[1px]` / `h-[1px]` | 1px | 1px width/height | | `w-1` / `h-1` | 4px | Tiny | | `w-4` / `h-4` | 16px | Small | | `w-16` / `h-16` | 64px | Medium | | `w-96` / `h-96` | 384px | Large | ### Percentages & Viewport | Class | Value | Description | |:------|:------|:------------| | `w-full` / `h-full` | 100% | Full parent width/height | | `w-screen` / `h-screen` | 100vw / 100vh | Full viewport width/height | | `w-1/2` / `h-1/2` | 50% | Half parent size | | `w-1/3` / `h-1/3` | 33.33% | Third parent size | | `w-2/3` / `h-2/3` | 66.66% | Two-thirds parent size | | `w-1/4` / `h-1/4` | 25% | Quarter parent size | | `w-3/4` / `h-3/4` | 75% | Three-quarters parent size | ## Width Set the width of an element using `w-{number}` or `w-{fraction}`. ```dart // Fixed width (128px) WDiv(className: 'w-32 bg-blue-200') // Fractional width (50%) WDiv(className: 'w-1/2 bg-blue-300') // Full width (100%) WDiv(className: 'w-full bg-blue-400') ``` ## Height Set the height of an element using `h-{number}`, `h-{fraction}`, or `h-screen`. ```dart // Fixed height (64px) WDiv(className: 'h-16 bg-red-200') // Full screen height WDiv(className: 'h-screen bg-red-300') ``` ## Size (Both Axes) `size-{value}` sets width AND height in a single token (the Tailwind v3.4+ shorthand). It accepts the same values as `w-` / `h-`: spacing numbers, `full`, fractions, and arbitrary `[px]` / `[%]`. It works on a childless `WDiv`, so a fixed-size square such as a status dot needs no wrapper. ```dart // 8x8 status dot, no child required WDiv(className: 'size-2 rounded-full bg-green-500 dark:bg-green-400') // Square avatar WDiv(className: 'size-12 rounded-full bg-gray-200 dark:bg-gray-700') // Fill the parent on both axes WDiv(className: 'size-full bg-blue-500 dark:bg-blue-600') ``` A later `w-*` or `h-*` overrides the matching axis: `size-4 w-8` is 32px wide, 16px tall. ## Min & Max Dimensions Constrain the size of elements using `min-w`, `max-w`, `min-h`, and `max-h` utilities. ### Max-Width Scale Wind includes a comprehensive max-width scale, perfect for keeping text readable ("measure") or constraining layout width. | Class | Value | |:------|:------| | `max-w-0` | 0px | | `max-w-none` | Sets nothing; to lift a limit at a breakpoint use `md:max-w-full` | | `max-w-xs` | 320px | | `max-w-sm` | 384px | | `max-w-md` | 448px | | `max-w-lg` | 512px | | `max-w-xl` | 576px | | `max-w-2xl` | 672px | | `max-w-3xl` | 768px | | `max-w-4xl` | 896px | | `max-w-5xl` | 1024px | | `max-w-6xl` | 1152px | | `max-w-7xl` | 1280px | | `max-w-full` | No limit (Infinity) | | `max-w-screen` | 100vw | | `max-w-prose` | 512px | ```dart // Constrain content width and center it WDiv( className: 'max-w-2xl mx-auto p-4', child: WText('Readable content...'), ) ``` ### Other Constraints | Class | Value | |:------|:------| | `min-w-0` | 0px | | `min-w-full` | 100vw (viewport, not parent) | | `min-w-screen` | 100vw | | `min-h-0` | 0px | | `min-h-full` | 100vh (viewport, not parent) | | `min-h-screen` | 100vh | | `max-h-full` | No limit (Infinity) | | `max-h-screen` | 100vh | ## Intrinsic Sizing Limitation Wrapping Wind content in `IntrinsicHeight` or `IntrinsicWidth` (or placing it in a `Row` / `Column` that needs child intrinsic heights, e.g. for equal-height columns) can throw: ``` LayoutBuilder does not support returning intrinsic dimensions. ``` **Why.** To resolve a size against the parent's real constraints, Wind used to introduce a `LayoutBuilder`, and a `LayoutBuilder` runs during the layout phase rather than the intrinsic-sizing phase, so any intrinsic query passing through one asserts. That is a Flutter constraint, not a Wind bug. **What still triggers it: `grid` only.** `grid-cols-*` composes a `Wrap` inside a `LayoutBuilder` (it needs the available width to compute a column width), so a `grid` anywhere inside the subtree you wrap in `IntrinsicHeight` / `IntrinsicWidth` still asserts. **What no longer does.** `h-full` and flex `basis-*` are both intrinsic-safe now. `h-full` resolves through the `WindFullHeightBox` render object, `basis-*` through `WindMainExtentProvider`, and the column cross-axis stretch through `WindCrossStretch`; a render object answers intrinsic queries, so all three render under an `IntrinsicHeight`, in a `Table` cell, or in an `items-stretch` grid cell without throwing. If you carry an escape hatch for `h-full` from an earlier version, you can drop it. **Escape hatches, for the `grid` case that remains.** - Prefer explicit sizing: give the cells a fixed `h-*` (or `size-*`) instead of a `grid` + `IntrinsicHeight`. - For an equal-height row, use a `Stack` with a `Positioned(top: 0, bottom: 0)` element for the part that must fill, or reserve equal content so natural heights already match. - Wind's own `items-stretch` grid equalises row heights with real layout rather than `IntrinsicHeight`, so reach for it instead of wrapping. ```dart // Throws: the wrapped subtree contains a grid. IntrinsicHeight( child: WDiv(className: 'grid grid-cols-2 gap-4', children: cards), ) // Safe: items-stretch equalises the row heights with real layout. WDiv( className: 'grid grid-cols-2 gap-4 items-stretch', children: cards, ) ``` ## Responsive Design Apply different sizing utilities at different breakpoints using standard prefixes. ```dart // Full width on mobile, 50% on tablet, 33% on desktop WDiv(className: 'w-full md:w-1/2 lg:w-1/3') ``` ## Dark Mode While sizing rarely changes based on theme, you can use `dark:` modifiers if needed. ```dart WDiv(className: 'w-64 dark:w-full') ``` ## Arbitrary Values Use square brackets to apply specific values that aren't in the theme scale. ```dart // Specific pixels WDiv(className: 'w-[350px] h-[50px]') // Specific percentage WDiv(className: 'w-[33%] h-[45%]') ``` ## Customizing Theme There is no spacing map: any number is a multiple of `baseSpacingUnit`, so `w-128` is already 512px. Change the unit with `WindThemeData(baseSpacingUnit: ...)`. This affects width, height, padding, margin, and positioning. ```dart WindThemeData( baseSpacingUnit: 8.0, // w-4 is now 32px ) ``` ## Related Documentation - [Padding & Margin](https://anilcancakir.com/wind_kit/docs/layout/spacing.md) - [Flexbox & Grid](https://anilcancakir.com/wind_kit/docs/layout/flexbox.md) - [Aspect Ratio](https://anilcancakir.com/wind_kit/docs/layout/aspect-ratio.md) --- # Spacing Utilities for controlling the padding and margin of elements. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Adds 16px padding inside, 16px margin outside WDiv(className: 'p-4 m-4 bg-white') ``` ## Basic Usage Control the spacing of an element using the `p-{size}` (padding) and `m-{size}` (margin) utilities. ### Padding Padding adds space **inside** the element's boundary. ```dart // Add padding to all sides WDiv(className: 'p-6 bg-blue-100', child: WText('Padding')) ``` ### Margin Margin adds space **outside** the element's boundary. ```dart // Add margin to all sides WDiv(className: 'm-4 bg-red-100', child: WText('Margin')) ``` ## Quick Reference The default spacing scale is based on a **4px** unit (`baseSpacingUnit: 4.0`). | Class | Value (Default) | Description | |:------|:----------------|:------------| | `p-0` / `m-0` | 0px | No spacing | | `p-1` / `m-1` | 4px | Tiny spacing | | `p-2` / `m-2` | 8px | Small spacing | | `p-4` / `m-4` | 16px | Normal spacing | | `p-8` / `m-8` | 32px | Large spacing | | `p-16` / `m-16` | 64px | Extra large spacing | | `px-{n}` / `mx-{n}` | - | Horizontal (Left + Right) | | `py-{n}` / `my-{n}` | - | Vertical (Top + Bottom) | | `pt-{n}` / `mt-{n}` | - | Top only | | `pr-{n}` / `mr-{n}` | - | Right only | | `pb-{n}` / `mb-{n}` | - | Bottom only | | `pl-{n}` / `ml-{n}` | - | Left only | ## Variants ### Directional Spacing Target specific sides or axes using direction prefixes. ```dart // Horizontal padding (Left + Right) WDiv(className: 'px-4 bg-green-100') // Vertical margin (Top + Bottom) WDiv(className: 'my-6 bg-yellow-100') // Top padding only WDiv(className: 'pt-8 bg-purple-100') ``` ### Horizontal Centering Use `mx-auto` to centre a container horizontally. This sets the left and right margins to align the element within its parent. > [!NOTE] > `mx-auto` only works when the element has a defined width less than its parent. Explicit `mx-{value}` classes (like `mx-0`) will override `mx-auto`. ```dart WDiv( className: 'mx-auto w-32 bg-blue-500', child: WText('Centered'), ) ``` ## Responsive Design Apply different spacing at different breakpoints using the standard `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` prefixes. ```dart // p-4 on mobile, p-8 on medium screens, p-12 on large screens WDiv(className: 'p-4 md:p-8 lg:p-12') ``` ## Dark Mode Use the `dark:` prefix to apply different spacing in dark mode. ```dart WDiv(className: 'p-4 dark:p-6') ``` ## Arbitrary Values If the built-in scale doesn't meet your needs, use bracket notation to apply exact pixel values. > [!WARNING] > Wind parsers for padding and margin currently support **pixels** (`[10px]`, `[10]`) but do NOT support percentages (`[10%]`). ```dart // Exact 13px padding WDiv(className: 'p-[13px]') // Exact 50px top margin WDiv(className: 'mt-[50px]') ``` ## Customizing Theme To extend or override the default spacing scale, modify the `WindThemeData` in your app root. The `baseSpacingUnit` controls the multiplier for all spacing utilities (`p-`, `m-`, `gap-`, `w-`, `h-`). ```dart WindThemeData( baseSpacingUnit: 8.0, // Sets 'p-1' to 8px, 'p-4' to 32px ) ``` ## Related Documentation - [Sizing](https://anilcancakir.com/wind_kit/docs/layout/sizing.md) - [Flexbox](https://anilcancakir.com/wind_kit/docs/layout/flexbox.md) (Gap utilities) - [Grid](https://anilcancakir.com/wind_kit/docs/layout/grid.md) (Gap utilities) --- # Positioning Utilities for controlling how elements are positioned in the layout using `relative` and `absolute` placement. - [Position Types](#position-types) - [Offset Utilities](#offset-utilities) - [Inset Shortcuts](#inset-shortcuts) - [Negative Offsets](#negative-offsets) - [Arbitrary Values](#arbitrary-values) - [Common Patterns](#common-patterns) - [Combining with Flex](#combining-with-flex) - [Future Work](#future-work) - [Related Documentation](#related-documentation) ```dart // Badge overlay: red dot on an avatar WDiv( className: 'relative', children: [ WDiv(className: 'w-12 h-12 rounded-full bg-gray-300'), WDiv(className: 'absolute top-0 right-0 w-3 h-3 rounded-full bg-red-500'), ], ) ``` ## Position Types Wind maps CSS `position` values to Flutter's `Stack` widget. A `relative` parent establishes a stacking context; `absolute` children are placed inside it using offset utilities. | Wind className | CSS Equivalent | Flutter Widget | |:---------------|:---------------|:---------------| | `relative` | `position: relative` | `Stack` (parent) | | `absolute` | `position: absolute` | `Positioned` (child inside `Stack`) | > [!NOTE] > An `absolute` child must live inside a `relative` parent. Wind will wrap the `relative` container in a `Stack` and each `absolute` child in a `Positioned` widget automatically. ```dart WDiv( className: 'relative w-32 h-32 bg-gray-100', children: [ WDiv(className: 'absolute top-2 left-2 w-8 h-8 bg-blue-500'), ], ) ``` ## Offset Utilities Control the position of `absolute` children using directional offset classes. Values follow the spacing scale (`spacing * n`). | Class | CSS Equivalent | Description | |:------|:---------------|:------------| | `top-{n}` | `top: {n}` | Distance from the top edge | | `right-{n}` | `right: {n}` | Distance from the right edge | | `bottom-{n}` | `bottom: {n}` | Distance from the bottom edge | | `left-{n}` | `left: {n}` | Distance from the left edge | Default spacing scale (base unit = 4px): | Class | Value | |:------|:------| | `top-0` | 0px | | `top-1` | 4px | | `top-2` | 8px | | `top-4` | 16px | | `top-6` | 24px | | `top-8` | 32px | The same scale applies to `right-*`, `bottom-*`, and `left-*`. ```dart WDiv( className: 'relative h-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg', children: [ WDiv( className: 'absolute bottom-4 right-4 px-3 py-2 bg-blue-600 rounded', child: WText('Action', className: 'text-sm text-white'), ), ], ) ``` ## Inset Shortcuts Apply offsets to multiple sides at once with `inset-*` shorthand classes. | Class | Sides Affected | Description | |:------|:---------------|:------------| | `inset-{n}` | top, right, bottom, left | All four sides | | `inset-x-{n}` | left, right | Horizontal sides only | | `inset-y-{n}` | top, bottom | Vertical sides only | | `inset-0` | top, right, bottom, left | Full stretch (fills parent) | ```dart // Full overlay: covers the entire relative parent WDiv( className: 'relative w-full h-48', children: [ WImage(src: 'assets/photo.jpg', className: 'w-full h-full'), // Semi-transparent overlay that fills the image WDiv( className: 'absolute inset-0 bg-black opacity-40', ), WText( 'Caption', className: 'absolute bottom-4 left-4 text-white font-semibold', ), ], ) ``` ```dart // Horizontal inset: leaves top and bottom unconstrained WDiv( className: 'absolute inset-x-4 bottom-4 bg-white rounded p-3', child: WText('Bottom bar'), ) ``` ## Negative Offsets Prefix any offset class with `-` to pull an element outside its parent's boundary. | Class | Value | Description | |:------|:------|:------------| | `-top-{n}` | negative top | Pulls element above the parent | | `-right-{n}` | negative right | Pulls element to the right of the parent | | `-bottom-{n}` | negative bottom | Pulls element below the parent | | `-left-{n}` | negative left | Pulls element to the left of the parent | | `-inset-{n}` | negative all sides | Expands element beyond all four parent edges | ```dart // Notification badge: overlaps the top-right corner of an icon button WDiv( className: 'relative', children: [ WDiv(className: 'w-10 h-10 rounded bg-gray-200 flex items-center justify-center'), WDiv( className: 'absolute -top-1 -right-1 w-4 h-4 rounded-full bg-red-500 flex items-center justify-center', child: WText('3', className: 'text-[10px] text-white font-bold'), ), ], ) ``` ## Arbitrary Values Use bracket notation when you need an exact pixel value not in the theme scale. Only `px` values are supported; percentage (`%`) offsets are not supported because Flutter's `Positioned` widget uses logical pixels, not relative units. ```dart // Exact pixel value WDiv(className: 'absolute top-[24px] left-[12px]') // Mixed: precise multi-side offset WDiv(className: 'absolute top-[12px] right-[8px] bottom-[12px] left-[8px]') ``` ## Common Patterns ### Badge Overlay A notification dot positioned on top of an avatar or icon. ```dart WDiv( className: 'relative w-12 h-12', children: [ WDiv(className: 'w-12 h-12 rounded-full bg-indigo-500'), WDiv( className: 'absolute -top-1 -right-1 w-5 h-5 rounded-full bg-red-500 border-2 border-white flex items-center justify-center', child: WText('2', className: 'text-[9px] text-white font-bold'), ), ], ) ``` ### Floating Action Button (FAB) An action button pinned to the bottom-right corner of a scrollable view. ```dart WDiv( className: 'relative flex-1', children: [ WDiv( className: 'w-full h-full overflow-y-auto p-4', child: WText('Scrollable content...'), ), WDiv( className: 'absolute bottom-6 right-6 w-14 h-14 rounded-full bg-blue-600 flex items-center justify-center shadow-lg', child: WIcon(Icons.add_outlined, className: 'text-white'), ), ], ) ``` ### Full Overlay A semi-transparent scrim that covers an entire card or image. ```dart WDiv( className: 'relative rounded-xl overflow-hidden', children: [ WImage(src: 'assets/hero.jpg', className: 'w-full h-48 object-cover'), WDiv(className: 'absolute inset-0 bg-gradient-to-t from-black/60 to-transparent'), WDiv( className: 'absolute bottom-0 left-0 right-0 p-4', child: WText('Hero Title', className: 'text-white text-lg font-bold'), ), ], ) ``` ## Combining with Flex `relative` and `absolute` compose naturally with flex layouts. The `relative` container itself can be a flex row or column; the `Stack` wraps around the flex widget, and `absolute` children are layered on top. ```dart // Navigation bar with an absolute badge on the icon WDiv( className: 'flex flex-row items-center justify-between px-4 py-3 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700', children: [ WText('Inbox', className: 'text-base font-semibold text-gray-900 dark:text-gray-100'), WDiv( className: 'relative', children: [ WIcon(Icons.notifications_outlined, className: 'text-gray-700 dark:text-gray-300'), WDiv( className: 'absolute -top-1 -right-1 w-4 h-4 rounded-full bg-red-500', ), ], ), ], ) ``` > [!NOTE] > When you add `absolute` children to a flex item, Wind promotes that item to a `Stack`. The flex layout of the parent is preserved. ## Future Work The following position types are tracked but not yet implemented in v1: | Class | Status | |:------|:-------| | `fixed` | Deferred: maps to Flutter's `Overlay`/`Stack` at the root level | | `sticky` | Deferred: requires custom `SliverPersistentHeader` integration | Until these land, use `Overlay` directly or Flutter's `Stack` at the `Scaffold` body level for fixed positioning. ## Related Documentation - [Flexbox & Layout](https://anilcancakir.com/wind_kit/docs/layout/flexbox.md) - [Grid Layout](https://anilcancakir.com/wind_kit/docs/layout/grid.md) - [Sizing](https://anilcancakir.com/wind_kit/docs/layout/sizing.md) - [Spacing](https://anilcancakir.com/wind_kit/docs/layout/spacing.md) --- # Z-Index Utilities for controlling the stack order of an element. ## Table of Contents - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Standard values WDiv(className: 'z-0') WDiv(className: 'z-50') // Arbitrary values WDiv(className: 'z-[100]') WDiv(className: 'z-[-1]') ``` ## Basic Usage `z-*` parses into `WindStyle.zIndex`, but no widget reads it yet: children paint in source order. Control stacking by child order. ```dart // Painted in source order: the green box ends up on top Stack( children: [ WDiv(className: 'z-0 bg-blue-500 w-20 h-20'), WDiv(className: 'z-10 bg-red-500 w-20 h-20 ml-10 mt-10'), WDiv(className: 'z-20 bg-green-500 w-20 h-20 ml-20 mt-20'), ], ) ``` ## Quick Reference | Class | Value | Description | |:------|:------|:------------| | `z-0` | 0 | Stack level 0 | | `z-10` | 10 | Stack level 10 | | `z-20` | 20 | Stack level 20 | | `z-30` | 30 | Stack level 30 | | `z-40` | 40 | Stack level 40 | | `z-50` | 50 | Stack level 50 | | `z-auto` | `null` | Resets stack level (default) | ## Variants ### Hover & Focus State prefixes such as `hover:z-50` parse like any other class, but because no widget reads `zIndex`, they do not change the paint order. ## Responsive Design Apply different z-indices at specific breakpoints using the `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` prefixes. ```dart // z-0 on mobile, z-50 on medium screens and up WDiv(className: 'z-0 md:z-50') ``` ## Dark Mode Use the `dark:` prefix to apply specific z-indices when the application is in dark mode. ```dart WDiv(className: 'z-10 dark:z-20') ``` ## Arbitrary Values Use square brackets `[]` to apply custom z-index values that aren't part of the theme. This supports both positive and negative integers. ```dart // Custom positive value WDiv(className: 'z-[100]') // Custom negative value WDiv(className: 'z-[-5]') ``` ## Customizing Theme To extend or override the default z-index scale, modify the `zIndices` property in `WindThemeData`. ```dart WindThemeData( zIndices: { // Override defaults '0': 0, '50': 50, // Add custom values '100': 100, 'modal': 9999, 'popover': 5000, }, ) ``` Usage with custom theme: ```dart WDiv(className: 'z-modal') // Parses to zIndex 9999 WDiv(className: 'z-popover') // Parses to zIndex 5000 ``` ## Related Documentation - [Position](https://anilcancakir.com/wind_kit/docs/layout/positioning.md) - [Display](https://anilcancakir.com/wind_kit/docs/layout/display.md) - [WindTheme](https://anilcancakir.com/wind_kit/docs/core-concepts/theming.md) --- # Overflow Utilities for controlling how an element handles content that is too large for the container. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Fill on Desktop, Scroll on Narrow](#min-width-scroll) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Basic examples WDiv(className: 'overflow-hidden') WDiv(className: 'overflow-scroll') WDiv(className: 'overflow-x-auto') ``` ## Basic Usage Use `overflow-{mode}` to control how content behaves when it exceeds the container's bounds. ### Hidden Use `overflow-hidden` to clip any content that extends beyond the container's edges. The clip follows the container's own corner radius, and the anti-aliasing follows the shape: a square clip uses `Clip.hardEdge`, which is the cheapest option and exactly right on an axis-aligned rectangle, while a ROUNDED one uses `Clip.antiAlias`. Hard-edging a curve can only cut on whole pixels, and because a border's stroke sits inside the clip path (`BorderSide.strokeAlign` defaults to `strokeAlignInside`), the staircase eats the border rather than the surface behind it: the line thins and vanishes through each corner while the straight runs stay crisp. ```dart WDiv( className: 'overflow-hidden w-20 h-20 bg-white rounded-lg', child: WDiv(className: 'w-40 h-40 bg-red-500'), ) ``` ### Scroll Use `overflow-scroll` to enable scrolling regardless of whether content overflows. This generally wraps the content in a `SingleChildScrollView`. ```dart WDiv( className: 'overflow-scroll w-64 h-64 bg-white', child: LongContentWidget(), ) ``` ### Auto Use `overflow-auto` to enable scrolling only when content actually overflows the container bounds. ```dart WDiv( className: 'overflow-auto max-h-48 bg-gray-100', child: LongContentWidget(), ) ``` ### Visible Use `overflow-visible` to allow content to spill out of the container. This applies `Clip.none` and prevents the container from introducing a scroll context. ```dart WDiv( className: 'overflow-visible', child: LargeContentWidget(), ) ``` ## Quick Reference | Class | Flutter Equivalent | Description | |:------|:-------------------|:------------| | `overflow-auto` | `SingleChildScrollView` (conditional) | Scroll only if content overflows | | `overflow-hidden` | `Clip.antiAlias` when rounded, `Clip.hardEdge` when square | Clip overflowing content; with a border, the clip sits inside it (the padding box), as in CSS | | `overflow-visible` | `Clip.none` | Content flows outside container | | `overflow-scroll` | `SingleChildScrollView` | Always enable scrolling | ## Variants ### Directional Overflow Use `overflow-x-{mode}` and `overflow-y-{mode}` to control overflow on a specific axis independently. ```dart WDiv(className: 'overflow-x-scroll overflow-y-hidden') ``` | Class | Description | |:------|:------------| | `overflow-x-auto` | Scroll horizontally if needed | | `overflow-y-scroll` | Always scroll vertically | | `overflow-x-hidden` | Clip horizontal overflow | | `overflow-y-visible` | Allow vertical overflow | A horizontally scrolling div keeps its own border, background and padding across at least the full viewport, the way CSS paints them on the scroll container. A tab strip's baseline runs edge to edge however few tabs it holds, and content wider than the viewport keeps its width and scrolls: ```dart WDiv( className: 'w-full flex flex-row overflow-x-auto border-b border-gray-200 dark:border-gray-700', children: [ WText('Overview', className: 'px-4 py-3'), WText('Metrics', className: 'px-4 py-3'), ], ) ``` ## Fill on Desktop, Scroll on Narrow (Responsive Table) Compose `overflow-x-auto` on a wrapper with `w-full` (optionally `min-w-[Npx]`) on the inner content, the same pattern shadcn's `` uses. The inner content fills the viewport when it is wide and honours its minimum width (so the wrapper scrolls) when it is narrow: ```dart WDiv( className: 'overflow-x-auto', child: WDiv( className: 'w-full min-w-[600px] flex flex-row', children: [ WDiv(className: 'flex-1', child: WText('Name')), WDiv(className: 'flex-1', child: WText('Status')), WDiv(className: 'flex-1', child: WText('Updated')), ], ), ) ``` On a viewport wider than `600px` the row fills the container (no horizontal scroll). On a narrower viewport it stays `600px` wide and the wrapper scrolls horizontally. This works with no new token: `w-full` inside a horizontal scroll is threaded the viewport width instead of asserting on the scroll's unbounded width, and `min-w-[Npx]` sets the scroll floor. Without a `min-w-*` floor, `w-full` simply fills the viewport. > **`h-full` inside a vertical scroll is a layout error.** A child that resolves `h-full` inside an `overflow-y-auto` / `overflow-y-scroll` parent has an unbounded height and produces a cryptic Flutter failure. Wind raises an actionable assert in debug pointing at the fix: use `flex-1` inside a `flex flex-col` (with the scroll on the column) instead of `h-full` inside a vertical scroll. The scroll container itself may still carry `h-full` (it is bounded by its own parent). ## Responsive Design Apply different overflow behaviours at different breakpoints using standard modifiers like `sm:`, `md:`, `lg:`, `xl:`, and `2xl:`. ```dart // Scroll on mobile, but show full content on desktop WDiv(className: 'overflow-scroll md:overflow-visible') ``` ## Dark Mode Change overflow behaviour in dark mode using the `dark:` prefix. ```dart WDiv(className: 'overflow-visible dark:overflow-hidden') ``` ## Arbitrary Values The overflow utility does not support arbitrary values. You must use one of the defined keywords (`hidden`, `visible`, `scroll`, `auto`). ## Customizing Theme Overflow utilities are hardcoded behaviour toggles and cannot be customised via `WindThemeData`. ## Related Documentation - [Sizing](https://anilcancakir.com/wind_kit/docs/layout/sizing.md) - [Display](https://anilcancakir.com/wind_kit/docs/layout/display.md) --- # Aspect Ratio Utilities for controlling the aspect ratio of an element. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Basic examples showing the syntax WDiv(className: 'aspect-square') WDiv(className: 'aspect-video') ``` ## Basic Usage Use `aspect-{ratio}` to set the preferred aspect ratio of an element. This is particularly useful for images, video players, or ensuring consistent card sizing. ### Video (16:9) Use `aspect-video` to force a 16:9 ratio, perfect for video content. ```dart WDiv( className: 'w-full aspect-video bg-black', child: WText('Video Placeholder', className: 'text-white'), ) ``` ### Square (1:1) Use `aspect-square` to create a perfect square, regardless of width. ```dart WDiv( className: 'w-1/2 aspect-square bg-blue-500', ) ``` ### Auto Use `aspect-auto` to remove any applied aspect ratio constraints. ```dart WDiv(className: 'aspect-auto') ``` ## Quick Reference | Class | Ratio | Description | |:------|:------|:------------| | `aspect-auto` | `null` | Default behaviour (no ratio) | | `aspect-square` | `1 / 1` | Square (1:1) | | `aspect-video` | `16 / 9` | Standard video (16:9) | ## Variants Wind supports standard state variants for aspect ratios. ```dart // Change aspect ratio on hover WDiv(className: 'aspect-square hover:aspect-video') ``` ## Responsive Design Apply different aspect ratios at different breakpoints using standard responsive prefixes. ```dart // Square on mobile, Video on desktop WDiv(className: 'aspect-square md:aspect-video') ``` ## Dark Mode While less common for layout structure, you can conditionally apply aspect ratios in dark mode. ```dart WDiv(className: 'aspect-square dark:aspect-video') ``` ## Arbitrary Values Use the bracket syntax `aspect-[w/h]` to apply any specific aspect ratio that isn't included in the default theme. ```dart // 4:3 ratio (Classic TV) WDiv(className: 'aspect-[4/3]') // 21:9 ratio (Ultrawide) WDiv(className: 'aspect-[21/9]') ``` > [!NOTE] > Arbitrary values must use the format `width/height` where both are numbers (e.g., `[4/3]`, `[16/10]`). ## Customizing Theme Currently, the aspect ratio scale is hardcoded to standard Tailwind defaults (`auto`, `square`, `video`) and cannot be extended via `WindThemeData`. To use custom ratios, we recommend using [Arbitrary Values](#arbitrary-values) or creating a custom parser if you need named utilities for specific ratios. ## Related Documentation - [Sizing](https://anilcancakir.com/wind_kit/docs/layout/sizing.md) - [Display](https://anilcancakir.com/wind_kit/docs/layout/display.md) --- # Font Family Utilities for controlling the typeface of text. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WText('Sans-serif text', className: 'font-sans') WText('Serif text', className: 'font-serif') WText('Monospace text', className: 'font-mono') ``` ## Basic Usage Use `font-{family}` utilities to apply a font stack to an element. ### Sans-serif Use `font-sans` to apply a web-safe sans-serif family string. This is the default font family for Wind. ```dart WText( 'The quick brown fox jumps over the lazy dog.', className: 'font-sans text-lg', ) ``` ### Serif Use `font-serif` to apply a serif family string, ideal for long-form text or editorial designs. ```dart WText( 'The quick brown fox jumps over the lazy dog.', className: 'font-serif text-lg', ) ``` ### Monospace Use `font-mono` to apply a monospace family string, perfect for code snippets or technical data. ```dart WText( 'const pi = 3.14159;', className: 'font-mono text-sm bg-gray-100 p-2 rounded', ) ``` ## Quick Reference | Class | Font Family | Description | |:------|:------------|:------------| | `font-sans` | `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif` | Default sans-serif family string. | | `font-serif` | `Georgia, Cambria, "Times New Roman", Times, serif` | Default serif family string. | | `font-mono` | `ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace` | Default monospace family string. | Each string goes whole to `TextStyle.fontFamily`; Wind does not split it into `fontFamilyFallback`, so Flutter does not try the listed families one by one. Register a single installed family under a custom key in `WindThemeData.fontFamilies` when the exact face matters. ## Variants Hover and focus states can also change the font family, though this is less common. ```dart WText( 'Hover me to switch fonts', className: 'font-sans hover:font-serif transition-all', ) ``` ## Responsive Design Apply different font families at different breakpoints using standard responsive prefixes. ```dart WText( 'Responsive Typography', className: 'font-sans md:font-serif lg:font-mono', ) ``` ## Dark Mode You can enforce specific font families in dark mode using the `dark:` prefix. ```dart WText( 'Code Snippet', className: 'font-sans dark:font-mono', ) ``` ## Arbitrary Values If you need a specific font family that isn't in your theme, use bracket notation. This is useful for one-off fonts. ```dart WText( 'Custom Branding', className: 'font-[Roboto]', ) ``` ## Customizing Theme To add your own font families (like `font-display` or `font-body`), update `WindThemeData` in your app root. ```dart WindTheme( data: WindThemeData( fontFamilies: { 'sans': 'Inter', // Overrides default font-sans 'display': 'Oswald', // Adds new font-display class 'body': 'Open Sans', // Adds new font-body class }, ), child: MaterialApp(home: MyApp()), ) ``` Now you can use these classes in your widgets: ```dart WText('Headlines', className: 'font-display text-4xl') WText('Body text', className: 'font-body') ``` > [!NOTE] > When using custom fonts, declare the font assets under `flutter: fonts:` in your app's `pubspec.yaml`, then reference the declared family name through `WindThemeData.fontFamilies`. ## Related Documentation - [Font Size](https://anilcancakir.com/wind_kit/docs/typography/font-size.md) - [Font Weight](https://anilcancakir.com/wind_kit/docs/typography/font-weight.md) - [Letter Spacing](https://anilcancakir.com/wind_kit/docs/typography/letter-spacing.md) --- # Font Size Utilities for controlling the font size of an element. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WText('Small text', className: 'text-sm') WText('Large text', className: 'text-lg') WText('Extra large', className: 'text-xl') ``` ## Basic Usage Use `text-{size}` to set the font size of an element. ```dart WText( 'The quick brown fox jumps over the lazy dog.', className: 'text-base', ) WText( 'Main Heading', className: 'text-4xl font-bold', ) ``` ## Quick Reference | Class | Size | Pixel Value | |:------|:-----|:------------| | `text-xs` | 0.75rem | 12px | | `text-sm` | 0.875rem | 14px | | `text-base` | 1rem | 16px | | `text-lg` | 1.125rem | 18px | | `text-xl` | 1.25rem | 20px | | `text-2xl` | 1.5rem | 24px | | `text-3xl` | 1.875rem | 30px | | `text-4xl` | 2.25rem | 36px | | `text-5xl` | 3rem | 48px | | `text-6xl` | 3.75rem | 60px | > For sizes beyond `text-6xl`, use the arbitrary-value syntax (`text-[80px]`) or extend `WindThemeData.fontSizes` with additional entries. ## Variants ### Line Height Modifier You can set the line height simultaneously by adding a forward slash and a line-height value to any font size utility. This supports numeric spacing values and arbitrary pixel values. A named line height (`text-lg/loose`) is stored as pixels, not as a multiplier, so use `leading-loose` for a named one. ```dart // Numeric spacing (text-base + 28px line height) // 7 * 4px = 28px WText('Spaced text', className: 'text-base/7') // Arbitrary line height WText('Precise text', className: 'text-xl/[32px]') ``` | Class | Size | Line Height | |:------|:-----|:------------| | `text-sm/6` | 14px | 24px | | `text-base/7` | 16px | 28px | | `text-xl/[32px]` | 20px | 32px | ## Responsive Design Apply different font sizes at different breakpoints using the standard `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` prefixes. ```dart WText( 'Responsive Text', className: 'text-sm md:text-base lg:text-xl', ) ``` ## Dark Mode Use the `dark:` prefix to apply different font sizes when the application is in dark mode. ```dart WText( 'Dynamic Sizing', className: 'text-base dark:text-lg', ) ``` ## Arbitrary Values If the built-in scale doesn't meet your needs, use bracket notation to apply custom values directly. ```dart // Exact pixel value WText('15px text', className: 'text-[15px]') // rem is read as px, so write px WText('24px text', className: 'text-[24px]') ``` ## Customizing Theme To extend or override the default font size scale, modify the `fontSizes` property in `WindThemeData`. ```dart WindThemeData( fontSizes: { // Override existing 'xs': 10.0, // Add new custom size 'mega': 100.0, }, ) ``` Usage: ```dart WText('Tiny text', className: 'text-xs') // 10.0 WText('Huge text', className: 'text-mega') // 100.0 ``` ## Related Documentation - [Font Weight](https://anilcancakir.com/wind_kit/docs/typography/font-weight.md) - [Line Height](https://anilcancakir.com/wind_kit/docs/typography/line-height.md) - [Text Colour](https://anilcancakir.com/wind_kit/docs/typography/text-color.md) --- # Font Weight Utilities for controlling the font weight of an element. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WDiv( className: 'flex flex-col gap-4', children: [ WText('The quick brown fox', className: 'font-light'), WText('The quick brown fox', className: 'font-normal'), WText('The quick brown fox', className: 'font-bold'), ], ) ``` ## Basic Usage Control the font weight of an element using the `font-{weight}` utilities. ```dart WText('Most text uses font-normal', className: 'font-normal') WText('Important text uses font-bold', className: 'font-bold') ``` ## Quick Reference | Class | Weight | Description | |:------|:-------|:------------| | `font-thin` | 100 | Thin | | `font-extralight` | 200 | Extra Light | | `font-light` | 300 | Light | | `font-normal` | 400 | Normal | | `font-medium` | 500 | Medium | | `font-semibold` | 600 | Semi Bold | | `font-bold` | 700 | Bold | | `font-extrabold` | 800 | Extra Bold | | `font-black` | 900 | Black | ## Variants ### Font Style Control the font style of an element using the `italic` and `not-italic` utilities. ```dart WText('This text is italicized.', className: 'italic') WText('This text is normal.', className: 'not-italic') ``` | Class | Style | Description | |:------|:------|:------------| | `italic` | `FontStyle.italic` | Renders text in italics | | `not-italic` | `FontStyle.normal` | Renders text normally (useful for resetting) | ## Responsive Design Apply different font weights at specific breakpoints using standard responsive prefixes. ```dart // Normal on mobile, bold on medium screens and up WText('Responsive Weight', className: 'font-normal md:font-bold') ``` ## Dark Mode Adjust font weight based on the theme brightness. This is useful for maintaining legibility against dark backgrounds, where lighter weights might appear too thin. ```dart // Lighter weight in dark mode for better readability WText('Adaptive Weight', className: 'font-medium dark:font-normal') ``` ## Arbitrary Values If you need a specific font weight that isn't included in your theme, use square bracket notation. ```dart // 100 to 999 truncates to the standard weight at or below it (700 -> bold, 550 -> medium); 1000 and above, or below 100, set no weight WText('Custom Weight', className: 'font-[700]') WText('Variable Weight', className: 'font-[550]') ``` ## Customizing Theme You can customise the `fontWeights` scale in your `WindThemeData` configuration. ```dart WindTheme( data: WindThemeData( fontWeights: { 'hairline': FontWeight.w100, 'heavy': FontWeight.w900, // Override default 'bold': FontWeight.w600, }, ), child: MyApp(), ) ``` ## Related Documentation - [Font Size](https://anilcancakir.com/wind_kit/docs/typography/font-size.md) - [Font Family](https://anilcancakir.com/wind_kit/docs/typography/font-family.md) - [Text Colour](https://anilcancakir.com/wind_kit/docs/typography/text-color.md) --- # Line Height Utilities for controlling the leading (line height) of an element. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Relative line-heights // `leading-none` is not supported yet; use `leading-tight` or override a named key. WText('Leading tight', className: 'leading-tight') WText('Leading loose', className: 'leading-loose') // Fixed line-heights WText('Leading 6', className: 'text-base leading-6') ``` ## Basic Usage Use `leading-{value}` to control the line height of an element. Relative line heights (like `normal`, `loose`) are multipliers of the font size, while fixed line heights (like `leading-6`) are logical pixels. A fixed line height needs a font size on the same widget (a `text-*` class): Wind divides the pixels by that size to get Flutter's height factor, and without one the line height is not applied. ```dart WDiv( className: 'max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl', child: WDiv( className: 'p-8', child: WText( 'The quick brown fox jumps over the lazy dog.', className: 'leading-loose text-gray-900', ), ), ) ``` > [!NOTE] > You can also set line height directly within the font size utility, e.g., `text-lg/7` or `text-lg/[28px]`. See [Font Size](https://anilcancakir.com/wind_kit/docs/typography/font-size.md) for details. ## Quick Reference | Class | Value | Description | |:--- |:--- |:--- | | `leading-none` | none | Not supported yet; use `leading-tight` or override a named key | | `leading-tight` | 1.25 | Relative height | | `leading-snug` | 1.375 | Relative height | | `leading-normal` | 1.5 | Relative height | | `leading-relaxed` | 1.625 | Relative height | | `leading-loose` | 2 | Relative height | | `leading-3` | 0.75rem | Fixed height (12px) | | `leading-4` | 1rem | Fixed height (16px) | | `leading-5` | 1.25rem | Fixed height (20px) | | `leading-6` | 1.5rem | Fixed height (24px) | | `leading-7` | 1.75rem | Fixed height (28px) | | `leading-8` | 2rem | Fixed height (32px) | | `leading-9` | 2.25rem | Fixed height (36px) | | `leading-10` | 2.5rem | Fixed height (40px) | ## Responsive Design Apply different line height values at different breakpoints using the standard `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` prefixes. ```dart WText( 'Responsive leading', className: 'leading-tight md:leading-normal lg:leading-loose', ) ``` ## Dark Mode Use the `dark:` prefix to apply different line heights when the application is in dark mode. ```dart WText( 'Dark mode leading', className: 'leading-normal dark:leading-loose', ) ``` ## Arbitrary Values If the built-in scale doesn't meet your needs, use bracket notation to apply custom values directly. ```dart // Fixed pixel value WText('Custom px', className: 'text-base leading-[24px]') // A bare number is also logical pixels WText('Custom bare number', className: 'text-base leading-[24]') ``` A fixed pixel line height needs a font size on the same widget (`text-base` above): Wind divides the pixels by that size to get Flutter's height factor, and without one the line height is not applied. A size inherited from a parent does not count. Arbitrary values are logical pixels, not a multiplier. `rem` is not converted: `leading-[1.8]` is 1.8 px and `leading-[3rem]` is 3 px. ## Customizing Theme To override the named line height scale, modify the `leading` property in `WindThemeData`. ```dart WindThemeData( leading: { 'loose': 2.5, }, ) ``` Only the six named keys parse (`none`, `tight`, `snug`, `normal`, `relaxed`, `loose`); override their values, do not add keys. `none` has no default value, so `leading-none` does nothing until you supply one (`'none': 1.0`). ## Related Documentation - [Font Size](https://anilcancakir.com/wind_kit/docs/typography/font-size.md) - Setting font size and line height together - [Letter Spacing](https://anilcancakir.com/wind_kit/docs/typography/letter-spacing.md) - Controlling tracking (letter spacing) --- # Letter Spacing Utilities for controlling the tracking (letter spacing) of an element. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Which Font Size an em Uses](#which-font-size-an-em-uses) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WDiv( className: 'flex flex-col gap-4', children: [ WText('Tight letter spacing', className: 'tracking-tight'), WText('Normal letter spacing', className: 'tracking-normal'), WText('Wide letter spacing', className: 'tracking-wide'), ], ) ``` ## Basic Usage Use `tracking-{size}` to control the letter spacing of an element. The named steps are in em, exactly as in Tailwind: a multiple of the font size of the text they apply to, so the same class reads the same on a caption and on a headline. ```dart WText( 'The quick brown fox jumps over the lazy dog.', className: 'text-sm tracking-wide', ) ``` This spaces 14px text by `14 * 0.025 = 0.35` logical pixels. ## Quick Reference | Class | Value | At 14px (`text-sm`) | At 30px (`text-3xl`) | |:------|:------|:--------------------|:---------------------| | `tracking-tighter` | -0.05em | -0.7 | -1.5 | | `tracking-tight` | -0.025em | -0.35 | -0.75 | | `tracking-normal` | 0em | 0 | 0 | | `tracking-wide` | 0.025em | 0.35 | 0.75 | | `tracking-wider` | 0.05em | 0.7 | 1.5 | | `tracking-widest` | 0.1em | 1.4 | 3.0 | ## Which Font Size an em Uses Flutter's `TextStyle.letterSpacing` is in logical pixels, so Wind multiplies the em by the size the text actually renders at, in this order: 1. The `text-*` size in the same className, including a responsive or state variant (`text-sm md:text-3xl`). On `WText`, a `textStyle:` font size wins over both, because it is what the text renders at. 2. The font size the element inherits from the nearest `DefaultTextStyle` (a parent `WDiv` with a `text-*` class publishes one, and so does a Material ancestor). 3. Flutter's `kDefaultFontSize` (14), the size unsized text paints at. The theme's `base` is not used: no text paints at it unless a `DefaultTextStyle` sets it, and the em has to match the glyphs. `WInput` skips the second step: `EditableText` reads no `DefaultTextStyle`, so a field with no `text-*` size paints at Flutter's `kDefaultFontSize` (14) and its em resolves there. The em is taken from the unscaled font size. Flutter scales `fontSize` by the accessibility `TextScaler` but leaves `letterSpacing` as written, so under large text the spacing is a smaller share of the glyph than the em says. A `WDiv` or `WButton` resolves its tracking at its own font size and publishes the result in pixels, the way CSS computes `letter-spacing`. A child with a different `text-*` size inherits those pixels, not the em; give the child its own `tracking-*` when it should scale. ```dart WDiv( className: 'text-xs uppercase tracking-widest', children: [ WText('Section'), // 12 * 0.1 = 1.2 ], ) ``` ## Responsive Design Apply different letter spacing at different breakpoints using the standard `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` prefixes. ```dart WText( 'Responsive Tracking', className: 'tracking-normal md:tracking-wide lg:tracking-widest', ) ``` ## Dark Mode Use the `dark:` prefix to apply different tracking when the application is in dark mode. ```dart WText( 'Dark Mode Tracking', className: 'tracking-normal dark:tracking-wide', ) ``` ## Arbitrary Values If the built-in scale doesn't meet your needs, use bracket notation to apply custom values directly. An `em` value scales with the font size like the named steps; a `px` value or a bare number is logical pixels, as it is in `leading-[24]` and `text-[20]`. Both take a negative sign. ```dart // -0.02em of 30px: letterSpacing -0.6 WText('Headline', className: 'text-3xl tracking-[-0.02em]') // Exactly 3 logical pixels at any size WText('Custom Spacing', className: 'tracking-[3px]') ``` ## Customizing Theme To retune the scale, override its keys through the `tracking` property in `WindThemeData`. The values are em multipliers, like the defaults: `0.05` is 5% of the font size, never 0.05 logical pixels. ```dart WindThemeData( tracking: { 'tight': -0.02, 'widest': 0.15, }, ) ``` > [!NOTE] > Only the six named keys (`tighter` through `widest`) parse as classes. A new key such as `tracking-mega` is not recognised; use an arbitrary value like `tracking-[0.2em]` instead. ## Related Documentation - [Font Size](https://anilcancakir.com/wind_kit/docs/typography/font-size.md) - [Line Height](https://anilcancakir.com/wind_kit/docs/typography/line-height.md) - [Font Weight](https://anilcancakir.com/wind_kit/docs/typography/font-weight.md) --- # Text Align Utilities for controlling the alignment of text. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Basic alignment examples WText('Left aligned', className: 'text-left') WText('Center aligned', className: 'text-center') WText('Right aligned', className: 'text-right') ``` ## Basic Usage Use `text-left`, `text-center`, `text-right`, and `text-justify` to control the text alignment of an element. ```dart WDiv( className: 'flex flex-col gap-4', children: [ WText('Left aligned text.', className: 'text-left'), WText('Center aligned text.', className: 'text-center'), WText('Right aligned text.', className: 'text-right'), WText('Justified text.', className: 'text-justify'), ], ) ``` ## Quick Reference | Class | Value | Description | |:------|:------|:------------| | `text-left` | `TextAlign.left` | Align text to the left. | | `text-center` | `TextAlign.center` | Align text to the centre. | | `text-right` | `TextAlign.right` | Align text to the right. | | `text-justify` | `TextAlign.justify` | Stretch lines of text to equal width. | | `text-start` | `TextAlign.start` | Align text to the start (direction-aware). | | `text-end` | `TextAlign.end` | Align text to the end (direction-aware). | ## Responsive Design Apply different alignment at different breakpoints using the standard `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` prefixes. ```dart // Center on mobile, left on medium screens and up WText( 'Responsive Alignment', className: 'text-center md:text-left', ) ``` ## Dark Mode Use the `dark:` prefix to apply different alignment in dark mode. While less common for alignment than colours, it is fully supported. ```dart WText('Dark mode alignment', className: 'text-left dark:text-center') ``` ## Arbitrary Values The text alignment utility does not support arbitrary values because it maps directly to Flutter's `TextAlign` enum. ## Customizing Theme Text alignment values are hardcoded to map to Flutter's `TextAlign` enum and cannot be customised via `WindThemeData`. ## Related Documentation - [Font Size](https://anilcancakir.com/wind_kit/docs/typography/font-size.md) - [Font Weight](https://anilcancakir.com/wind_kit/docs/typography/font-weight.md) - [Line Height](https://anilcancakir.com/wind_kit/docs/typography/line-height.md) --- # Text Color Utilities for controlling the text colour of an element. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Opacity Modifier](#opacity-modifier) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Basic text colors WText('Error', className: 'text-red-500') WText('Success', className: 'text-green-600') WText('Info', className: 'text-blue-500') ``` ## Basic Usage Control the text colour of an element using the `text-{color}-{shade}` utilities. ```dart WDiv( className: 'p-4 bg-white rounded-lg shadow-sm', children: [ WText('The quick brown fox', className: 'text-slate-900 font-bold text-xl'), WText('Jumps over the lazy dog', className: 'text-slate-500'), ], ) ``` ## Quick Reference Wind includes the standard Tailwind colour palette. Here are some examples: | Class | Value | Description | |:------|:------|:------------| | `text-slate-500` | #64748b | Slate text colour | | `text-gray-500` | #6b7280 | Gray text colour | | `text-red-500` | #ef4444 | Red text colour | | `text-blue-600` | #2563eb | Blue text colour | | `text-white` | #ffffff | White text | | `text-black` | #000000 | Black text | | `text-transparent` | transparent | Transparent text | ## Opacity Modifier Control the opacity of the text colour using the colour opacity modifier. Add a forward slash and the opacity percentage value to any colour utility. ```dart WDiv( className: 'flex flex-col gap-2', children: [ WText('Text opacity 100%', className: 'text-blue-500'), WText('Text opacity 75%', className: 'text-blue-500/75'), WText('Text opacity 50%', className: 'text-blue-500/50'), WText('Text opacity 25%', className: 'text-blue-500/25'), ], ) ``` ## Responsive Design Apply different text colours at specific breakpoints using standard responsive prefixes. ```dart WText( 'Responsive Text Color', className: 'text-blue-500 md:text-green-500 lg:text-red-500', ) ``` ## Dark Mode Use the `dark:` prefix to apply specific text colours when dark mode is active. ```dart WDiv( className: 'bg-white dark:bg-slate-900 p-6', child: WText( 'Dark Mode Support', className: 'text-slate-900 dark:text-white', ), ) ``` ## Arbitrary Values If you need a specific colour that isn't in your theme, use square brackets to generate a property on the fly using a hex code. ```dart WText( 'Custom Hex Color', className: 'text-[#50d71e]', ) ``` Three, four, six and eight hex digits are accepted, matching [`hexToColor`](https://anilcancakir.com/wind_kit/docs/utilities/color-helpers.md#hex-colors). The four- and eight-digit forms carry alpha and it leads, because Flutter packs `AARRGGBB` where CSS writes `RRGGBBAA`. The same lengths apply to `decoration-[#...]`. ```dart WText('Half-faded', className: 'text-[#8050d71e]') WText('Underlined', className: 'underline decoration-[#80ff0000]') ``` ## Customizing Theme To add your own colours, modify the `colors` property in `WindThemeData`. ```dart WindThemeData( colors: { // Add custom color maps 'brand': MaterialColor(0xFF1E40AF, {500: Color(0xFF1E40AF)}), 'tahiti': MaterialColor(0xFF164E63, {100: Color(0xFFCFFAFE), 900: Color(0xFF164E63)}), }, ) ``` The map merges with the default palette by name, so the built-in colours stay available. A name that matches a built-in palette (`blue`, say) replaces that whole palette, not shade by shade: list every shade you still use, or `text-blue-600` stops resolving. Then use them in your classes: ```dart WText('My Brand Color', className: 'text-brand-500') WText('Tahiti Color', className: 'text-tahiti-900') ``` ## Related Documentation - [Background Colour](https://anilcancakir.com/wind_kit/docs/styling/background-color.md) - [Text Decoration](https://anilcancakir.com/wind_kit/docs/typography/text-decoration.md#decoration-color) --- # Text Decoration Utilities for controlling the decoration of text. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Basic decoration WText('Underline', className: 'underline') // With color, style, and thickness WText('Styled', className: 'underline decoration-blue-500 decoration-wavy decoration-2') ``` ## Basic Usage Use `underline`, `overline`, or `line-through` to decorate text. Use `no-underline` to remove existing decorations. ```dart WText('The quick brown fox...', className: 'underline') WText('The quick brown fox...', className: 'overline') WText('The quick brown fox...', className: 'line-through') WText('Link without underline', className: 'no-underline') ``` ## Quick Reference | Class | Properties | Description | |:------|:-----------|:------------| | `underline` | `decoration: underline` | Adds an underline. | | `overline` | `decoration: overline` | Adds an overline. | | `line-through` | `decoration: line-through` | Adds a strikethrough. | | `no-underline` | `decoration: none` | Removes decorations. | ## Variants ### Decoration Color Use the `decoration-{color}` utilities to change the colour of the text decoration. ```dart WText('Error', className: 'underline decoration-red-500') WText('Success', className: 'underline decoration-green-600') ``` ### Decoration Style Use the `decoration-{style}` utilities to change the style of the text decoration. ```dart WText('Solid', className: 'underline decoration-solid') WText('Double', className: 'underline decoration-double') WText('Dotted', className: 'underline decoration-dotted') WText('Dashed', className: 'underline decoration-dashed') WText('Wavy', className: 'underline decoration-wavy') ``` | Class | Description | |:------|:------------| | `decoration-solid` | Sets the decoration style to solid. | | `decoration-double` | Sets the decoration style to double. | | `decoration-dotted` | Sets the decoration style to dotted. | | `decoration-dashed` | Sets the decoration style to dashed. | | `decoration-wavy` | Sets the decoration style to wavy. | ### Decoration Thickness Use the `decoration-{width}` utilities to change the thickness of the text decoration. ```dart WText('1px', className: 'underline decoration-1') WText('2px', className: 'underline decoration-2') WText('4px', className: 'underline decoration-4') WText('8px', className: 'underline decoration-8') ``` ## Responsive Design Apply different decoration utilities at different breakpoints using standard responsive modifiers. ```dart WText('Responsive decoration', className: 'no-underline md:underline') ``` ## Dark Mode Use the `dark:` prefix to apply different decoration styles in dark mode. ```dart WText('Link', className: 'text-gray-900 underline decoration-gray-900 dark:text-white dark:decoration-white') ``` ## Arbitrary Values Use bracket notation for custom decoration colours or thickness. ```dart // Custom color WText('Brand underline', className: 'underline decoration-[#50d71e]') // Custom thickness WText('3px underline', className: 'underline decoration-[3px]') ``` ## Customizing Theme ### Decoration Color Decoration colours use your theme's `colors` configuration. ```dart WindThemeData( colors: { 'brand': MaterialColor(0xFF1E88E5, {500: Color(0xFF1E88E5)}), }, ) ``` The map merges with the default palette by name, so the built-in colours stay available. A name that matches a built-in palette (`blue`, say) replaces that whole palette, not shade by shade: list every shade you still use. ## Related Documentation - [Text Colour](https://anilcancakir.com/wind_kit/docs/typography/text-color.md) - [Font Weight](https://anilcancakir.com/wind_kit/docs/typography/font-weight.md) --- # Text Overflow Utilities for controlling how text behaves when it overflows its container. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Truncate](#truncate) - [Ellipsis & Clip](#ellipsis--clip) - [Line Clamp](#line-clamp) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Basic truncation WText( 'Long text that should be truncated...', className: 'truncate', ) // Limit to specific lines WText( 'Multi-line text block...', className: 'line-clamp-3', ) ``` ## Basic Usage Use `truncate` to prevent text from wrapping and truncate overflowing text with an ellipsis (...) if needed. This is the most common way to handle overflow for single-line text. ```dart WDiv( className: 'w-64', child: WText( 'The quick brown fox jumps over the lazy dog.', className: 'truncate', ), ) ``` ## Quick Reference | Class | Properties | Description | |:------|:-----------|:------------| | `truncate` | `maxLines: 1`, `softWrap: false`, `overflow: ellipsis` | Truncate text with an ellipsis on a single line. | | `text-ellipsis` | `overflow: ellipsis` | Use an ellipsis for overflow. | | `text-clip` | `overflow: clip` | Clip overflowing text (no ellipsis). | | `line-clamp-{n}` | `maxLines: n`, `overflow: ellipsis` | Limit text to n lines. | | `line-clamp-none` | `maxLines: null` | Remove line limits. | ## Truncate The `truncate` utility is a shorthand that sets three properties at once: 1. Sets `maxLines` to 1 2. Disables `softWrap` 3. Sets `overflow` to `TextOverflow.ellipsis` ```dart WText( 'Long text that will be cut off with an ellipsis if it gets too long', className: 'truncate', ) ``` ## Ellipsis & Clip Use `text-ellipsis` or `text-clip` to control the visual overflow behaviour specifically. - **text-ellipsis**: Renders an ellipsis (...) when text overflows. - **text-clip**: Simply cuts off the text at the boundary. ```dart // Ellipsis (default for truncate/line-clamp) WText('...', className: 'text-ellipsis') // Clip (hard cut) WText('...', className: 'text-clip') ``` ## Line Clamp Use `line-clamp-{n}` to limit text to a specific number of lines. This automatically sets the overflow style to ellipsis. Supported values are any integer number (e.g., `line-clamp-2`, `line-clamp-5`) and `line-clamp-none`. ```dart WText( 'This is a long paragraph that needs to be clamped to a specific number of lines...', className: 'line-clamp-3', ) ``` `line-clamp-none` and `whitespace-normal` cannot undo an earlier `line-clamp-*` / `truncate`; apply the clamp only where you want it (`md:line-clamp-3`). ## Responsive Design Apply different overflow utilities at different breakpoints using standard responsive prefixes. ```dart // Single line from tablet up, wrapping on mobile WText( 'Responsive text behaviour...', className: 'md:truncate', ) // 2 lines on mobile, 4 lines on tablet and up WText( 'Responsive line clamping...', className: 'line-clamp-2 md:line-clamp-4', ) ``` ## Dark Mode While overflow utilities don't visually change in dark mode, you can still apply them conditionally using the `dark:` prefix if your design requires different text handling in dark mode. ```dart WText( '...', className: 'truncate dark:line-clamp-2', ) ``` ## Arbitrary Values > [!NOTE] > Arbitrary values (bracket syntax) are not currently supported for `text-` overflow or `line-clamp-` utilities. > > `line-clamp-{n}` supports any integer number directly (e.g., `line-clamp-7`), so bracket syntax is generally not needed. ## Customizing Theme These utilities do not use theme variables. `line-clamp` accepts numeric values directly, and overflow types are static. ## Related Documentation - [Font Size](https://anilcancakir.com/wind_kit/docs/typography/font-size.md) --- # Text Transform Utilities for controlling the capitalisation and wrapping of text. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Locale-aware casing](#locale-aware-casing) - [Whitespace & Wrapping](#whitespace--wrapping) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Uppercase WText('Hello World', className: 'uppercase') // Capitalize WText('hello world', className: 'capitalize') // Prevent wrapping WText('Long text that should not wrap...', className: 'whitespace-nowrap') ``` ## Basic Usage Use `uppercase` and `lowercase` to force text casing. `capitalize` raises the first letter of every word and leaves the rest of the word as typed, so an acronym you passed in survives. > [!NOTE] > A word runs through letters, digits, underscores, apostrophes and the marks that attach to them; anything else opens a new one. So `well-known issue` renders `Well-Known Issue` and `read/write` renders `Read/Write`, while `l'orange` stays `L'orange` and `3rd party` stays `3rd Party`. That is what browsers do with CSS `text-transform: capitalize`. ```dart WDiv( className: 'flex flex-col gap-4', children: [ WText('uppercase text', className: 'uppercase'), // UPPERCASE TEXT WText('LOWERCASE TEXT', className: 'lowercase'), // lowercase text WText('capitalize this text', className: 'capitalize'), // Capitalize This Text WText('the HTTP client', className: 'capitalize'), // The HTTP Client WText('Normal Case Text', className: 'normal-case'), // Normal Case Text ], ) ``` ## Quick Reference | Class | Transform | Description | |:------|:----------|:------------| | `uppercase` | Uppercase | Converts all text to uppercase, under the ambient locale's rules. | | `lowercase` | Lowercase | Converts all text to lowercase, under the ambient locale's rules. | | `capitalize` | Capitalize | Raises the first letter of each word, under the ambient locale's rules. The rest of the word stays as typed. | | `normal-case` | None | Resets text transformation (useful for overrides). | ### Locale-aware casing The three transforms above read the ambient locale through `Localizations.maybeLocaleOf` and cast accordingly. This matters for Turkish and Azerbaijani, which distinguish a dotted from a dotless `i`: the uppercase of `i` is `İ` and the uppercase of `ı` is `I`, where Dart's locale-independent `String.toUpperCase()` produces `I` for both. ```dart // Under Locale('tr') WText('izleyiciler', className: 'uppercase') // İZLEYİCİLER WText('kullanılan', className: 'uppercase') // KULLANILAN WText('izleyici ışıkları', className: 'capitalize') // İzleyici Işıkları // Under Locale('en') WText('monitors up', className: 'uppercase') // MONITORS UP ``` **With no `Localizations` ancestor the transform falls back to locale-independent casing.** That is the case a consumer meets without noticing: a bare widget test, or any subtree pumped outside a `MaterialApp` / `WidgetsApp`, casts the Dart way. Wrap the subtree in `Localizations` when a test asserts Turkish casing. ## Whitespace & Wrapping Control how text handles whitespace and wrapping using `whitespace-` utilities. ```dart // Prevent text from wrapping WText( 'This is a long sentence that will not wrap to the next line.', className: 'whitespace-nowrap', ) ``` | Class | Wrap | Description | |:------|:-----|:------------| | `whitespace-normal` | Yes | Allow text to wrap normally (default). | | `whitespace-nowrap` | No | Prevent text from wrapping. | | `text-wrap` | Yes | Alias for `whitespace-normal`. | | `text-nowrap` | No | Alias for `whitespace-nowrap`. | | `text-balance` | Yes | Only sets `softWrap: true`; lines are not balanced. | ## Responsive Design Apply transforms or whitespace rules conditionally at different breakpoints. ```dart // Uppercase on mobile, normal case on tablet+ WText('Responsive Text', className: 'uppercase md:normal-case') // Wrap on mobile, no-wrap on large screens WText('Responsive Wrap', className: 'whitespace-normal lg:whitespace-nowrap') ``` ## Dark Mode While text transforms rarely change based on theme, you can apply them conditionally if needed. ```dart WText('Dark Mode Text', className: 'normal-case dark:uppercase') ``` ## Customizing Theme Text transform and whitespace utilities are hardcoded in the parser and **cannot be customised** via `WindThemeData`. ## Related Documentation - [Text Overflow](https://anilcancakir.com/wind_kit/docs/typography/text-overflow.md) - [Font Weight](https://anilcancakir.com/wind_kit/docs/typography/font-weight.md) - [Letter Spacing](https://anilcancakir.com/wind_kit/docs/typography/letter-spacing.md) --- # Border Width, Color, & Radius Utilities for controlling border width, colour, style, and border radius. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ## Basic Usage ```dart // Basic border (1px solid gray-200 by default) WDiv(className: 'border') // Thicker border with color WDiv(className: 'border-2 border-blue-500') // Rounded corners WDiv(className: 'border rounded-lg p-4') // Specific side border WDiv(className: 'border-b-2 border-red-500') ``` ## Quick Reference ### Border Width | Class | CSS Equivalent | Description | |:--- |:--- |:--- | | `border-0` | `border-width: 0px` | No border | | `border` | `border-width: 1px` | 1px border | | `border-2` | `border-width: 2px` | 2px border | | `border-4` | `border-width: 4px` | 4px border | | `border-8` | `border-width: 8px` | 8px border | ### Border Style | Class | CSS Equivalent | Description | |:--- |:--- |:--- | | `border-solid` | `border-style: solid` | Solid border (default) | | `border-none` | `border-style: none` | No border | ### Border Radius | Class | Value | Description | |:--- |:--- |:--- | | `rounded-none` | `0px` | No rounding | | `rounded-sm` | `2px` | Small radius | | `rounded` | `4px` | Default radius | | `rounded-md` | `6px` | Medium radius | | `rounded-lg` | `8px` | Large radius | | `rounded-xl` | `12px` | Extra large radius | | `rounded-2xl` | `16px` | 2x Extra large | | `rounded-3xl` | `24px` | 3x Extra large | | `rounded-full` | `9999px` | Fully rounded (pill/circle) | ## Variants ### Individual Sides Control border width for specific sides using `-t`, `-r`, `-b`, and `-l` suffixes. ```dart // Top border only WDiv(className: 'border-t-4 border-indigo-500') // Right and Left borders WDiv(className: 'border-r border-l border-gray-300') ``` ### Corner Radius Control radius for specific corners or sides. ```dart // Top corners only WDiv(className: 'rounded-t-lg bg-white') // Top-left corner only WDiv(className: 'rounded-tl-md bg-blue-100') ``` | Class | Sides/Corner | |:--- |:--- | | `rounded-t-*` | Top-left & Top-right | | `rounded-r-*` | Top-right & Bottom-right | | `rounded-b-*` | Bottom-left & Bottom-right | | `rounded-l-*` | Top-left & Bottom-left | | `rounded-tl-*` | Top-left only | | `rounded-tr-*` | Top-right only | | `rounded-br-*` | Bottom-right only | | `rounded-bl-*` | Bottom-left only | ### Color Opacity You can control the opacity of border colours using the colour opacity modifier. ```dart // 50% opacity red border WDiv(className: 'border-2 border-red-500/50') ``` ## Responsive Design Prefix any border utility with a breakpoint variant to apply it at specific screen sizes. ```dart // No border on mobile, 2px border on tablet+ WDiv(className: 'border-0 md:border-2') // Square on mobile, rounded on desktop WDiv(className: 'rounded-none lg:rounded-xl') ``` ## Dark Mode Use the `dark:` prefix to style borders differently in dark mode. ```dart WDiv( className: 'border border-gray-200 dark:border-gray-700' ) ``` ## Arbitrary Values If you need a value that doesn't fit the theme, use square bracket notation. ```dart // Specific pixel width WDiv(className: 'border-[3px]') // Specific hex color WDiv(className: 'border-[#1da1f2]') ``` Border radius has no arbitrary form: `rounded-[10px]` is dropped silently. Pick a scale step (`rounded-lg`) or add the value to the theme's `borderRadius`. ## Customizing Theme Customise standard values in `WindThemeData`. ```dart WindThemeData( // Custom border widths borderWidths: { 'DEFAULT': 1.0, '3': 3.0, // adds border-3 'thin': 0.5, // adds border-thin }, // Custom border radius borderRadius: { 'DEFAULT': 4.0, 'xl': 12.0, 'mega': 32.0, // adds rounded-mega }, // Custom colors colors: { 'brand': Colors.indigo, // adds border-brand }, ) ``` ## Related Documentation - [Ring Utilities](https://anilcancakir.com/wind_kit/docs/borders/ring.md) - Focus rings and outlines --- # Ring Width, Color, and Offset Utilities for drawing an outline ring around an element. These are useful for focus states or highlighting elements without affecting layout. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Default ring (3px blue-500) WDiv(className: 'ring') // Custom width and color WDiv(className: 'ring-4 ring-red-500') // With offset WDiv(className: 'ring-2 ring-offset-2') ``` ## Basic Usage Use `ring-{width}` utilities to draw a solid outline around an element. By default, this adds a 3px blue ring. > [!NOTE] > Tailwind draws a ring as a box-shadow, which a browser clips to the outside of the element. Wind paints the same thing as an outline stroke over the element's box, on its corner radius, so an element with no background stays empty inside its ring. A ring does not take up space in the layout, unlike a border. ```dart WDiv( className: 'ring-4 ring-blue-500 rounded-lg p-4', child: WText('Ring Example'), ) ``` ## Quick Reference | Class | Value | Description | |:------|:------|:------------| | `ring-0` | 0px | No ring | | `ring-1` | 1px | 1px ring width | | `ring-2` | 2px | 2px ring width | | `ring` | 3px | Default ring width (3px) | | `ring-4` | 4px | 4px ring width | | `ring-8` | 8px | 8px ring width | | `ring-inset` | - | Forces ring to be on the inside | | `ring-offset-{width}` | - | Moves the ring out, leaving an unpainted gap | ## Variants ### Ring Color Use `ring-{color}` to set the colour of the ring. You can also use opacity modifiers like `/50`. ```dart WDiv(className: 'ring-2 ring-blue-500') WDiv(className: 'ring-2 ring-red-500/50') // 50% opacity ``` ### Ring Offset Use `ring-offset-{width}` to leave space between the element and the ring. The gap is not painted, so whatever is behind the element shows through it; Wind has no `ring-offset-{color}`. ```dart // Creates a 2px gap between the element and the ring WDiv(className: 'ring-2 ring-offset-2') ``` | Class | Value | Description | |:------|:------|:------------| | `ring-offset-0` | 0px | No offset | | `ring-offset-1` | 1px | 1px offset | | `ring-offset-2` | 2px | 2px offset | | `ring-offset-4` | 4px | 4px offset | | `ring-offset-8` | 8px | 8px offset | ### Ring Inset Use `ring-inset` to force the ring to render on the inside of the element instead of the outside. This is useful for elements that shouldn't overflow their container. ```dart WDiv(className: 'ring-2 ring-inset ring-pink-500') ``` ## Responsive Design Apply different ring utilities at different breakpoints using the standard `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` prefixes. ```dart WDiv(className: 'ring-2 md:ring-4 lg:ring-8') ``` ## Dark Mode Use the `dark:` prefix to apply different ring styles when the application is in dark mode. ```dart WDiv(className: 'ring-slate-900/5 dark:ring-white/10') ``` ## Arbitrary Values If the built-in scale doesn't meet your needs, use bracket notation to apply custom values directly. ```dart // Custom color WDiv(className: 'ring-[#50d71e]') // Custom color with opacity WDiv(className: 'ring-[#50d71e]/50') ``` > [!NOTE] > Currently, arbitrary values are supported for **colours only** (`ring-[#hex]`). Arbitrary widths (`ring-[10px]`) are not yet supported in the parser. ## Customizing Theme To extend or override the default scale for rings, modify the `WindThemeData` in your app root. ```dart WindTheme( data: WindThemeData( // Default color when no ring color class is present ringColor: Colors.blue, // Custom ring widths ringWidths: { 'DEFAULT': 3, '6': 6, '10': 10, }, // Custom offset widths ringOffsets: { '3': 3, '6': 6, }, ), child: MyApp(), ) ``` ## Related Documentation - [Borders](https://anilcancakir.com/wind_kit/docs/borders/borders.md) - [Box Shadow](https://anilcancakir.com/wind_kit/docs/styling/shadow.md) --- # Background Color Utilities for controlling an element's background colour. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Opacity Modifier](#opacity-modifier) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Solid color WDiv(className: 'bg-blue-500 h-12 w-12 rounded') // With opacity WDiv(className: 'bg-blue-500/50 h-12 w-12 rounded') // White and Black WDiv(className: 'bg-white h-12 w-12 rounded') WDiv(className: 'bg-black h-12 w-12 rounded') ``` ## Basic Usage Use `bg-{color}-{shade}` to set the background colour of an element. Wind includes a comprehensive palette of colours (slate, gray, red, blue, etc.) with shades from 50 to 950. ```dart WDiv( className: 'bg-blue-500 p-4 rounded-lg', child: WText('Blue Background', className: 'text-white'), ) ``` You can also use standalone colours like `bg-white`, `bg-black`, and `bg-transparent`. ```dart WDiv(className: 'bg-white shadow-md p-6') WDiv(className: 'bg-transparent border border-gray-300') ``` ## Quick Reference | Class | Description | |:------|:------------| | `bg-{color}-{shade}` | Sets background colour (e.g., `bg-red-500`, `bg-slate-900`). | | `bg-white` | Sets background to white. | | `bg-black` | Sets background to black. | | `bg-transparent` | Sets background to transparent. | ## Opacity Modifier Control the opacity of a background colour using the slash modifier `/{opacity}`. The value ranges from 0 to 100. ```dart WDiv(className: 'bg-blue-500/100') // 100% (Default) WDiv(className: 'bg-blue-500/75') // 75% WDiv(className: 'bg-blue-500/50') // 50% WDiv(className: 'bg-blue-500/25') // 25% ``` ## Responsive Design Change the background colour at specific breakpoints using standard responsive prefixes (`sm:`, `md:`, `lg:`, `xl:`, `2xl:`). ```dart // Red on mobile, Blue on medium screens and up WDiv(className: 'bg-red-500 md:bg-blue-500 h-32 w-full') ``` ## Dark Mode Use the `dark:` prefix to apply a different background colour when dark mode is active. ```dart WDiv( className: 'bg-white dark:bg-slate-900 p-6 rounded-lg', child: WText('Adaptive Background', className: 'text-gray-900 dark:text-white'), ) ``` ## Arbitrary Values If you need a specific colour not in your theme, use square bracket notation `bg-[#RRGGBB]`. ```dart WDiv(className: 'bg-[#1da1f2] text-white p-4') // Twitter Blue WDiv(className: 'bg-[#ff0000]') // Bright Red ``` Three, four, six and eight hex digits are accepted, matching [`hexToColor`](https://anilcancakir.com/wind_kit/docs/utilities/color-helpers.md#hex-colors). The four- and eight-digit forms carry alpha and it leads, because Flutter packs `AARRGGBB` where CSS writes `RRGGBBAA`. ```dart WDiv(className: 'bg-[#f00]') // #FF0000, opaque WDiv(className: 'bg-[#80ff0000]') // #FF0000 at alpha 0x80 WDiv(className: 'bg-[#8f00]') // #FF0000 at alpha 0x88 ``` > [!NOTE] > The short forms expand by doubling each digit, so `#8f00` is `#88ff0000` and not `#80ff0000`. The 4-digit form cannot spell every alpha; write the 8-digit one when the exact value matters. > [!NOTE] > The opacity modifier is the shorter route to the same result on a theme colour: `bg-red-500/50`. Reach for the alpha hex when the colour itself is not in the theme. ## Customizing Theme To add your own colours, modify the `colors` map in `WindThemeData`. ```dart WindThemeData( colors: { // Add a custom brand color 'brand': MaterialColor(0xFF1DA1F2, {500: Color(0xFF1DA1F2), 600: Color(0xFF1A91DA)}), }, ) ``` The map merges with the default palette, so the built-in colours stay available. Now you can use `bg-brand-500` or `bg-brand-600`. ## Related Documentation - [Background Gradient](https://anilcancakir.com/wind_kit/docs/styling/background-gradient.md) - [Background Image](https://anilcancakir.com/wind_kit/docs/styling/background-image.md) - [Text Colour](https://anilcancakir.com/wind_kit/docs/typography/text-color.md) - [Border Width, Colour, & Radius](https://anilcancakir.com/wind_kit/docs/borders/borders.md) --- # Background Gradient Utilities for creating linear gradients with customisable directions and colour stops. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Basic gradient from cyan to blue WDiv(className: 'bg-gradient-to-r from-cyan-500 to-blue-500') // Three-color gradient WDiv(className: 'bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500') ``` ## Basic Usage To create a gradient, you need to specify a direction (e.g., `bg-gradient-to-r`) and at least a starting colour (`from-*`). Wind will create a linear gradient based on these parameters. ```dart WDiv( className: 'h-24 bg-gradient-to-r from-cyan-500 to-blue-500', child: WText('Gradient Background'), ) ``` If you only provide a `from` colour, the gradient will fade to transparent by default. If you provide `to` but no `from`, it will also handle it gracefully, but standard usage typically defines both or relies on the fade-to-transparent behaviour. ## Quick Reference | Class | Description | |:------|:------------| | `bg-gradient-to-t` | Gradient to top | | `bg-gradient-to-tr` | Gradient to top right | | `bg-gradient-to-r` | Gradient to right | | `bg-gradient-to-br` | Gradient to bottom right | | `bg-gradient-to-b` | Gradient to bottom | | `bg-gradient-to-bl` | Gradient to bottom left | | `bg-gradient-to-l` | Gradient to left | | `bg-gradient-to-tl` | Gradient to top left | | `from-{color}` | Starting colour | | `via-{color}` | Middle colour | | `to-{color}` | Ending colour | ## Variants ### Gradient Stops Use `from-*`, `via-*`, and `to-*` utilities to define the colour stops of your gradient. ```dart // Start color only (fades to transparent) WDiv(className: 'bg-gradient-to-r from-green-400') // Start and end colors WDiv(className: 'bg-gradient-to-r from-green-400 to-blue-500') // Start, middle, and end colors WDiv(className: 'bg-gradient-to-r from-green-400 via-blue-500 to-purple-600') ``` ### Color Opacity You can control the opacity of gradient colours using the `/opacity` modifier. ```dart WDiv(className: 'bg-gradient-to-r from-red-500/50 to-red-500/0') ``` ## Responsive Design Apply different gradient directions or colours at different breakpoints using standard responsive prefixes. ```dart WDiv(className: 'bg-gradient-to-r md:bg-gradient-to-b from-blue-500 to-green-500') ``` ## Dark Mode Use `dark:` to specify different gradient colours for dark mode. ```dart WDiv(className: 'bg-gradient-to-r from-slate-100 to-slate-200 dark:from-slate-800 dark:to-slate-900') ``` ## Arbitrary Values If you need specific hex colours that aren't in your theme, use square bracket notation. ```dart WDiv(className: 'bg-gradient-to-r from-[#1da1f2] to-[#1a91da]') ``` Three, four, six and eight hex digits are accepted, matching [`hexToColor`](https://anilcancakir.com/wind_kit/docs/utilities/color-helpers.md#hex-colors). The four- and eight-digit forms carry alpha and it leads, because Flutter packs `AARRGGBB` where CSS writes `RRGGBBAA`. A scrim fading out is the usual reason to reach for one: ```dart WDiv(className: 'bg-gradient-to-t from-[#cc000000] to-transparent') ``` ## Customizing Theme Gradient colours use your theme's colour palette. To add new colours, update the `colors` key in `WindThemeData`. ```dart WindThemeData( colors: { 'brand': MaterialColor(0xFF1DA1F2, {500: Color(0xFF1DA1F2), 600: Color(0xFF1A91DA)}), }, ) ``` The map merges with the default palette, so the built-in colours stay available. Then use them in your gradients: ```dart WDiv(className: 'bg-gradient-to-r from-brand-500 to-brand-600') ``` ## Related Documentation - [Background Colour](https://anilcancakir.com/wind_kit/docs/styling/background-color.md) - [Background Image](https://anilcancakir.com/wind_kit/docs/styling/background-image.md) --- # Background Image Utilities for controlling background images, sizing, positioning, and repeat behaviour. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WDiv( className: 'bg-[url(images/hero.png)] bg-cover bg-center h-64 w-full rounded-lg', child: WText('Hero Section', className: 'text-white font-bold'), ) ``` ## Basic Usage ### Setting a Background Image Use the `bg-[url(...)]` utility to set a background image. Wind supports asset paths and absolute file paths. An absolute file path (a `/path` value) loads a `FileImage`, so it works only on platforms with `dart:io` file support: on web and WASM it produces no background image. ```dart // Asset image (shorthand for assets/) WDiv(className: 'bg-[url(images/hero.png)]') // Asset with explicit prefixes WDiv(className: 'bg-[url(~/images/hero.png)]') // Resolves to assets/images/hero.png WDiv(className: 'bg-[url(@/images/hero.png)]') // Resolves to assets/images/hero.png ``` ## Quick Reference | Class | Description | Flutter Equivalent | |:--- |:--- |:--- | | `bg-[url(...)]` | Sets the background image | `DecorationImage(image: ...)` | | `bg-cover` | Scales image to cover container | `fit: BoxFit.cover` | | `bg-contain` | Scales image to fit container | `fit: BoxFit.contain` | | `bg-center` | Centres the image | `alignment: Alignment.center` | | `bg-top` | Aligns image to top | `alignment: Alignment.topCenter` | | `bg-bottom` | Aligns image to bottom | `alignment: Alignment.bottomCenter` | | `bg-left` | Aligns image to left | `alignment: Alignment.centerLeft` | | `bg-right` | Aligns image to right | `alignment: Alignment.centerRight` | | `bg-no-repeat` | Prevents image repetition | `repeat: ImageRepeat.noRepeat` | ## Variants ### Size Control how the background image fills its container using `bg-cover` or `bg-contain`. ```dart WDiv(className: 'bg-[url(...)] bg-cover') // Scale to cover (may crop) WDiv(className: 'bg-[url(...)] bg-contain') // Scale to fit (may have gaps) ``` ### Position Control the position of the background image using `bg-{side}` utilities. ```dart WDiv(className: 'bg-[url(...)] bg-center') // Center WDiv(className: 'bg-[url(...)] bg-top-left') // Top Left WDiv(className: 'bg-[url(...)] bg-bottom') // Bottom Center ``` Supported values: `top`, `bottom`, `left`, `right`, `center`, `top-left`, `top-right`, `bottom-left`, `bottom-right`. ### Repeat Control how the background image repeats. ```dart WDiv(className: 'bg-[url(...)] bg-no-repeat') // Default WDiv(className: 'bg-[url(...)] bg-repeat') // Tile horizontally and vertically WDiv(className: 'bg-[url(...)] bg-repeat-x') // Tile horizontally WDiv(className: 'bg-[url(...)] bg-repeat-y') // Tile vertically ``` ## Responsive Design Prefix any background utility with a breakpoint modifier to apply it at specific screen sizes. ```dart WDiv( // Mobile: Cover // Desktop: Contain className: 'bg-[url(...)] bg-cover lg:bg-contain' ) ``` ## Dark Mode Use the `dark:` prefix to change the background image in dark mode. ```dart WDiv( className: 'bg-[url(bg-light.png)] dark:bg-[url(bg-dark.png)]' ) ``` ## Arbitrary Values The `bg-[...]` syntax allows you to pass any string value. ```dart // Standard URL syntax WDiv(className: 'bg-[url(images/hero.png)]') // Direct path syntax (also supported) // An absolute path loads a FileImage: native platforms only, no background on web or WASM WDiv(className: 'bg-[/path/to/local/file.png]') ``` ## Customizing Theme Background images are typically dynamic and not defined in the theme. However, the parser handles `bg-*` classes for colours as well. If you need to customise background **colours**, see the [Background Colour](https://anilcancakir.com/wind_kit/docs/styling/background-color.md) documentation. For images, Wind uses standard Flutter `ImageProvider` implementations based on the URL format: - URLs starting with `/` become `FileImage` - All others become `AssetImage` (prefixed with `assets/` unless using `~/` or `@/`) A URL containing `:` (`http://`, `https://`) cannot sit in a className: the prefix resolver splits on `:` and drops the token. For a network image, stack a `WImage(src: ...)` under the content. ## Related Documentation - [Background Colour](https://anilcancakir.com/wind_kit/docs/styling/background-color.md) - [Background Gradient](https://anilcancakir.com/wind_kit/docs/styling/background-gradient.md) --- # Opacity Utilities for controlling the opacity of an element. ## Table of Contents - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WDiv(className: 'opacity-100 bg-blue-500') WDiv(className: 'opacity-75 bg-blue-500') WDiv(className: 'opacity-50 bg-blue-500') WDiv(className: 'opacity-25 bg-blue-500') WDiv(className: 'opacity-0 bg-blue-500') ``` ## Basic Usage Control the opacity of an element using `opacity-{value}` utilities. This affects the entire element, including its content, background, and borders. ```dart WDiv( className: 'opacity-50 bg-blue-500 text-white p-4', child: WText('This entire element is 50% opaque'), ) ``` > [!NOTE] > This utility wraps the element in a Flutter `Opacity` widget (an `AnimatedOpacity` when a transition is set). To change only the background colour's opacity without affecting text, use the colour opacity modifier (e.g., `bg-blue-500/50`) instead. ## Quick Reference | Class | Value | Description | |:--- |:--- |:--- | | `opacity-0` | 0 | Fully transparent | | `opacity-5` | 0.05 | | | `opacity-10` | 0.1 | | | `opacity-20` | 0.2 | | | `opacity-25` | 0.25 | | | `opacity-30` | 0.3 | | | `opacity-40` | 0.4 | | | `opacity-50` | 0.5 | | | `opacity-60` | 0.6 | | | `opacity-70` | 0.7 | | | `opacity-75` | 0.75 | | | `opacity-80` | 0.8 | | | `opacity-90` | 0.9 | | | `opacity-95` | 0.95 | | | `opacity-100` | 1 | Fully opaque | ## Variants Wind supports all standard state variants for opacity. ```dart // Change opacity on hover WDiv(className: 'opacity-50 hover:opacity-100') // Change opacity when button is disabled WButton(disabled: true, className: 'bg-blue-500 disabled:opacity-50', child: Text('Save')) ``` ## Responsive Design Change opacity based on screen size using responsive prefixes. ```dart WDiv(className: 'opacity-100 md:opacity-50 lg:opacity-25') ``` ## Dark Mode Adjust opacity based on the current theme brightness. ```dart WDiv(className: 'opacity-100 dark:opacity-80') ``` ## Arbitrary Values Use square brackets to define a custom opacity value between `0.0` and `1.0`. ```dart WDiv(className: 'opacity-[0.67]') WDiv(className: 'opacity-[0.125]') ``` > [!WARNING] > Values are clamped between 0.0 and 1.0. `opacity-[1.5]` will resolve to `1.0`. ## Customizing Theme You can customise the available opacity scale in your `WindThemeData`. ```dart WindTheme( data: WindThemeData( opacities: { 'disabled': 0.35, 'faint': 0.10, 'glass': 0.85, }, ), child: MyApp(), ) ``` Now you can use these custom keys in your utilities: ```dart WDiv(className: 'opacity-disabled') // 0.35 WDiv(className: 'opacity-glass') // 0.85 ``` ## Related Documentation - [Background Colour](https://anilcancakir.com/wind_kit/docs/styling/background-color.md) - Using `/alpha` modifiers for backgrounds - [Text Colour](https://anilcancakir.com/wind_kit/docs/typography/text-color.md) - Using `/alpha` modifiers for text - [Shadow](https://anilcancakir.com/wind_kit/docs/styling/shadow.md) - Box shadow utilities --- # Shadow Utilities for controlling the box shadow of an element. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Basic examples WDiv(className: 'shadow') WDiv(className: 'shadow-lg') WDiv(className: 'shadow-md shadow-red-500') WDiv(className: 'shadow-none') ``` ## Basic Usage Use `shadow-{size}` utilities to apply different shadow intensities to an element. ```dart WDiv( className: 'shadow-lg bg-white rounded-lg p-6', child: WText('Shadow Example'), ) ``` ## Quick Reference | Class | Properties | Description | |:------|:-----------|:------------| | `shadow-sm` | `blur: 2` | Subtle shadow | | `shadow` | `blur: 3` | Default shadow | | `shadow-md` | `blur: 6` | Medium shadow | | `shadow-lg` | `blur: 15` | Large shadow | | `shadow-xl` | `blur: 25` | Extra large shadow | | `shadow-2xl` | `blur: 50` | Dramatic shadow | | `shadow-none` | `none` | Removes shadow | ## Variants ### Shadow Colors Use `shadow-{color}` utilities to change the colour of the shadow. This preserves the original shadow's opacity structure while tinting it with the specified colour. ```dart // Blue tinted shadow WDiv(className: 'shadow-lg shadow-blue-500 dark:shadow-blue-400') // Red tinted shadow WDiv(className: 'shadow-md shadow-red-500 dark:shadow-red-400') ``` | Class | Description | |:------|:------------| | `shadow-{color}` | Tints the shadow with the specified colour (e.g., `shadow-red-500`) | A colour needs a shade (`shadow-red-500`); only `shadow-white` and `shadow-black` resolve without one. ### Shadow Opacity `shadow-{color}` keeps each preset layer's own alpha; a `/opacity` modifier is currently ignored when a size is present. ```dart // The xl layers' own alpha decides the strength WDiv(className: 'shadow-xl shadow-red-500 dark:shadow-red-400') ``` ### Removing Shadows Use `shadow-none` to remove any existing box shadows from an element. This is useful for resetting shadows at specific breakpoints. ```dart WDiv(className: 'shadow-lg md:shadow-none') ``` ## Responsive Design Apply different shadow utilities at different breakpoints using the standard `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` prefixes. ```dart WDiv(className: 'shadow-sm md:shadow-lg xl:shadow-2xl') ``` ## Dark Mode Use the `dark:` prefix to apply different shadow styles when the application is in dark mode. ```dart WDiv(className: 'shadow-lg bg-white dark:bg-gray-800 dark:shadow-black') ``` ## Arbitrary Values If the built-in scale doesn't meet your needs, use bracket notation to apply custom shadow colours. ```dart // Custom shadow color hex WDiv(className: 'shadow-lg shadow-[#50d71e]') ``` > [!NOTE] > Currently, arbitrary values only support custom colours. For custom shadow sizes (offsets, blur), use the theme customisation options. ## Customizing Theme To extend or override the default shadow scale, modify the `shadows` property in `WindThemeData`. ```dart WindThemeData( shadows: { // Add a custom shadow preset 'soft': [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: Offset(0, 4), ), ], // Override the default 'xl' shadow 'xl': [ BoxShadow( color: Colors.blue.withValues(alpha: 0.2), blurRadius: 30, spreadRadius: 5, ), ], }, ) ``` Usage: ```dart WDiv(className: 'shadow-soft') ``` ## Related Documentation - [Opacity](https://anilcancakir.com/wind_kit/docs/styling/opacity.md) - [Ring](https://anilcancakir.com/wind_kit/docs/borders/ring.md) - [Background Colour](https://anilcancakir.com/wind_kit/docs/styling/background-color.md) --- # WindRecipe A `tv()` (tailwind-variants) equivalent for Wind. Composes a className from a `base`, a set of `variants` (one axis per key, each mapping a value to a className), optional `compoundVariants` (multi-condition rules), and `defaultVariants` (the selection applied when an axis is not passed by the caller). `WindSlotRecipe` extends the same model to multi-part components: instead of a single string, each call returns a `Map` of slot-name to className. ## Table of Contents - [Basic Usage](#basic-usage) - [Props: WindRecipe](#props-windrecipe) - [Props: WindSlotRecipe](#props-windslotrecipe) - [Constructor: WindRecipe](#constructor-windrecipe) - [Constructor: WindSlotRecipe](#constructor-windslotrecipe) - [Resolution Order](#resolution-order) - [Override Contract](#override-contract) - [Styling Examples](#styling-examples) - [Related Documentation](#related-documentation) ## Basic Usage ```dart final button = WindRecipe( base: 'inline-flex items-center rounded-lg font-medium', variants: { 'intent': { 'primary': 'bg-blue-600 dark:bg-blue-500 text-white', 'ghost': 'bg-transparent text-blue-600 dark:text-blue-400', }, 'size': { 'sm': 'px-3 py-1.5 text-sm', 'md': 'px-4 py-2 text-base', }, }, defaultVariants: {'intent': 'primary', 'size': 'md'}, ); // Full defaults: "inline-flex items-center rounded-lg font-medium bg-blue-600 dark:bg-blue-500 text-white px-4 py-2 text-base" button() // Override intent, keep size default: button(variants: {'intent': 'ghost'}) // Append extra classes: button(className: 'w-full') ``` ## Props: WindRecipe | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `base` | `String` | `''` | Always-applied className. | | `variants` | `Map>` | `{}` | Variant axes: axis -> (value -> className). Iteration order fixes emission order. | | `compoundVariants` | `List` | `[]` | Multi-condition rules applied in list order after variant classes. | | `defaultVariants` | `Map` | `{}` | Default selection per axis. Overridden by the `variants:` argument at call site; a `null` there clears the default, while `defaultVariants` itself holds strings only. | **Call signature** (`WindRecipe` is callable): | Param | Type | Default | Description | |:------|:-----|:--------|:------------| | `variants` | `Map?` | `null` | Caller's axis selections. `null` for an axis clears its default (no class emitted). Unknown axis throws `ArgumentError`. | | `className` | `String?` | `null` | Extra caller className appended last. | ## Props: WindSlotRecipe | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `slots` | `Map` | `{}` | Base className per slot. Slot identity comes from these keys. | | `variants` | `Map>>` | `{}` | Variant axes: axis -> (value -> (slot -> className)). | | `compoundVariants` | `List` | `[]` | Multi-condition rules applied per slot in list order. | | `defaultVariants` | `Map` | `{}` | Default selection per axis. | **Call signature** (`WindSlotRecipe` is callable): | Param | Type | Default | Description | |:------|:-----|:--------|:------------| | `variants` | `Map?` | `null` | Caller's axis selections. Unknown axis throws `ArgumentError`. | | `classNames` | `Map?` | `null` | Extra caller className per slot, appended last for that slot. | ## Constructor: WindRecipe ```dart WindRecipe({ String base = '', Map> variants = const {}, List compoundVariants = const [], Map defaultVariants = const {}, }) ``` `WindCompoundVariant` constructor: ```dart WindCompoundVariant({ required Map conditions, required String className, }) ``` A condition value is a `String` (exact match) or `List` (OR match). ## Constructor: WindSlotRecipe ```dart WindSlotRecipe({ Map slots = const {}, Map>> variants = const {}, List compoundVariants = const [], Map defaultVariants = const {}, }) ``` `WindSlotCompoundVariant` constructor: ```dart WindSlotCompoundVariant({ required Map conditions, required Map classNames, }) ``` ## Resolution Order Every call resolves in this strict order: no dedupe, no sort, no twMerge: ```text base ++ variant-classes(definition order) ++ matched-compound(array order) ++ caller ``` The caller's `className` / `classNames` always arrive last, so they override earlier tokens via Wind's per-family last-wins rule. A `null` value in `variants:` clears the default for that axis (no class emitted for it), matching `tv()`'s `undefined`-clears semantics. ### The Caller-Append Contract (No twMerge) `WindRecipe` and `WindSlotRecipe` only APPEND the caller's `className` / `classNames`; they never dedupe, sort, or resolve a conflict themselves. A caller passing a className that conflicts with the base (e.g. `WindRecipe(base: 'w-1/2')(className: 'w-full')`) gets both tokens in the emitted string, in that order: `'w-1/2 w-full'`. The conflict is resolved one layer down, when the string reaches `WindParser`: `WindParser.findAndGroupClasses` groups same-family classes together WITHOUT deduping, and each parser (e.g. `SizingParser.parse`) resolves them with last-class-wins. So the appended `w-full` wins over the base `w-1/2` at parse time, not at recipe-call time. There is intentionally no Dart port of `tailwind-merge`: wind's parse-time per-family last-wins already achieves the same outcome for every token family the parsers understand, which is why `WindRecipe` deliberately only appends (see the no-twMerge rationale in the `WindRecipe` doc comment in `lib/src/recipe/wind_recipe.dart`). This is verified by `test/recipe/wind_recipe_test.dart` (proves the append, order preserved) and `test/parser/parsers/sizing_parser_test.dart` (proves the appended `w-full` resolves to the winning style). Note: the className-REPLACE anti-pattern (a component treating an incoming `className` as a full recipe bypass instead of appending it) is a consumer-component bug, not a wind defect; wind's own recipes always append. When it appears, it lives in a downstream consumer's component layer, outside this package. ## Override Contract A recipe emits the base first and the variants after it, and the parser resolves conflicts per family with last-class-wins, so a variant overrides the base. How far the override reaches depends on the family: | Family | A longhand after a shorthand | Result | |:-------|:-----------------------------|:-------| | Padding, margin, inset | `p-4` then `px-2` | Composes per side: 8 on the left and right, 16 on the top and bottom. | | Border radius | `rounded-lg` then `rounded-t-none` | Does not compose: every corner becomes square. | | Border width | `border-2` then `border-t-4` | Does not compose: only the top border is kept. | ```dart // Padding composes: the variant changes only the horizontal sides. WindRecipe( base: 'p-4', variants: {'size': {'sm': 'px-2'}}, ) // Radius and border width do not: the variant restates every corner and side. WindRecipe( base: 'rounded-lg border-2', variants: {'shape': {'pill': 'rounded-full border-4', 'flat': 'rounded-none border-0'}}, ) ``` In debug builds, `WindRecipe` logs a warning the first time it sees a shorthand and a longhand of one family in the same recipe; for padding, margin and inset the result still composes per side, as in the table. ## Styling Examples ### Button with Compound Variants Compound variants contribute a className only when every condition is satisfied: ```dart final button = WindRecipe( base: 'inline-flex items-center rounded-lg font-medium', variants: { 'intent': { 'primary': 'bg-blue-600 dark:bg-blue-500 text-white', 'destructive': 'bg-red-600 dark:bg-red-500 text-white', }, 'size': {'sm': 'px-3 py-1.5 text-sm', 'lg': 'px-6 py-3 text-lg'}, }, compoundVariants: [ WindCompoundVariant( // Activates when intent is 'primary' OR 'destructive', AND size is 'lg'. conditions: {'intent': ['primary', 'destructive'], 'size': 'lg'}, className: 'shadow-lg', ), ], defaultVariants: {'intent': 'primary', 'size': 'sm'}, ); ``` ### Multi-Slot Card Recipe ```dart final card = WindSlotRecipe( slots: { 'root': 'rounded-xl border overflow-hidden', 'header': 'px-5 py-4 border-b', 'body': 'px-5 py-4', }, variants: { 'tone': { 'default': { 'root': 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700', 'header': 'border-gray-200 dark:border-gray-700', 'body': '', }, 'highlighted': { 'root': 'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800', 'header': 'border-blue-200 dark:border-blue-800', 'body': '', }, }, }, defaultVariants: {'tone': 'default'}, ); final classes = card(variants: {'tone': 'highlighted'}); WCard( className: classes['root'], header: WDiv(className: classes['header'], child: WText('Summary')), child: WDiv(className: classes['body'], child: WText('Body')), ) ``` ## Related Documentation - [WBadge](https://anilcancakir.com/wind_kit/docs/widgets/w-badge.md): a natural fit for a `WindRecipe` badge tone axis. - [WCard](https://anilcancakir.com/wind_kit/docs/widgets/w-card.md): pairs with `WindSlotRecipe` for multi-slot card variants. - [WButton](https://anilcancakir.com/wind_kit/docs/widgets/w-button.md): commonly driven by a `WindRecipe` for intent + size axes. --- # Animation Utilities for animating elements with CSS-like animation classes. Whether you're building a loading state or adding some life to your UI, Wind's animation utilities make it easy to drop in common motion patterns. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Spin](#spin) - [Ping](#ping) - [Pulse](#pulse) - [Bounce](#bounce) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart // Spinning loader WIcon(Icons.refresh, className: 'animate-spin text-blue-500') // Pulsing skeleton WDiv(className: 'animate-pulse w-full h-4 bg-gray-200 rounded') ``` ## Basic Usage Use the `animate-{type}` classes to add pre-defined animations to any widget. These are great for loading indicators, notification badges, or grabbing a user's attention. ```dart WIcon( Icons.refresh, className: 'animate-spin text-blue-600', ) ``` ## Quick Reference | Class | Value | Description | |:------|:------|:------------| | `animate-none` | none | Removes any active animation | | `animate-spin` | spin | Continuous 360-degree rotation | | `animate-ping` | ping | Scaling outward like a radar ping | | `animate-pulse` | pulse | Gentle opacity fade in/out | | `animate-bounce` | bounce | Vertical bouncing motion | ## Variants ### Spin Use `animate-spin` for things like loading indicators. It provides a smooth, linear rotation. Let's look at a basic spinner: ```dart WIcon(Icons.refresh, className: 'animate-spin text-blue-500') ``` ### Ping The `animate-ping` utility makes an element scale and fade out, resembling a radar ping or notification alert. This is perfect for status indicators. ```dart WDiv(className: 'relative flex h-3 w-3', children: [ WDiv(className: 'animate-ping absolute h-full w-full rounded-full bg-sky-400 opacity-75'), WDiv(className: 'relative rounded-full h-3 w-3 bg-sky-500'), ]) ``` ### Pulse Use `animate-pulse` to create a skeleton loading effect. It gently fades the opacity of the element, making it ideal for content that is still loading. ```dart WDiv(className: 'animate-pulse flex space-x-4', children: [ WDiv(className: 'rounded-full bg-slate-200 h-10 w-10'), WDiv(className: 'flex-1 space-y-6 py-1', children: [ WDiv(className: 'h-2 bg-slate-200 rounded'), WDiv(className: 'grid grid-cols-3 gap-4', children: [ WDiv(className: 'h-2 bg-slate-200 rounded'), WDiv(className: 'h-2 bg-slate-200 rounded'), ]), ]), ]) ``` ### Bounce The `animate-bounce` utility is perfect for scroll indicators or call-to-action buttons that need a bit of personality. ```dart WIcon(Icons.keyboard_arrow_down, className: 'animate-bounce text-slate-400') ``` ## Responsive Design You can enable or disable animations at specific breakpoints. For example, you might want to only animate an element on larger screens to keep the mobile experience "quiet". ```dart WDiv(className: 'animate-none md:animate-spin') ``` ## Dark Mode Animations work seamlessly with dark mode. You'll typically just change the colours of the animated element when switching to dark mode. ```dart WDiv(className: 'animate-pulse bg-gray-200 dark:bg-gray-700') ``` ## Arbitrary Values Animations have no arbitrary form: `animate-[...]` is not parsed. Use one of the built-in types, or register your own name in the theme (see below). ## Customizing Theme Want to add your own animations to the system? You can extend the `animations` map in your `WindThemeData`. ```dart WindThemeData( animations: { 'animate-wiggle': WindAnimationType.bounce, // keys include the animate- prefix; only the five built-in types exist }, ) ``` ## Related Documentation - [Transitions](https://anilcancakir.com/wind_kit/docs/interactivity/transition.md) - Smooth property changes - [Hover States](https://anilcancakir.com/wind_kit/docs/core-concepts/state-management.md) - Interactive triggers --- # Transitions Utilities for controlling the duration and easing of state transitions in Wind. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [Variants](#variants) - [Duration](#duration) - [Easing](#easing) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Arbitrary Values](#arbitrary-values) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ## Basic Usage Transitions allow you to change property values smoothly over a given duration. In Wind, these are typically paired with state modifiers like `hover:` or `focus:`. `active:` is not wired to the pointer; it applies only when the widget is given `states: {'active'}`. ```dart // Smoothly transition background color on hover WDiv( className: 'bg-blue-500 hover:bg-blue-700 duration-300 ease-in-out', child: WText('Hover Me', className: 'text-white'), ) ``` By adding `duration-200`, the background colour shift happens over 200 milliseconds instead of snapping instantly. ## Quick Reference ### Duration | Class | Value | Description | |:------|:------|:------------| | `duration-75` | 75ms | Very fast transition | | `duration-100` | 100ms | Fast transition | | `duration-150` | 150ms | | | `duration-200` | 200ms | Standard UI transition | | `duration-300` | 300ms | | | `duration-500` | 500ms | Noticeable transition | | `duration-700` | 700ms | | | `duration-1000` | 1000ms | Slow transition | ### Easing (Timing Function) | Class | Value | Description | |:------|:------|:------------| | `ease-linear` | `Curves.linear` | Constant speed | | `ease-in` | `Curves.easeIn` | Starts slow, ends fast | | `ease-out` | `Curves.easeOut` | Starts fast, ends slow | | `ease-in-out` | `Curves.easeInOut` | Slow start and end | ## Variants ### Duration Use the `duration-{ms}` utility to set the length of the transition animation. ```dart WDiv(className: 'duration-500 ...') ``` ### Easing Control the rate of change with `ease-{curve}` utilities. This defines the "feel" of the animation. ```dart WDiv(className: 'ease-out duration-300 ...') ``` ## Responsive Design Apply different transition speeds or curves at different breakpoints using the standard responsive prefixes. ```dart // Slow transition on desktop, fast on mobile WDiv(className: 'duration-150 lg:duration-500') ``` ## Dark Mode Use the `dark:` prefix to apply different transition styles when the application is in dark mode. ```dart WDiv(className: 'duration-200 dark:duration-500') ``` ## Arbitrary Values If the standard scale doesn't fit your specific animation needs, use bracket notation to provide a custom millisecond value. Easing has no arbitrary form; pick one of the named curves. ```dart WDiv(className: 'duration-[420ms] ease-out') ``` ## Customizing Theme To override the default transition scales, modify `WindThemeData` in your app configuration. ```dart WindTheme( data: WindThemeData( transitionDurations: { '200': Duration(milliseconds: 150), }, transitionCurves: { 'out': Curves.easeOutCubic, }, ), child: MyApp(), ) ``` Only the default keys parse (`75` to `1000`, `linear`, `in`, `out`, `in-out`); you can override their values, but new keys are not recognised. ## Related Documentation - [Animation](https://anilcancakir.com/wind_kit/docs/interactivity/animation.md) - Explicit keyframe animations - [Hover States](https://anilcancakir.com/wind_kit/docs/core-concepts/state-management.md) - Handling interactions - [Opacity](https://anilcancakir.com/wind_kit/docs/styling/opacity.md) - Fade transitions --- # Cursor Utilities for controlling the mouse cursor shown when the pointer is over an element on web and desktop targets. - [Basic Usage](#basic-usage) - [Quick Reference](#quick-reference) - [How It Works](#how-it-works) - [Responsive Design](#responsive-design) - [Dark Mode](#dark-mode) - [Related Documentation](#related-documentation) ## Basic Usage `cursor-*` maps a Tailwind cursor name to the matching native cursor and applies it through a `MouseRegion`. A plain `WDiv` can show a pointer cursor without being wrapped in `WAnchor` or a manual `MouseRegion`. ```dart // A clickable-feeling row without WAnchor WDiv( className: 'cursor-pointer px-4 py-3 rounded-lg bg-blue-500 dark:bg-blue-600', child: WText('Click me', className: 'text-white'), ) ``` The cursor is a hover affordance only; it changes how the pointer looks, not how taps are handled. Wire taps through `WAnchor` / `WButton` as usual. ## Quick Reference | Class | Flutter cursor | |:------|:---------------| | `cursor-auto` / `cursor-default` | `SystemMouseCursors.basic` | | `cursor-pointer` | `SystemMouseCursors.click` | | `cursor-wait` | `SystemMouseCursors.wait` | | `cursor-progress` | `SystemMouseCursors.progress` | | `cursor-text` | `SystemMouseCursors.text` | | `cursor-vertical-text` | `SystemMouseCursors.verticalText` | | `cursor-move` | `SystemMouseCursors.move` | | `cursor-help` | `SystemMouseCursors.help` | | `cursor-not-allowed` | `SystemMouseCursors.forbidden` | | `cursor-none` | `SystemMouseCursors.none` | | `cursor-context-menu` | `SystemMouseCursors.contextMenu` | | `cursor-cell` | `SystemMouseCursors.cell` | | `cursor-crosshair` | `SystemMouseCursors.precise` | | `cursor-alias` | `SystemMouseCursors.alias` | | `cursor-copy` | `SystemMouseCursors.copy` | | `cursor-no-drop` | `SystemMouseCursors.noDrop` | | `cursor-grab` | `SystemMouseCursors.grab` | | `cursor-grabbing` | `SystemMouseCursors.grabbing` | | `cursor-all-scroll` | `SystemMouseCursors.allScroll` | | `cursor-col-resize` | `SystemMouseCursors.resizeColumn` | | `cursor-row-resize` | `SystemMouseCursors.resizeRow` | | `cursor-n-resize` / `cursor-e-resize` / `cursor-s-resize` / `cursor-w-resize` | `resizeUp` / `resizeRight` / `resizeDown` / `resizeLeft` | | `cursor-ne-resize` / `cursor-nw-resize` / `cursor-se-resize` / `cursor-sw-resize` | `resizeUpRight` / `resizeUpLeft` / `resizeDownRight` / `resizeDownLeft` | | `cursor-ew-resize` / `cursor-ns-resize` | `resizeLeftRight` / `resizeUpDown` | | `cursor-nesw-resize` / `cursor-nwse-resize` | `resizeUpRightDownLeft` / `resizeUpLeftDownRight` | | `cursor-zoom-in` / `cursor-zoom-out` | `zoomIn` / `zoomOut` | Flutter has no "auto" cursor, so `cursor-auto` resolves to the same basic arrow as `cursor-default`. ## How It Works `WDiv` wraps its content in a `MouseRegion` when a `cursor-*` token is present. When the `WDiv` also carries `hover:` / `focus:` / `active:` classes it is already wrapped in `WAnchor` (whose `MouseRegion` uses `click` when the anchor has gesture callbacks, `basic` when it is disabled, and with neither it defers to the next applicable cursor, which can be the click cursor of an enclosing control such as a clickable `WAnchor` around the `WDiv`); the `cursor-*` `MouseRegion` sits deeper in the tree, so its cursor wins for the area it covers regardless of the anchor's default. Last token wins: `cursor-pointer cursor-text` resolves to text. Cursors are a no-op on touch platforms (there is no pointer to style); the token is simply inert there. ## Responsive Design Combine `cursor-*` with breakpoint prefixes to change the affordance per viewport. ```dart WDiv(className: 'cursor-default md:cursor-pointer') ``` ## Dark Mode The cursor itself is theme-independent, but pair the surrounding colour tokens with their `dark:` peers as usual. ```dart WDiv(className: 'cursor-pointer bg-white dark:bg-gray-800') ``` ## Related Documentation - [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md): exposes a `mouseCursor` constructor prop for fully imperative control. - [Transitions](https://anilcancakir.com/wind_kit/docs/interactivity/transition.md): smooth state changes on hover. --- # WDiv The fundamental building block of Wind. `WDiv` acts as a multi-purpose container that replaces standard Flutter widgets like `Container`, `Row`, `Column`, `Flex`, `Grid`, `Wrap`, and `SingleChildScrollView`. It embodies the "Intelligent Composition" philosophy, dynamically constructing the most efficient widget tree based on the provided utility classes. Instead of blindly wrapping content, `WDiv` inspects the `className` and selectively applies specialised widgets like `Padding`, `Align`, `Row`, or a `Wrap` inside a `LayoutBuilder` (for `grid`) only when necessary. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Runtime-Dynamic Colors](#runtime-dynamic-colors) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WDiv( className: 'flex flex-col gap-4 p-6 bg-white rounded-xl shadow-lg', children: [ WText('Hello Wind', className: 'text-2xl font-bold'), WText('This is a styled container.', className: 'text-gray-600'), ], ) ``` ## Basic Usage Use `WDiv` with a single `child` for simple wrapping or with `children` when using layout modes like `flex` or `grid`. ```dart // Simple container WDiv( className: 'p-4 bg-blue-500 rounded', child: WText('Single Child', className: 'text-white'), ) // Flex row WDiv( className: 'flex flex-row gap-2', children: [ WDiv(className: 'w-10 h-10 bg-red-500'), WDiv(className: 'w-10 h-10 bg-green-500'), ], ) ``` ## Constructor ```dart const WDiv({ Key? key, String? className, Widget? child, List? children, WindStyle? style, Set? states, bool scrollPrimary = false, Color? backgroundColor, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `className` | `String?` | `null` | Utility class string defining layout and styles. | | `child` | `Widget?` | `null` | Single child widget. Mutually exclusive with `children`. | | `children` | `List?` | `null` | List of children widgets. Mutually exclusive with `child`. | | `style` | `WindStyle?` | `null` | Explicit style object that serves as a base for `className`. | | `states` | `Set?` | `null` | Custom states to activate prefix classes (e.g., `loading:bg-blue-500`). | | `scrollPrimary` | `bool` | `false` | Whether this is the primary scroll view for iOS tap-to-top and desktop scrollbar integration. | | `backgroundColor` | `Color?` | `null` | Inline background colour for runtime-dynamic values. Overrides any `bg-*` / `dark:bg-*` from `className`. See [Runtime-Dynamic Colors](#runtime-dynamic-colors). | ## Runtime-Dynamic Colors Utility classes are for **design tokens** (`bg-primary-500`, `bg-red-500`). When the colour is a **runtime value**, a hex from a colour picker or a brand colour loaded per tenant, use the `backgroundColor` prop instead of interpolating into `bg-[#$hex]`. ```dart // Good: runtime-dynamic color via inline prop WDiv( backgroundColor: userBrandColor, // Color from a picker or API className: 'w-16 h-16 rounded-xl', ) // Avoid: string interpolation bloats the parser cache, one entry per unique hex WDiv(className: 'w-16 h-16 rounded-xl bg-[#${userBrandColor.toARGB32().toRadixString(16)}]') ``` Precedence: inline `backgroundColor` wins over any `bg-*` / `dark:bg-*` resolved from `className`. When `backgroundColor` is `null`, className behaviour, including the `dark:` fallback, is unchanged. The inline colour does not participate in the parser cache key. ## Layout Modes `WDiv` dynamically switches its internal structure based on the `display` utility classes provided in `className`. - **Block (Default)**: Standard vertical stack or single child wrapper. If `children` is used without `flex` or `grid`, it defaults to a `Column`. - **Flex**: Enabled via `flex`. Supports `flex-row`, `flex-col`, `gap-*`, `items-*`, `justify-*`. It mimics CSS Flexbox behaviour, including automatic `Flexible` wrapping for row children so they can shrink. That wrap is skipped when a child claims a grow share (`flex-1`, `grow`, `flex-auto`, a bare `w-full`, or a raw `Expanded`/`Flexible`), so the growing child keeps the free space and its siblings stay at their content width. See [Flexbox](https://anilcancakir.com/wind_kit/docs/layout/flexbox.md#flex-grow--shrink). - **Grid**: Enabled via `grid`. Uses a combination of `Wrap` and `LayoutBuilder` to achieve Tailwind-like grid behaviour (`grid-cols-*`) with intrinsic item heights. - **Wrap**: Enabled via `wrap`. Elements wrap to the next line when space is insufficient, similar to `flex-wrap` in CSS. - **Hidden**: Enabled via `hidden`. The widget short-circuits to `SizedBox.shrink()` to save resources. ## Event Handling `WDiv` automatically becomes interactive when state-based prefixes like `hover:`, `focus:`, or `active:` are used in the `className`. Under the hood, it wraps the content in a `WAnchor` to detect gestures and focus. That wrap is styling only: the auto-wrapped `WAnchor` carries no gesture, so it publishes no `button` node to assistive technology. A `WDiv` with `hover:bg-slate-100` styles on hover and still announces as whatever its content is, rather than as a control that does nothing when activated. It is not a keyboard or remote stop either. Inside a tappable `WAnchor` the wrapper inherits that anchor's `focus` and `disabled` rather than claiming a stop of its own, so the control is reached once and the ring is drawn on the element the user activates. Standing alone, with no anchor above it, a `WDiv` carrying `focus:` keeps its own focus node and stays reachable; one whose only state classes are `hover:` or `active:` has no focus node and is never a stop, since it has nothing to show when focused. `hover` is never inherited, so sibling divs inside one anchor still highlight independently. See [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md#keyboard-and-remote-control). For direct gesture support (taps, long presses) or to create semantic buttons, use [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md) or [WButton](https://anilcancakir.com/wind_kit/docs/widgets/w-button.md). ## State Variants `WDiv` supports all Wind state prefixes, allowing you to change styles based on interaction or environment. ```dart WDiv( className: 'bg-blue-500 dark:bg-blue-600 hover:bg-blue-600 dark:hover:bg-blue-700 duration-200', child: WText('Interactive', className: 'text-white'), ) ``` Common prefixes: - `hover:` - Applies when the mouse pointer is over the element. - `focus:` - Applies when the element has keyboard focus. - `disabled:` - Applies when the element or parent is disabled. - `dark:` - Applies when the application is in dark mode. - `active:` - Reserved: it applies only when you pass `states: {'active'}`. Neither `WDiv` nor `WAnchor` tracks the press, so keep it in your own state and pass `states: {'pressed'}` with `pressed:` classes. ## Styling Examples ### Backgrounds & Borders Combine background colours, gradients, and border properties. ```dart WDiv(className: 'bg-red-500 border-2 border-red-700 rounded-lg') ``` ### Shadows & Rings Apply depth with shadows or focus indicators with rings. ```dart WDiv(className: 'shadow-xl ring-2 ring-blue-500 ring-offset-2') ``` ### Responsive Design `WDiv` uses mobile-first breakpoints to adapt layouts to different screen sizes. ```dart // Stacked on mobile, side-by-side on tablet/desktop WDiv(className: 'flex flex-col md:flex-row gap-4') // Variable widths WDiv(className: 'w-full md:w-1/2 lg:w-1/3') ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | **Display** | `block`, `flex`, `grid`, `wrap`, `hidden` | | **Flexbox** | `flex-row`, `flex-col`, `justify-*`, `items-*`, `gap-*`, `flex-1`, `flex-2`, etc. | | **Grid** | `grid-cols-*`, `gap-x-*`, `gap-y-*` | | **Sizing** | `w-*`, `h-*`, `min-w-*`, `max-w-*`, `aspect-*` | | **Spacing** | `p-*`, `px-*`, `py-*`, `m-*`, `mx-*`, `my-*`, `space-x-*`, `space-y-*` | | **Background** | `bg-*`, `bg-gradient-*` | | **Borders** | `border-*`, `rounded-*` | | **Effects** | `shadow-*`, `opacity-*`, `ring-*`, `ring-offset-*` | | **Overflow** | `overflow-hidden`, `overflow-scroll`, `overflow-auto`, `overflow-x-*`, `overflow-y-*` | | **Animation** | `animate-spin`, `animate-pulse`, `animate-bounce`, `animate-ping` | | **Transitions** | `transition-*`, `duration-*`, `ease-*` | ## Customizing Theme `WDiv` respects the configuration defined in `WindThemeData`. ```dart WindTheme( data: WindThemeData( baseSpacingUnit: 4.0, // Multiplier for p-1, m-4, etc. colors: { ... }, // Custom color palette for bg-*, border-* screens: { ... }, // Custom screen sizes for md:, lg:, etc. ), child: ..., ) ``` ## Related Documentation - [Flexbox](https://anilcancakir.com/wind_kit/docs/layout/flexbox.md) & [Grid](https://anilcancakir.com/wind_kit/docs/layout/grid.md) - [Sizing Utilities](https://anilcancakir.com/wind_kit/docs/layout/sizing.md) - [Spacing & Margins](https://anilcancakir.com/wind_kit/docs/layout/spacing.md) - [Interaction State Prefixes](https://anilcancakir.com/wind_kit/docs/core-concepts/state-management.md) - [WAnchor Widget](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md) - [WSpacer Widget](https://anilcancakir.com/wind_kit/docs/widgets/w-spacer.md) --- # WText A utility-first text component that translates Tailwind-like class strings into optimised Flutter typography. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Runtime-Dynamic Colors](#runtime-dynamic-colors) - [Typography Styling](#typography-styling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WText( 'Design is not just what it looks like and feels like.', className: 'text-2xl font-bold text-slate-900 text-center leading-tight', ) ``` ## Basic Usage The `WText` widget handles all text rendering in Wind. Unlike standard Flutter `Text` widgets, it supports direct styling via the `className` property, including responsive and state-based modifiers. ```dart WText( 'Utility-first styling for Flutter', className: 'text-lg text-blue-600 font-semibold italic p-4', ) ``` ## Constructor ```dart const WText( String data, { Key? key, String? className, WindStyle? style, TextStyle? textStyle, bool selectable = false, Set? states, Color? foregroundColor, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `data` | `String` | **Required** | The text string to display. | | `className` | `String?` | `null` | Tailwind-like utility classes. | | `style` | `WindStyle?` | `null` | Explicit WindStyle object as a base. | | `textStyle` | `TextStyle?` | `null` | Standard Flutter TextStyle to merge (className takes precedence). | | `selectable` | `bool` | `false` | Whether the text should be selectable (renders `SelectableText`). | | `states` | `Set?` | `null` | Custom states for dynamic styling (e.g., 'loading'). | | `foregroundColor` | `Color?` | `null` | Inline text colour for runtime-dynamic values. Overrides any `text-*` / `dark:text-*` from `className`. See [Runtime-Dynamic Colors](#runtime-dynamic-colors). | ## Runtime-Dynamic Colors Utility classes are for **design tokens** (`text-primary-500`, `text-red-500`). When the colour is a **runtime value**, a user-picked brand colour or a hex loaded from an API, use the `foregroundColor` prop instead of interpolating into `text-[#$hex]`. ```dart // Good: runtime-dynamic color via inline prop WText( 'Brand', foregroundColor: userBrandColor, className: 'text-lg font-bold', ) // Avoid: string interpolation bloats the parser cache, one entry per unique hex WText( 'Brand', className: 'text-lg font-bold text-[#${userBrandColor.toARGB32().toRadixString(16)}]', ) ``` Precedence: inline `foregroundColor` wins over any `text-*` / `dark:text-*` resolved from `className`. When `foregroundColor` is `null`, className behaviour, including the `dark:` fallback, is unchanged. The inline colour does not participate in the parser cache key. ## Typography Styling WText supports a wide range of typography utilities that map directly to Flutter's `TextStyle`. ### Alignment and Transform ```dart WText('CENTERED UPPERCASE', className: 'text-center uppercase') WText('capitalize me', className: 'capitalize') ``` ### Overflow and Clamping Handle long text gracefully using truncation or line clamping. ```dart WText( 'A very long text that will be truncated with an ellipsis after two lines.', className: 'line-clamp-2 text-gray-500', ) ``` ## State Variants Text colours and weights can react to states when provided via the `states` prop or when nested within state-aware widgets like `WButton`. `WText` has no anchor of its own, so `hover:` only fires when an ancestor publishes state (`WAnchor`, `WButton`, `WPopover`). ```dart WAnchor( onTap: () {}, child: WText( 'Hover me', className: 'text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400', ), ) ``` ## Styling Examples ### Gradient and Opacity While standard text uses `text-{color}`, you can also apply opacity modifiers. ```dart WText( 'Faded Text', className: 'text-blue-500/50 font-medium', ) ``` ### Composition Pipeline WText uniquely supports layout utilities (padding, margin, alignment) by automatically wrapping the text in the necessary layout widgets. ```dart WText( 'Alert Message', className: 'bg-red-100 text-red-700 p-4 rounded-lg border border-red-200', ) ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | **Font Size** | `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl` ... `text-6xl` | | **Font Weight** | `font-thin`, `font-light`, `font-normal`, `font-medium`, `font-bold` ... `font-black` | | **Colour** | `text-{color}`, `text-{color}/{opacity}` | | **Align** | `text-left`, `text-center`, `text-right`, `text-justify` | | **Transform** | `uppercase`, `lowercase`, `capitalize`, `normal-case` | | **Decoration** | `underline`, `line-through`, `no-underline` | | **Spacing** | `leading-{size}` (Line Height), `tracking-{size}` (Letter Spacing) | | **Overflow** | `truncate`, `text-ellipsis`, `line-clamp-{n}` | ## Customizing Theme Override default typography scales in your `WindThemeData`. ```dart WindThemeData( fontSizes: { 'huge': 120.0, }, fontWeights: { 'thick': FontWeight.w900, }, ) ``` ## Related Documentation - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md) - For complex layouts and containers. - [Typography Settings](https://anilcancakir.com/wind_kit/docs/typography/font-size.md) - Deep dive into font sizes and scales. - [State Management](https://anilcancakir.com/wind_kit/docs/core-concepts/state-management.md) - How `hover:` and `focus:` work. --- # WButton `WButton` is an interactive component that combines `WAnchor` state management with Tailwind-like utility styling and built-in loading states. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WButton( onTap: () => print('Button tapped'), className: 'bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors', child: WText('Click Me'), ) ``` ## Basic Usage The `WButton` widget handles common interactive states automatically. It provides a simple way to create buttons that react to hover, focus, and disabled states without manual state management. ```dart WButton( onTap: _submit, className: 'bg-indigo-500 text-white p-3 rounded shadow hover:shadow-lg', child: WText('Submit Form'), ) ``` ## Constructor ```dart const WButton({ Key? key, required Widget child, VoidCallback? onTap, VoidCallback? onLongPress, VoidCallback? onDoubleTap, bool isLoading = false, bool disabled = false, String? className, String? loadingText, Widget? loadingWidget, double loadingSize = 16, Color? loadingColor, Set? states, String? semanticLabel, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `child` | `Widget` | **Required** | The button content (usually `WText` or `WIcon`). | | `className` | `String?` | `null` | Wind utility classes for styling. | | `onTap` | `VoidCallback?` | `null` | Callback when the button is tapped. Disabled if `isLoading` or `disabled` is true. | | `onLongPress` | `VoidCallback?` | `null` | Callback for long press gestures. | | `onDoubleTap` | `VoidCallback?` | `null` | Callback for double tap gestures. | | `isLoading` | `bool` | `false` | Activates loading state: disables interaction and shows a spinner. | | `disabled` | `bool` | `false` | Activates disabled state: disables interaction and changes cursor. | | `loadingText` | `String?` | `null` | Text to display next to the loading spinner. | | `loadingWidget` | `Widget?` | `null` | Custom widget to replace the default spinner. | | `loadingSize` | `double` | `16` | Size of the default loading spinner. | | `loadingColor` | `Color?` | `null` | Colour of the spinner. Falls back to text colour, then auto-computes contrast via W3C luminance when no colour is resolvable. | | `states` | `Set?` | `null` | Custom state prefixes (e.g., `{'error'}` for `error:` classes). | | `semanticLabel` | `String?` | `null` | Accessible name for icon-only controls. When set, excludes the child subtree from semantics so the label overrides any child text; prefer it for icon-only controls rather than buttons that already expose readable text. | ## Layout Modes You can control content alignment using standard utility classes. ### Centered Content By default, using `justify-center` in the `className` will centre the child within the button's constraints. ```dart WButton( className: 'w-full bg-blue-500 justify-center py-3 text-white', child: WText('Centered Full Width'), ) ``` ## Event Handling `WButton` exposes standard gesture callbacks. All interaction is automatically suppressed when `isLoading` or `disabled` is set to `true`. A disabled or loading button still appears to assistive technology as a button, marked not enabled, so a screen reader announces it as "dimmed" instead of skipping it. The same holds for a button with no callback at all (`onTap: null`): it is a button that is not enabled, not plain text. ```dart WButton( onTap: () => print('Tapped'), onLongPress: () => print('Long Pressed'), className: 'bg-gray-200 p-4', child: WText('Press Me'), ) ``` ## State Variants `WButton` supports multiple state-based prefixes. These classes are applied only when the corresponding state is active. ```dart WButton( onTap: _submit, className: 'bg-blue-600 hover:bg-blue-700 focus:ring-2 disabled:bg-gray-400 loading:opacity-50', child: WText('Stateful Button'), ) ``` | Prefix | Trigger | |:-------|:--------| | `hover:` | When the mouse pointer is over the button. | | `focus:` | When the button has keyboard focus. | | `disabled:` | When the `disabled` prop is `true`. | | `loading:` | When the `isLoading` prop is `true`. | ## Styling Examples ### Loading States When `isLoading` is true, `WButton` replaces the `child` with a spinner. You can add text or a custom widget. ```dart // Basic spinner WButton( isLoading: true, className: 'bg-blue-500 text-white p-2', child: WText('Login'), ) // Spinner with text WButton( isLoading: true, loadingText: 'Processing...', className: 'bg-green-600 text-white px-4 py-2', child: WText('Submit'), ) ``` > [!NOTE] > When no explicit `loadingColor` is set and no text colour is available from the className, the spinner automatically picks a contrasting colour (light or dark) based on the button's background luminance using the W3C relative luminance algorithm. ### Icon Buttons Combine `WButton` with `WIcon` for utility-styled icon buttons. ```dart WButton( className: 'bg-red-500 hover:bg-red-600 p-3 rounded-full text-white shadow-lg', child: WIcon(Icons.delete), ) ``` ## All Supported Classes `WButton` reads sizing, padding, background, border, radius, shadow, ring, text colour, size and weight, opacity and `justify-center` / alignment. Layout (`flex`, `grid`, `hidden`, `gap`), margin and text transforms are not applied; put them on a wrapping `WDiv` or the child `WText`. | Category | Classes | |:---------|:--------| | Layout | `justify-center` | | Spacing | `p-{n}`, `px-{n}`, `py-{n}` | | Sizing | `w-{size}`, `h-{size}`, `min-w-{size}`, `max-w-{size}` | | Typography | `text-{size}`, `font-{weight}`, `text-{color}` | | Background | `bg-{color}`, `bg-gradient-to-{dir}` | | Borders | `border`, `border-{n}`, `rounded`, `rounded-{size}`, `ring` | | Effects | `shadow`, `opacity-{n}`, `duration-{n}`, `ease-{type}` | ## Customizing Theme You can customise the default behaviour and appearance of buttons by modifying `WindThemeData`. ```dart WindThemeData( // Customize the base spacing unit used for p-4, m-2, etc. baseSpacingUnit: 4.0, // Add custom colors for specific button brands colors: { 'brand': Colors.deepPurple, }, ) ``` ## Related Documentation - [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md) - The base interactive wrapper - [WText](https://anilcancakir.com/wind_kit/docs/widgets/w-text.md) - For styling button labels - [Background Colour](https://anilcancakir.com/wind_kit/docs/styling/background-color.md) - Colour utilities - [Text Colour](https://anilcancakir.com/wind_kit/docs/typography/text-color.md) - Typography colour utilities - [Spacing](https://anilcancakir.com/wind_kit/docs/layout/spacing.md) - Padding and margin utilities --- # WIcon The utility-first icon component for displaying vector icons with Tailwind-like styling. It wraps Flutter's `Icon` widget and applies utility classes for sizing, colouring, and animations while inheriting styles from the surrounding text context. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Runtime-Dynamic Colors](#runtime-dynamic-colors) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WIcon( Icons.star, className: 'text-yellow-400 text-3xl', ) ``` ## Basic Usage The `WIcon` widget allows you to style icons using `className`. By default, it inherits colour and font size from the surrounding `DefaultTextStyle` (e.g., from a parent `WDiv` or `WText`), allowing icons to automatically match adjacent text. ```dart WIcon( Icons.favorite, className: 'text-red-500 text-xl', ) ``` ## Constructor ```dart const WIcon( IconData icon, { Key? key, String? className, Set? states, Color? foregroundColor, String? semanticLabel, TextDirection? textDirection, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `icon` | `IconData` | **Required** | The icon to display. | | `className` | `String?` | `null` | Wind utility classes for styling. | | `states` | `Set?` | `null` | Custom states to activate prefix classes. | | `foregroundColor` | `Color?` | `null` | Inline icon colour for runtime-dynamic values. Overrides any `text-*` / `dark:text-*` from `className`. See [Runtime-Dynamic Colors](#runtime-dynamic-colors). | | `semanticLabel` | `String?` | `null` | Optional semantic label for accessibility. | | `textDirection` | `TextDirection?` | `null` | Text direction for the icon. | ## Runtime-Dynamic Colors Utility classes are for **design tokens** (`text-primary-500`, `text-red-500`). When the colour is a **runtime value**, a category icon colour from the database or a user-picked brand colour, use the `foregroundColor` prop instead of interpolating into `text-[#$hex]`. ```dart // `category.color` is a Color loaded at runtime (e.g. from the database). // Good: runtime-dynamic color via inline prop WIcon( Icons.star, foregroundColor: category.color, className: 'text-lg', ) // Avoid: interpolating a runtime value into className bloats the parser // cache with one entry per unique color (`categoryHex` is a String like 'ff5500') WIcon(Icons.star, className: 'text-lg text-[#$categoryHex]') ``` Precedence: inline `foregroundColor` wins over any `text-*` / `dark:text-*` resolved from `className`. When `foregroundColor` is `null`, className behaviour, including the `dark:` fallback, is unchanged. The inline colour does not participate in the parser cache key. ## Layout Modes While icons don't have complex layout modes, `WIcon` supports two primary sizing methods. Explicit sizing (`w-*` / `h-*`) takes precedence over typography-based sizing (`text-*`). ### Sizing Modes ```dart // Typography-based sizing (inherits line-height context) WIcon(Icons.info, className: 'text-lg') // Explicit dimension sizing (absolute pixels) WIcon(Icons.info, className: 'w-12 h-12') ``` ## Event Handling `WIcon` is a purely visual component. To handle user interactions, wrap it in a `WAnchor` or `WButton`. You can pass the parent's state to the `states` prop to trigger state-aware styling. ```dart WAnchor( onTap: () => print('Settings tapped'), child: WIcon( Icons.settings, className: 'text-gray-400 hover:text-blue-500 transition-colors', ), ) ``` ## State Variants Use state prefixes for dynamic appearance changes. `WIcon` has no anchor of its own, so `hover:` only fires when an ancestor publishes state (`WAnchor`, `WButton`, `WPopover`). ```dart WAnchor( onTap: () {}, child: WIcon( Icons.check_circle, className: 'text-green-500 dark:text-green-400 hover:text-green-600 dark:hover:text-green-300', ), ) ``` ## Styling Examples ### Colors and Opacity Icons support full colour palette integration including opacity modifiers. ```dart // Red icon with 50% opacity WIcon(Icons.error, className: 'text-red-500/50') ``` ### Animations Apply built-in animations to icons for loading states or attention-grabbing effects. ```dart // Spinning refresh icon WIcon(Icons.refresh, className: 'text-blue-600 animate-spin') // Pulsing alert icon WIcon(Icons.notifications, className: 'text-amber-500 animate-pulse') ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | Sizing | `text-{size}`, `w-{size}`, `h-{size}` | | Colours | `text-{color}`, `text-{color}/{opacity}` | | Opacity | `opacity-{n}` | | Animation | `animate-spin`, `animate-pulse`, `animate-bounce`, `animate-ping` | | Transitions | `duration-{ms}`, `ease-{curve}` | ## Customizing Theme Icons respect the global configuration in `WindThemeData`. Changing the `fontSizes` or `colors` scales will update all icons using those classes. ```dart WindThemeData( fontSizes: { 'xl': 20.0, }, colors: { 'brand': Colors.indigo, }, ) ``` ## Related Documentation - [WText](https://anilcancakir.com/wind_kit/docs/widgets/w-text.md) - Cascades typography styles to icons - [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md) - For making icons interactive - [WSvg](https://anilcancakir.com/wind_kit/docs/widgets/w-svg.md) - For custom vector illustrations - [WImage](https://anilcancakir.com/wind_kit/docs/widgets/w-image.md) - For rasterised images --- # WImage The `WImage` widget brings utility-first styling to images in Flutter, providing HTML-like semantics with Tailwind-inspired classes for sizing, object fitting, and decoration. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WImage( src: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e', alt: 'Forest landscape', className: 'w-96 h-64 object-cover rounded-xl', ) ``` ## Basic Usage The `WImage` widget handles both network and asset images. For network images, simply provide the URL to the `src` property. For asset images, use the `asset://` prefix. ```dart // Network Image WImage( src: 'https://example.com/image.jpg', className: 'w-32 h-32 rounded-full', ) // Asset Image WImage( src: 'asset://assets/images/logo.png', className: 'w-12 h-12', ) ``` ## Constructor ```dart const WImage({ Key? key, String? src, ImageProvider? image, String? alt, String? className, Set? states, Widget? placeholder, ImageErrorBuilder? errorBuilder, Widget Function(BuildContext, Widget, ImageChunkEvent?)? loadingBuilder, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `src` | `String?` | `null` | Image source URL. Prefix with `asset://` for local assets. | | `image` | `ImageProvider?` | `null` | Direct ImageProvider (e.g., `NetworkImage`, `FileImage`). | | `alt` | `String?` | `null` | Alternative text for accessibility (semantic label). | | `className` | `String?` | `null` | Wind utility classes for sizing, fit, and decoration. | | `states` | `Set?` | `null` | Custom states for dynamic styling. | | `placeholder` | `Widget?` | `null` | Widget to show while a network image is loading. Only applies to a network `src`; ignored for `asset://` sources and `image` providers. | | `errorBuilder` | `ImageErrorBuilder?` | `null` | Custom builder for error handling. | | `loadingBuilder` | `Widget Function(BuildContext, Widget, ImageChunkEvent?)?` | `null` | Custom builder for loading indicators. Only applies to a network `src`, and takes precedence over `placeholder`. | > [!NOTE] > Either `src` or `image` must be provided. If both are provided, `image` takes precedence. > > `WImage` declares its own `ImageLoadingBuilder` typedef with the same shape as Flutter's, so naming it in a file that also imports Flutter's widgets library is an ambiguous import. Spell out the function type, as in the constructor above. ## Layout Modes `WImage` supports common image layout utilities to control how the image is resized and fitted within its container. Only fixed `w-{n}` and `h-{n}` sizes are applied; `w-full` and other fractional widths are ignored. For a full-width image, wrap it in `WDiv(className: 'w-full')`. ### Object Fit Control how the image content should be resized to fit its container using `object-{fit}` classes. ```dart WImage( src: 'https://example.com/photo.jpg', className: 'w-64 h-64 object-contain bg-gray-200', ) ``` ### Object Position `object-cover` centres the crop, so in a short, wide banner the upper part of a tall photo (a face, a skyline) is cut off. Choose which part stays in view with `object-{position}` classes: `object-top`, `object-bottom`, `object-left`, `object-right`, `object-left-top`, `object-left-bottom`, `object-right-top`, `object-right-bottom` and `object-center` (the default). They combine with every fit class and map to the matching Flutter `Alignment` on the image. ```dart WImage( src: 'https://example.com/backdrop.jpg', className: 'w-96 h-40 object-cover object-top', ) ``` > [!NOTE] > Like the `object-{fit}` classes, position classes are read straight off `className`: the first match wins and prefixed forms such as `md:object-top` or `hover:object-bottom` are not applied. ### Aspect Ratio Ensure the image maintains a specific aspect ratio regardless of its content dimensions. ```dart WImage( src: 'https://example.com/banner.jpg', className: 'w-96 aspect-video object-cover', ) ``` ## Event Handling `WImage` is a display-only widget and does not include built-in gesture handlers. To make an image interactive, wrap it in a `WAnchor` or `WButton`. ```dart WAnchor( onTap: () => print('Image clicked!'), child: WImage( src: 'https://example.com/avatar.jpg', className: 'w-12 h-12 rounded-full hover:opacity-80', ), ) ``` ## State Variants You can use state prefixes to change the image's appearance based on parent state or custom state sets. `WImage` has no anchor of its own, so `hover:` only fires when an ancestor publishes state (`WAnchor`, `WButton`, `WPopover`). ```dart WAnchor( onTap: () {}, child: WImage( src: 'https://example.com/photo.jpg', className: 'w-64 h-40 opacity-100 hover:opacity-75', ), ) ``` ## Styling Examples ### Profile Avatars Creating circular avatars with borders. ```dart WImage( src: 'https://example.com/user.jpg', className: 'w-16 h-16 rounded-full border-2 border-white dark:border-gray-800 object-cover', ) ``` ### Rounded Cards Using a specific border radius on the top corners. ```dart WImage( src: 'https://example.com/thumbnail.jpg', className: 'w-80 h-48 rounded-t-lg object-cover', ) ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | Sizing | `w-{size}`, `h-{size}` | | Object Fit | `object-cover`, `object-contain`, `object-fill`, `object-none`, `object-scale-down` | | Object Position | `object-top`, `object-bottom`, `object-left`, `object-right`, `object-left-top`, `object-left-bottom`, `object-right-top`, `object-right-bottom`, `object-center` | | Aspect Ratio | `aspect-square`, `aspect-video`, `aspect-{ratio}` | | Borders | `border`, `border-{width}`, `border-{color}`, `rounded-{size}` | | Effects | `opacity-{n}` | `WImage` does not apply `shadow-*` classes. To cast a shadow, put the image inside a `WDiv` that carries the shadow. ## Customizing Theme The default spacing, border radius, and colours used by `WImage` are controlled via `WindThemeData`. ```dart WindThemeData( borderRadius: { 'xl': 12.0, }, // WImage also respects the default spacing scale for w-* and h-* classes ) ``` ## Related Documentation - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md) - The fundamental layout container. - [WSvg](https://anilcancakir.com/wind_kit/docs/widgets/w-svg.md) - For vector-based graphics. - [WIcon](https://anilcancakir.com/wind_kit/docs/widgets/w-icon.md) - For system and custom icons. --- # WSvg The utility-first SVG component. `WSvg` brings HTML SVG semantics to Flutter with Tailwind-like utility classes for styling, supporting both asset paths and raw SVG strings. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [Preserving Original Colors](#preserving-original-colors) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WAnchor( onTap: () {}, child: WSvg( src: 'assets/icons/star.svg', className: 'fill-yellow-500 dark:fill-yellow-400 hover:fill-yellow-600 dark:hover:fill-yellow-300 w-6 h-6', ), ) ``` ## Basic Usage The `WSvg` widget allows you to render SVG graphics and style them using utility classes. It inherits colours and sizes from parent styles if not explicitly defined. ### From Asset Use the default constructor for files located in your assets folder. ```dart WSvg( src: 'assets/logo.svg', className: 'w-12 h-12 fill-blue-600', ) ``` ### From String Use the `WSvg.string` constructor to render raw SVG content. ```dart WSvg.string( '...', className: 'stroke-red-500 w-8 h-8', ) ``` ## Constructor ```dart const WSvg({ Key? key, required String? src, String? className, Set? states, String? semanticsLabel, }) const WSvg.string( String svg, { Key? key, String? className, Set? states, String? semanticsLabel, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `className` | `String?` | `null` | Wind utility classes for sizing, colouring, and effects. | | `src` | `String?` | **Required** | The asset path to the SVG file (default constructor). | | `svg` | `String` | **Required** | Raw SVG string content, passed positionally to `WSvg.string`. | | `states` | `Set?` | `null` | Custom states for dynamic styling prefixes. | | `semanticsLabel` | `String?` | `null` | Semantic label for accessibility. | ## Layout Modes `WSvg` behaves as a fixed-size or flexible graphic depending on the classes provided. ### Sizing Priority The widget determines its size based on the following priority: 1. `w-{n}` or `h-{n}` classes. 2. `text-{size}` classes (font size). 3. Inherited font size from the parent `DefaultTextStyle`. ```dart WSvg( src: 'assets/icon.svg', className: 'w-10 h-10', // Explicit size ) ``` ## Event Handling `WSvg` does not handle events directly. To add interactivity, wrap it in a `WAnchor` or `WButton`. ```dart WAnchor( onTap: () => print('SVG clicked'), child: WSvg( src: 'assets/icon.svg', className: 'hover:fill-blue-500 dark:hover:fill-blue-400 transition-colors', ), ) ``` ## State Variants The interaction prefixes (`hover:`, `focus:`, `disabled:`) need an ancestor that publishes state (`WAnchor`, `WButton`, `WPopover`); `WSvg` has none of its own. Custom prefixes driven by the `states` prop and `dark:` do not need one. ```dart WAnchor( onTap: () {}, child: WSvg( src: 'assets/icon.svg', className: 'fill-gray-400 hover:fill-blue-500 dark:fill-white dark:hover:fill-blue-400', ), ) ``` ## Styling Examples ### Color Priority The widget applies a colour filter using the following priority: 1. `stroke-{color}` (ideal for outlined icons) 2. `fill-{color}` 3. `text-{color}` (fallback) 4. Inherited colour from parent ```dart // Applying a stroke color to an outlined SVG WSvg( src: 'assets/outline-star.svg', className: 'stroke-amber-500', ) ``` > [!NOTE] > Adding the `preserve-colors` class bypasses the entire colour priority chain. No `ColorFilter` is applied, and the SVG renders with its original embedded colours. ### Opacity and Transitions You can apply opacity and animate it using transition classes. ```dart WAnchor( onTap: () {}, child: WSvg( src: 'assets/logo.svg', className: 'opacity-50 hover:opacity-100 duration-300', ), ) ``` ### Preserving Original Colors By default, `WSvg` applies a `ColorFilter` based on the colour priority chain above. For SVGs that contain multiple colours you want to keep intact (like QR codes, branded logos, or multi-colour illustrations), use the `preserve-colors` utility class. ```dart // Multi-color logo: keep original colors WSvg( src: 'assets/logo-colored.svg', className: 'w-32 h-32 preserve-colors', ) // QR code: must not be tinted WSvg( src: 'assets/qr-code.svg', className: 'w-48 h-48 preserve-colors', ) ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | Sizing | `w-{size}`, `h-{size}`, `text-{size}` | | Colouring | `fill-{color}`, `stroke-{color}`, `text-{color}` | | Preserve | `preserve-colors` | | Opacity | `opacity-{n}` | | Transitions| `duration-{ms}`, `ease-{curve}` | `WSvg` does not apply `animate-*` classes. To animate an SVG, wrap it in a `WindAnimationWrapper`. ## Customizing Theme You can define custom colours in your `WindThemeData` that will be available to `WSvg`. ```dart WindThemeData( colors: { 'brand': Colors.blue, }, ) ``` Then use it as: `className: 'fill-brand-500'`. ## Related Documentation - [WIcon](https://anilcancakir.com/wind_kit/docs/widgets/w-icon.md) - [WImage](https://anilcancakir.com/wind_kit/docs/widgets/w-image.md) --- # WInput A utility-first form input that combines React-style controlled state management with Tailwind-like styling. It renders Material-free (`EditableText` core inside a `Container`) and works under any theming ancestor: Material, Cupertino, a custom app, or a bare `WidgetsApp`. No Material ancestor required. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Keyboard Clearance](#keyboard-clearance) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Accessibility](#accessibility) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WInput( value: _email, onChanged: (value) => setState(() => _email = value), type: InputType.email, placeholder: 'Enter your email', className: 'p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500', placeholderClassName: 'text-gray-400 italic', ) ``` ## Basic Usage Pass `value` and `onChanged` for controlled (React-style) binding. Omit both to let the field manage its own state internally. `WInput` works under any ancestor widget tree. No `MaterialApp` required. ```dart WInput( placeholder: 'Type something...', className: 'w-full p-2 border rounded', onChanged: (val) => print(val), ) ``` ## Constructor ```dart const WInput({ Key? key, String? value, ValueChanged? onChanged, InputType type = InputType.text, String? className, String? placeholderClassName, String? placeholder, bool enabled = true, bool readOnly = false, bool autofocus = false, TextInputAction? textInputAction, Iterable? autofillHints = const [], ValueChanged? onSubmitted, VoidCallback? onEditingComplete, VoidCallback? onTap, TapRegionCallback? onTapOutside, int? maxLines, int minLines = 1, FocusNode? focusNode, TextEditingController? controller, Set? states, List? inputFormatters, String decimalSeparator = '.', TextCapitalization textCapitalization = TextCapitalization.none, bool autocorrect = true, bool enableSuggestions = true, Widget? prefix, Widget? suffix, String? semanticLabel, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `className` | `String?` | `null` | Wind utility classes for the input field | | `placeholderClassName` | `String?` | `null` | Utility classes for styling the hint text | | `value` | `String?` | `null` | The controlled value of the input | | `onChanged` | `ValueChanged?` | `null` | Callback when text changes | | `type` | `InputType` | `InputType.text` | Input keyboard and behaviour (`text`, `password`, `email`, `number`, `multiline`). `number` restricts input to a signed decimal on every platform (web included), with `decimalSeparator` as its one decimal mark; pass `inputFormatters` to override. | | `placeholder` | `String?` | `null` | Hint text shown when input is empty | | `enabled` | `bool` | `true` | Whether the input is interactive | | `readOnly` | `bool` | `false` | Whether the input is read-only | | `autofocus` | `bool` | `false` | Autofocus on mount | | `textInputAction` | `TextInputAction?` | `null` | Keyboard action (e.g., `.done`, `.next`). When `null`, resolves to `.done` on a single-line field and `.newline` on a multiline one. | | `autofillHints` | `Iterable?` | `const []` | Hints for a password manager or the platform autofill service (e.g., `[AutofillHints.email]`). The empty default keeps platform autofill on with no hint; `null` opts the field out. Fields of one form go inside an `AutofillGroup`, which the host provides; `WInput` never creates one. | | `onSubmitted` | `ValueChanged?` | `null` | Callback when action button is pressed | | `onTapOutside` | `TapRegionCallback?` | `null` | Callback when tapping outside (useful for blur) | | `maxLines` | `int?` | `null` | Max lines for multiline input | | `minLines` | `int` | `1` | Min lines for multiline input | | `prefix` | `Widget?` | `null` | Widget displayed before text | | `suffix` | `Widget?` | `null` | Widget displayed after text | | `controller` | `TextEditingController?` | `null` | Optional external controller | | `states` | `Set?` | `null` | Custom states for dynamic styling | | `onEditingComplete` | `VoidCallback?` | `null` | Callback fired when the user finishes editing (loses focus, presses done, etc.) | | `onTap` | `VoidCallback?` | `null` | Callback fired when the field is tapped | | `focusNode` | `FocusNode?` | `null` | External focus controller for programmatic focus management | | `inputFormatters` | `List?` | `null` | Formatters applied as the user types (digit-only, masks, etc.) | | `decimalSeparator` | `String` | `'.'` | The one decimal mark a `number` field accepts. Pass the locale's mark (`','` in Turkish or German) so `2,5` is not typed as `25`; the text stays as typed, so convert it to `.` before it goes on the wire. One character, never a digit, `+` or `-` (debug asserts). Ignored when `inputFormatters` is set. | | `textCapitalization` | `TextCapitalization` | `TextCapitalization.none` | Auto-capitalise behaviour (`none` / `sentences` / `words` / `characters`) | | `autocorrect` | `bool` | `true` | Enable OS autocorrect suggestions | | `enableSuggestions` | `bool` | `true` | Enable OS suggestion bar (Android) | | `semanticLabel` | `String?` | `null` | Accessible name for the single textbox semantics node. Falls back to `placeholder` when `null`. Used by screen readers and `getByLabel`-style locators. | ## Layout Modes A single-line field centres its text and placeholder vertically in whatever height `min-h-*` or `h-*` gives the box, as a CSS input does; a multiline field keeps its text at the top. ### Multiline Input For text areas, set the `type` to `InputType.multiline`. You can control the height using `minLines` or standard sizing classes. ```dart WInput( type: InputType.multiline, minLines: 3, maxLines: 5, className: 'w-full p-4 border rounded-xl bg-gray-50', placeholder: 'Enter your message...', ) ``` ## Keyboard Clearance A focused `WInput` lifts the WHOLE field above the software keyboard, not just the line the caret is on. Flutter reveals the caret rect with `scrollPadding` around it, which is the same box as the field while the field is one line tall. On a multiline field it is not: the caret sits on the first line, so line one clears the keyboard and every line below it stays under it. `WInput` widens `scrollPadding` by however much of the field sits below the caret, so Flutter's own caret scroll carries the rest of the field with it, and it recomputes as the caret moves down through the text. The keyboard is not the only thing in the way. A [WKeyboardActions](https://anilcancakir.com/wind_kit/docs/widgets/w-keyboard-actions.md) toolbar is drawn on top of the keyboard and the engine reports nothing about it, so the region a field has to clear is the keyboard plus the bar. `WInput` reads the bar's published height and adds it, which means a field inside a `WKeyboardActions` needs no setup at all. ```dart WInput( type: InputType.multiline, minLines: 3, maxLines: 5, className: 'w-full p-4 border rounded-xl', placeholder: 'Write an update...', ) ``` > [!NOTE] > Nothing here is a prop. The clearance is computed from the field's own geometry, so a field that sits inside your own scroll view behaves the same as one that does not. ## Event Handling `WInput` provides standard callbacks for user interaction. Use `onSubmitted` for handling "Enter" or keyboard action buttons. ```dart WInput( className: 'border p-2', textInputAction: TextInputAction.search, onSubmitted: (value) { _performSearch(value); }, onTapOutside: (_) => FocusScope.of(context).unfocus(), ) ``` > **The Return key closes the keyboard by default.** With no `textInputAction`, a single-line field resolves to `TextInputAction.done` and a multiline one to `.newline`. Passing `.next` is a deliberate opt-in, because Flutter implements it as `focusNode.nextFocus()`: the next FOCUSABLE widget, not the next text field. On a form whose traversal order runs through buttons, colour swatches and switches, the same Return key does two different things on one screen, dismissing the keyboard where the next focusable is a button and jumping past several fields where it happens to be editable. Reach for `.next` only where you know the following focusable IS the next input, which is a fact only the form itself has. For a toolbar that advances through a known field order instead, see [WKeyboardActions](https://anilcancakir.com/wind_kit/docs/widgets/w-keyboard-actions.md). ## State Variants Wind automatically manages `focus:`, `disabled:` (when `enabled: false`), and `readonly:` (when `readOnly: true`) states. You can also trigger custom states like `error:` by passing them to the `states` property. ```dart WInput( states: _hasError ? {'error'} : {}, className: 'border border-gray-300 focus:border-blue-500 error:border-red-500', ) ``` > [!NOTE] > `focus:` styles (like `ring-2`) are applied automatically when the field gains focus. ## Accessibility `WInput` emits exactly **one** typeable textbox semantics node. Its accessible name is `semanticLabel` when provided, falling back to `placeholder`. This single-node shape ensures label-by-text locators (e.g., Playwright `getByLabel`, `tapir` label lookup) resolve cleanly with no duplicate accessible names. `InputType.password` fields report `isObscured: true` in the semantics tree so screen readers announce the field as a password input. When the field is `enabled: false`, it is non-editable and reports `isReadOnly` in the semantics tree. > [!NOTE] > `WInput` supports native text selection (drag-select, double-tap word, long-press) with lollipop selection handles and a Cut / Copy / Paste toolbar that Wind draws itself on every platform, in light or dark to match the Wind theme. Neither needs a Material or Cupertino ancestor, so the look is the same under any root. Interactive selection requires an `Overlay` ancestor (`MaterialApp` / `CupertinoApp` / `WidgetsApp` all provide one). Under a bare root with no `Overlay` (unusual in practice), typing and focus still work, but all interactive selection (drag-select, double-tap word, long-press, the drag handles, and the toolbar) is suppressed rather than throwing. > [!NOTE] > **Web:** Flutter renders to a canvas (CanvasKit or Skwasm) and does not use a native browser ``. Mouse drag-select, double-click word, and keyboard copy (Ctrl+C / Cmd+C) all work through Flutter's own selection layer. Right-click opens the browser's native context menu by default (not WInput's selection toolbar). Apps that want WInput's selection toolbar on right-click can call `BrowserContextMenu.disableContextMenu()` once at startup; this is an app-level opt-in and not WInput behaviour. No magnifier is shown on web. > [!WARNING] > Passing both `value` and `controller` at the same time throws an `AssertionError` in debug mode. Use either `value` + `onChanged` (controlled) or `controller` (external), not both. ## Styling Examples ### Search Input with Prefix You can use the `prefix` prop to add icons while maintaining the utility-first styling of the input. ```dart WInput( prefix: Icon(Icons.search, color: Colors.grey), className: 'p-2 pl-10 bg-gray-100 rounded-full border-transparent focus:bg-white focus:border-blue-500', placeholder: 'Search...', ) ``` ### Password Field Use `InputType.password` to obscure text. You can use the `suffix` prop to add a visibility toggle. ```dart WInput( type: _obscure ? InputType.password : InputType.text, suffix: IconButton( icon: Icon(_obscure ? Icons.visibility : Icons.visibility_off), onPressed: () => setState(() => _obscure = !_obscure), ), className: 'p-3 border rounded', ) ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | Layout | `w-{size}`, `h-{size}`, `flex-1`, `w-full` | | Spacing | `p-{n}`, `px-{n}`, `py-{n}`, `m-{n}` | | Typography | `text-{size}`, `font-{weight}`, `italic` | | Colours | `bg-{color}`, `text-{color}` | | Borders | `border`, `border-{n}`, `rounded-{size}`, `border-{color}` | | Effects | `shadow-{size}`, `ring-{n}`, `ring-offset-{n}` | ## Customizing Theme `WInput` draws its default border in a fixed colour that the theme does not control. Set a border colour on each field with `border-{color}` and its `dark:` pair in `className`. The theme does scale the `p-*` spacing classes through `baseSpacingUnit`: ```dart WindThemeData( // WInput respects global spacing units for p-x classes baseSpacingUnit: 4.0, ) ``` ## Related Documentation - [WFormInput](https://anilcancakir.com/wind_kit/docs/widgets/w-form-input.md) - Form-integrated input with labels and validation. - [WButton](https://anilcancakir.com/wind_kit/docs/widgets/w-button.md) - Interactive button component. - [WText](https://anilcancakir.com/wind_kit/docs/widgets/w-text.md) - Typography component. --- # WFormInput A Wind-styled input that integrates seamlessly with Flutter's native Form validation system. It extends `FormField` to provide automatic error handling, label management, and state-based styling through utility classes. `WFormInput` delegates its rendering to `WInput` and therefore inherits Material-free rendering: it works under any ancestor widget tree (Material, Cupertino, custom root) without requiring a Material ancestor. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WFormInput( label: 'Email Address', placeholder: 'you@example.com', className: 'p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 error:border-red-500', validator: (value) { if (value == null || value.isEmpty) return 'Email is required'; if (!value.contains('@')) return 'Invalid email address'; return null; }, ) ``` ## Basic Usage The `WFormInput` widget wraps a standard `WInput` with a `FormField`. It automatically manages its own internal `TextEditingController` if one isn't provided, and synchronises its state with the parent `Form` widget. When validation fails, the widget automatically activates the `error` state, enabling any `error:` prefixed utility classes in your `className`. ```dart Form( key: _formKey, child: Column( children: [ WFormInput( label: 'Username', className: 'border p-2 error:bg-red-50', validator: (v) => v!.length < 3 ? 'Too short' : null, ), WButton( onTap: () => _formKey.currentState!.validate(), child: Text('Submit'), ), ], ), ) ``` ## Constructor ```dart WFormInput({ Key? key, TextEditingController? controller, String? initialValue, String? Function(String?)? validator, void Function(String?)? onSaved, AutovalidateMode? autovalidateMode, String? restorationId, bool enabled = true, // WInput props FocusNode? focusNode, InputType type = InputType.text, String? placeholder, String? className, String? placeholderClassName, TextInputAction? textInputAction, Iterable? autofillHints = const [], ValueChanged? onSubmitted, ValueChanged? onChanged, VoidCallback? onEditingComplete, VoidCallback? onTap, TapRegionCallback? onTapOutside, bool readOnly = false, bool autofocus = false, int? maxLines, int minLines = 1, Set? states, List? inputFormatters, String decimalSeparator = '.', TextCapitalization textCapitalization = TextCapitalization.none, bool autocorrect = true, bool enableSuggestions = true, // Layout props String? label, String labelClassName = 'text-sm font-medium text-gray-700 dark:text-gray-300 mb-1', String? hint, String hintClassName = 'text-gray-500 dark:text-gray-400 text-xs mt-1', bool showError = true, String errorClassName = 'text-red-500 dark:text-red-400 text-xs mt-1', // Prefix/Suffix Widget? prefix, Widget? suffix, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `className` | `String?` | `null` | Utility classes for the input field. Supports `error:`, `focus:`, and `disabled:` prefixes. | | `label` | `String?` | `null` | Label text displayed above the input. | | `labelClassName` | `String` | `'text-sm...'` | Styling for the label text. | | `hint` | `String?` | `null` | Helper text displayed below the input. Hidden when validation error is active. | | `hintClassName` | `String` | `'text-gray-500...'` | Styling for the hint text. | | `showError` | `bool` | `true` | Whether to display the error message string below the input. | | `errorClassName` | `String` | `'text-red-500...'` | Styling for the validation error message text. | | `controller` | `TextEditingController?` | `null` | Optional external controller for manual text management. | | `restorationId` | `String?` | `null` | Restoration bucket ID used by Flutter state restoration. | | `validator` | `String? Function(String?)?` | `null` | Form validation logic returning error string or null. | | `type` | `InputType` | `InputType.text` | Determines keyboard layout and visual masking (e.g., `password`). | | `decimalSeparator` | `String` | `'.'` | The one decimal mark a `number` field accepts, forwarded to `WInput.decimalSeparator`. | | `autofillHints` | `Iterable?` | `const []` | Hints for a password manager or the platform autofill service (e.g., `[AutofillHints.password]`). The empty default keeps platform autofill on with no hint; `null` opts the field out. Fields of one form go inside an `AutofillGroup`, which the host provides. | | `prefix` | `Widget?` | `null` | Widget (like an Icon) displayed before the input text. | | `suffix` | `Widget?` | `null` | Widget (like a visibility toggle) displayed after the input text. | ## Layout Modes ### Vertical Stack (Default) `WFormInput` organises its components in a vertical column: label at the top, followed by the input field, and then the hint or error message at the bottom. ```dart WFormInput( label: 'Account Number', hint: 'Enter the 12-digit number found on your statement.', className: 'border p-3 rounded', ) ``` ## Event Handling `WFormInput` supports all standard text input events. `onChanged` automatically notifies the internal `FormFieldState` to track validation in real-time if `autovalidateMode` is enabled. ```dart WFormInput( className: 'border p-2', onChanged: (value) => print('Typing: $value'), onSubmitted: (value) => print('Final value: $value'), ) ``` ## State Variants Interactive styling is achieved through state prefixes. `WFormInput` automatically manages the `error` state based on validation results. ```dart WFormInput( className: ''' border border-gray-300 focus:border-blue-500 focus:ring-1 error:border-red-500 error:bg-red-50 disabled:bg-gray-100 disabled:text-gray-400 ''', validator: (v) => v!.isEmpty ? 'Required' : null, ) ``` ## Styling Examples ### Custom Error Feedback You can completely customise how the error appears by using `error:` classes on the main input and styling the error message specifically. ```dart WFormInput( label: 'Password', type: InputType.password, className: 'border-b-2 error:border-red-600', errorClassName: 'text-red-600 font-bold mt-2 italic', validator: (v) => v!.length < 8 ? 'Password too short' : null, ) ``` ### Minimalist Style Removing the default labels and hints for a tighter, cleaner UI. ```dart WFormInput( placeholder: 'Search...', className: 'bg-gray-100 rounded-full px-4 py-2 border-none focus:bg-white focus:ring-2 focus:ring-blue-500/50', showError: false, // Only use error: styling on the input itself ) ``` ## All Supported Classes Since `WFormInput` uses `WInput` internally, it supports all standard utility categories. | Category | Classes | |:---------|:--------| | Spacing | `p-{n}`, `m-{n}` | | Sizing | `w-{size}`, `h-{size}` | | Typography | `text-{size}`, `font-{weight}` | | Colours | `bg-{color}`, `text-{color}` | | Borders | `border`, `rounded`, `ring` | | State | `focus:`, `disabled:`, `error:` | Layout classes (`flex`, `grid`, `hidden`, `gap-{n}`) and `hover:` have no effect on the field itself; put them on a wrapping `WDiv`. ## Customizing Theme The default label, hint and error styles are the default values of the `labelClassName`, `hintClassName` and `errorClassName` parameters, not theme values; override them per field. The tokens inside any class string still resolve through `WindThemeData`. ```dart WindThemeData( // The base spacing unit affects all p-{n} and m-{n} classes baseSpacingUnit: 4.0, ) ``` ## Related Documentation - [WInput](https://anilcancakir.com/wind_kit/docs/widgets/w-input.md) - The base input widget. - [WButton](https://anilcancakir.com/wind_kit/docs/widgets/w-button.md) - Standard button for form submission. - [WFormCheckbox](https://anilcancakir.com/wind_kit/docs/widgets/w-form-checkbox.md) - Form-integrated checkbox. - [WText](https://anilcancakir.com/wind_kit/docs/widgets/w-text.md) - The underlying typography system. --- # WSelect A highly customisable, utility-first select component that supports single selection, multi-selection, searching, and remote data fetching. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [Accessibility](#accessibility) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WSelect( value: _selected, options: [ SelectOption(value: 'dart', label: 'Dart'), SelectOption(value: 'flutter', label: 'Flutter'), ], onChange: (value) => setState(() => _selected = value), className: 'w-64 bg-white border border-gray-300 rounded-lg', ) ``` ## Basic Usage The `WSelect` widget replaces the standard Material Dropdown with a utility-first approach. It manages its own open state while providing a controlled interface for selection. ```dart WSelect( placeholder: 'Choose a number', options: List.generate(5, (i) => SelectOption(value: i, label: 'Option $i')), onChange: (val) => print('Selected: $val'), className: 'bg-white border p-3 rounded-md', ) ``` ## Constructor ```dart const WSelect({ Key? key, T? value, ValueChanged? onChange, bool isMulti = false, List? values, ValueChanged>? onMultiChange, SelectedChipBuilder? selectedChipBuilder, required List> options, bool searchable = false, Future>> Function(String)? onSearch, String searchPlaceholder = 'Search...', Future> Function(String)? onCreateOption, CreateOptionBuilder? createOptionBuilder, Future>> Function()? onLoadMore, bool hasMore = false, VoidCallback? onOpen, String? className, String? menuClassName, String placeholder = 'Select an option', bool disabled = false, double? menuWidth, double maxMenuHeight = 300, Set? states, SelectTriggerBuilder? triggerBuilder, MultiSelectTriggerBuilder? multiTriggerBuilder, SelectItemBuilder? itemBuilder, EmptyStateBuilder? emptyBuilder, LoadingBuilder? loadingBuilder, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `className` | `String?` | `null` | Utility classes for the trigger container; when null the trigger uses `bg-white border border-gray-300 rounded-lg p-3` | | `menuClassName` | `String?` | `null` | Utility classes for the dropdown menu | | `options` | `List>` | **Required** | List of items to display | | `value` | `T?` | `null` | Selected value in single-select mode | | `onChange` | `ValueChanged?` | `null` | Callback for single selection changes | | `isMulti` | `bool` | `false` | Enables multi-select mode | | `values` | `List?` | `null` | Selected values in multi-select mode | | `onMultiChange` | `ValueChanged>?` | `null` | Callback for multi-selection changes | | `searchable` | `bool` | `false` | Shows a search input in the menu | | `searchPlaceholder` | `String` | `'Search...'` | Placeholder text in the search input | | `onSearch` | `Future>> Function(String)?` | `null` | Async remote search handler; returns filtered options for the query | | `onCreateOption` | `Future> Function(String)?` | `null` | Tagging handler; called when the user creates a new option from search input | | `createOptionBuilder` | `CreateOptionBuilder?` | `null` | Custom builder for the "create new option" row in the menu | | `onLoadMore` | `Future>> Function()?` | `null` | Pagination handler; called when the user scrolls past the current list | | `hasMore` | `bool` | `false` | When true, indicates more pages are available for `onLoadMore` | | `onOpen` | `VoidCallback?` | `null` | Called when the menu opens, after this widget has reset its own search and visible list. A paginating caller resets its cursor here; see below | | `placeholder` | `String` | `'Select an option'` | Text shown when no value is selected | | `disabled` | `bool` | `false` | Prevents interaction | | `menuWidth` | `double?` | `null` | Fixed dropdown width; defaults to the trigger width | | `maxMenuHeight`| `double` | `300` | Maximum height of the dropdown list | | `states` | `Set?` | `null` | Custom states for dynamic styling | | `triggerBuilder` | `SelectTriggerBuilder?` | `null` | Custom trigger renderer for single-select | | `multiTriggerBuilder` | `MultiSelectTriggerBuilder?` | `null` | Custom trigger renderer for multi-select | | `selectedChipBuilder` | `SelectedChipBuilder?` | `null` | Custom chip renderer for selected items in multi-select | | `itemBuilder` | `SelectItemBuilder?` | `null` | Custom renderer for each option in the menu | | `emptyBuilder` | `EmptyStateBuilder?` | `null` | Custom widget when no options match the search | | `loadingBuilder` | `LoadingBuilder?` | `null` | Custom widget shown while `onSearch` or `onLoadMore` is pending | ## Layout Modes ### Single Select The default mode for selecting a single item. It displays the label of the selected option in the trigger. ```dart WSelect( value: 'apple', options: [ SelectOption(value: 'apple', label: 'Apple'), SelectOption(value: 'banana', label: 'Banana'), ], onChange: (v) => print(v), ) ``` ### Multi Select Enable `isMulti: true` to allow multiple selections. By default, it displays selected items as chips. ```dart WSelect( isMulti: true, values: ['red', 'blue'], options: [ SelectOption(value: 'red', label: 'Red'), SelectOption(value: 'blue', label: 'Blue'), SelectOption(value: 'green', label: 'Green'), ], onMultiChange: (list) => print(list), ) ``` ## Event Handling `WSelect` provides callbacks for selection changes and remote interactions. ```dart WSelect( options: _options, searchable: true, hasMore: true, onChange: (value) { // Handle single selection }, onSearch: (query) async { // Return list of options based on search query return fetchRemoteOptions(query); }, onLoadMore: () async { // Load next page of options return fetchNextPage(); }, onOpen: resetCursorToPageOne, ) ``` ### Pagination and reopening Opening the menu resets this widget's own view of the list: the search is cleared and the visible options go back to `options`. Rows that arrived through `onLoadMore` were appended to the visible list only, so they are gone. A caller holding a page cursor keeps it across that reset unless `onOpen` tells it, and the next scroll to the bottom then asks for the page AFTER the one the reader can see. Measured on a searchable select over a paged endpoint: open, scroll once to pull page two, close, reopen, and page two's rows were unreachable without searching for them. The reset advice assumes `options` goes back to page one along with the visible list, which is the ordinary shape: return rows from `onLoadMore` and leave `options` alone. A caller that instead MIRRORS fetched pages into `options` already has all of them on reopen, so resetting its cursor there would re-fetch page two and append duplicates. Reset the cursor only if `options` resets too. ## Accessibility The closed trigger is one button whose accessible name is its visible text: the selected option's label (labels joined with `, ` in multi-select), and the `placeholder` only while nothing is selected. A test or tapir script that finds a select with a value by its placeholder text must look for the selected label instead. The default placeholder is English and wind has no localisation layer, so pass a translated `placeholder` in a localised app. The closed trigger publishes exactly one semantics node: it carries the button role, the enabled state, the name, the tap action that opens or closes the menu (absent when `disabled`) and the keyboard focus state. The trigger's own content, including multi-select chips and the output of `triggerBuilder` / `multiTriggerBuilder`, is excluded from semantics so it is not announced a second time. The search field of an open menu keeps its own semantics. ## State Variants `WSelect` automatically manages several states that you can style using prefixes in `className`: - `hover:` - When the mouse is over the trigger. - `focus:` - When the dropdown is open, or when the trigger has keyboard focus (Tab). Never while `disabled`; a disabled select is skipped by Tab. - `disabled:` - When the widget is disabled. - `selected:` - When a value is selected. ```dart WSelect( options: _options, className: 'border-gray-300 hover:border-blue-500 focus:ring-2 focus:ring-blue-200', ) ``` ## Styling Examples ### Searchable Select Combine `searchable: true` with custom menu styling for a robust selection experience. ```dart WSelect( searchable: true, searchPlaceholder: 'Search countries...', menuClassName: 'bg-white shadow-xl rounded-xl border border-gray-100', options: countries, onChange: (val) => _country = val, ) ``` ### Tagging (Create Option) Allow users to create new options if a search yields no results. ```dart WSelect( searchable: true, onCreateOption: (query) async { final newOpt = SelectOption(value: query.toLowerCase(), label: query); // Add to your local state/database return newOpt; }, options: existingTags, ) ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | Layout | `flex`, `hidden`, `w-{size}`, `h-{size}` | | Spacing | `p-{n}`, `m-{n}`, `gap-{n}` | | Visuals | `bg-{color}`, `border`, `rounded`, `shadow` | | States | `hover:`, `focus:`, `disabled:`, `dark:` | | Custom | `open:`, `selected:` | ## Customizing Theme The default styling for `WSelect` and its chips can be influenced by the `WindThemeData`. ```dart WindThemeData( colors: { 'primary': Colors.indigo, }, ) ``` ## Related Documentation - [WFormSelect](https://anilcancakir.com/wind_kit/docs/widgets/w-form-select.md) - Form-integrated version with validation - [WPopover](https://anilcancakir.com/wind_kit/docs/widgets/w-popover.md) - The underlying overlay engine - [WInput](https://anilcancakir.com/wind_kit/docs/widgets/w-input.md) - Used for the internal search field --- # WFormSelect A Wind-styled single or multi-select dropdown that integrates seamlessly with Flutter's Form validation. It wraps the core `WSelect` widget with `FormField` logic, providing labels, hint text, and automatic error state handling. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [Accessibility](#accessibility) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WFormSelect( label: 'Country', placeholder: 'Select your country', options: [ SelectOption(value: 'us', label: 'United States'), SelectOption(value: 'uk', label: 'United Kingdom'), ], className: 'w-full border rounded-lg error:border-red-500', validator: (value) => value == null ? 'Please select a country' : null, ) ``` ## Basic Usage The `WFormSelect` (and its counterpart `WFormMultiSelect`) is designed to be used inside a `Form` widget. It automatically manages its own state and displays validation errors using the `error:` utility prefix when validation fails. ```dart Form( key: _formKey, child: Column( children: [ WFormSelect( value: _selectedValue, options: myOptions, onChange: (v) => setState(() => _selectedValue = v), label: 'Select Category', validator: (v) => v == null ? 'Required' : null, ), WButton( onTap: () { if (_formKey.currentState!.validate()) { // Process form } }, child: WText('Submit'), ), ], ), ) ``` ## Constructor ### WFormSelect ```dart WFormSelect({ Key? key, T? value, String? Function(T?)? validator, void Function(T?)? onSaved, AutovalidateMode? autovalidateMode, bool enabled = true, required List> options, ValueChanged? onChange, String placeholder = 'Select an option', String? className, String? menuClassName, bool searchable = false, String? label, String? hint, bool showError = true, // ... other WSelect props }) ``` ### WFormMultiSelect ```dart WFormMultiSelect({ Key? key, List? values, String? Function(List?)? validator, void Function(List?)? onSaved, required List> options, ValueChanged>? onMultiChange, String placeholder = 'Select options', String? label, String? hint, // ... other multi-select props }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `className` | `String?` | `null` | Wind utility classes for the select trigger. | | `options` | `List>` | **Required** | The list of available options to display. | | `value` | `T?` | `null` | The currently selected value (single select). | | `values` | `List?` | `null` | The currently selected values (multi select); null is treated as empty. | | `label` | `String?` | `null` | Optional label text displayed above the select. | | `hint` | `String?` | `null` | Optional hint text displayed below the select. | | `validator` | `String? Function(T?)?` | `null` | Form validation logic. | | `showError` | `bool` | `true` | Whether to display the error message when invalid. | | `searchable` | `bool` | `false` | Enables a search input inside the dropdown. | | `disabled` | `bool` | `false` | Disables user interaction. | | `labelClassName`| `String` | `text-sm font-medium...` | Styling for the label text. | | `errorClassName`| `String` | `text-red-500 dark:text-red-400 text-xs mt-1` | Styling for the error message. | ## Layout Modes `WFormSelect` always uses a vertical `flex-col` layout to stack the label, the select trigger, and the error/hint text. ### Standard Stack By default, the widget arranges elements in a clean vertical flow. ```dart WFormSelect( label: 'Department', hint: 'Choose your primary department', options: departments, className: 'border p-3 rounded-md', ) ``` ## Event Handling `WFormSelect` provides standard Form event handlers alongside the select-specific change callbacks. ```dart WFormSelect( options: options, onChange: (value) { print('Selected: $value'); }, onSaved: (value) { // Called when FormState.save() is invoked _formData.dept = value; }, validator: (value) { if (value == null) return 'Selection required'; return null; }, ) ``` ## Accessibility The underlying `WSelect` trigger is one button named by its visible text: the selected option's label (labels joined with `, ` for `WFormMultiSelect`), and the `placeholder` only while nothing is selected. Pass a translated `placeholder` in a localised app; the default is English. ## State Variants Since `WFormSelect` injects the `error` state into the underlying `WSelect`, you can use the `error:` prefix to change the styling when validation fails. ```dart WFormSelect( className: 'border-gray-300 focus:border-blue-500 error:border-red-500 error:bg-red-50', options: options, validator: (v) => v == null ? 'Error' : null, ) ``` ## Styling Examples ### Multi-Select Form Field The `WFormMultiSelect` variation handles lists of values and provides a different set of styling options for selected chips. ```dart WFormMultiSelect( label: 'Interests', values: _selectedInterests, options: interestOptions, className: 'border-2 rounded-xl p-2', onMultiChange: (vals) => setState(() => _selectedInterests = vals), validator: (vals) => vals!.isEmpty ? 'Select at least one' : null, ) ``` ### Searchable Form Select Perfect for long lists of options where users need to filter quickly. ```dart WFormSelect( label: 'User', searchable: true, searchPlaceholder: 'Search by name...', options: users, className: 'w-full px-4 py-3 bg-white border', ) ``` ## All Supported Classes Since `WFormSelect` wraps `WSelect`, it supports all standard Wind utilities on the `className` and `menuClassName` props. | Category | Classes | |:---------|:--------| | Sizing | `w-full`, `max-w-md`, `h-12` | | Spacing | `p-{n}`, `px-{n}`, `py-{n}`, `m-{n}` | | Borders | `border`, `rounded-{size}`, `ring-{n}` | | Colours | `bg-{color}`, `text-{color}`, `border-{color}` | | States | `hover:`, `focus:`, `disabled:`, `error:` | ## Customizing Theme The default styling of labels, hints and error messages does not come from `WindThemeData`: it is the default value of the `labelClassName`, `hintClassName` and `errorClassName` constructor parameters. Override those per field. ```dart WFormSelect( label: 'Country', options: countries, labelClassName: 'text-sm font-semibold text-gray-900 dark:text-white mb-2', errorClassName: 'text-red-600 dark:text-red-400 text-xs mt-1', ) ``` ## Related Documentation - [WSelect - Core Dropdown Widget](https://anilcancakir.com/wind_kit/docs/widgets/w-select.md) - [WFormInput - Validated Text Input](https://anilcancakir.com/wind_kit/docs/widgets/w-form-input.md) - [WFormCheckbox - Validated Checkbox](https://anilcancakir.com/wind_kit/docs/widgets/w-form-checkbox.md) --- # WCheckbox A utility-first checkbox component that translates Tailwind-style classes into a fully customisable checkbox widget. It sidesteps the limitations of native Flutter checkboxes by using a composable architecture of `WAnchor` and `WDiv`. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [Accessibility](#accessibility) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WCheckbox( value: isChecked, onChanged: (val) => setState(() => isChecked = val), className: 'w-6 h-6 rounded-md border-gray-400 checked:bg-indigo-600', ) ``` ## Basic Usage The `WCheckbox` widget provides a highly flexible alternative to native Flutter checkboxes. It supports all Wind utility classes, allowing for complex styling like custom borders, focus rings, and state-based colours using the `checked:` prefix. ```dart WCheckbox( value: _value, onChanged: (val) => setState(() => _value = val), className: 'w-5 h-5 rounded border-2 border-slate-300 checked:bg-blue-500 checked:border-transparent', ) ``` ## Constructor ```dart const WCheckbox({ Key? key, required bool value, ValueChanged? onChanged, String? className, String? iconClassName, bool disabled = false, IconData? checkIcon, Set? states, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `value` | `bool` | **Required** | Whether the checkbox is currently checked. | | `onChanged` | `ValueChanged?` | `null` | Callback triggered when the checkbox state is toggled. `null` makes the checkbox display-only, exactly like `disabled: true`. See [Accessibility](#accessibility). | | `className` | `String?` | `null` | Wind utility classes for the checkbox container (dimensions, border, background). | | `iconClassName` | `String?` | `null` | Utility classes applied to the check icon (e.g., `'text-white text-xs'`). | | `disabled` | `bool` | `false` | When true, prevents interaction and applies the `disabled:` prefix styles. | | `checkIcon` | `IconData?` | `null` | Custom icon shown when `value` is true. When `null`, the widget renders `Icons.check`. | | `states` | `Set?` | `null` | Custom state identifiers merged with built-in `checked` and `disabled` states. | ## Layout Modes ### Dimensions A checkbox is 20x20 by default, because its built-in classes start with `w-5 h-5`. Your `className` is appended after those defaults, so add width (`w-{n}`) and height (`h-{n}`) classes only to change the clickable area and visual size. ```dart WCheckbox( value: true, className: 'w-4 h-4 rounded-sm', // Small checkbox ) WCheckbox( value: true, className: 'w-8 h-8 rounded-full', // Large circular checkbox ) ``` ## Event Handling The `onChanged` callback is triggered when the user taps the checkbox. It provides the toggled boolean value. ```dart WCheckbox( value: isChecked, onChanged: (bool newValue) { setState(() { isChecked = newValue; }); }, ) ``` Leaving `onChanged` null is how you render a display-only checkbox, such as a read-only summary row or a tile whose own tap drives the toggle. ## Accessibility `WCheckbox` publishes two platform semantics nodes while it is interactive. The outer node carries the `checked` state but no tap action, so `getByRole('checkbox')` resolves and a screen reader announces whether the box is ticked. The inner node is the `WAnchor` button, and it carries the tap action. A display-only checkbox has no gesture, so only the outer node remains. The widget renders its check glyph as the only child, so it has no name of its own, and a `WText` sitting beside it in a row is a visible label rather than a semantic one: the nodes stay separate. Wrap the pair in `MergeSemantics` to publish a single node carrying both the text and the checked state. ```dart // One node, named "Accept terms", reporting checked. MergeSemantics( child: WDiv( className: 'flex flex-row items-center gap-3', children: [ WCheckbox( value: accepted, onChanged: (val) => setState(() => accepted = val), className: 'w-5 h-5 rounded border', ), const WText( 'Accept terms', className: 'text-slate-900 dark:text-white', ), ], ), ) ``` A null `onChanged` is read exactly like `disabled: true`. There is no callback to run, so the checkbox attaches no gesture, publishes no tap action, reports itself as not enabled, and activates the `disabled:` prefix. That last part is visible: a display-only checkbox styled `disabled:opacity-50` renders dimmed. ```dart // Interactive: tappable, announced as enabled. WCheckbox( value: isChecked, onChanged: (val) => setState(() => isChecked = val), className: 'w-5 h-5 rounded border', ) // Display-only: no tap action, announced as unavailable, disabled: styles apply. const WCheckbox( value: true, className: 'w-5 h-5 rounded border disabled:opacity-50', ) ``` [WRadio](https://anilcancakir.com/wind_kit/docs/widgets/w-radio.md) and [WSwitch](https://anilcancakir.com/wind_kit/docs/widgets/w-switch.md) read a null callback the same way, so the three controls behave alike. ## State Variants `WCheckbox` uses the `checked:` prefix to apply styles when the value is true. It also supports standard interaction prefixes like `hover:`, `focus:`, and `disabled:`. ```dart WCheckbox( value: isChecked, className: 'border-gray-300 checked:bg-blue-600 checked:border-transparent hover:border-blue-400 disabled:opacity-50', ) ``` ## Styling Examples ### Custom Icons and Colors You can completely change the look of the checkbox by providing a custom icon and using background utilities. ```dart WCheckbox( value: true, checkIcon: Icons.favorite, className: 'w-6 h-6 bg-pink-100 border-pink-300 checked:bg-pink-500 checked:border-transparent', iconClassName: 'text-white text-xs', ) ``` ### Validation States Using the `states` property allows you to apply conditional styling for errors or success states. ```dart WCheckbox( value: false, states: {'error'}, className: 'border-gray-300 error:border-red-500 error:ring-2 error:ring-red-100', ) ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | Sizing | `w-{n}`, `h-{n}`, `min-w-{n}`, `aspect-square` | | Spacing | `p-{n}`, `m-{n}` | | Appearance | `rounded`, `border`, `bg-{color}`, `shadow` | | States | `checked:`, `hover:`, `focus:`, `disabled:`, `error:` | | Effects | `opacity-{n}`, `ring`, `duration-{n}`, `ease-{n}` | ## Customizing Theme The default checked colour can be controlled via the `primary` colour in your `WindThemeData`. ```dart WindThemeData( colors: { 'primary': Colors.indigo, }, ) ``` ## Related Documentation - [WFormCheckbox](https://anilcancakir.com/wind_kit/docs/widgets/w-form-checkbox.md) - Form-integrated checkbox with label and validation. - [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md) - The base interactive component used by WCheckbox. - [WIcon](https://anilcancakir.com/wind_kit/docs/widgets/w-icon.md) - Documentation for the check icon styling. --- # WFormCheckbox A Wind-styled checkbox that integrates with Flutter's Form validation, providing built-in support for labels, hints, and error states. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WFormCheckbox( value: _agreeTerms, onChanged: (v) => setState(() => _agreeTerms = v), labelText: 'I agree to Terms of Service', className: 'w-5 h-5 rounded border checked:bg-blue-500 error:border-red-500', validator: (value) => value != true ? 'You must agree to terms' : null, ) ``` ## Basic Usage The `WFormCheckbox` widget wraps a `WCheckbox` with a `FormField`. This allows you to use it directly inside a `Form` widget for easy validation and state management. When validation fails, it automatically triggers the `error:` state for its utility classes. ```dart Form( key: _formKey, child: WFormCheckbox( labelText: 'Newsletter', hint: 'We will send you updates once a week.', value: isSubscribed, onChanged: (v) => setState(() => isSubscribed = v), validator: (v) => v == false ? 'Required' : null, ), ) ``` ## Constructor ```dart WFormCheckbox({ Key? key, bool value = false, FormFieldValidator? validator, FormFieldSetter? onSaved, AutovalidateMode? autovalidateMode, String? restorationId, bool enabled = true, ValueChanged? onChanged, String? className, String? iconClassName, bool disabled = false, IconData? checkIcon, Set? states, Widget? label, String? labelText, String labelClassName = 'text-sm text-gray-700 dark:text-gray-300', String? hint, String hintClassName = 'text-gray-500 dark:text-gray-400 text-xs mt-1', bool showError = true, String errorClassName = 'text-red-500 dark:text-red-400 text-xs mt-1', }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `value` | `bool` | `false` | The current value of the checkbox. | | `className` | `String?` | `null` | Wind utility classes for the checkbox container. | | `onChanged` | `ValueChanged?` | `null` | Callback when the value changes. | | `labelText` | `String?` | `null` | Simple text label displayed next to the checkbox. | | `label` | `Widget?` | `null` | Custom label widget (takes precedence over `labelText`). | | `labelClassName` | `String` | `'text-sm text-gray-700 dark:text-gray-300'` | Styling for the text label. | | `hint` | `String?` | `null` | Optional hint text displayed below the checkbox. | | `hintClassName` | `String` | `'text-gray-500 dark:text-gray-400 text-xs mt-1'` | Styling for the hint text. | | `showError` | `bool` | `true` | Whether to display validation error messages. | | `errorClassName` | `String` | `'text-red-500 dark:text-red-400 text-xs mt-1'` | Styling for the error message. | | `iconClassName` | `String?` | `null` | Utility classes for the check icon. | | `checkIcon` | `IconData?` | `null` | Custom icon to use when checked. | | `disabled` | `bool` | `false` | Disables the checkbox and its label interaction. | | `states` | `Set?` | `null` | Custom state identifiers merged with built-in `checked` and `disabled` triggers. | ## Layout Modes ### Label & Hint Layout `WFormCheckbox` automatically handles the layout of the checkbox, label, and hint/error text. It uses a vertical flex container where the checkbox and label are aligned horizontally, and the hint or error is positioned below them. ```dart WFormCheckbox( labelText: 'Receive notifications', hint: 'Enable to get push notifications on your device.', className: 'w-6 h-6', ) ``` ## Event Handling The `onChanged` callback is triggered whenever the user toggles the checkbox or clicks on the associated label. Since it's a `FormField`, it also integrates with standard form lifecycle events like `onSaved` and `validator`. ```dart WFormCheckbox( value: _val, onChanged: (newValue) { print('Checkbox is now: $newValue'); setState(() => _val = newValue); }, onSaved: (v) => _model.termsAccepted = v ?? false, ) ``` ## State Variants `WFormCheckbox` supports all standard Wind state prefixes. Specifically, it activates the `error:` prefix when the form field validation fails. ```dart WFormCheckbox( // Base style is gray, turns blue when checked, red on error className: 'border-gray-300 checked:bg-blue-500 error:border-red-500', validator: (v) => v != true ? 'Required' : null, ) ``` ## Styling Examples ### Custom Colors and Sizes You can easily customise the appearance of the checkbox and its icon using standard Tailwind-like classes. ```dart WFormCheckbox( className: 'w-8 h-8 rounded-full border-2 border-indigo-200 checked:bg-indigo-600 checked:border-transparent', iconClassName: 'text-white text-xl', labelText: 'Large Indigo Checkbox', labelClassName: 'text-lg font-bold text-indigo-900', ) ``` ### Error State Customization You can control how the error message appears when validation fails. ```dart WFormCheckbox( labelText: 'Agree to terms', errorClassName: 'text-orange-600 italic font-medium mt-2', validator: (v) => v != true ? 'This field is mandatory!' : null, ) ``` ## All Supported Classes | Category | Classes | |:---------|:--------| | Layout | `flex`, `flex-col`, `items-start`, `gap-{n}` | | Spacing | `p-{n}`, `m-{n}`, `pt-{n}`, `pl-{n}` | | Sizing | `w-{size}`, `h-{size}`, `aspect-square` | | Typography | `text-{color}`, `text-{size}`, `font-{weight}`, `italic` | | Colours | `bg-{color}`, `border-{color}`, `checked:bg-{color}` | | Borders | `border`, `border-{width}`, `rounded`, `rounded-{size}` | | Form | `error:{utility}`, `disabled:{utility}` | ## Customizing Theme You can define default styles for form components in your `WindThemeData`. ```dart WindThemeData( // While WFormCheckbox doesn't have a specific theme object, // it respects global color and spacing scales. colors: { 'primary': Colors.blue, 'danger': Colors.red, }, baseSpacingUnit: 4.0, ) ``` ## Related Documentation - [WCheckbox](https://anilcancakir.com/wind_kit/docs/widgets/w-checkbox.md) - The base checkbox component. - [WFormInput](https://anilcancakir.com/wind_kit/docs/widgets/w-form-input.md) - Form-integrated text input. - [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md) - The wrapper used for label interactivity. - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md) - The container used for layout. --- # WRadio Controlled radio button. Composes `WAnchor` (interaction state) + `WDiv` (outer ring shell) + `WDiv` (inner filled indicator). Drives the `selected:` state prefix when `value == groupValue`, mirroring how `WCheckbox` drives `checked:`. Mutually-exclusive group behaviour is the caller's responsibility: pass the same `groupValue` to every `WRadio` in a group and update it through `onChanged`. - [Basic Usage](#basic-usage) - [Props](#props) - [Constructor](#constructor) - [Styling Examples](#styling-examples) - [Related Documentation](#related-documentation) ## Basic Usage ```dart WRadio( value: 'light', groupValue: _theme, onChanged: (val) => setState(() => _theme = val), className: ''' w-5 h-5 rounded-full border border-gray-300 dark:border-gray-600 flex items-center justify-center selected:border-blue-500 dark:selected:border-blue-400 hover:border-blue-400 dark:hover:border-blue-300 ''', indicatorClassName: 'w-2.5 h-2.5 rounded-full bg-blue-500 dark:bg-blue-400', ) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `value` | `T` | **Required** | The value this radio represents. | | `groupValue` | `T?` | **Required** | The currently selected value for the group. When `value == groupValue`, `selected:` state activates. | | `onChanged` | `ValueChanged?` | `null` | Called with `value` when this radio is tapped and not already selected. Omit or pass `null` for a non-interactive radio. | | `className` | `String?` | `null` | Utility classes for the outer ring shell. Defaults to `'w-5 h-5 rounded-full border border-gray-300 flex items-center justify-center selected:border-primary-500'` when `null`. | | `indicatorClassName` | `String?` | `null` | Utility classes for the inner filled dot (visible only when selected). Defaults to `'w-2.5 h-2.5 rounded-full bg-primary selected:opacity-100'` when `null`. | | `disabled` | `bool` | `false` | Blocks tap and activates the `disabled:` prefix. | | `states` | `Set?` | `null` | Extra custom states merged with built-in `selected` and `disabled`. | | `semanticLabel` | `String?` | `null` | Accessible label, forwarded to the inner `WAnchor`. It names the inner button node, not the outer node that carries the checked state. Set it on icon-only radios, which have no text to derive a label from. | ## Constructor ```dart const WRadio({ Key? key, required T value, required T? groupValue, ValueChanged? onChanged, String? className, String? indicatorClassName, bool disabled = false, Set? states, String? semanticLabel, }) ``` ## Styling Examples ### Vertical Radio Group Each radio in the group shares the same `groupValue` and `onChanged`. The `selected:` prefix styles the active choice: ```dart WRadio( value: 'pro', groupValue: _plan, onChanged: (val) => setState(() => _plan = val), className: ''' w-5 h-5 rounded-full border-2 border-gray-300 dark:border-gray-600 flex items-center justify-center selected:border-indigo-500 dark:selected:border-indigo-400 hover:border-indigo-400 dark:hover:border-indigo-300 disabled:opacity-50 ''', indicatorClassName: ''' w-2.5 h-2.5 rounded-full bg-indigo-500 dark:bg-indigo-400 ''', ) ``` ### Horizontal Row Arrange radios in a `flex-row` container for inline selection: ```dart WDiv( className: 'flex flex-row gap-6', children: [ for (final option in ['light', 'dark', 'system']) WDiv( className: 'flex flex-row items-center gap-2', children: [ WRadio( value: option, groupValue: _theme, onChanged: (val) => setState(() => _theme = val), className: 'w-5 h-5 rounded-full border border-gray-300 dark:border-gray-600 flex items-center justify-center selected:border-blue-500 dark:selected:border-blue-400', indicatorClassName: 'w-2.5 h-2.5 rounded-full bg-blue-500 dark:bg-blue-400', ), WText(option, className: 'text-sm text-gray-700 dark:text-gray-300'), ], ), ], ) ``` ### Default Fallback Styling When `className` or `indicatorClassName` is omitted, `WRadio` falls back to its built-in defaults, which route through the theme `primary` token (blue by default; override `primary` in `WindThemeData` to rebrand). Override the classNames only when the design calls for a different tone: ```dart // Uses built-in primary defaults (blue unless the theme's primary is overridden) WRadio( value: 'option', groupValue: _value, onChanged: (val) => setState(() => _value = val), ) ``` ## Related Documentation - [WCheckbox](https://anilcancakir.com/wind_kit/docs/widgets/w-checkbox.md): boolean input that mirrors the state-prefix pattern. - [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md): provides hover and focus state propagation used by `WRadio` internally. - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md): renders the ring shell and indicator surfaces. --- # WSwitch Controlled toggle switch. Composes `WAnchor` + `WDiv` (track) + `WDiv` (thumb). Pushes the `checked:` state prefix into the className context when `value` is `true`, so callers can drive all visual changes: track colour, thumb position, opacity: through className tokens alone. - [Basic Usage](#basic-usage) - [Props](#props) - [Constructor](#constructor) - [Styling Examples](#styling-examples) - [Related Documentation](#related-documentation) ## Basic Usage ```dart WSwitch( value: isOn, onChanged: (val) => setState(() => isOn = val), className: ''' w-11 h-6 rounded-full px-0.5 border-2 flex items-center justify-start checked:justify-end bg-gray-200 checked:bg-blue-600 border-gray-300 checked:border-blue-600 dark:bg-gray-700 dark:checked:bg-blue-500 dark:border-gray-600 dark:checked:border-blue-500 ''', thumbClassName: 'w-4 h-4 rounded-full bg-white dark:bg-gray-100 shadow', ) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `value` | `bool` | **Required** | Whether the switch is on. Drives the `checked:` state prefix. | | `onChanged` | `ValueChanged?` | `null` | Called with the new value when the switch is tapped. Omit or pass `null` to make the switch non-interactive. | | `className` | `String?` | `null` | Utility classes for the track (the outer pill). Supports `checked:`, `disabled:`, `hover:`, and `focus:` prefixes. | | `thumbClassName` | `String?` | `null` | Utility classes for the thumb (the circular indicator), shape and colour only. The thumb is a flex child of the track; position it with `justify-start` / `checked:justify-end` on the track `className`, not with `translate-x-*` (Wind has no transform parser, so translate is a no-op). | | `disabled` | `bool` | `false` | Blocks tap and activates the `disabled:` prefix. | | `states` | `Set?` | `null` | Extra custom states merged with built-in `checked` and `disabled`. | | `semanticLabel` | `String?` | `null` | Accessible label, forwarded to the inner `WAnchor`. It names the inner button node, not the outer node that carries the toggled state. A `WText` beside the switch is a visible label only, not a semantic one, so either set this or wrap the pair in `MergeSemantics` to publish one node carrying the text and the toggled state, as [WCheckbox](https://anilcancakir.com/wind_kit/docs/widgets/w-checkbox.md#accessibility) shows. | ## Constructor ```dart const WSwitch({ Key? key, required bool value, ValueChanged? onChanged, String? className, String? thumbClassName, bool disabled = false, Set? states, String? semanticLabel, }) ``` ## Styling Examples ### Track Color with checked: State The `checked:` prefix activates when `value` is `true`. Toggle track background, border, and ring in one className: ```dart WSwitch( value: isOn, onChanged: (val) => setState(() => isOn = val), className: ''' w-11 h-6 rounded-full px-0.5 border-2 flex items-center justify-start checked:justify-end bg-gray-200 dark:bg-gray-700 border-gray-300 dark:border-gray-600 checked:bg-teal-500 dark:checked:bg-teal-400 checked:border-teal-500 dark:checked:border-teal-400 focus:ring-2 focus:ring-teal-500 dark:focus:ring-teal-400 focus:ring-offset-2 ''', thumbClassName: 'w-4 h-4 rounded-full bg-white dark:bg-gray-100 shadow', ) ``` ### Thumb Position The thumb is a flex child of the track, so the track owns its position. Put `flex items-center` on the track to lay the thumb out, then slide it with `justify-start` (left, off) to `checked:justify-end` (right, on). A little horizontal padding (`px-0.5`) keeps the thumb off the track edge: ```dart // Track w-11 (44px), thumb w-5 (20px): flex justify-end seats it on the right className: 'w-11 h-6 rounded-full px-0.5 flex items-center justify-start checked:justify-end ...', thumbClassName: 'w-5 h-5 rounded-full bg-white dark:bg-gray-100 shadow', ``` Do NOT use `translate-x-*`: Wind has no transform parser, so a translate-based thumb never moves. ### Disabled State ```dart WSwitch( value: true, onChanged: null, disabled: true, className: ''' w-11 h-6 rounded-full px-0.5 border-2 flex items-center justify-start checked:justify-end checked:bg-teal-400 checked:border-teal-400 dark:checked:bg-teal-600 dark:checked:border-teal-600 disabled:opacity-50 ''', thumbClassName: 'w-4 h-4 rounded-full bg-white dark:bg-gray-100', ) ``` ### Custom State via states: ```dart WSwitch( value: isOn, onChanged: (_) {}, states: {'error'}, className: 'w-11 h-6 rounded-full px-0.5 flex items-center justify-start checked:justify-end border-2 border-gray-300 error:border-red-500 error:ring-2 error:ring-red-100', thumbClassName: 'w-4 h-4 rounded-full bg-white dark:bg-gray-100', ) ``` ## Related Documentation - [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md): provides hover, focus, and gesture state propagation for the switch. - [WCheckbox](https://anilcancakir.com/wind_kit/docs/widgets/w-checkbox.md): boolean input that mirrors the `checked:` state pattern. - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md): renders the track and thumb surfaces. --- # WDatePicker A utility-first date picker component built on [WPopover](https://anilcancakir.com/wind_kit/docs/widgets/w-popover.md) with support for single date selection, date range selection, date-with-time selection, min/max constraints, and custom display formatting. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Types](#types) - [Date Range Selection](#date-range-selection) - [Date + Time Selection](#date--time-selection) - [Min/Max Constraints](#minmax-constraints) - [Custom Display Format](#custom-display-format) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [Calendar Internals](#calendar-internals) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WDatePicker( value: _selectedDate, onChanged: (date) => setState(() => _selectedDate = date), className: 'w-full p-3 border border-gray-300 rounded-lg', placeholder: 'Select a date', ) ``` ## Basic Usage `WDatePicker` renders a trigger container that opens a popover-based calendar on click. It handles month navigation, today highlighting, and date constraints out of the box. ```dart DateTime? _selectedDate; WDatePicker( value: _selectedDate, onChanged: (date) { setState(() => _selectedDate = date); }, className: 'bg-white border rounded-md px-4 py-2 hover:border-blue-500', ) ``` When no `className` is provided, the trigger uses this default styling: ```dart // Default trigger className 'bg-white border border-gray-300 rounded-lg p-3 dark:bg-gray-800 dark:border-gray-600' ``` The calendar popover opens below the trigger and auto-flips if there isn't enough space. In single mode, the popover closes automatically after a date is selected. ## Constructor ```dart const WDatePicker({ Key? key, WDatePickerMode mode = WDatePickerMode.single, DateTime? value, DateRange? range, ValueChanged? onChanged, ValueChanged? onRangeChanged, DateTime? minDate, DateTime? maxDate, String? className, String placeholder = 'Select date', bool disabled = false, Set states = const {}, DateDisplayFormat? displayFormat, int minuteStep = 5, String timeLabel = 'Time', String doneLabel = 'Done', }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `mode` | `WDatePickerMode` | `single` | Selection mode: `single`, `range` or `dateTime` | | `value` | `DateTime?` | `null` | Currently selected instant (`single` and `dateTime` modes; carries the time of day in `dateTime`) | | `range` | `DateRange?` | `null` | Currently selected range (range mode) | | `onChanged` | `ValueChanged?` | `null` | Callback fired on selection (`single` and `dateTime` modes; in `dateTime` it fires on every day tap, and on every time step only while `value` is non-null (with no date picked a step moves the pending time and emits nothing); the confirm control only closes the popover) | | `onRangeChanged` | `ValueChanged?` | `null` | Callback fired on range selection (range mode) | | `minDate` | `DateTime?` | `null` | Earliest selectable date; day cells compare at day granularity, the composed instant is additionally clamped in `dateTime` mode | | `maxDate` | `DateTime?` | `null` | Latest selectable date; a bare-day bound is that day at `00:00`, so `dateTime` mode needs an explicit end-of-day to admit the whole last day | | `className` | `String?` | `null` | Wind utility classes for the trigger container | | `placeholder` | `String` | `'Select date'` | Text shown when no value is selected | | `disabled` | `bool` | `false` | Prevents interaction, shows forbidden cursor | | `states` | `Set` | `const {}` | Custom states for dynamic styling | | `displayFormat` | `DateDisplayFormat?` | `null` | Custom function to format dates for display | | `minuteStep` | `int` | `5` | Minutes each step control moves (`dateTime` mode only) | | `timeLabel` | `String` | `'Time'` | Label above the time row (`dateTime` mode only) | | `doneLabel` | `String` | `'Done'` | Confirm control text that closes the popover (`dateTime` mode only) | ## Types ### WDatePickerMode Determines whether the picker selects a single date, a date range, or a single date carrying a time of day. ```dart enum WDatePickerMode { single, // Pick a single date (time struck to midnight) range, // Pick a start and end date dateTime, // Pick a single date AND a time of day } ``` `single` and `range` normalise every value to midnight. `dateTime` is the only mode that preserves an hour and a minute, so it is the one to use when the value is an instant rather than a calendar day. It adds a stepped time row below the calendar and keeps the popover open until the confirm control is pressed. ### DateRange Represents a date range with a required start and optional end date. ```dart class DateRange { final DateTime start; final DateTime? end; bool get isComplete => end != null; DateRange copyWith({DateTime? start, DateTime? end}); } ``` ### DateDisplayFormat A typedef for custom date formatting: ```dart typedef DateDisplayFormat = String Function(DateTime date); ``` When no `displayFormat` is provided, dates display as `"Jan 15, 2025"` format. ## Date Range Selection Setting `mode: WDatePickerMode.range` enables two-click range selection with hover preview. ```dart DateRange? _dateRange; WDatePicker( mode: WDatePickerMode.range, range: _dateRange, onRangeChanged: (range) => setState(() => _dateRange = range), placeholder: 'Check-in / Check-out', className: 'w-64 border p-3 rounded-lg', ) ``` How range selection works: 1. **First click**: Sets the range start. The `onRangeChanged` callback fires with a `DateRange` where `end` is `null`. 2. **Hover**: As the user moves the mouse, dates between start and the hovered date are highlighted with a blue tint. 3. **Second click**: Completes the range. If the second date is before the first, they're automatically swapped. The popover closes. The trigger display text updates throughout: `"Jan 15, 2025 - ..."` while in progress, then `"Jan 15, 2025 - Jan 20, 2025"` when complete. ## Date + Time Selection Setting `mode: WDatePickerMode.dateTime` keeps the time of day. The tapped day is composed with a stepped time row and emitted as a single local `DateTime`, where `single` and `range` strike everything to midnight. ```dart DateTime? _startsAt; WDatePicker( mode: WDatePickerMode.dateTime, value: _startsAt, onChanged: (value) => setState(() => _startsAt = value), minuteStep: 15, placeholder: 'Schedule the deploy', className: 'w-full p-3 border rounded-lg', ) ``` How the mode differs from `single`: 1. **The popover stays open on a day tap.** The time row below the calendar is the second half of the selection, so the confirm control (`doneLabel`) is what closes it. 2. **`onChanged` fires on every change**, both the day tap and each time step, always with the full composed instant. A time step only emits once a day has been picked: before that it moves the pending time and nothing is emitted. Closing the popover commits nothing new. 3. **The mode is controlled.** Feed each emitted value back through `value` or the calendar highlight, the time row, and the bounds will drift apart from what the user sees. 4. **The time row is Wind markup**, two 24-hour spinners plus the confirm control built from `WDiv` / `WText` / `WIcon`. It is not a Material `showTimePicker` dialog, so it themes with the rest of the picker. The spinners never wrap: stepping up from 23:00 would move the instant a full day backwards while the calendar kept showing the same day, so an edge step renders disabled instead. `minuteStep` sets how far one press of the minute spinner moves (default `5`). > [!NOTE] > The emitted `DateTime` is local and carries no offset. Dart's `DateTime` cannot hold an arbitrary one ([dart-lang/sdk#54993](https://github.com/dart-lang/sdk/issues/54993)), so a value crossing a network boundary is the caller's `toUtc()` to make. ## Min/Max Constraints Use `minDate` and `maxDate` to restrict which dates are selectable. Dates outside the range appear dimmed and don't respond to clicks. ```dart WDatePicker( value: _selectedDate, onChanged: (date) => setState(() => _selectedDate = date), minDate: DateTime.now(), maxDate: DateTime.now().add(const Duration(days: 90)), className: 'p-3 border rounded-lg', placeholder: 'Next 90 days only', ) ``` > [!NOTE] > In `single` and `range` mode, constraints are compared at day-level granularity and time components are stripped before comparison. In `dateTime` mode a day cell stays selectable whenever ANY instant in it is legal, and the composed instant is then pulled back inside the window, so the emitted value is always the same legal instant the trigger displays. ### Bounds in `dateTime` mode Because the bound is compared as a full instant, a `maxDate` written as a bare day is that day at midnight, and the last day then admits only `00:00`: every time you pick on it collapses back to midnight and its step controls render disabled. Spell out the end of the day when you mean the whole day. ```dart WDatePicker( mode: WDatePickerMode.dateTime, value: _startsAt, onChanged: (value) => setState(() => _startsAt = value), minDate: DateTime.now(), // Not DateTime(2026, 8, 31), which caps the last day at 00:00. maxDate: DateTime(2026, 8, 31, 23, 59), className: 'p-3 border rounded-lg', ) ``` ## Custom Display Format Override the default `"Jan 15, 2025"` format with a custom function: ```dart WDatePicker( value: _selectedDate, onChanged: (date) => setState(() => _selectedDate = date), displayFormat: (date) => '${date.day}/${date.month}/${date.year}', className: 'p-3 border rounded-lg', ) ``` In range mode, the format function applies to both start and end dates independently. ## Event Handling ### Single Mode `onChanged` fires with a normalised `DateTime` (midnight, no time component) when the user selects a date: ```dart WDatePicker( value: _date, onChanged: (date) { setState(() => _date = date); _loadSchedule(date); }, ) ``` ### Range Mode `onRangeChanged` fires twice during a range selection: once on the first click (start only) and once on the second click (complete range): ```dart WDatePicker( mode: WDatePickerMode.range, range: _range, onRangeChanged: (range) { setState(() => _range = range); if (range.isComplete) { _calculateDuration(range.start, range.end!); } }, ) ``` ### DateTime Mode `onChanged` reuses the single-mode callback but fires on every change, the day tap and each time step alike, always carrying the full composed instant: ```dart WDatePicker( mode: WDatePickerMode.dateTime, value: _startsAt, onChanged: (value) { // Fires again on every hour / minute step, not only on the day tap. setState(() => _startsAt = value); }, ) ``` Treat it as a live value rather than a commit signal. The confirm control only closes the popover; if you need a "the user finished" moment, act on the value you already hold once the popover closes. ## State Variants `WDatePicker` automatically manages several interactive states. Use state prefixes in `className` to apply conditional styles. | State | Activated When | |:------|:---------------| | `hover:` | Mouse hovers over the trigger | | `focus:` | Calendar popover is open | | `open:` | Calendar popover is open (alias for `focus:`) | | `disabled:` | `disabled` prop is `true` | | `selected:` | A date or range has been selected | ```dart WDatePicker( value: _date, onChanged: (date) => setState(() => _date = date), className: 'p-3 border border-gray-300 rounded-lg ' 'hover:border-blue-400 ' 'focus:border-blue-500 focus:ring-2 focus:ring-blue-200 ' 'selected:bg-blue-50 ' 'disabled:opacity-50 disabled:bg-gray-100', ) ``` ### Disabled State When `disabled: true`, the trigger shows a forbidden cursor and the popover won't open: ```dart WDatePicker( disabled: true, value: DateTime(2025, 6, 15), className: 'p-3 border rounded-lg disabled:opacity-50 disabled:bg-gray-100', ) ``` ## Styling Examples ### Default Styling Without a `className`, the trigger uses built-in defaults with dark mode support: ```dart WDatePicker( value: _date, onChanged: (date) => setState(() => _date = date), // Uses: 'bg-white border border-gray-300 rounded-lg p-3 // dark:bg-gray-800 dark:border-gray-600' ) ``` ### Interactive with Ring Focus ```dart WDatePicker( value: _date, onChanged: (date) => setState(() => _date = date), className: 'w-full p-3 bg-white dark:bg-gray-800 ' 'border border-gray-300 dark:border-gray-600 rounded-lg ' 'hover:border-blue-400 dark:hover:border-blue-500 ' 'focus:border-blue-500 focus:ring-2 focus:ring-blue-200 ' 'dark:focus:ring-blue-800 ' 'selected:border-blue-500', ) ``` ### Compact Inline ```dart WDatePicker( value: _date, onChanged: (date) => setState(() => _date = date), className: 'px-2 py-1 text-sm border rounded bg-gray-50 hover:bg-white', placeholder: 'Date', ) ``` ### Borderless with Shadow ```dart WDatePicker( value: _date, onChanged: (date) => setState(() => _date = date), className: 'p-3 bg-white rounded-xl shadow-md hover:shadow-lg', ) ``` ## Calendar Internals The calendar popover is styled with a fixed-width container: ``` 'w-[320px] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-xl p-4 overflow-y-auto' ``` The calendar grid consists of: | Component | Details | |:----------|:--------| | **Header** | Month/year label with left/right navigation arrows | | **Weekday row** | `Mo Tu We Th Fr Sa Su` (Monday start) | | **Date grid** | 6 rows × 7 columns = 42 cells | | **Today** | Highlighted with `bg-gray-100 dark:bg-gray-700 rounded-full` | | **Selected** | `bg-primary text-white rounded-full` | | **In range** | `bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300` | | **Out of month** | `text-gray-300 dark:text-gray-600` | | **Disabled** | `text-gray-300 dark:text-gray-600`, no click | > [!NOTE] > The calendar chrome (header, grid, day cells) uses hardcoded Wind classes and is not configurable via `className`. The `className` prop only controls the trigger element. ## All Supported Classes ### Trigger (className) The `className` prop styles the trigger container. All Wind utility classes are supported: | Category | Examples | |:---------|:---------| | Background | `bg-white`, `bg-gray-50`, `dark:bg-gray-800` | | Border | `border`, `border-2`, `border-gray-300`, `rounded-lg`, `rounded-xl` | | Padding | `p-3`, `px-4`, `py-2` | | Sizing | `w-full`, `w-64`, `w-[300px]` | | Ring | `ring-2`, `ring-blue-200`, `ring-offset-2` | | Shadow | `shadow-sm`, `shadow-md`, `shadow-lg` | | Typography | `text-sm` (sizes the placeholder and display text; a `text-{color}` class tints the calendar icon) | | Opacity | `opacity-50`, `opacity-75` | | State prefixes | `hover:`, `focus:`, `open:`, `disabled:`, `selected:`, `dark:` | | Responsive | `sm:`, `md:`, `lg:`, `xl:`, `2xl:` | ### What className Does NOT Control The calendar popover, header, weekday labels, and day cells use internal Wind classes that are not configurable via props. ## Customizing Theme The calendar's selection colours route through the theme `primary` token (`bg-primary` for the selected day, `bg-primary-100` / `text-primary-700` for the in-range fill). Override `primary` to recolour the calendar to your brand; the neutral grays remain configurable via their own palette keys. The default `primary` is aliased to the Tailwind blue swatch, so leaving it unchanged keeps the original blue look. ```dart WindTheme( data: WindThemeData( colors: { 'primary': MaterialColor(0xFF16A34A, { 100: Color(0xFFDCFCE7), // Range fill 300: Color(0xFF86EFAC), // Dark mode range text 500: Color(0xFF16A34A), // Selected day (shade 500 == bg-primary) 700: Color(0xFF15803D), // Range text 900: Color(0xFF14532D), // Dark mode range }), }, ), child: MyApp(), ) ``` The calendar only reads shades `100` / `300` / `500` / `700` / `900`, so the partial swatch above is enough for it. Other `primary`-driven widgets read further shades (`WSelect` uses `400` / `600` / `700` for its raw icon colours and class tokens such as `text-primary-800` for its selected chip text). A missing shade falls back to the swatch's base colour only for those raw icons, so nothing throws there; a class token such as `text-primary-800` resolves to nothing when its shade is absent, and the requested colour is not applied. If you share one `primary` across widgets, use a complete `50`-`950` swatch (e.g. a generated `MaterialColor`) so every shade renders as intended. ## Related Documentation - [WFormDatePicker](https://anilcancakir.com/wind_kit/docs/widgets/w-form-date-picker.md) - Form-integrated date picker with validation - [WSelect](https://anilcancakir.com/wind_kit/docs/widgets/w-select.md) - Dropdown selection component - [WPopover](https://anilcancakir.com/wind_kit/docs/widgets/w-popover.md) - The underlying overlay engine - [WInput](https://anilcancakir.com/wind_kit/docs/widgets/w-input.md) - Standard text input widget --- # WFormDatePicker A form-integrated date picker that wraps [WDatePicker](https://anilcancakir.com/wind_kit/docs/widgets/w-date-picker.md) with Flutter's `FormField`. Provides native form validation, automatic `error:` state styling, and optional label, hint, and error message display. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Form Validation](#form-validation) - [Range Mode in Forms](#range-mode-in-forms) - [Date + Time in Forms](#date--time-in-forms) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WFormDatePicker( label: 'Birth Date', hint: 'We need your date of birth for verification', className: 'p-3 border border-gray-300 rounded-lg error:border-red-500', validator: (value) => value == null ? 'Please select a date' : null, onChanged: (date) => print('Selected: $date'), ) ``` ## Basic Usage `WFormDatePicker` works inside a Flutter `Form` widget. It renders a vertical layout with an optional label above the date picker and error/hint text below. ```dart final _formKey = GlobalKey(); Form( key: _formKey, child: Column( children: [ WFormDatePicker( label: 'Event Date', className: 'p-3 border rounded-lg error:border-red-500', validator: (date) => date == null ? 'Date is required' : null, onChanged: (date) => _eventDate = date, ), WButton( className: 'mt-4 bg-blue-500 text-white px-4 py-2 rounded-lg', onTap: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); } }, child: WText('Submit'), ), ], ), ) ``` When validation fails, the widget automatically adds the `error` state to the inner `WDatePicker`, activating any `error:` prefixed classes. ## Constructor ```dart WFormDatePicker({ Key? key, // FormField params FormFieldValidator? validator, FormFieldSetter? onSaved, AutovalidateMode? autovalidateMode, bool enabled = true, // WDatePicker params DateTime? initialValue, DateRange? initialRange, WDatePickerMode mode = WDatePickerMode.single, ValueChanged? onChanged, ValueChanged? onRangeChanged, DateTime? minDate, DateTime? maxDate, String? className, String? placeholder, Set? states, DateDisplayFormat? displayFormat, // dateTime mode params, forwarded to WDatePicker int minuteStep = 5, String timeLabel = 'Time', String doneLabel = 'Done', // Form wrapper params String? label, String labelClassName = 'text-sm font-medium text-gray-700 dark:text-gray-300 mb-1', bool showError = true, String errorClassName = 'text-red-500 dark:text-red-400 text-xs mt-1', String? hint, String hintClassName = 'text-gray-500 dark:text-gray-400 text-xs mt-1', }) ``` ## Props ### FormField Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `validator` | `FormFieldValidator?` | `null` | Validation function, receives `DateTime?` | | `onSaved` | `FormFieldSetter?` | `null` | Called when form is saved | | `autovalidateMode` | `AutovalidateMode?` | `null` | When to auto-validate | | `enabled` | `bool` | `true` | Whether the picker is interactive | | `initialValue` | `DateTime?` | `null` | Initial selected instant (`single` and `dateTime` modes; carries the time of day in `dateTime`). Seeds the field's state on first build only, as `FormField` does: changing it later does not move an already-picked value | ### Date Picker Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `mode` | `WDatePickerMode` | `single` | Selection mode: `single`, `range` or `dateTime` | | `initialRange` | `DateRange?` | `null` | Initial date range (range mode) | | `onChanged` | `ValueChanged?` | `null` | Called on selection (`single` and `dateTime` modes) | | `onRangeChanged` | `ValueChanged?` | `null` | Called on range selection (range mode) | | `minDate` | `DateTime?` | `null` | Earliest selectable date | | `maxDate` | `DateTime?` | `null` | Latest selectable date | | `className` | `String?` | `null` | Wind utility classes for the trigger | | `placeholder` | `String?` | `null` | Placeholder text (defaults to `'Select date'`) | | `states` | `Set?` | `null` | Custom states for dynamic styling | | `displayFormat` | `DateDisplayFormat?` | `null` | Custom date display format function | | `minuteStep` | `int` | `5` | Minutes each step control moves, forwarded to the picker (`dateTime` mode only) | | `timeLabel` | `String` | `'Time'` | Label above the time row, forwarded to the picker (`dateTime` mode only) | | `doneLabel` | `String` | `'Done'` | Confirm control text that closes the popover, forwarded to the picker (`dateTime` mode only) | ### Layout Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `label` | `String?` | `null` | Label text displayed above the picker | | `labelClassName` | `String` | `'text-sm font-medium text-gray-700 dark:text-gray-300 mb-1'` | Wind classes for the label | | `showError` | `bool` | `true` | Whether to display validation error text | | `errorClassName` | `String` | `'text-red-500 dark:text-red-400 text-xs mt-1'` | Wind classes for error message | | `hint` | `String?` | `null` | Hint text displayed below the picker | | `hintClassName` | `String` | `'text-gray-500 dark:text-gray-400 text-xs mt-1'` | Wind classes for hint text | > [!NOTE] > When both an error and hint are present, the error message takes priority and the hint is hidden. ## Form Validation The `validator` function receives the currently selected `DateTime?` value. Return a `String` to show an error, or `null` if valid. ```dart WFormDatePicker( label: 'Departure Date', className: 'p-3 border rounded-lg error:border-red-500 error:bg-red-50', validator: (date) { if (date == null) return 'Please select a departure date'; if (date.isBefore(DateTime.now())) return 'Date must be in the future'; return null; }, ) ``` ### Auto-Validation Use `autovalidateMode` to validate as the user interacts: ```dart WFormDatePicker( label: 'Start Date', autovalidateMode: AutovalidateMode.onUserInteraction, className: 'p-3 border rounded-lg error:border-red-500', validator: (date) => date == null ? 'Required' : null, ) ``` ### Using onSaved ```dart DateTime? _savedDate; WFormDatePicker( label: 'Meeting Date', className: 'p-3 border rounded-lg', onSaved: (date) => _savedDate = date, validator: (date) => date == null ? 'Required' : null, ) ``` ## Range Mode in Forms In range mode, the `FormField` internally tracks the range's **start date** for validation purposes. This means a simple null check still works as a "required" validator. ```dart WFormDatePicker( label: 'Trip Dates', mode: WDatePickerMode.range, initialRange: null, className: 'p-3 border rounded-lg error:border-red-500', placeholder: 'Select check-in / check-out', validator: (date) => date == null ? 'Please select your trip dates' : null, onRangeChanged: (range) { if (range.isComplete) { _calculateTripDuration(range); } }, ) ``` > [!NOTE] > The `validator` receives the range's start `DateTime`, not a `DateRange` object. For more complex range validation (e.g., minimum stay duration), use the `onRangeChanged` callback to manage validation externally. ## Date + Time in Forms With `mode: WDatePickerMode.dateTime` the field holds a full `DateTime` carrying the picked hour and minute, so a validator can compare times of day rather than just checking for null. `minuteStep`, `timeLabel`, and `doneLabel` are forwarded to [WDatePicker](https://anilcancakir.com/wind_kit/docs/widgets/w-date-picker.md#date--time-selection) unchanged. ```dart WFormDatePicker( mode: WDatePickerMode.dateTime, label: 'Call with the customer', hint: 'Office hours run 09:00 to 18:00', minuteStep: 15, doneLabel: 'Confirm', className: 'p-3 border rounded-lg error:border-red-500', autovalidateMode: AutovalidateMode.onUserInteraction, validator: (value) { if (value == null) return 'Pick a date and a time'; if (value.hour < 9 || value.hour >= 18) { return 'Office hours run 09:00 to 18:00'; } return null; }, ) ``` Two behaviours carry over from the raw widget. Once a day is picked, every time step updates the field value, not only the day tap, because `onChanged` fires on each one; the validator runs on those changes when `autovalidateMode` is `always` or `onUserInteraction` (as above), when an enclosing `Form` sets its own mode, and on `validate()`; `AutovalidateMode.onUnfocus` validates on focus loss instead, not on each change; and `initialValue` seeds the field on first build only, the way `FormField` does, so changing it later does not move an already-picked value. ## Event Handling `onChanged` and `onRangeChanged` fire alongside the form state updates. The `FormFieldState` is updated automatically; you don't need to call `didChange` yourself. ```dart WFormDatePicker( label: 'Appointment', onChanged: (date) { // FormField state is already updated at this point _loadAvailableSlots(date); }, ) ``` ## State Variants `WFormDatePicker` inherits all state variants from `WDatePicker` and adds the `error` state automatically when validation fails. | State | Activated When | |:------|:---------------| | `hover:` | Mouse hovers over the trigger | | `focus:` | Calendar popover is open | | `open:` | Calendar popover is open (alias) | | `disabled:` | `enabled` is `false` | | `selected:` | A date or range has been selected | | `error:` | Form validation has failed | ```dart WFormDatePicker( label: 'Date', className: 'p-3 border border-gray-300 rounded-lg ' 'hover:border-blue-400 ' 'focus:border-blue-500 focus:ring-2 focus:ring-blue-200 ' 'error:border-red-500 error:ring-2 error:ring-red-200 ' 'disabled:opacity-50 disabled:bg-gray-100', validator: (date) => date == null ? 'Required' : null, ) ``` ## Styling Examples ### Standard Form Field ```dart WFormDatePicker( label: 'Date of Birth', hint: 'You must be at least 18 years old', className: 'w-full p-3 border border-gray-300 rounded-lg ' 'focus:border-blue-500 focus:ring-2 focus:ring-blue-200 ' 'error:border-red-500', maxDate: DateTime.now().subtract(const Duration(days: 365 * 18)), validator: (date) => date == null ? 'Date of birth is required' : null, ) ``` ### Dark Mode Ready ```dart WFormDatePicker( label: 'Schedule', labelClassName: 'text-sm font-medium text-gray-700 dark:text-gray-300', className: 'p-3 bg-white dark:bg-gray-800 ' 'border border-gray-300 dark:border-gray-600 rounded-lg ' 'hover:border-blue-400 dark:hover:border-blue-500 ' 'error:border-red-500 dark:error:border-red-400', errorClassName: 'text-red-500 dark:text-red-400 text-xs mt-1', validator: (date) => date == null ? 'Required' : null, ) ``` ### Minimalist ```dart WFormDatePicker( label: 'When', labelClassName: 'text-xs text-gray-500 uppercase tracking-wider', className: 'border-b border-gray-200 py-2 rounded-none ' 'focus:border-blue-500 error:border-red-500', hintClassName: 'text-gray-400 text-xs mt-0.5', hint: 'Optional', ) ``` ## All Supported Classes ### Trigger (className) All Wind utility classes work on the trigger container. The most commonly used: | Category | Examples | |:---------|:---------| | Background | `bg-white`, `bg-gray-50`, `dark:bg-gray-800` | | Border | `border`, `border-gray-300`, `rounded-lg` | | Padding | `p-3`, `px-4`, `py-2` | | Sizing | `w-full`, `w-64` | | Ring | `ring-2`, `ring-blue-200` | | Shadow | `shadow-sm` | | State prefixes | `hover:`, `focus:`, `disabled:`, `selected:`, `error:`, `dark:` | ### Label (labelClassName) Default: `text-sm font-medium text-gray-700 dark:text-gray-300 mb-1` ### Error (errorClassName) Default: `text-red-500 dark:text-red-400 text-xs mt-1` ### Hint (hintClassName) Default: `text-gray-500 dark:text-gray-400 text-xs mt-1` ## Customizing Theme `WFormDatePicker` inherits all theme customisation from `WDatePicker`. Override Tailwind colour scales in `WindThemeData` to change selection colours, error states, and calendar chrome. The colours you pass merge over the defaults by name: a name you supply replaces that whole palette, so list every shade you use. ```dart WindTheme( data: WindThemeData( colors: { 'red': MaterialColor(0xFFEF4444, { 200: Color(0xFFFECACA), // Error ring color 400: Color(0xFFF87171), // Dark mode error text (errorClassName) 500: Color(0xFFEF4444), // Error border and error text color }), }, ), child: MyApp(), ) ``` ## Related Documentation - [WDatePicker](https://anilcancakir.com/wind_kit/docs/widgets/w-date-picker.md) - The base date picker widget - [WFormInput](https://anilcancakir.com/wind_kit/docs/widgets/w-form-input.md) - Form-integrated text input - [WFormSelect](https://anilcancakir.com/wind_kit/docs/widgets/w-form-select.md) - Form-integrated dropdown - [WFormCheckbox](https://anilcancakir.com/wind_kit/docs/widgets/w-form-checkbox.md) - Form-integrated checkbox - [WPopover](https://anilcancakir.com/wind_kit/docs/widgets/w-popover.md) - The underlying overlay engine --- # WPopover A flexible popover component for creating dropdown menus, notification panels, user menus, tooltips, and similar overlay patterns. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WPopover( alignment: PopoverAlignment.bottomRight, className: 'w-64 bg-white dark:bg-gray-800 rounded-xl shadow-xl p-2', triggerBuilder: (context, isOpen, isHovering) => WButton( className: 'bg-blue-600 text-white', child: Text('Open Menu'), ), contentBuilder: (context, close) => Column( mainAxisSize: MainAxisSize.min, children: [ ListTile(title: Text('Profile'), onTap: close), ListTile(title: Text('Settings'), onTap: close), ], ), ) ``` ## Basic Usage The `WPopover` widget uses two builders: `triggerBuilder` for the element that activates the popover, and `contentBuilder` for the overlay content. It manages the overlay state and positioning automatically. ```dart WPopover( triggerBuilder: (context, isOpen, isHovering) => WText( 'Click me', className: 'text-blue-500 cursor-pointer', ), contentBuilder: (context, close) => WDiv( className: 'p-4', child: WText('Hello from Popover!'), ), ) ``` ## Constructor ```dart const WPopover({ Key? key, required PopoverTriggerBuilder triggerBuilder, required PopoverContentBuilder contentBuilder, PopoverController? controller, bool enableTriggerOnTap = true, PopoverAlignment alignment = PopoverAlignment.bottomLeft, String? className, Offset offset = const Offset(0, 4), double maxHeight = 400, double? width, double? maxWidth, bool disabled = false, bool closeOnContentTap = false, VoidCallback? onOpen, VoidCallback? onClose, bool autoFlip = true, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `triggerBuilder` | `PopoverTriggerBuilder` | **Required** | Builder for the trigger widget. Receives `isOpen` and `isHovering` states; `hover:`, `focus:` and `disabled:` classes on the trigger also follow the popover. | | `contentBuilder` | `PopoverContentBuilder` | **Required** | Builder for the popover content. Receives a `close` callback. | | `className` | `String?` | `null` | Wind utility classes for the popover container styling. | | `controller` | `PopoverController?` | `null` | Optional controller for programmatic show/hide/toggle. | | `alignment` | `PopoverAlignment` | `bottomLeft` | Where to position the popover relative to the trigger. | | `offset` | `Offset` | `Offset(0, 4)` | Gap between the trigger and the popover. | | `maxHeight` | `double` | `400` | Maximum height for the overlay. Content taller than this does not scroll by itself: give the popover `overflow-y-auto` (or build the content on a scroll view). With `autoFlip` on, the overlay is also capped at the room left on its side of the trigger, so it never runs off the window. | | `width` | `double?` | `null` | Pins the overlay to a fixed width (like `WSelect`'s `menuWidth`). Overrides a `w-*` token in `className`. | | `maxWidth` | `double?` | `null` | Upper bound for the overlay width. Falls back to a `max-w-*` token, then to the screen width, so the overlay never stretches off-screen. Ignored when a fixed `width` (or `w-*`) is set. | | `enableTriggerOnTap` | `bool` | `true` | Whether tapping the trigger toggles the popover. | | `closeOnContentTap` | `bool` | `false` | Whether tapping inside the content closes the popover. | | `disabled` | `bool` | `false` | When true, the trigger will not respond to interactions and is not a Tab stop. | | `onOpen` | `VoidCallback?` | `null` | Callback fired when the popover is opened. | | `onClose` | `VoidCallback?` | `null` | Callback fired when the popover is closed. | | `autoFlip` | `bool` | `true` | When true, the popover flips its alignment to the opposite side if the natural position would render off-screen, and is held inside the viewport: pulled in horizontally, and capped vertically at the room on its side (the content needs `overflow-y-auto` or a scroll view to scroll). It never shrinks the panel: a fixed `width` (or `w-*`) wider than the screen still spills off it, so constrain fixed widths yourself. | ## Layout Modes While `WPopover` itself isn't a layout container, it supports various **Alignment Modes** to determine how the overlay is positioned relative to the trigger. ### Alignment `WPopover` intelligently "flips" the alignment if the requested position would cause the overlay to overflow the screen edges. When neither side has room for `maxHeight`, it keeps the roomier side and shrinks to the space there, 8 pixels short of the edge, so a tall menu scrolls instead of running off a small window. The content has to be able to scroll for that: give the popover `overflow-y-auto` (or build the content on a scroll view), or a plain column overflows once capped. | Alignment | Description | |:----------|:------------| | `bottomLeft` | Below trigger, aligned to left edge (Default). | | `bottomRight` | Below trigger, aligned to right edge. | | `bottomCenter` | Below trigger, centred horizontally. | | `topLeft` | Above trigger, aligned to left edge. | | `topRight` | Above trigger, aligned to right edge. | | `topCenter` | Above trigger, centred horizontally. | ## Event Handling The `triggerBuilder` provides the `isOpen` and `isHovering` states, allowing you to reactively style the trigger. The `contentBuilder` provides a `close` callback to dismiss the popover from within the content (e.g., when a menu item is clicked). When `enableTriggerOnTap` is `true` (the default), tapping the trigger toggles the popover, and this works even when the trigger is an interactive widget that owns its own `onTap` (a `WButton` or `WAnchor`): the trigger's own callback and the popover toggle both fire on the same tap. Set `enableTriggerOnTap: false` for manual control via a `PopoverController`. ```dart WPopover( triggerBuilder: (context, isOpen, isHovering) => WDiv( className: 'p-2 rounded ${isOpen ? "bg-blue-100" : "bg-gray-100"}', child: WText(isOpen ? 'Active' : 'Idle'), ), contentBuilder: (context, close) => WButton( onTap: close, child: Text('Click to Close'), ), ) ``` ### Keyboard The trigger is one Tab stop. Enter, Space, and the Android TV D-pad centre toggle the popover, through the same `ActivateIntent` a `WAnchor` answers, so the keys follow whatever the platform binds to activation. Escape closes an open popover from the trigger or from anything focused inside the panel, returns focus to the trigger, and stops there: one press closes the popover and not also the dialog or sheet holding it, and a page pushed over an open popover keeps its own Escape. While the popover is closed, Escape is left alone and reaches the app's own shortcuts, so a dialog holding a popover still closes on it. After a keyboard open, focus stays on the trigger even on the web, where the browser drops it as the panel mounts. The trigger and the panel form one traversal group in widget order, so the next Tab moves into the panel, top to bottom, whether it opened below the trigger or flipped above it, and Tab past its last control leaves the popover. A disabled popover is not a Tab stop. With `enableTriggerOnTap: false`, Enter and Space do nothing, as a tap does nothing; Escape still closes a popover the controller opened. ## State Variants Since the popover trigger is built via `triggerBuilder`, you can apply state-based styling manually or use Wind's state prefixes if the trigger is a Wind widget like `WButton` or `WDiv`. The popover publishes its trigger's interaction state the way a `WAnchor` does, so a plain `WDiv` trigger answers `hover:`, `focus:` and `disabled:` without an anchor of its own. Give the trigger a `focus:` class, or a keyboard user cannot see where focus is: ```dart WPopover( triggerBuilder: (context, isOpen, isHovering) => WDiv( className: ''' p-2 rounded-lg bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 ''', child: WIcon(Icons.notifications_outlined), ), contentBuilder: (context, close) => MyContent(), ) ``` ```dart WPopover( triggerBuilder: (context, isOpen, isHovering) => WButton( // Use the isOpen boolean for manual state toggling className: 'p-2 rounded-lg ${isOpen ? "bg-blue-600 text-white" : "bg-white text-gray-800"}', child: Text('Toggle Menu'), ), contentBuilder: (context, close) => MyContent(), ) ``` ## Styling Examples ### Custom Size and Shadow If no width is specified in `className`, the popover uses the trigger's width as a minimum. Use sizing utilities to define a specific width. ```dart WPopover( className: 'w-80 bg-white rounded-2xl shadow-2xl border border-gray-100', triggerBuilder: (context, isOpen, isHovering) => Icon(Icons.more_horiz), contentBuilder: (context, close) => MyLargeMenu(), ) ``` ### Dark Mode `WPopover` fully supports dark mode styling using the `dark:` prefix. ```dart WPopover( className: 'bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-800 shadow-xl', triggerBuilder: (context, isOpen, isHovering) => MyTrigger(), contentBuilder: (context, close) => MyContent(), ) ``` ## All Supported Classes The popover overlay container is a `WDiv` and supports all standard Wind utilities. | Category | Classes | |:---------|:--------| | Sizing | `w-{size}`, `max-w-{size}`, `h-{size}`, `max-h-{size}` | | Background | `bg-{color}` | | Borders | `border`, `border-{color}`, `rounded-{size}` | | Shadow | `shadow-{size}`, `shadow-{color}-{shade}` | | Padding | `p-{n}`, `px-{n}`, `py-{n}` | ## Customizing Theme `WPopover` has no theme keys of its own: style the panel through `className`. Colour and spacing tokens inside `className` resolve through `WindThemeData` like any other Wind class. When `className` is `null`, the panel uses `bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-xl`. A `className` you pass replaces that default rather than extending it. ## Related Documentation - [WSelect - A high-level dropdown built using WPopover](https://anilcancakir.com/wind_kit/docs/widgets/w-select.md) - [WDiv - The base container widget](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md) - [WAnchor - Useful for interactive elements inside the popover](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md) --- # WTabs Controlled tabs widget. Renders a tab list and a content panel. The `selected:` state prefix activates on the currently active tab, so callers can supply tokens like `selected:border-b-2 selected:text-blue-600` through `tabClassName`. This is a fully controlled widget: `selectedIndex` is managed by the caller. - [Basic Usage](#basic-usage) - [Props](#props) - [Constructor](#constructor) - [Styling Examples](#styling-examples) - [Related Documentation](#related-documentation) ## Basic Usage ```dart WTabs( tabs: const ['Overview', 'Details', 'Settings'], selectedIndex: _selectedTab, onChanged: (i) => setState(() => _selectedTab = i), listClassName: 'flex flex-row border-b border-gray-200 dark:border-gray-700', tabClassName: ''' px-4 py-2 text-sm text-gray-500 dark:text-gray-400 selected:text-blue-600 dark:selected:text-blue-400 selected:border-b-2 selected:border-blue-600 dark:selected:border-blue-400 ''', panelClassName: 'pt-4', panelBuilder: (index) => _panels[index], ) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `tabs` | `List` | **Required** | The labels rendered for each tab, in display order. | | `selectedIndex` | `int` | **Required** | Zero-based index of the currently selected tab. | | `panelBuilder` | `Widget Function(int index)` | **Required** | Returns the panel content for the currently selected tab. | | `onChanged` | `ValueChanged?` | `null` | Called with the tapped tab's index. The caller updates `selectedIndex` in response. | | `listClassName` | `String?` | `null` | Utility classes for the tab-list row container. Example: `'flex flex-row border-b border-gray-200 dark:border-gray-700'`. | | `tabClassName` | `String?` | `null` | Utility classes applied to every tab's inner `WDiv`. Supports `selected:` prefixed tokens: they activate only on the selected tab. | | `selectedTabClassName` | `String?` | `null` | Extra utility classes appended to the active tab's `WDiv` only. Applied after `tabClassName`. | | `panelClassName` | `String?` | `null` | Utility classes for the panel wrapper `WDiv`. Example: `'pt-4'`. | | `fullWidthList` | `bool` | `true` | When true, prepends `w-full` to the tab list so a `border-b` baseline spans the full container width (tabs stay content-sized, left-aligned). If `listClassName` already sets a width token (`w-*`), that width is respected and `w-full` is not prepended, so an explicit width wins. Set `false` for a content-width strip (centred pill tabs). | ## Constructor ```dart WTabs({ Key? key, required List tabs, required int selectedIndex, required Widget Function(int index) panelBuilder, ValueChanged? onChanged, String? listClassName, String? tabClassName, String? selectedTabClassName, String? panelClassName, bool fullWidthList = true, }) ``` ## Styling Examples ### Underline Tabs Classic underline style: `selected:border-b-2` on the active tab. Pair border and text colours with `dark:` variants: ```dart WTabs( tabs: const ['Overview', 'Activity', 'Settings'], selectedIndex: _tab, onChanged: (i) => setState(() => _tab = i), listClassName: 'flex flex-row border-b border-gray-200 dark:border-gray-700', tabClassName: ''' px-4 py-3 text-sm font-medium text-gray-500 dark:text-gray-400 selected:text-rose-600 dark:selected:text-rose-400 selected:border-b-2 selected:border-rose-600 dark:selected:border-rose-400 ''', panelClassName: 'pt-4', panelBuilder: (index) => panels[index], ) ``` ### Pill / Segmented Control Background-based active tab with rounded container: ```dart WTabs( tabs: const ['All', 'Open', 'Resolved'], selectedIndex: _tab, onChanged: (i) => setState(() => _tab = i), listClassName: 'flex flex-row gap-1 p-1 rounded-lg bg-gray-100 dark:bg-gray-800', tabClassName: ''' px-3 py-1.5 rounded-md text-sm font-medium text-gray-600 dark:text-gray-400 selected:bg-white dark:selected:bg-gray-700 selected:text-gray-900 dark:selected:text-white selected:shadow-sm ''', panelClassName: 'pt-4', panelBuilder: (index) => panels[index], ) ``` ### selectedTabClassName for Active-Only Tokens Use `selectedTabClassName` to append active-only classes without `selected:` prefixes: ```dart WTabs( tabs: const ['Profile', 'Security', 'Billing'], selectedIndex: _tab, onChanged: (i) => setState(() => _tab = i), tabClassName: 'px-4 py-2 text-sm text-gray-500 dark:text-gray-400', selectedTabClassName: 'text-blue-600 dark:text-blue-400 border-b-2 border-blue-600 dark:border-blue-400', panelBuilder: (index) => panels[index], ) ``` ## Related Documentation - [WAnchor](https://anilcancakir.com/wind_kit/docs/widgets/w-anchor.md): each tab is wrapped in a `WAnchor` providing hover and focus state. - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md): renders the tab list and panel containers. - [WCheckbox](https://anilcancakir.com/wind_kit/docs/widgets/w-checkbox.md): another controlled widget that drives state prefixes from a prop. --- # WCard Surface container with optional `header` and `footer` slots. Composes a `WDiv` (flex-col) with up to three slots: an optional header above the body, the required `child` body, and an optional footer below. All styling is className-driven; no colours or spacing are baked in. - [Basic Usage](#basic-usage) - [Props](#props) - [Constructor](#constructor) - [Styling Examples](#styling-examples) - [Related Documentation](#related-documentation) ## Basic Usage ```dart WCard( className: 'rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 p-5', child: WText('Body content'), ) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `child` | `Widget` | **Required** | The main body content of the card. | | `header` | `Widget?` | `null` | Optional widget rendered above the body. Typical use: a title row, a card image, or a section label. | | `footer` | `Widget?` | `null` | Optional widget rendered below the body. Typical use: action buttons or status/metadata rows. | | `className` | `String?` | `null` | Utility classes for the outer container. When omitted, defaults to `'w-full flex flex-col'` (no colours, no spacing). | ## Constructor ```dart const WCard({ Key? key, required Widget child, Widget? header, Widget? footer, String? className, }) ``` ## Styling Examples ### Basic Card with Border and Shadow ```dart WCard( className: ''' rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-md p-5 ''', child: WText('Settings content here'), ) ``` ### Card with Header Slot Add a visual header section separated by a border: ```dart WCard( className: 'rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 overflow-hidden', header: WDiv( className: 'px-5 py-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900', child: WText('Recent Activity', className: 'text-sm font-semibold text-gray-700 dark:text-gray-300'), ), child: WDiv( className: 'px-5 py-4', child: WText('Activity list here'), ), ) ``` ### Card with Header and Footer Three-slot card with an action footer: ```dart WCard( className: 'rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 overflow-hidden', header: WDiv( className: 'px-5 py-4 border-b border-gray-200 dark:border-gray-700', child: WText('Team Plan', className: 'text-base font-semibold text-gray-900 dark:text-white'), ), child: WDiv( className: 'px-5 py-4 flex flex-col gap-1', children: [ WText('\$49 / month', className: 'text-2xl font-bold text-gray-900 dark:text-white'), WText('Up to 25 members, 100 GB storage.', className: 'text-sm text-gray-500 dark:text-gray-400'), ], ), footer: WDiv( className: 'px-5 py-4 border-t border-gray-200 dark:border-gray-700 flex flex-row justify-end', child: WButton( onTap: save, className: 'px-4 py-2 rounded-lg bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-600', child: WText('Save', className: 'text-white text-sm font-medium'), ), ), ) ``` ### Driven by a WindSlotRecipe Use `WindSlotRecipe` to keep card tone variations in one place: ```dart final card = WindSlotRecipe( slots: {'root': 'rounded-xl border overflow-hidden', 'header': 'px-5 py-4 border-b', 'body': 'px-5 py-4'}, variants: { 'tone': { 'default': {'root': 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700', 'header': 'border-gray-200 dark:border-gray-700', 'body': ''}, 'highlighted': {'root': 'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800', 'header': 'border-blue-200 dark:border-blue-800', 'body': ''}, }, }, defaultVariants: {'tone': 'default'}, ); final classes = card(variants: {'tone': 'highlighted'}); WCard( className: classes['root'], header: WDiv(className: classes['header'], child: WText('Summary')), child: WDiv(className: classes['body'], child: WText('Body')), ) ``` ## Related Documentation - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md): the container `WCard` delegates all rendering to. - [WindRecipe](https://anilcancakir.com/wind_kit/docs/styling/wind-recipe.md): drive card tone from variant axes without repeating className strings. - [WButton](https://anilcancakir.com/wind_kit/docs/widgets/w-button.md): commonly used in the `footer` slot. --- # WBadge Inline status or label pill. Composes a pill-shaped `WDiv` (rounded-full) around a `WText` (text-xs). All visual styling is className-driven: the caller supplies tone via `bg-*` and `text-*` tokens (with `dark:` pairs); no colours are baked in. - [Basic Usage](#basic-usage) - [Props](#props) - [Constructor](#constructor) - [Styling Examples](#styling-examples) - [Related Documentation](#related-documentation) ## Basic Usage ```dart WBadge( 'Active', className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100', ) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `label` | `String` | **Required** | The text displayed inside the badge. | | `className` | `String?` | `null` | Caller-supplied utility classes appended after the default layout classes (`inline-flex items-center rounded-full px-2 py-0.5`). Use this to supply `bg-*`, `text-*`, and their `dark:` pairs. | ## Constructor ```dart const WBadge( String label, { Key? key, String? className, }) ``` `label` is a required positional parameter. ## Styling Examples ### Status Tones Supply tone through `bg-*` + `text-*` + their `dark:` pairs. The default layout provides no background or text colour: ```dart // Success WBadge('Paid', className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100') // Warning WBadge('Pending', className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100') // Danger WBadge('Error', className: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100') // Neutral WBadge('Draft', className: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300') ``` ### Inline Beside Text The default classes include `inline-flex`, but Wind treats it as an inert token (it has no inline display), so the badge sits beside other widgets because of the parent `flex flex-row` container: ```dart WDiv( className: 'flex flex-row items-center gap-2', children: [ WText('Invoice #1042', className: 'text-base font-medium text-gray-900 dark:text-white'), WBadge('Paid', className: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200'), ], ) ``` ### With a WindRecipe Use `WindRecipe` to drive tone from a variant axis instead of repeating className strings: ```dart final badge = WindRecipe( base: 'items-center rounded-full px-2.5 py-0.5 text-xs font-medium', variants: { 'tone': { 'success': 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200', 'warning': 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200', }, }, defaultVariants: {'tone': 'success'}, ); WBadge('Deployed', className: badge(variants: {'tone': 'success'})) WBadge('Pending', className: badge(variants: {'tone': 'warning'})) ``` ## Related Documentation - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md): the pill container `WBadge` delegates to internally. - [WText](https://anilcancakir.com/wind_kit/docs/widgets/w-text.md): the label renderer inside the badge. - [WindRecipe](https://anilcancakir.com/wind_kit/docs/styling/wind-recipe.md): compose badge tone variants without repeating className strings. --- # WAnchor The foundational state wrapper that detects hover and focus, runs the gestures you give it and propagates that state down to all descendant widgets via `WindAnchorStateProvider`. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Layout Modes](#layout-modes) - [Event Handling](#event-handling) - [Keyboard and Remote Control](#keyboard-and-remote-control) - [State Variants](#state-variants) - [Styling Examples](#styling-examples) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Accessibility](#accessibility) - [Related Documentation](#related-documentation) ```dart WAnchor( onTap: () => print('Pressed'), child: WDiv( // Reacts to hover state provided by WAnchor className: 'p-4 bg-white hover:bg-gray-100 transition-colors', child: WText('Hover Me'), ), ) ``` ## Basic Usage The `WAnchor` widget acts as the "brain" for interaction state management in Wind. It handles gesture detection and focus management, making it possible for child widgets to use prefixes like `hover:`, `focus:`, and `disabled:`. Unlike most Wind widgets, `WAnchor` does not take a `className` itself. Instead, it provides the context needed for its children to respond to interactive states. ```dart WAnchor( onTap: () => print('Tapped!'), child: WDiv( className: 'p-4 bg-blue-500 hover:bg-blue-600 rounded-lg', child: WText('Interactive Box', className: 'text-white'), ), ) ``` ## Constructor ```dart const WAnchor({ Key? key, required Widget child, VoidCallback? onTap, VoidCallback? onLongPress, VoidCallback? onDoubleTap, bool isDisabled = false, Set? states, MouseCursor? mouseCursor, String? semanticLabel, bool trackFocus = true, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `child` | `Widget` | **Required** | The widget that will receive the hover, focus, and gesture states. | | `onTap` | `VoidCallback?` | `null` | Triggered when the widget is tapped. | | `onLongPress` | `VoidCallback?` | `null` | Triggered when the widget is long-pressed. | | `onDoubleTap` | `VoidCallback?` | `null` | Triggered when the widget is double-tapped. | | `isDisabled` | `bool` | `false` | When true, gestures are ignored and the `disabled:` prefix is activated. | | `states` | `Set?` | `null` | Custom states for dynamic styling (e.g., `{'selected'}`). They reach `WDiv` and `WButton` descendants only, and a `WDiv` sees them only while its className has no `hover:`, `focus:` or `active:` class (such a `WDiv` wraps itself in its own anchor, which shadows this one). `WText` ignores them. | | `mouseCursor` | `MouseCursor?` | `null` | Custom cursor. Defaults to click when interactive, basic when disabled, and `MouseCursor.defer` when the anchor has no gesture, so a styling wrapper keeps the cursor of the control around it. | | `semanticLabel` | `String?` | `null` | Accessible name for icon-only controls. When set, excludes the child subtree from semantics so the label overrides any child text; prefer it for icon-only controls rather than controls that already expose readable text. | | `trackFocus` | `bool` | `true` | Whether a gestureless anchor installs a focus node. `WDiv` passes `false` when its className has no `focus:` class. Ignored when the anchor has a gesture, which always keeps its node. | ## Layout Modes `WAnchor` is a logic wrapper and does not provide layout properties of its own. To control the layout of the interactive area, use a `WDiv` or another layout widget as the immediate child. ### Flex Layout Wrapper ```dart WAnchor( onTap: () {}, child: WDiv( className: 'flex items-center gap-2 p-3 bg-gray-50 hover:bg-gray-100 rounded', children: [ WIcon(Icons.add, className: 'text-blue-500'), WText('Add Item'), ], ), ) ``` ## Event Handling `WAnchor` supports standard touch and mouse gestures. These events are only triggered if `isDisabled` is false. ```dart WAnchor( onTap: () => print('Single Tap'), onDoubleTap: () => print('Double Tap'), onLongPress: () => print('Long Press'), child: WDiv( className: 'p-10 bg-zinc-200 text-center', child: WText('Gesture Interaction Area') ), ) ``` ## Keyboard and Remote Control A focused `WAnchor` runs its `onTap` when the user presses the activation key. `WAnchor` binds no key of its own: it answers `ActivateIntent`, which `WidgetsApp` already raises for `Enter`, `Space`, the numeric keypad `Enter`, the gamepad A button and `select`. `select` is the D-pad centre on Android TV and the click on the Apple TV remote, so one binding covers a keyboard, a gamepad and a remote, and a key the platform adds later arrives for free. ```dart // Reachable by Tab, activated by Enter, Space or the D-pad centre. WAnchor(onTap: play, child: const WText('Play')) ``` Only `onTap` is bound. `ActivateIntent` means the primary action and there is no second key for a secondary one, so `onLongPress` and `onDoubleTap` stay pointer-only, exactly as they are on Flutter's own buttons. An anchor with no `onTap`, or a disabled one, binds nothing and lets the key travel on to whatever is above it. That is narrower than it may look: a `CallbackAction` is always enabled and `ShortcutManager` reports a key handled for any enabled action, so binding unconditionally would have made a long-press-only anchor eat the activation key belonging to the row around it, and on web eat `Space`'s scroll with it, because `Space` maps to `PrioritizedIntents([ActivateIntent, ScrollIntent])` and an always-enabled action wins that race. ### One control is one stop A control has to cost one press of the remote, so only an anchor that carries a gesture is a traversal stop. A gestureless `WAnchor` is a styling wrapper, and it inherits from the nearest anchor above it instead of publishing its own state. This matters because `WDiv` wraps itself in a gestureless `WAnchor` whenever its className carries `hover:`, `focus:` or `active:`. Without the inheritance, the element carrying `focus:ring-2` would be the one element that could not see the focus: ```dart // One stop. Tab lands on the anchor, the ring is drawn on the div, and // Enter fires onTap. WAnchor( onTap: clear, child: const WDiv( className: 'p-2 rounded-full focus:ring-2 focus:ring-blue-500', child: WIcon(Icons.close), ), ) ``` A `WDiv` whose only state classes are `hover:` or `active:` carries no focus node at all (its wrapper gets `trackFocus: false`): it cannot draw focus, so a stop on it was a key press that lit nothing. The primary focus chained down from an anchor above it still passes through; hover does not, because each wrapper publishes its own. One thing it no longer publishes is focus-within to its own descendants. A `focus:` class on a label reads the nearest wrapper, so in `WDiv(className: 'hover:bg-gray-50', children: [WText('Email', className: 'focus:text-blue-600'), WInput(...)])` the label stays gray while the field has focus. Give the row any `focus:` class of its own (`hover:bg-gray-50 focus:bg-gray-50`) and its wrapper keeps the node, so the label lights again. Two shapes are unaffected. A `WDiv` carrying `focus:` with no anchor above it keeps its own focus node, because that is how a consumer styles a custom control. And a focusable descendant still lights the wrapper's ring: `FocusNode.hasFocus` covers descendants, so a `WInput` inside a ring-styled `WDiv` draws the ring around the field the user is typing in. One consequence to know: whether the node exists follows the className, so a `WDiv` that adds or drops a `focus:` class at runtime changes its subtree's shape, and the children below remount and lose their `State` (a `WInput`'s text among it). Keep a `focus:` class present on a div whose children hold state, and switch what it paints instead. What the wrapper inherits is deliberately narrow: the ancestor's PRIMARY focus, never its focus-within. It passes that signal on rather than stopping at it, because any `hover:` or `active:` class on a div in between creates a second wrapper whose own node never holds focus, and a ring two wrappers deep would otherwise stay dark. The two are different questions and `WindAnchorState` now exposes both. A tappable card containing a text field reports focus-within the whole time the user types, so a wrapper inheriting that would light up even when it sits *beside* the field rather than around it: ```dart // The ring belongs to nothing here, and stays dark while the field has focus. WAnchor( onTap: open, child: const WDiv( className: 'flex flex-row', children: [ WDiv(className: 'p-2 focus:ring-2', child: WText('Label')), WDiv(className: 'flex-1 min-w-0', child: WInput()), ], ), ) ``` `hover` is not inherited at all. It is a pointer position, and two siblings inside one anchor legitimately highlight independently. ### What is not here Wind ships no `FocusTraversalPolicy`. Directional movement is Flutter's default `DirectionalFocusTraversalPolicyMixin`, which scopes left and right to the enclosing horizontal `Scrollable` and up and down to the vertical one, so a stack of horizontal rails behaves reasonably without configuration. Focus memory per region, edge behaviour (wrap, stop or leave) and ordering beyond geometry are a consumer concern today; reach for `FocusTraversalGroup` with your own policy. One upstream limit is worth knowing before it is diagnosed as a Wind bug: directional traversal cannot reach a list item that has not been built, so focus stops at the edge of a lazy list's cache extent ([flutter/flutter#91741](https://github.com/flutter/flutter/issues/91741)), and it can land on a cached item that is scrolled out of sight ([flutter/flutter#91795](https://github.com/flutter/flutter/issues/91795)). ## State Variants `WAnchor` enables several state prefixes for all Wind widgets in its subtree. This allows you to define complex interactive styles easily. ```dart WAnchor( isDisabled: false, child: WDiv( className: ''' bg-blue-500 hover:bg-blue-600 focus:ring-2 focus:ring-blue-300 disabled:bg-gray-400 disabled:opacity-50 ''', child: WText('Interactive States', className: 'text-white'), ), ) ``` ## Styling Examples ### Card Lift Effect ```dart WAnchor( onTap: () {}, child: WDiv( className: 'p-6 bg-white dark:bg-gray-800 shadow hover:shadow-lg rounded-xl duration-300', child: WText('Hover to see the shadow effect'), ), ) ``` ### Navigation Link ```dart WAnchor( onTap: () {}, child: WDiv( className: 'px-4 py-2 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded', child: WText('Dashboard'), ), ) ``` ## All Supported Classes While `WAnchor` does not take a `className`, it facilitates the use of these state prefixes for the Wind widgets within its subtree. Custom `states` are narrower: they reach `WDiv` and `WButton` only, and a `WDiv` with its own `hover:`, `focus:` or `active:` class shadows them. `WText` ignores them. | Category | Prefixes / Features | |:---------|:--------------------| | Interaction | `hover:`, `focus:`, `disabled:` | | Custom States | Any value passed to the `states` prop (e.g., `selected:`) | | Gestures | Enables `onTap`, `onLongPress`, `onDoubleTap` | | Keys | `onTap` also runs on `ActivateIntent` (`Enter`, `Space`, gamepad A, D-pad `select`) | ## Customizing Theme `WAnchor` behaviour is primarily state-driven. You can customise the global interaction defaults via `WindThemeData`. ```dart WindTheme( data: WindThemeData( // Theme scales influence the styles applied during states baseSpacingUnit: 4.0, ), child: MyApp(), ) ``` ## Accessibility A `WAnchor` that carries a gesture (`onTap`, `onLongPress` or `onDoubleTap`) publishes one `Semantics(button: true)` node, and `MergeSemantics` folds the child's text into it so the control gets its name from what it renders. Pass `semanticLabel` when the child has no readable text, such as an icon-only anchor; that label replaces the child subtree rather than concatenating with it. A `WAnchor` with no gesture **and no `semanticLabel`** publishes nothing of its own. It is the state propagator in that mode, which is how `WDiv` uses it to serve `hover:`, `focus:` and `active:` classes (the auto-wrap never passes a label), and a decorative surface must not announce itself as a button with no action behind it. `semanticLabel` is the exception, and deliberately so: setting it always publishes the named button node, gestures or not. It is how a DISABLED control still tells assistive technology that a control is there and currently unavailable, which is information the user needs. That makes the label a statement of intent rather than a formatting choice: set it on a control, never on decoration. ```dart // One button node named "Save". WAnchor(onTap: save, child: const WText('Save')) // One button node named "Save", announced disabled, with no tap action. const WAnchor(isDisabled: true, semanticLabel: 'Save', child: WText('Save')) // No node at all: hover styling only. const WAnchor(child: WDiv(className: 'hover:bg-slate-100', child: WText('Card'))) ``` ## Related Documentation - [WButton](https://anilcancakir.com/wind_kit/docs/widgets/w-button.md) - High-level button widget built on WAnchor. - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md) - The primary layout widget used with WAnchor. - [State Management](https://anilcancakir.com/wind_kit/docs/core-concepts/state-management.md) - Deep dive into state prefixes. --- # WSpacer The `WSpacer` is a lightweight widget designed specifically for adding consistent gaps between elements. Unlike `WDiv`, which supports a full range of decorations and layout properties, `WSpacer` renders as a simple `SizedBox`, making it highly efficient for layouts where you only need spacing. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Responsive Spacing](#responsive-spacing) - [Why WSpacer?](#why-wspacer) - [All Supported Classes](#all-supported-classes) - [Customizing Theme](#customizing-theme) - [Related Documentation](#related-documentation) ```dart WDiv( className: 'flex flex-col', children: [ WText('Label', className: 'font-bold'), WSpacer(className: 'h-2'), // 8px vertical gap WInput(placeholder: 'Enter text...'), ], ) ``` ## Basic Usage The `WSpacer` widget extracts width and height values from your utility classes and applies them to a `SizedBox`. It supports the standard Wind spacing scale (based on a 4px unit by default). ### Vertical Spacing Use the `h-{n}` classes to add vertical gaps in columns: ```dart WSpacer(className: 'h-4') // 16px height ``` ### Horizontal Spacing Use the `w-{n}` classes to add horizontal gaps in rows: ```dart WSpacer(className: 'w-2') // 8px width ``` ## Constructor ```dart const WSpacer({ Key? key, String? className, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `className` | `String?` | `null` | Wind utility classes for sizing (h-*, w-*). | > [!NOTE] > Non-sizing classes (like `bg-red-500` or `p-4`) are parsed but ignored by `WSpacer` as it renders a `SizedBox` which does not support decoration or padding. ## Responsive Spacing You can adjust spacing based on screen size using responsive prefixes. This is particularly useful for increasing margins on tablet or desktop layouts. ```dart WSpacer(className: 'h-4 md:h-8') ``` ## Why WSpacer? While you could use `WDiv` with padding or margin, `WSpacer` is preferred for explicit gaps for several reasons: 1. **Performance:** It bypasses complex composition and renders as a single `SizedBox`. 2. **Semantics:** It clearly communicates "this widget exists only for spacing" to other developers. 3. **Convenience:** It replaces the verbose `SizedBox(height: 16)` with the more maintainable `WSpacer(className: 'h-4')`. ## All Supported Classes | Category | Classes | Description | |:---------|:--------|:------------| | Sizing | `h-{n}`, `w-{n}` | Standard scale (h-1, h-2, etc.) | | Arbitrary | `h-[15px]`, `w-[2px]` | Custom pixel values | | Responsive | `md:h-*`, `lg:w-*` | Breakpoint-specific spacing | ## Customizing Theme The pixel values for `h-{n}` and `w-{n}` are derived from the `baseSpacingUnit` in your theme. ```dart WindThemeData( baseSpacingUnit: 4.0, // Default: h-1 = 4px ) ``` ## Related Documentation - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md) - [Sizing Utilities](https://anilcancakir.com/wind_kit/docs/layout/sizing.md) - [Spacing Utilities](https://anilcancakir.com/wind_kit/docs/layout/spacing.md) - [Responsive Design](https://anilcancakir.com/wind_kit/docs/core-concepts/responsive-design.md) --- # WBreakpoint Declarative breakpoint-keyed builder for rendering different widget trees per responsive breakpoint. An **escape hatch** for cases where the widget structure genuinely differs between breakpoints; reach for className prefixes first. - [Basic Usage](#basic-usage) - [When to Use](#when-to-use) - [Constructor](#constructor) - [Props](#props) - [Resolution Semantics](#resolution-semantics) - [Custom Breakpoints](#custom-breakpoints) - [Nested Theme Overrides](#nested-theme-overrides) - [Related Documentation](#related-documentation) ```dart WBreakpoint( base: (ctx) => const MobileLayout(), md: (ctx) => const TabletLayout(), lg: (ctx) => const DesktopLayout(), ) ``` ## Basic Usage `WBreakpoint` picks one widget tree per breakpoint using Wind's own breakpoint resolution. `base` is required; other builders are optional fallbacks. ```dart WBreakpoint( base: (ctx) => WDiv( className: 'flex flex-col gap-2', children: [/* stacked cards */], ), md: (ctx) => WDiv( className: 'grid grid-cols-3 gap-4', children: [/* grid cards */], ), ) ``` ## When to Use Prefer className-first patterns. `WBreakpoint` is a last resort: | Need | Prefer | |:-----|:-------| | Swap styles per breakpoint | `sm:flex-row`, `md:gap-8` | | Swap visibility per breakpoint | `hidden sm:block` / `block sm:hidden` | | Reorder flex children | `order-2 md:order-1` | | Render different widget **types** | `WBreakpoint` | | Render different child **counts** | `WBreakpoint` | If your two branches differ only in className, use prefixes instead: you'll get less code and a cleaner diff. ## Constructor ```dart const WBreakpoint({ Key? key, required WidgetBuilder base, WidgetBuilder? sm, WidgetBuilder? md, WidgetBuilder? lg, WidgetBuilder? xl, WidgetBuilder? xxl, Map custom = const {}, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `base` | `WidgetBuilder` | **Required** | Fallback builder when no higher breakpoint matches. | | `sm` | `WidgetBuilder?` | `null` | Builder for `sm` breakpoint (default min width 640). | | `md` | `WidgetBuilder?` | `null` | Builder for `md` breakpoint (default min width 768). | | `lg` | `WidgetBuilder?` | `null` | Builder for `lg` breakpoint (default min width 1024). | | `xl` | `WidgetBuilder?` | `null` | Builder for `xl` breakpoint (default min width 1280). | | `xxl` | `WidgetBuilder?` | `null` | Builder for `2xl` breakpoint (default min width 1536). | | `custom` | `Map` | `{}` | Builders for custom breakpoint keys defined in `WindThemeData.screens`. | ## Resolution Semantics 1. Read the active breakpoint from the `WindContext`. 2. Collect all breakpoints defined in `WindThemeData.screens` that are at or below the active width. 3. Sort them descending and pick the first one that has a builder. 4. Fall back to `base` if nothing matches. Given default screens and width `1200` (active = `lg`): ```dart WBreakpoint( base: (_) => A(), sm: (_) => B(), md: (_) => C(), ) // Resolution walks lg → md → sm. md has a builder → renders C(). ``` ## Custom Breakpoints Define your own screen in `WindThemeData.screens` and drive `WBreakpoint` through the `custom` map: ```dart WindTheme( data: WindThemeData().copyWith( screens: const { 'sm': 640, 'md': 768, 'tablet': 900, // custom 'lg': 1024, 'xl': 1280, '2xl': 1536, }, ), child: WBreakpoint( base: (_) => const MobileTable(), custom: {'tablet': (_) => const TabletTable()}, lg: (_) => const DesktopTable(), ), ) ``` Custom keys participate in the same descending resolution as built-ins. ## Nested Theme Overrides `WBreakpoint` reads through any `WindTheme` ancestor, so a nested `WindTheme` override with its own `screens` map applies inside that subtree. Useful for design-system playgrounds and test harnesses. ## Related Documentation - [Responsive Design](https://anilcancakir.com/wind_kit/docs/core-concepts/responsive-design.md) - [Flexbox & Layout](https://anilcancakir.com/wind_kit/docs/layout/flexbox.md) - [WindTheme](https://anilcancakir.com/wind_kit/docs/core-concepts/theming.md) --- # WDynamic `WDynamic` renders a Flutter widget tree from a JSON/Map configuration at runtime, with built-in action handling and form state management. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [JSON Schema](#json-schema) - [Action Handling](#action-handling) - [State Management](#state-management) - [Custom Builders](#custom-builders) - [Security](#security) - [Error Handling](#error-handling) - [Related Documentation](#related-documentation) ```dart WDynamic( json: const { 'type': 'WDiv', 'props': { 'className': 'p-6 bg-white dark:bg-gray-800 rounded-xl shadow-sm' }, 'children': [ { 'type': 'WText', 'props': { 'text': 'Hello from JSON!', 'className': 'text-xl font-bold text-gray-800 dark:text-gray-100' } }, ], }, ) ``` ## Basic Usage The `WDynamic` widget converts JSON/Map structures into live Flutter widgets. This enables server-driven UI, dynamic form generation, and runtime-configurable layouts without rebuilding your app. ```dart WDynamic( json: const { 'type': 'WDiv', 'props': {'className': 'flex gap-4 p-4'}, 'children': [ { 'type': 'WButton', 'props': { 'className': 'bg-blue-500 text-white px-4 py-2 rounded', 'onTap': {'action': 'submit'}, }, 'children': [ {'type': 'WText', 'props': {'text': 'Submit'}} ], } ], }, actions: { 'submit': (args) => print('Button tapped'), }, ) ``` ## Constructor ```dart const WDynamic({ Key? key, required Map json, Map actions = const {}, WDynamicController? controller, Set? denyWidgets, Map? builders, Map? customIcons, int maxDepth = 50, Widget Function(String type, Object error)? onError, Widget Function(String type, Map props)? onUnknownWidget, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `json` | `Map` | **Required** | JSON configuration defining the widget tree. Must include `type`, optional `props` and `children`. | | `actions` | `Map` | `{}` | Action handlers keyed by name. Called when interactive widgets trigger events. | | `controller` | `WDynamicController?` | `null` | Optional controller for external state access (read/write form values). | | `denyWidgets` | `Set?` | `null` | Widget types to remove from the default whitelist. | | `builders` | `Map?` | `null` | Custom widget builders keyed by type name. Signature: `Widget Function(Map props, List children)`. | | `customIcons` | `Map?` | `null` | Custom icon mappings for `WIcon` widgets. Keys are icon names used in the JSON `icon` prop, values are `IconData` instances. Extends a small built-in map of common names; an unknown name renders a help icon. | | `maxDepth` | `int` | `50` | Maximum recursion depth for nested widgets. Prevents infinite loops. | | `onError` | `Widget Function(String, Object)?` | `null` | Error handler for widget build failures. Returns a fallback widget. | | `onUnknownWidget` | `Widget Function(String, Map)?` | `null` | Handler for unknown or denied widget types. Returns a fallback widget. | ## JSON Schema Every widget node in the JSON structure follows this format: ```dart { 'type': 'WidgetType', // Required: Wind widget, Flutter widget, or custom builder 'props': { // Optional: properties passed to the widget 'className': 'flex gap-4', 'text': 'Hello', 'onTap': {'action': 'handleTap', 'args': {'id': 1}}, 'id': 'username', // Form state key (WInput, WCheckbox, WSelect, WDatePicker) }, 'children': [ // Optional: nested widget array { /* child node */ } ] } ``` ### Type Field The `type` field determines which widget to render: - **Wind Widgets**: `WDiv`, `WText`, `WButton`, `WInput`, `WCheckbox`, `WSelect`, `WDatePicker`, `WIcon`, `WImage`, `WSvg`, `WPopover`, `WAnchor`, `WSpacer` - **Flutter Widgets**: `Column`, `Row`, `Center`, `SizedBox`, `Expanded`, `Container`, `Wrap`, `Stack`, `Positioned`, `Padding`, `Align`, `Opacity`, `AspectRatio`, `FittedBox`, `ClipRRect`, `Spacer` - **Custom Widgets**: Any key from the `builders` map ### Props Field Widget properties are passed directly to the constructor. Common props include: ```dart { 'type': 'WDiv', 'props': { 'className': 'flex gap-4 p-6 bg-white rounded-xl', // Wind utilities } } ``` ```dart { 'type': 'WText', 'props': { 'text': 'Welcome!', // Text content 'className': 'text-lg font-bold', // Wind utilities } } ``` ```dart { 'type': 'WInput', 'props': { 'id': 'email', // Auto-tracked in controller state 'placeholder': 'Enter email...', 'className': 'border rounded-lg', } } ``` ### Children Field An array of nested widget nodes. Used by layout widgets like `WDiv`, `Column`, `Row`, etc. ```dart { 'type': 'WDiv', 'props': {'className': 'flex gap-2'}, 'children': [ {'type': 'WText', 'props': {'text': 'First'}}, {'type': 'WText', 'props': {'text': 'Second'}}, ] } ``` ## Action Handling Interactive widgets like `WButton` can trigger actions defined in the `actions` map. Actions are referenced in the JSON using an object format. ### Action Format ```dart { 'type': 'WButton', 'props': { 'onTap': { 'action': 'actionName', // Required: key from actions map 'args': {'key': 'value'} // Optional: arguments passed to handler } } } ``` ### Action Signatures Actions can accept arguments only, or both arguments and state: ```dart // Simple: (Map args) WDynamic( json: myJson, actions: { 'showAlert': (Map args) { print('Alert: ${args['message']}'); }, }, ) ``` ```dart // With state access: (Map args, WDynamicState state) WDynamic( json: myJson, actions: { 'submitForm': (Map args, WDynamicState state) { final email = state.get('email'); final password = state.get('password'); print('Submitting: $email'); }, }, ) ``` ### Example ```dart WDynamic( json: const { 'type': 'WButton', 'props': { 'className': 'bg-blue-500 text-white px-4 py-2 rounded', 'onTap': { 'action': 'increment', 'args': {'step': 1} }, }, 'children': [ {'type': 'WText', 'props': {'text': 'Tap Me'}} ], }, actions: { 'increment': (Map args, WDynamicState state) { final step = args['step'] ?? 1; setState(() => count += step); }, }, ) ``` ## State Management `WDynamic` automatically tracks form widget values using the `id` prop. Values are stored in an internal state object accessible via `WDynamicController` or action handlers. ### Auto-Tracking with ID A `WInput`, `WCheckbox`, `WSelect` or `WDatePicker` with an `id` prop stores its value in state after the user changes it. The `value` prop only sets the displayed initial value and does not write state, so an untouched field reads as `null` unless the controller already holds a value for its `id`, which the field then displays. Other widgets, such as `WDiv`, do not track an `id`: ```dart { 'type': 'WInput', 'props': { 'id': 'username', // Auto-tracked 'placeholder': 'Username...', } } ``` ### Using the Controller Create a `WDynamicController` to access state externally: ```dart final controller = WDynamicController(); // Pre-fill values controller.setValue('email', 'user@example.com'); // Read values final email = controller.getValue('email'); final allValues = controller.getAll(); // Listen for changes final dispose = controller.addListener('email', (value) { print('Email changed: $value'); }); // Pass to WDynamic WDynamic( controller: controller, json: myJson, ) // Clean up controller.dispose(); ``` ### Accessing State in Actions Action handlers receive the state object as a second parameter: ```dart WDynamic( json: const { 'type': 'WDiv', 'children': [ { 'type': 'WInput', 'props': {'id': 'name', 'placeholder': 'Your name...'} }, { 'type': 'WButton', 'props': { 'onTap': {'action': 'greet'}, 'className': 'bg-green-500 text-white px-4 py-2 rounded', }, 'children': [ {'type': 'WText', 'props': {'text': 'Greet'}} ], } ], }, actions: { 'greet': (args, state) { final name = state.get('name') ?? 'World'; print('Hello, $name!'); }, }, ) ``` ## Custom Builders Extend `WDynamic` with custom widget types using the `builders` prop. ### WWidgetBuilder Signature ```dart typedef WWidgetBuilder = Widget Function( Map props, List children, ); ``` ### Example ```dart WDynamic( json: const { 'type': 'InfoCard', 'props': { 'title': 'Custom Widget', 'subtitle': 'Built with a custom builder' }, }, builders: { 'InfoCard': (Map props, List children) { return WDiv( className: 'p-4 bg-blue-50 rounded-xl border border-blue-200', child: WDiv( className: 'flex items-center gap-3', children: [ WIcon(Icons.info, className: 'text-blue-500 text-2xl'), WDiv( className: 'flex flex-col', children: [ WText(props['title'] ?? '', className: 'font-bold text-blue-800'), WText(props['subtitle'] ?? '', className: 'text-sm text-blue-600'), ], ), ], ), ); }, }, ) ``` Custom builders have priority over default widgets. You can override built-in widgets by providing a builder with the same type name. ### Custom Icons Map string icon names to `IconData` for use in JSON `WIcon` widget nodes. Custom icons extend (not replace) a small built-in map of common names; add yours with `customIcons`. Names are matched in lower case, so write the keys in lower case. ```dart WDynamic( json: const { 'type': 'WDiv', 'props': {'className': 'flex gap-4 items-center'}, 'children': [ { 'type': 'WIcon', 'props': {'icon': 'heart', 'className': 'text-red-500 text-2xl'}, }, { 'type': 'WIcon', 'props': {'icon': 'star', 'className': 'text-yellow-500 text-2xl'}, }, ], }, customIcons: { 'heart': Icons.favorite, 'star': Icons.star, 'settings': Icons.settings, }, ) ``` ## Security `WDynamic` uses a whitelist approach for security. Only explicitly allowed widget types can be rendered. ### Default Whitelist **Wind Widgets**: - `WDiv`, `WText`, `WButton`, `WImage`, `WIcon`, `WAnchor`, `WInput`, `WCheckbox`, `WSvg`, `WSelect`, `WPopover`, `WDatePicker`, `WSpacer` **Flutter Widgets**: - `Column`, `Row`, `Center`, `SizedBox`, `Expanded`, `Container`, `Wrap`, `Stack`, `Positioned`, `Padding`, `Align`, `Opacity`, `AspectRatio`, `FittedBox`, `ClipRRect`, `Spacer` ### Denying Widgets Remove specific widget types from the whitelist: ```dart WDynamic( json: myJson, denyWidgets: const {'WButton', 'WInput'}, // Block buttons and inputs ) ``` ### Unknown Widget Handler Provide a fallback for unknown or denied widget types: ```dart WDynamic( json: myJson, onUnknownWidget: (String type, Map props) { return WDiv( className: 'p-2 bg-red-100 border border-red-300 rounded', child: WText('Unknown widget: $type', className: 'text-red-700 text-sm'), ); }, ) ``` ## Error Handling Handle widget build failures gracefully with the `onError` callback. ```dart WDynamic( json: myJson, onError: (String type, Object error) { return WDiv( className: 'p-4 bg-yellow-50 border border-yellow-300 rounded', child: WDiv( className: 'flex flex-col gap-2', children: [ WText('Error rendering $type', className: 'font-bold text-yellow-800'), WText(error.toString(), className: 'text-sm text-yellow-700'), ], ), ); }, ) ``` ### Max Depth Protection Prevent infinite recursion with the `maxDepth` prop (default: 50): ```dart WDynamic( json: deeplyNestedJson, maxDepth: 100, // Allow deeper nesting ) ``` When max depth is exceeded, that branch renders a built-in red error box ("Error: MaxDepth") instead of its widget. `onError` is not called for it, so it cannot replace that box. ## Related Documentation - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md) - Primary layout container - [WButton](https://anilcancakir.com/wind_kit/docs/widgets/w-button.md) - Interactive button widget - [WInput](https://anilcancakir.com/wind_kit/docs/widgets/w-input.md) - Text input widget - [WSelect](https://anilcancakir.com/wind_kit/docs/widgets/w-select.md) - Dropdown select widget - [State Management](https://anilcancakir.com/wind_kit/docs/core-concepts/state-management.md) - State handling in Wind - [Dynamic Rendering](https://anilcancakir.com/wind_kit/docs/core-concepts/dynamic-rendering.md) - Server-driven UI concepts --- # WKeyboardActions A wrapper that renders an OK button and field-navigation toolbar above the keyboard for any group of input fields, with optional platform targeting and custom close widget. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Platform Targeting](#platform-targeting) - [Navigation Between Fields](#navigation-between-fields) - [Toolbar Styling](#toolbar-styling) - [Custom Close Button](#custom-close-button) - [The Toolbar Occludes, and Says So](#the-toolbar-occludes-and-says-so) - [Styling Examples](#styling-examples) - [Related Documentation](#related-documentation) ```dart WKeyboardActions( focusNodes: [_nameFocus, _amountFocus], toolbarClassName: 'bg-gray-100 dark:bg-gray-800', child: Column( children: [ WInput(focusNode: _nameFocus, placeholder: 'Jane Doe'), WInput( focusNode: _amountFocus, placeholder: '0.00', type: InputType.number, ), ], ), ) ``` ## Basic Usage Create a `FocusNode` for each input field and pass them all in the `focusNodes` list. `WKeyboardActions` attaches listeners to every node and shows the toolbar as long as any one of them is focused. ```dart class _MyFormState extends State { final _nameFocus = FocusNode(); final _amountFocus = FocusNode(); @override void dispose() { _nameFocus.dispose(); _amountFocus.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return WKeyboardActions( focusNodes: [_nameFocus, _amountFocus], child: Column( children: [ WInput(focusNode: _nameFocus, placeholder: 'Full Name'), WInput( focusNode: _amountFocus, placeholder: '0.00', type: InputType.number, ), ], ), ); } } ``` > **The toolbar is hosted in the ROOT `Overlay`, not the nearest one.** It positions itself in screen terms (`bottom: viewInsets.bottom`, so it sits on top of the keyboard), and a nested `Overlay` is measured in its own box rather than the screen's. An app whose page host owns an `Overlay` inside a scroll view gives that overlay the height of the scrolled content, so a nearest-overlay toolbar would render hundreds of points below the viewport: present in the widget tree, absent from the screen. You need no `Overlay` of your own for this to work; `MaterialApp` and `CupertinoApp` both provide the root one. > **The default close button needs `material_ui`'s `MaterialLocalizations`.** Its OK label comes from `MaterialLocalizations.of(context).okButtonLabel`, and `wind_kit` reads the `material_ui` type, so the app must provide that one: `material_ui`'s `MaterialApp` does. Flutter's own `MaterialApp` (from `package:flutter/material.dart`) provides a different type, so under it the toolbar throws "No MaterialLocalizations found" when a field takes focus. Either migrate the root to `material_ui`'s `MaterialApp`, or keep the root and add `GlobalMaterialLocalizations.delegates` from `material_ui` to its `localizationsDelegates` (checked: Flutter's `MaterialApp` then resolves the `material_ui` `MaterialLocalizations`). Under a bare `WidgetsApp` or a `CupertinoApp`, add `GlobalMaterialLocalizations.delegates` from `material_ui` to the app's `localizationsDelegates` (it covers every supported locale), or `DefaultMaterialLocalizations.delegate` when the app is English only: that delegate supports no other locale, so under, say, a Spanish locale the button still throws. A `closeWidgetBuilder` replaces the button and needs none of this. ```dart import 'package:cupertino_ui/cupertino_ui.dart'; import 'package:material_ui/material_ui.dart'; import 'package:wind_kit/wind_kit.dart'; CupertinoApp( // GlobalMaterialLocalizations.delegates covers every locale; use // DefaultMaterialLocalizations.delegate for an English only app. localizationsDelegates: GlobalMaterialLocalizations.delegates, // Set the locales your app ships; the default is US English only. supportedLocales: const [Locale('en'), Locale('es')], home: MyForm(), ) ``` ## Constructor ```dart const WKeyboardActions({ Key? key, required Widget child, required List focusNodes, String platform = 'all', bool nextFocus = true, String? toolbarClassName, Widget Function(FocusNode)? closeWidgetBuilder, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `child` | `Widget` | **Required** | The form or column of inputs the toolbar is attached to. | | `focusNodes` | `List` | **Required** | FocusNodes for the inputs that need keyboard actions. Order determines navigation order. | | `platform` | `String` | `'all'` | Platform gate: `'all'`, `'ios'`, or `'android'`. | | `nextFocus` | `bool` | `true` | When true, Previous/Next arrow buttons appear in the toolbar for field navigation. | | `toolbarClassName` | `String?` | `null` | Wind utility classes for the toolbar background. Only the background colour (`bg-*`) is read; other classes are ignored. | | `closeWidgetBuilder` | `Widget Function(FocusNode)?` | `null` | Builder that replaces the default "OK" button. Receives the currently-focused FocusNode. | ## Platform Targeting The `platform` prop gates the toolbar to a specific OS. The most common value is `'ios'`, because iOS numeric keyboards have no built-in Done button. | Value | Toolbar visible on | |:------|:-------------------| | `'all'` | Every platform, including web and desktop (default) | | `'ios'` | iOS only | | `'android'` | Android only | ```dart WKeyboardActions( platform: 'ios', focusNodes: [_amountFocus], child: WInput( focusNode: _amountFocus, placeholder: '0.00', type: InputType.number, ), ) ``` ## Navigation Between Fields When `nextFocus: true` (the default), the toolbar shows Previous and Next arrow buttons. They move focus to the previous or next node in the `focusNodes` list, in the order they were provided. The Previous button is disabled at the first field; the Next button is disabled at the last. Set `nextFocus: false` to show only the OK button, which is useful for single-field forms. ```dart WKeyboardActions( focusNodes: [_nameFocus, _emailFocus, _amountFocus], nextFocus: true, child: Column( children: [ WInput(focusNode: _nameFocus, placeholder: 'Jane Doe'), WInput(focusNode: _emailFocus, placeholder: 'jane@example.com'), WInput(focusNode: _amountFocus, placeholder: '0.00'), ], ), ) ``` ## Toolbar Styling Pass Wind `bg-*` utility classes via `toolbarClassName` to set the toolbar background. The `dark:` pair is required. ```dart WKeyboardActions( toolbarClassName: 'bg-gray-100 dark:bg-gray-800', focusNodes: [_focusNode], child: myInputField, ) ``` When `toolbarClassName` is null, the toolbar uses `Theme.of(context).colorScheme.surfaceContainerHighest`. ## Custom Close Button Supply `closeWidgetBuilder` to replace the default "OK" label with any widget. The builder receives the currently-focused `FocusNode`, which you can call `unfocus()` on. ```dart WKeyboardActions( focusNodes: [_focusNode], closeWidgetBuilder: (node) => WButton( onTap: node.unfocus, className: 'bg-blue-600 text-white px-4 py-1.5 rounded-md text-sm font-medium', child: const WText('Save & Close', className: 'text-white text-sm font-medium'), ), child: myInputField, ) ``` ## The Toolbar Occludes, and Says So The toolbar is drawn in the root overlay at `bottom: viewInsets.bottom`, which puts it directly ON TOP of the keyboard. The engine reports the keyboard through `viewInsets` and knows nothing about the bar, so the region a focused field has to clear is the keyboard PLUS the toolbar, and anything reading `viewInsets` alone reserves too little. A field that cleared the keyboard came out from under it and straight under the bar. `WKeyboardActions` measures its own bar and publishes the height to its subtree: ```dart final double toolbar = WKeyboardToolbarInset.of(context); ``` Zero when no bar is up, which covers every platform the widget is gated off and every moment nothing is focused. `WInput` already consumes it, so a plain field inside a `WKeyboardActions` needs nothing; reach for it in a widget of your own that positions something against the keyboard. The height is measured rather than assumed, because the row is built from `IconButton`s whose size comes from the ambient theme. ## Styling Examples ### Minimal (single field, iOS only) ```dart WKeyboardActions( platform: 'ios', nextFocus: false, focusNodes: [_amountFocus], toolbarClassName: 'bg-white dark:bg-gray-900', child: WInput( focusNode: _amountFocus, placeholder: '0.00', type: InputType.number, ), ) ``` ### Multi-field form with custom toolbar colour ```dart WKeyboardActions( focusNodes: [_nameFocus, _emailFocus, _amountFocus], toolbarClassName: 'bg-blue-50 dark:bg-blue-950', child: Column( children: [ WInput(focusNode: _nameFocus, placeholder: 'Jane Doe'), WInput(focusNode: _emailFocus, placeholder: 'jane@example.com'), WInput(focusNode: _amountFocus, placeholder: '0.00'), ], ), ) ``` ### Custom close widget ```dart WKeyboardActions( focusNodes: [_focusNode], toolbarClassName: 'bg-emerald-50 dark:bg-emerald-900', closeWidgetBuilder: (node) => TextButton.icon( onPressed: node.unfocus, icon: const Icon(Icons.check_circle_outlined), label: const Text('Done'), ), child: myInputField, ) ``` ## Related Documentation - [WInput - The unstyled text input widget](https://anilcancakir.com/wind_kit/docs/widgets/w-input.md) - [WFormInput - Form-bound WInput with validator](https://anilcancakir.com/wind_kit/docs/widgets/w-form-input.md) - [WDiv - General-purpose container for laying out form fields](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md) --- # WindAnimationWrapper `WindAnimationWrapper` wraps any widget in a looping animation driven by `WindAnimationType`: spin, ping, pulse, or bounce. It is the underlying engine behind `animate-*` utility classes; you can also use it directly when you need programmatic control over the type or duration. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [Animation Types](#animation-types) - [Styling Examples](#styling-examples) - [Related Documentation](#related-documentation) ## Basic Usage ```dart WindAnimationWrapper( animationType: WindAnimationType.spin, child: WIcon(Icons.refresh_outlined, className: 'text-blue-500 dark:text-blue-400 text-3xl'), ) ``` Use `animate-*` className tokens for most cases. Reach for `WindAnimationWrapper` directly when you need to vary the duration at runtime, or when wrapping a widget that does not accept a `className` prop. `curve` is currently ignored by every animation type; use `duration`. ## Constructor ```dart const WindAnimationWrapper({ Key? key, required Widget child, required WindAnimationType animationType, Duration duration = const Duration(milliseconds: 1000), Curve curve = Curves.linear, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `child` | `Widget` | **Required** | The widget to animate. | | `animationType` | `WindAnimationType` | **Required** | The animation style. One of `spin`, `ping`, `pulse`, `bounce`, or `none`. | | `duration` | `Duration` | `Duration(milliseconds: 1000)` | Full cycle length. Applies to all animation types. | | `curve` | `Curve` | `Curves.linear` | `curve` is currently ignored by every animation type; use `duration`. | ## Animation Types | `WindAnimationType` | Effect | Typical use | |:--------------------|:-------|:------------| | `spin` | Continuous 360-degree rotation. | Loading spinners, refresh icons. | | `ping` | Scale from 1.0 to 1.5 with matching opacity fade, repeating. | Notification badges, live indicators. | | `pulse` | Opacity oscillates from 1.0 to 0.5 and back. | Skeleton placeholders, content loading. | | `bounce` | Vertical offset oscillates between 0 and -5 px (20 * 0.25). | Scroll indicators, attention grabbers. | | `none` | No animation; returns `child` unchanged. | Default state; safe to pass unconditionally. | ## Styling Examples ### Custom duration Slow a pulse down to a 2-second cycle for a calmer skeleton effect: ```dart WindAnimationWrapper( animationType: WindAnimationType.pulse, duration: const Duration(milliseconds: 2000), child: WDiv( className: 'h-4 w-48 bg-gray-200 dark:bg-gray-700 rounded', ), ) ``` ### Ping badge Combine with `Stack` + `Positioned` for a notification badge: ```dart Stack( children: [ WIcon(Icons.notifications_outlined, className: 'text-gray-600 dark:text-gray-300 text-3xl'), Positioned( right: 2, top: 2, child: WindAnimationWrapper( animationType: WindAnimationType.ping, child: WDiv(className: 'w-3 h-3 bg-red-500 dark:bg-red-400 rounded-full'), ), ), ], ) ``` ### Spin with fast duration Reduce the cycle time for a snappier loading indicator: ```dart WindAnimationWrapper( animationType: WindAnimationType.spin, duration: const Duration(milliseconds: 600), child: WIcon(Icons.sync_outlined, className: 'text-indigo-500 dark:text-indigo-400 text-2xl'), ) ``` ### Via className (most common) For typical use the `animate-*` className token is the shorter path: ```dart WIcon( Icons.refresh_outlined, className: 'text-blue-500 dark:text-blue-400 text-2xl animate-spin', ) ``` ## Related Documentation - [WDiv](https://anilcancakir.com/wind_kit/docs/widgets/w-div.md): Universal container; supports `animate-*` via `className`. - [WIcon](https://anilcancakir.com/wind_kit/docs/widgets/w-icon.md): Icon widget; wraps automatically in `WindAnimationWrapper` when className contains `animate-*`. - [Animation](https://anilcancakir.com/wind_kit/docs/interactivity/animation.md): Full reference for the `animate-*` tokens. - [Transition](https://anilcancakir.com/wind_kit/docs/interactivity/transition.md): Reference for `transition-*`, `duration-*`, and `ease-*` tokens. --- # Style Parser Parse a className string into a `WindStyle` yourself with `wStyle` and `WindParser.parse`. - [Introduction](#introduction) - [The wStyle Helper](#wstyle-helper) - [WindParser Engine](#windparser-engine) - [Performance & Caching](#performance-and-caching) - [Context Extensions](#context-extensions) ## Introduction While Wind widgets like `WDiv` and `WText` are the primary way to use utility classes, you may sometimes need to parse utility strings programmatically. The Style Parser API allows you to convert any `className` string into a structured `WindStyle` object that can be applied to standard Flutter widgets. ## The wStyle Helper The `wStyle` function is a global helper that provides the simplest way to parse utility classes on the fly. ```dart WindStyle wStyle(BuildContext context, String className) ``` ### Usage Example This is particularly useful when building custom widgets that need to accept Wind utilities for styling: ```dart Widget buildCustomCard(BuildContext context, {String? className}) { final style = wStyle(context, className ?? 'bg-white p-4 shadow-md'); return Container( decoration: style.decoration, padding: style.padding, child: Text('Styled with WindStyle'), ); } ``` ## WindParser Engine For more advanced scenarios, you can interface directly with `WindParser.parse`. This method allows for base style merging and manual state injection. ```dart static WindStyle parse( String className, BuildContext context, { WindStyle? baseStyle, Set? states, }) ``` ### Parameters | Parameter | Type | Description | |:---|:---|:---| | `className` | `String` | The utility class string to parse. | | `context` | `BuildContext` | Used to resolve theme values, breakpoints, and platform. | | `baseStyle` | `WindStyle?` | An optional base style to merge the new utilities into. | | `states` | `Set?` | Manual state overrides (e.g., `{'loading', 'active'}`). | ### Accessing Parsed Values The resulting `WindStyle` object contains resolved Flutter properties: ```dart final style = wStyle(context, 'bg-red-500 text-lg font-bold'); Color? color = style.decoration?.color; // Resolved bg-red-500 TextStyle textStyle = style.toTextStyle(); // Resolved text-lg and font-bold EdgeInsets? padding = style.padding; // resolved p-* classes ``` ## Performance & Caching Wind uses an internal memoisation system to ensure that parsing is extremely fast. The `WindParser` maintains a static cache keyed by a combination of: 1. The raw `className` string. 2. The current active breakpoint (`sm`, `md`, etc.). 3. The current brightness (Light/Dark mode). 4. The target platform (iOS/Android/Web). 5. The active interaction states (`hover`, `focus`, etc.). > [!NOTE] > Because of this caching, calling `wStyle` multiple times with the same parameters in a build method has negligible performance impact. ## Context Extensions For cleaner syntax, Wind provides a BuildContext extension that mirrors the global helper: ```dart // Using global helper final style = wStyle(context, 'p-4'); // Using extension final style = context.wStyleExt('p-4'); ``` ### Related Documentation - [Context Extensions](https://anilcancakir.com/wind_kit/docs/utilities/context-extensions.md) - [Colour Helpers](https://anilcancakir.com/wind_kit/docs/utilities/color-helpers.md) - [Theming Concepts](https://anilcancakir.com/wind_kit/docs/core-concepts/theming.md) --- # Color Helpers Resolve theme colours, hex values and opacity from Dart with `wColor` and the context helpers. - [Resolving Colors](#resolving-colors) - [Hex Colors](#hex-colors) - [Opacity Handling](#opacity-handling) - [Contrast](#contrast) - [Material Color Inversion](#material-color-inversion) - [Context Extensions](#context-extensions) Wind provides a set of utility functions and extensions to help you resolve and manipulate colours programmatically, staying consistent with your theme. ## Resolving Colors The `wColor` function is the primary way to resolve colours from the active `WindTheme`. It handles colour names, shades, and even automatic dark mode overrides. ```dart Color? wColor( BuildContext context, String colorName, { int shade = 500, String? darkColorName, int darkShade = 500, }) ``` ### Basic Usage You can resolve a colour by its name and shade: ```dart // Resolves blue-500 from the theme final primary = wColor(context, 'blue'); // Resolves red-600 final error = wColor(context, 'red', shade: 600); // Resolves using color-shade string format final danger = wColor(context, 'rose-500'); ``` ### Dark Mode Overrides You can specify a different colour or shade to be used when the theme is in dark mode. `darkColorName` takes a bare colour name, and the shade goes in `darkShade`; a name that carries a shade, such as `'slate-900'`, resolves to nothing in dark mode: ```dart final background = wColor( context, 'slate-50', darkColorName: 'slate', darkShade: 900, ); ``` ## Hex Colors The `hexToColor` function converts various hex string formats into Flutter `Color` objects. ```dart Color hexToColor(String code) ``` ### Supported Formats | Format | Example | Result | |:-------|:--------|:-------| | 3-digit Hex | `#RGB` | `#RRGGBB` | | 4-digit Hex | `#ARGB` | `#AARRGGBB` | | 6-digit Hex | `#RRGGBB` | `0xFFRRGGBB` | | 8-digit Hex | `#AARRGGBB` | `0xAARRGGBB` | > [!NOTE] > The hash (`#`) prefix is optional. `hexToColor('FF0000')` works identically to `hexToColor('#FF0000')`. ## Opacity Handling Wind includes utilities for parsing Tailwind-style opacity modifiers and applying them to colours. ### Parsing Opacity The `parseColorOpacity` function splits a class string into the colour part and its opacity factor. ```dart // Returns (colorPart: 'blue-500', opacity: 0.5) final result = parseColorOpacity('blue-500/50'); // Supports arbitrary values: (colorPart: 'red-500', opacity: 0.75) final arbitrary = parseColorOpacity('red-500/[75]'); ``` ### Applying Opacity Use `applyOpacity` to programmatically adjust the alpha channel of any `Color`. ```dart Color applyOpacity(Color color, double opacity) ``` ```dart final semiTransparent = applyOpacity(Colors.blue, 0.5); ``` ## Contrast `contrastRatio` and `contrastForeground` compute WCAG 2.x contrast for a colour your theme does not own, such as a brand colour an end user picks for a public status page. A theme token already has a correct `dark:` pair; these two exist for the colour that does not. ```dart double contrastRatio(Color a, Color b) Color contrastForeground( Color background, { Color light = const Color(0xFFFFFFFF), Color dark = const Color(0xFF000000), }) ``` `contrastRatio` returns a value from 1 (identical colours) to 21 (pure black against pure white). `contrastForeground` picks whichever of `light` or `dark` has the higher ratio against `background`, comparing the two ratios directly rather than checking `background`'s luminance against the usual 0.179 crossover. That threshold is only correct when both candidates are pure black and pure white; pass a `dark` tuned away from pure black (a near-black brand tone, say) and the threshold can pick the candidate that reads worse. Both helpers take opaque colours. Luminance ignores alpha, so a translucent colour reports the ratio of its opaque self rather than of what renders; composite it over its backdrop (`Color.alphaBlend(color, backdrop)`) before calling either one. ```dart // A status page renders its operator's brand colour behind white text. // The brand colour is not a theme token, so it needs a computed answer. final Color heading = contrastForeground(brandColor); // Compare a fixed pair directly. final double ratio = contrastRatio(brandColor, Colors.white); ``` ## Material Color Inversion The `invertMaterialColor` utility is a standalone helper that returns a palette with its shades swapped (e.g., 50 ↔ 900, 100 ↔ 800). The theme engine does not call it, so dark mode variants come from the `dark:` pairs in your classes. ```dart MaterialColor invertMaterialColor(MaterialColor color) ``` ## Context Extensions For more ergonomic access, Wind extends `BuildContext` with several colour-related shortcuts. ### `context.windColors` Returns the full colour map from the current `WindThemeData`. ```dart final redPalette = context.windColors['red']; ``` ### `context.wColorExt` A shortcut for calling `wColor(context, ...)`. ```dart final primary = context.wColorExt('blue', shade: 600); ``` ### `context.windIsDark` Returns a boolean indicating if the current theme brightness is `Brightness.dark`. ```dart if (context.windIsDark) { // Do something specific for dark mode } ``` --- # Context Extensions Read theme data, breakpoints, brightness and styling helpers straight from a `BuildContext`. - [Overview](#overview) - [Theme Extensions](#theme-extensions) - [Responsive Extensions](#responsive-extensions) - [Helper Extensions](#helper-extensions) - [API Reference](#api-reference) - [Common Patterns](#common-patterns) - [Related Documentation](#related-documentation) ## Overview Wind provides a set of ergonomic extensions on `BuildContext` to make accessing theme data, responsive breakpoints, and styling helpers as concise as possible. Instead of verbose provider lookups, you can access the entire Wind ecosystem directly from the `context`. ## Theme Extensions These extensions provide direct access to the `WindTheme` configuration and its underlying data. ### Theme Control Use `windTheme` to interact with the theme controller, typically for toggling between light and dark modes. ```dart // Flip light <-> dark and take over from the OS context.windTheme.toggleTheme(); // Pin a specific brightness as a user preference final controller = context.windTheme; controller.setTheme( controller.data.copyWith(brightness: Brightness.dark, syncWithSystem: false), ); // Hand brightness back to the OS after a manual override context.windTheme.resetToSystem(); ``` > [!NOTE] > `toggleTheme()` sets `syncWithSystem: false` for you, which is what makes it a real user preference. `updateTheme(brightness: ...)` does not: it is the partial-update call the framework's own `didChangePlatformBrightness` listener uses, so while `syncWithSystem` is `true` the next OS brightness change overwrites whatever you set. Pin the preference through `setTheme` with `syncWithSystem: false`, as above, and use `resetToSystem()` to give control back. ### Theme Data Access the raw `WindThemeData` or specific properties like colours and brightness. ```dart // Access the full theme data object final theme = context.windThemeData; // Quick access to the colors map final primary = context.windColors['blue']; // Check current brightness state if (context.windIsDark) { // Apply dark mode logic } ``` ## Responsive Extensions Responsive extensions allow you to make layout decisions in your widget tree based on the current screen size and defined breakpoints. ### Breakpoint Shortcuts Quickly determine the current device category. These are based on your theme's `screens` configuration (defaults to Tailwind breakpoints). ```dart if (context.wIsMobile) { return MobileLayout(); // < md } if (context.wIsTablet) { return TabletLayout(); // >= md && < lg } if (context.wIsDesktop) { return DesktopLayout(); // >= lg } ``` ### Active Breakpoint You can also retrieve the exact name of the active breakpoint. ```dart WText('Current breakpoint: ${context.wActiveBreakpoint}'); ``` ## Helper Extensions These extensions are shorthand for Wind's global helper functions, allowing you to resolve specific values from the theme without passing the context manually. ### Colors and Spacing Resolve colours and spacing values based on your theme's unit scale. ```dart // Get 'red-500' from the theme final alertColor = context.wColorExt('red', shade: 500); // Get 4 units of spacing (4 * baseSpacingUnit) final padding = context.wSpacingExt(4); ``` ### Typography and Styling Directly resolve font properties or even parse full class strings into a `WindStyle` object. ```dart final weight = context.wFontWeightExt('bold'); final size = context.wFontSizeExt('lg'); // Parse a class string manually final style = context.wStyleExt('p-4 bg-blue-500 rounded-lg'); ``` ## API Reference | Property / Method | Returns | Description | |:---|:---|:---| | `windTheme` | `WindThemeController` | Access the controller for toggling or updating the theme. | | `windThemeData` | `WindThemeData` | Access the read-only theme configuration. | | `windColors` | `Map` | Returns the full colour palette from the theme. | | `windScreens` | `Map` | Returns the breakpoint map (e.g., `md: 768`). | | `windBrightness` | `Brightness` | Returns the current theme brightness (`light` or `dark`). | | `windIsDark` | `bool` | Convenience getter for `brightness == Brightness.dark`. | | `wActiveBreakpoint` | `String` | Returns the name of the currently active breakpoint. | | `wIsMobile` | `bool` | Returns `true` if screen width is less than `md`. | | `wIsTablet` | `bool` | Returns `true` if screen width is between `md` and `lg`. | | `wIsDesktop` | `bool` | Returns `true` if screen width is `lg` or larger. | | `wColorExt(name, {shade})`| `Color?` | Resolves a theme colour by name and optional shade. | | `wSpacingExt(multiplier)` | `double` | Returns spacing calculated by `multiplier * baseSpacingUnit`. | | `wFontSizeExt(name)` | `double?` | Returns the font size value for a key (e.g., `xl`). | | `wFontWeightExt(name)` | `FontWeight?` | Returns the font weight for a key (e.g., `bold`). | | `wScreenIsExt(name)` | `bool` | Checks if a specific breakpoint is currently active. | | `wStyleExt(className)` | `WindStyle` | Parses a utility string into a `WindStyle` object. | ## Common Patterns ### Conditional Layouts Using responsive extensions to change widget parameters. ```dart WDiv( className: 'bg-white rounded-lg', // Use extension to adjust padding dynamically in logic child: Padding( padding: EdgeInsets.all(context.wIsMobile ? 12 : 24), child: Content(), ), ) ``` ### Manual Style Resolution Sometimes you need to apply Wind styles to standard Flutter widgets that don't support `className`. ```dart final style = context.wStyleExt('text-blue-600 font-bold italic'); return Text( 'Direct Styling', style: style.toTextStyle(), ); ``` ### Theme Toggling Implementing a theme switcher is trivial with the `windTheme` extension. ```dart IconButton( icon: Icon(context.windIsDark ? Icons.light_mode : Icons.dark_mode), onPressed: () => context.windTheme.toggleTheme(), ) ``` ## Related Documentation - [Theming Concepts](https://anilcancakir.com/wind_kit/docs/core-concepts/theming.md) - [Responsive Design](https://anilcancakir.com/wind_kit/docs/core-concepts/responsive-design.md) - [Colour Helpers](https://anilcancakir.com/wind_kit/docs/utilities/color-helpers.md) --- # Responsive Helpers Responsive helpers provide programmatic access to Wind's breakpoint system, platform detection, and screen metrics directly from your Dart code. - [Context Extensions](#context-extensions) - [Helper Functions](#helper-functions) - [Window Size Class](#window-size-class) - [Breakpoint Reference](#breakpoint-reference) - [Platform Detection](#platform-detection) ## Context Extensions Wind extends `BuildContext` with ergonomic getters to check the current screen state. These are the most common way to handle conditional logic in your `build` methods. ### Breakpoint Shortcuts | Extension | Type | Description | |:----------|:-----|:------------| | `context.wIsMobile` | `bool` | Returns `true` if screen is smaller than `md` | | `context.wIsTablet` | `bool` | Returns `true` if screen is between `md` and `lg` | | `context.wIsDesktop` | `bool` | Returns `true` if screen is at least `lg` | | `context.wActiveBreakpoint` | `String` | Returns current breakpoint name (`sm`, `md`, etc.) | Let's look at an example: ```dart @override Widget build(BuildContext context) { // Use extensions for conditional layouts if (context.wIsMobile) { return MobileView(); } return DesktopView(); } ``` ### Manual Checks If you need to check against a specific breakpoint name: ```dart if (context.wScreenIsExt('xl')) { // Screen is extra large or larger } ``` ## Helper Functions While extensions are preferred for brevity, these global functions provide the underlying logic and can be used in any part of your widget tree. ### `wScreenIs(context, name)` Returns `true` if the current screen width is **at least** the pixel value defined for the given breakpoint. ```dart if (wScreenIs(context, 'md')) { print('Tablet or larger'); } ``` ### `wScreenCurrent(context)` Returns the name of the currently active breakpoint based on the screen width. ```dart String current = wScreenCurrent(context); // 'base', 'sm', 'md', etc. ``` ### `wScreen(context, name)` Returns the raw pixel value (`int`) for a specific breakpoint name from your theme. ```dart int? mdValue = wScreen(context, 'md'); // 768 ``` ## Window Size Class `wWindowClassOf(context)` sorts the window width into one of three `WindowSizeClass` values, cut at the theme's `md` and `lg` screens. Use it to choose between a bottom bar and a side rail by window width instead of by platform: Wind treats iOS as mobile, so an iPad reads as a phone by platform, and `wIsTablet` is only the `md` to `lg` band. | Value | Width | |:------|:------| | `WindowSizeClass.compact` | below `md` | | `WindowSizeClass.medium` | `md` up to, but not including, `lg` | | `WindowSizeClass.expanded` | `lg` and wider | ```dart final bool useRail = wWindowClassOf(context) != WindowSizeClass.compact; ``` A custom `screens` table in `WindThemeData` moves the cuts. The width is the window's, so an iPad in split view is `compact`. ## Breakpoint Reference By default, Wind uses the standard Tailwind CSS breakpoint scale. You can override these in your `WindThemeData`. | Name | Value | Range | |:-----|:------|:------| | `base` | `0px` | `< 640px` | | `sm` | `640px` | `>= 640px` | | `md` | `768px` | `>= 768px` | | `lg` | `1024px` | `>= 1024px` | | `xl` | `1280px` | `>= 1280px` | | `2xl` | `1536px` | `>= 1536px` | > [!NOTE] > Breakpoint checks are inclusive (mobile-first). `wScreenIs(context, 'md')` is true for any width >= 768px, including `lg` and `xl` sizes. ## Platform Detection Wind provides platform-specific modifiers for utility classes (e.g., `ios:p-4`), but you can also access platform information programmatically via `WindContext`. ```dart final wind = WindContext.build(context); if (wind.isMobile) { // Physical mobile device (iOS/Android) } print(wind.platform); // 'ios', 'android', 'web', 'macos', etc. ``` For more details on responsive styling using class names, see the [Responsive Design](https://anilcancakir.com/wind_kit/docs/core-concepts/responsive-design.md) guide. --- # Spacing Helpers Wind provides programmatic access to your theme's spacing scale through helper functions and extensions. This allows you to maintain consistency when building custom widgets or manual layouts that aren't using `className` strings. > [!NOTE] > This page covers the **Dart API** for spacing. For CSS-like utility classes such as `p-4`, `m-2`, or `gap-6`, see the [Spacing Layout](https://anilcancakir.com/wind_kit/docs/layout/spacing.md) documentation. - [The wSpacing Function](#wspacing-function) - [Context Extensions](#context-extensions) - [Programmatic Access](#programmatic-access) - [Theme Configuration](#theme-configuration) ## The wSpacing Function The `wSpacing` helper is the primary way to calculate pixel values based on your theme's `baseSpacingUnit` (defaulting to 4px). ```dart import 'package:wind_kit/wind_kit.dart'; // Calculates: multiplier * baseSpacingUnit double padding = wSpacing(context, 4); // 16.0 double gap = wSpacing(context, 2.5); // 10.0 double tight = wSpacing(context, 0.5); // 2.0 ``` ### Signature ```dart double wSpacing(BuildContext context, num multiplier) ``` | Parameter | Type | Description | |:---|:---|:---| | `context` | `BuildContext` | Required to look up the active `WindTheme`. | | `multiplier` | `num` | The scale factor (e.g., 4 in `p-4`). | ## Context Extensions For more ergonomic access, Wind adds the `wSpacingExt` shortcut to `BuildContext`. ```dart Container( padding: EdgeInsets.all(context.wSpacingExt(4)), // 16.0 child: MyWidget(), ) ``` ## Programmatic Access If you need to access the raw spacing unit or the entire theme configuration, you can use the `windThemeData` extension. ```dart // Access the base unit directly final unit = context.windThemeData.baseSpacingUnit; // 4.0 // Use it in complex calculations double customCalc = (unit * 4) + 2.0; ``` ## Theme Configuration The spacing scale is controlled by the `baseSpacingUnit` property in your `WindThemeData`. Changing this value will globally update both your programmatic spacing and all CSS utility classes (like `p-4`). ```dart WindTheme( data: WindThemeData( baseSpacingUnit: 5.0, // 1 unit = 5px ), child: MyApp(), ) ``` With the above configuration: - `wSpacing(context, 4)` returns **20.0** - `WDiv(className: 'p-4')` applies **20.0px** padding. Related Documentation: - [Spacing Layout Utilities](https://anilcancakir.com/wind_kit/docs/layout/spacing.md) - [Theming Guide](https://anilcancakir.com/wind_kit/docs/core-concepts/theming.md) - [Context Extensions](https://anilcancakir.com/wind_kit/docs/utilities/context-extensions.md) --- # Typography Helpers Read the theme's font sizes and weights from Dart with `wFontSize`, `wFontWeight` and their context extensions. - [Introduction](#introduction) - [Global Functions](#global-functions) - [wFontSize](#wfontsize) - [wFontWeight](#wfontweight) - [wInheritedFontSize](#winheritedfontsize) - [BuildContext Extensions](#buildcontext-extensions) - [Theme Scale Access](#theme-scale-access) - [Customizing Scales](#customizing-scales) ## Introduction Wind provides a set of helper functions and extensions to access typography values directly from your Dart code. While utility classes are the primary way to style widgets, these helpers are essential when you need to calculate dimensions, pass styles to standard Material widgets, or build custom components that respect the design system. ## Global Functions These functions provide a safe way to resolve typography values using the current `BuildContext`. ### wFontSize The `wFontSize` function retrieves a pixel value from the theme's font size scale. It accepts the size name (e.g., 'sm', 'lg', '2xl') and returns the corresponding `double`. ```dart double? size = wFontSize(context, 'lg'); // Returns 18.0 (default) ``` ### wFontWeight The `wFontWeight` function resolves a named font weight into a Flutter `FontWeight` object. ```dart FontWeight? weight = wFontWeight(context, 'bold'); // Returns FontWeight.w700 ``` Supported weight names include `thin`, `extralight`, `light`, `normal`, `medium`, `semibold`, `bold`, `extrabold`, and `black`. ### wInheritedFontSize The `wInheritedFontSize` function returns the font size text at `context` inherits: the nearest `DefaultTextStyle`'s size, else Flutter's `kDefaultFontSize` (14), the size unsized text paints at. W-widgets use it to turn an em `tracking-*` into pixels; pass it to `WindStyle.toTextStyle` when you build your own text from a parsed style. ```dart final style = wStyle(context, 'tracking-wide'); final textStyle = style.toTextStyle( inheritedFontSize: wInheritedFontSize(context), ); ``` ## BuildContext Extensions For a more ergonomic API, Wind extends `BuildContext` with shortcut methods. These are wrappers around the global functions and provide a cleaner syntax when working inside a `build` method. ```dart // Font Size shortcut final h1Size = context.wFontSizeExt('4xl'); // Font Weight shortcut final strongWeight = context.wFontWeightExt('bold'); ``` ## Theme Scale Access If you need to iterate over the entire scale or access specific metadata, you can retrieve the `WindThemeData` object directly from the context. ```dart final theme = context.windThemeData; // Access the raw scales Map sizes = theme.fontSizes; Map weights = theme.fontWeights; Map letterSpacing = theme.tracking; // em multipliers Map lineHeights = theme.leading; Map families = theme.fontFamilies; ``` This is particularly useful when building dynamic UI elements like font pickers or settings menus: ```dart // Example: Listing all available font sizes return ListView( children: context.windThemeData.fontSizes.keys.map((name) { return ListTile( title: Text('Size: $name'), trailing: Text('${context.wFontSizeExt(name)}px'), ); }).toList(), ); ``` ## Customizing Scales You can override any part of the typography system when initialising your `WindTheme`. This ensures that your custom values are available through all the helper functions described above. ```dart WindTheme( data: WindThemeData( fontSizes: { 'display': 120.0, 'caption': 10.0, }, fontWeights: { 'heavy': FontWeight.w900, }, ), child: MyApp(), ) ``` Once defined, these custom keys are accessible just like the defaults: ```dart final displaySize = context.wFontSizeExt('display'); // 120.0 ```