# Flutter AI rules — UI: theming, layout and overlays Material theming, WidgetStateProperty, layout, and overlays. _Verbatim from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md), split across four files only to keep each under the repo's 250-line cap. Re-fetch periodically to stay current; do not hand-edit._ * **Responsiveness:** Use `LayoutBuilder` or `MediaQuery` to create responsive UIs. * **Text:** Use `Theme.of(context).textTheme` for text styles. * **Text Fields:** Configure `textCapitalization`, `keyboardType`, and `placeholder`. ### Material Theming Best Practices #### Embrace `ThemeData` and Material 3 * **Use `ColorScheme.fromSeed()`:** Use this to generate a complete, harmonious color palette for both light and dark modes from a single seed color. * **Define Light and Dark Themes:** Provide both `theme` and `darkTheme` to your `MaterialApp` to support system brightness settings seamlessly. * **Centralize Component Styles:** Customize specific component themes (e.g., `elevatedButtonTheme`, `cardTheme`, `appBarTheme`) within `ThemeData` to ensure consistency. * **Dark/Light Mode and Theme Toggle:** Implement support for both light and dark themes using `theme` and `darkTheme` properties of `MaterialApp`. The `themeMode` property can be dynamically controlled (e.g., via a `ChangeNotifierProvider`) to allow for toggling between `ThemeMode.light`, `ThemeMode.dark`, or `ThemeMode.system`. ```dart // main.dart MaterialApp( theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.deepPurple, brightness: Brightness.light, ), textTheme: const TextTheme( displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold), bodyMedium: TextStyle(fontSize: 14.0, height: 1.4), ), ), darkTheme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.deepPurple, brightness: Brightness.dark, ), ), home: const MyHomePage(), ); ``` #### Implement Design Tokens with `ThemeExtension` For custom styles that aren't part of the standard `ThemeData`, use `ThemeExtension` to define reusable design tokens. * **Create a Custom Theme Extension:** Define a class that extends `ThemeExtension` and include your custom properties. * **Implement `copyWith` and `lerp`:** These methods are required for the extension to work correctly with theme transitions. * **Register in `ThemeData`:** Add your custom extension to the `extensions` list in your `ThemeData`. * **Access Tokens in Widgets:** Use `Theme.of(context).extension()!` to access your custom tokens. ```dart // 1. Define the extension @immutable class MyColors extends ThemeExtension { const MyColors({required this.success, required this.danger}); final Color? success; final Color? danger; @override ThemeExtension copyWith({Color? success, Color? danger}) { return MyColors(success: success ?? this.success, danger: danger ?? this.danger); } @override ThemeExtension lerp(ThemeExtension? other, double t) { if (other is! MyColors) return this; return MyColors( success: Color.lerp(success, other.success, t), danger: Color.lerp(danger, other.danger, t), ); } } // 2. Register it in ThemeData theme: ThemeData( extensions: const >[ MyColors(success: Colors.green, danger: Colors.red), ], ), // 3. Use it in a widget Container( color: Theme.of(context).extension()!.success, ) ``` #### Styling with `WidgetStateProperty` * **`WidgetStateProperty.resolveWith`:** Provide a function that receives a `Set` and returns the appropriate value for the current state. * **`WidgetStateProperty.all`:** A shorthand for when the value is the same for all states. ```dart // Example: Creating a button style that changes color when pressed. final ButtonStyle myButtonStyle = ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith( (Set states) { if (states.contains(WidgetState.pressed)) { return Colors.green; // Color when pressed } return Colors.red; // Default color }, ), ); ``` ### Layout Best Practices #### Building Flexible and Overflow-Safe Layouts ##### For Rows and Columns * **`Expanded`:** Use to make a child widget fill the remaining available space along the main axis. * **`Flexible`:** Use when you want a widget to shrink to fit, but not necessarily grow. Don't combine `Flexible` and `Expanded` in the same `Row` or `Column`. * **`Wrap`:** Use when you have a series of widgets that would overflow a `Row` or `Column`, and you want them to move to the next line. ##### For General Content * **`SingleChildScrollView`:** Use when your content is intrinsically larger than the viewport, but is a fixed size. * **`ListView` / `GridView`:** For long lists or grids of content, always use a builder constructor (`.builder`). * **`FittedBox`:** Use to scale or fit a single child widget within its parent. * **`LayoutBuilder`:** Use for complex, responsive layouts to make decisions based on the available space. #### Layering Widgets with Stack * **`Positioned`:** Use to precisely place a child within a `Stack` by anchoring it to the edges. * **`Align`:** Use to position a child within a `Stack` using alignments like `Alignment.center`. #### Advanced Layout with Overlays * **`OverlayPortal`:** Use this widget to show UI elements (like custom dropdowns or tooltips) "on top" of everything else. It manages the `OverlayEntry` for you. ```dart class MyDropdown extends StatefulWidget { const MyDropdown({super.key}); @override State createState() => _MyDropdownState(); } class _MyDropdownState extends State { final _controller = OverlayPortalController(); @override Widget build(BuildContext context) { return OverlayPortal( controller: _controller, overlayChildBuilder: (BuildContext context) { return const Positioned( top: 50, left: 10, child: Card( child: Padding( padding: EdgeInsets.all(8.0), child: Text('I am an overlay!'), ), ), ); }, child: ElevatedButton( onPressed: _controller.toggle, child: const Text('Toggle Overlay'), ), ); } } ``` ### Color Scheme Best Practices