|
| 1 | +--- |
| 2 | +name: vgv-ui-package |
| 3 | +description: Best practices for building a Flutter UI package on top of Material — custom components, ThemeExtension-based theming, consistent APIs, and widget tests. Use when user says "create a ui package". Supports app_ui_package template. |
| 4 | +allowed-tools: Edit,mcp__very-good-cli__create |
| 5 | +--- |
| 6 | + |
| 7 | +# UI Package |
| 8 | + |
| 9 | +Best practices for creating a Flutter UI package — a reusable widget library that builds on top of `package:flutter/material.dart`, extending it with app-specific components, custom design tokens via `ThemeExtension`, and a consistent API surface. |
| 10 | + |
| 11 | +> **Theming foundation:** This skill focuses on UI package structure, widget APIs, and testing. For foundational Material 3 theming (`ColorScheme`, `TextTheme`, component themes, spacing constants, light/dark mode), see the **Material Theming** skill (`/vgv-material-theming`). The two skills are complementary — Material Theming covers how to set up and use `ThemeData`; this skill covers how to extend it with `ThemeExtension` tokens and package reusable widgets around it. |
| 12 | +
|
| 13 | +## Core Standards |
| 14 | + |
| 15 | +Apply these standards to ALL UI package work: |
| 16 | + |
| 17 | +- **Build on Material** — depend on `flutter/material.dart` and compose Material widgets; do not rebuild primitives that Material already provides |
| 18 | +- **One widget per file** — each public widget lives in its own file named after the widget in snake_case (e.g., `app_button.dart`) |
| 19 | +- **Barrel file for public API** — expose all public widgets and theme classes through a single barrel file (e.g., `lib/my_ui.dart`) that also re-exports `material.dart` |
| 20 | +- **Extend theming with `ThemeExtension`** — use Material's `ThemeData`, `ColorScheme`, and `TextTheme` as the base (see Material Theming skill); add app-specific tokens (spacing, custom colors) via `ThemeExtension<T>` |
| 21 | +- **Every widget has a corresponding widget test** — behavioral tests verify interactions, callbacks, and state changes |
| 22 | +- **Prefix all public classes** — use a consistent prefix (e.g., `App`, `Vg`) to avoid naming collisions with Material widgets |
| 23 | +- **Use `const` constructors everywhere possible** — all widget constructors must be `const` when feasible |
| 24 | +- **Document every public member** — every public class, constructor parameter, and method has a dartdoc comment |
| 25 | + |
| 26 | +## Package Structure |
| 27 | + |
| 28 | +```text |
| 29 | +my_ui/ |
| 30 | +├── lib/ |
| 31 | +│ ├── my_ui.dart # Barrel file — re-exports material.dart + all public API |
| 32 | +│ └── src/ |
| 33 | +│ ├── theme/ |
| 34 | +│ │ ├── app_theme.dart # AppTheme class with light/dark ThemeData builders |
| 35 | +│ │ ├── app_colors.dart # AppColors ThemeExtension for custom color tokens |
| 36 | +│ │ ├── app_spacing.dart # AppSpacing ThemeExtension for spacing tokens |
| 37 | +│ │ └── app_text_styles.dart # Optional: extra text styles beyond Material's TextTheme |
| 38 | +│ ├── widgets/ |
| 39 | +│ │ ├── app_button.dart |
| 40 | +│ │ ├── app_text_field.dart |
| 41 | +│ │ ├── app_card.dart |
| 42 | +│ │ └── ... |
| 43 | +│ └── extensions/ |
| 44 | +│ └── build_context_extensions.dart # context.appColors, context.appSpacing shortcuts |
| 45 | +├── test/ |
| 46 | +│ ├── src/ |
| 47 | +│ │ ├── theme/ |
| 48 | +│ │ │ └── app_theme_test.dart |
| 49 | +│ │ └── widgets/ |
| 50 | +│ │ ├── app_button_test.dart |
| 51 | +│ │ └── ... |
| 52 | +│ └── helpers/ |
| 53 | +│ └── pump_app.dart # Test helper wrapping widgets in MaterialApp + theme |
| 54 | +├── widgetbook/ # Widgetbook catalog submodule (sandbox + showcase) |
| 55 | +│ └── ... |
| 56 | +└── pubspec.yaml |
| 57 | +``` |
| 58 | + |
| 59 | +## Building Widgets |
| 60 | + |
| 61 | +### Widget API Guidelines |
| 62 | + |
| 63 | +- Compose Material widgets — use `FilledButton`, `OutlinedButton`, `TextField`, `Card`, etc. as building blocks |
| 64 | +- Accept only the minimum required parameters — avoid "kitchen sink" constructors |
| 65 | +- Use named parameters for everything except `key` and `child`/`children` |
| 66 | +- Provide sensible defaults derived from the theme when a parameter is not supplied |
| 67 | +- Expose callbacks with `ValueChanged<T>` or `VoidCallback` — do not use raw `Function` |
| 68 | +- Use `Widget?` for optional slot-based composition (leading, trailing icons, etc.) |
| 69 | + |
| 70 | +## Anti-Patterns |
| 71 | + |
| 72 | +| Anti-Pattern | Correct Approach | |
| 73 | +| ------------ | ---------------- | |
| 74 | +| Rebuilding widgets Material already provides (e.g., custom button from `GestureDetector` + `DecoratedBox`) | Compose Material widgets (`FilledButton`, `OutlinedButton`) and style them | |
| 75 | +| Creating a parallel theme system with custom `InheritedWidget` | Use Material's `ThemeData` as the base and `ThemeExtension` for custom tokens | |
| 76 | +| Hardcoding `Color(0xFF...)` in widget code | Use `Theme.of(context).colorScheme` for standard colors and `context.appColors` for custom tokens | |
| 77 | +| Duplicating Material's `ColorScheme` roles in a custom class | Only create `ThemeExtension` tokens for values Material does not cover (e.g., success, warning, info) | |
| 78 | +| Using `dynamic` or `Object` for callback types | Use `VoidCallback`, `ValueChanged<T>`, or specific function typedefs | |
| 79 | +| Exposing internal implementation files directly | Use a barrel file; keep all files under `src/` private | |
| 80 | + |
| 81 | +## Creating the Package |
| 82 | + |
| 83 | +Use the Very Good CLI MCP tool to scaffold the `app_ui_package`. |
0 commit comments