Skip to content

Commit 7322ffb

Browse files
authored
Merge pull request #27 from flutter/generate-skills
Add initial set of skills
2 parents 9c1ebd0 + 0cbe39f commit 7322ffb

File tree

22 files changed

+5015
-0
lines changed

22 files changed

+5015
-0
lines changed

resources/flutter_skills.yaml

Lines changed: 1461 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
---
2+
name: "flutter-accessibility"
3+
description: "Configure your Flutter app to support assistive technologies like Screen Readers"
4+
metadata:
5+
model: "models/gemini-3.1-pro-preview"
6+
last_modified: "Tue, 03 Mar 2026 19:03:49 GMT"
7+
8+
---
9+
# flutter-accessibility-and-adaptive-design
10+
11+
## Goal
12+
Implements, audits, and enforces accessibility (a11y) and adaptive design standards in Flutter applications. Ensures compliance with WCAG 2 and EN 301 549 by applying proper semantic roles, contrast ratios, tap target sizes, and assistive technology integrations. Constructs adaptive layouts that respond to available screen space and input modalities (touch, mouse, keyboard) without relying on hardware-specific checks or locked orientations.
13+
14+
## Decision Logic
15+
16+
When implementing UI components, follow this decision tree to determine the required accessibility and adaptive design implementations:
17+
18+
1. **Is the app targeting Flutter Web?**
19+
* **Yes:** Ensure `SemanticsBinding.instance.ensureSemantics();` is called at startup. Explicitly map custom widgets to `SemanticsRole` to generate correct ARIA tags.
20+
* **No:** Proceed to standard mobile/desktop semantics.
21+
2. **Is the widget interactive?**
22+
* **Yes:** Wrap in `Semantics` with `button: true` or appropriate role. Ensure tap target is $\ge$ 48x48 logical pixels.
23+
* **No:** Ensure text contrast meets WCAG standards (4.5:1 for small text, 3.0:1 for large text).
24+
3. **Does the layout need to change based on screen size?**
25+
* **Yes:** Use `LayoutBuilder` or `MediaQuery.sizeOf(context)`. Do NOT use `MediaQuery.orientation` or hardware type checks (e.g., `isTablet`).
26+
* **No:** Use standard flexible widgets (`Expanded`, `Flexible`) to fill available space.
27+
4. **Does the app support desktop/web input?**
28+
* **Yes:** Implement `FocusableActionDetector`, `Shortcuts`, and `MouseRegion` for hover states and keyboard traversal.
29+
* **No:** Focus primarily on touch targets and screen reader traversal order.
30+
31+
## Instructions
32+
33+
1. **Initialize Web Accessibility (If Applicable)**
34+
For web targets, accessibility is disabled by default for performance. Force enable it at the entry point.
35+
```dart
36+
import 'package:flutter/foundation.dart';
37+
import 'package:flutter/semantics.dart';
38+
39+
void main() {
40+
runApp(const MyApp());
41+
if (kIsWeb) {
42+
SemanticsBinding.instance.ensureSemantics();
43+
}
44+
}
45+
```
46+
47+
2. **Apply Semantic Annotations**
48+
Use `Semantics`, `MergeSemantics`, and `ExcludeSemantics` to build a clean accessibility tree. For custom web components, explicitly define the `SemanticsRole`.
49+
```dart
50+
Semantics(
51+
role: SemanticsRole.button,
52+
label: 'Submit Form',
53+
hint: 'Press to send your application',
54+
button: true,
55+
child: GestureDetector(
56+
onTap: _submit,
57+
child: const CustomButtonUI(),
58+
),
59+
)
60+
```
61+
62+
3. **Enforce Tap Target and Contrast Standards**
63+
Ensure all interactive elements meet the 48x48 dp minimum (Android) or 44x44 pt minimum (iOS/Web).
64+
```dart
65+
// Example of enforcing minimum tap target size
66+
ConstrainedBox(
67+
constraints: const BoxConstraints(
68+
minWidth: 48.0,
69+
minHeight: 48.0,
70+
),
71+
child: IconButton(
72+
icon: const Icon(Icons.info),
73+
onPressed: () {},
74+
tooltip: 'Information', // Tooltip.message follows Tooltip.child in semantics tree
75+
),
76+
)
77+
```
78+
79+
4. **Implement Adaptive Layouts**
80+
Use `LayoutBuilder` to respond to available space rather than device type.
81+
```dart
82+
LayoutBuilder(
83+
builder: (context, constraints) {
84+
if (constraints.maxWidth > 600) {
85+
return const WideDesktopLayout();
86+
} else {
87+
return const NarrowMobileLayout();
88+
}
89+
},
90+
)
91+
```
92+
93+
5. **Implement Keyboard and Mouse Support**
94+
Use `FocusableActionDetector` for custom controls to handle focus, hover, and keyboard shortcuts simultaneously.
95+
```dart
96+
FocusableActionDetector(
97+
onFocusChange: (hasFocus) => setState(() => _hasFocus = hasFocus),
98+
onShowHoverHighlight: (hasHover) => setState(() => _hasHover = hasHover),
99+
actions: <Type, Action<Intent>>{
100+
ActivateIntent: CallbackAction<Intent>(
101+
onInvoke: (intent) {
102+
_performAction();
103+
return null;
104+
},
105+
),
106+
},
107+
child: MouseRegion(
108+
cursor: SystemMouseCursors.click,
109+
child: CustomWidget(isHovered: _hasHover, isFocused: _hasFocus),
110+
),
111+
)
112+
```
113+
114+
6. **Manage Focus Traversal**
115+
Group related widgets using `FocusTraversalGroup` to ensure logical tab order for keyboard users.
116+
```dart
117+
FocusTraversalGroup(
118+
policy: OrderedTraversalPolicy(),
119+
child: Column(
120+
children: [
121+
FocusTraversalOrder(
122+
order: const NumericFocusOrder(1.0),
123+
child: TextField(),
124+
),
125+
FocusTraversalOrder(
126+
order: const NumericFocusOrder(2.0),
127+
child: ElevatedButton(onPressed: () {}, child: Text('Submit')),
128+
),
129+
],
130+
),
131+
)
132+
```
133+
134+
7. **Validate Accessibility via Automated Tests**
135+
**STOP AND ASK THE USER:** "Would you like me to generate widget tests to validate accessibility guidelines (contrast, tap targets) for your UI?"
136+
If yes, implement the `AccessibilityGuideline` API in the test suite:
137+
```dart
138+
import 'package:flutter_test/flutter_test.dart';
139+
140+
void main() {
141+
testWidgets('Validates a11y guidelines', (WidgetTester tester) async {
142+
final SemanticsHandle handle = tester.ensureSemantics();
143+
await tester.pumpWidget(const MyApp());
144+
145+
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
146+
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
147+
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
148+
await expectLater(tester, meetsGuideline(textContrastGuideline));
149+
150+
handle.dispose();
151+
});
152+
}
153+
```
154+
155+
## Constraints
156+
157+
* **Never lock device orientation.** Apps must support both portrait and landscape modes to comply with accessibility standards.
158+
* **Never use hardware type checks** (e.g., checking if the device is a phone or tablet) for layout decisions. Always use `MediaQuery.sizeOf` or `LayoutBuilder`.
159+
* **Never use `MediaQuery.orientation`** near the top of the widget tree to switch layouts. Rely on available width/height breakpoints.
160+
* **Always provide `Semantics` labels** for custom interactive widgets or images that convey meaning.
161+
* **Always use `PageStorageKey`** on scrollable lists that do not change layout during orientation shifts to preserve scroll state.
162+
* **Do not consume infinite horizontal space** for text fields or lists on large screens; constrain maximum widths for readability.
163+
* **Account for Tooltip semantics order:** `Tooltip.message` is visited immediately *after* `Tooltip.child` in the semantics tree. Update tests accordingly.

skills/flutter-animation/SKILL.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
---
2+
name: "flutter-animation"
3+
description: "Add animated effects to your Flutter app"
4+
metadata:
5+
model: "models/gemini-3.1-pro-preview"
6+
last_modified: "Mon, 02 Mar 2026 21:40:10 GMT"
7+
8+
---
9+
# Flutter Animations Implementation
10+
11+
## Goal
12+
Implements and manages Flutter animations, selecting the appropriate animation strategy (implicit, explicit, tween, physics, hero, or staggered) based on UI requirements. Assumes a working Flutter environment, stateful/stateless widget competence, and a standard widget tree structure.
13+
14+
## Instructions
15+
16+
### 1. Determine Animation Strategy (Decision Logic)
17+
Evaluate the UI requirement using the following decision tree to select the correct animation approach:
18+
1. **Is the animation a simple property change (color, size, alignment) on a single widget?**
19+
* YES: Use **Implicit Animations** (e.g., `AnimatedContainer`).
20+
* NO: Proceed to 2.
21+
2. **Does the animation model real-world movement (springs, gravity, velocity)?**
22+
* YES: Use **Physics-based animation** (`SpringSimulation`, `animateWith`).
23+
* NO: Proceed to 3.
24+
3. **Does the animation involve a widget flying between two different screens/routes?**
25+
* YES: Use **Hero Animations** (`Hero` widget).
26+
* NO: Proceed to 4.
27+
4. **Does the animation involve multiple sequential or overlapping movements?**
28+
* YES: Use **Staggered Animations** (Single `AnimationController` with multiple `Tween`s and `Interval` curves).
29+
* NO: Use **Standard Explicit Animations** (`AnimationController`, `Tween`, `AnimatedBuilder` / `AnimatedWidget`).
30+
31+
**STOP AND ASK THE USER:** If the requirement is ambiguous, pause and ask the user to clarify the desired visual effect before writing implementation code.
32+
33+
### 2. Implement Implicit Animations
34+
For simple transitions between values, use implicit animation widgets. Do not manually manage state or controllers.
35+
36+
```dart
37+
AnimatedContainer(
38+
duration: const Duration(milliseconds: 500),
39+
curve: Curves.bounceIn,
40+
width: _isExpanded ? 200.0 : 100.0,
41+
height: _isExpanded ? 200.0 : 100.0,
42+
decoration: BoxDecoration(
43+
color: _isExpanded ? Colors.green : Colors.blue,
44+
borderRadius: BorderRadius.circular(_isExpanded ? 50.0 : 8.0),
45+
),
46+
child: const FlutterLogo(),
47+
)
48+
```
49+
50+
### 3. Implement Explicit Animations (Tween-based)
51+
When you need to control the animation (play, pause, reverse), use an `AnimationController` with a `Tween`. Separate the transition rendering from the state using `AnimatedBuilder`.
52+
53+
```dart
54+
class _MyAnimatedWidgetState extends State<MyAnimatedWidget> with SingleTickerProviderStateMixin {
55+
late AnimationController _controller;
56+
late Animation<double> _animation;
57+
58+
@override
59+
void initState() {
60+
super.initState();
61+
_controller = AnimationController(
62+
duration: const Duration(seconds: 2),
63+
vsync: this,
64+
);
65+
66+
_animation = Tween<double>(begin: 0, end: 300).animate(
67+
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
68+
)..addStatusListener((status) {
69+
if (status == AnimationStatus.completed) {
70+
_controller.reverse();
71+
} else if (status == AnimationStatus.dismissed) {
72+
_controller.forward();
73+
}
74+
});
75+
76+
_controller.forward();
77+
}
78+
79+
@override
80+
void dispose() {
81+
_controller.dispose(); // STRICT REQUIREMENT
82+
super.dispose();
83+
}
84+
85+
@override
86+
Widget build(BuildContext context) {
87+
return AnimatedBuilder(
88+
animation: _animation,
89+
builder: (context, child) {
90+
return SizedBox(
91+
height: _animation.value,
92+
width: _animation.value,
93+
child: child,
94+
);
95+
},
96+
child: const FlutterLogo(), // Passed as child for performance
97+
);
98+
}
99+
}
100+
```
101+
102+
### 4. Implement Page Route Transitions
103+
To animate transitions between routes, use `PageRouteBuilder` and chain a `CurveTween` with a `Tween<Offset>`.
104+
105+
```dart
106+
Route<void> _createRoute() {
107+
return PageRouteBuilder(
108+
pageBuilder: (context, animation, secondaryAnimation) => const DestinationPage(),
109+
transitionsBuilder: (context, animation, secondaryAnimation, child) {
110+
const begin = Offset(0.0, 1.0);
111+
const end = Offset.zero;
112+
const curve = Curves.ease;
113+
114+
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
115+
116+
return SlideTransition(
117+
position: animation.drive(tween),
118+
child: child,
119+
);
120+
},
121+
);
122+
}
123+
```
124+
125+
### 5. Implement Physics-Based Animations
126+
For realistic motion (e.g., snapping back after a drag), calculate velocity and apply a `SpringSimulation`.
127+
128+
```dart
129+
void _runSpringAnimation(Offset pixelsPerSecond, Size size, Alignment dragAlignment) {
130+
_animation = _controller.drive(
131+
AlignmentTween(begin: dragAlignment, end: Alignment.center),
132+
);
133+
134+
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
135+
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
136+
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
137+
final unitVelocity = unitsPerSecond.distance;
138+
139+
const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1);
140+
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
141+
142+
_controller.animateWith(simulation);
143+
}
144+
```
145+
146+
### 6. Implement Hero Animations (Shared Element)
147+
To fly a widget between routes, wrap the identical widget tree in both routes with a `Hero` widget using the exact same `tag`.
148+
149+
```dart
150+
// Source Route
151+
Hero(
152+
tag: 'unique-photo-tag',
153+
child: Image.asset('photo.png', width: 100),
154+
)
155+
156+
// Destination Route
157+
Hero(
158+
tag: 'unique-photo-tag',
159+
child: Image.asset('photo.png', width: 300),
160+
)
161+
```
162+
163+
### 7. Implement Staggered Animations
164+
For sequential or overlapping animations, use a single `AnimationController` and define multiple `Tween`s with `Interval` curves.
165+
166+
```dart
167+
class StaggerAnimation extends StatelessWidget {
168+
StaggerAnimation({super.key, required this.controller}) :
169+
opacity = Tween<double>(begin: 0.0, end: 1.0).animate(
170+
CurvedAnimation(
171+
parent: controller,
172+
curve: const Interval(0.0, 0.100, curve: Curves.ease),
173+
),
174+
),
175+
width = Tween<double>(begin: 50.0, end: 150.0).animate(
176+
CurvedAnimation(
177+
parent: controller,
178+
curve: const Interval(0.125, 0.250, curve: Curves.ease),
179+
),
180+
);
181+
182+
final AnimationController controller;
183+
final Animation<double> opacity;
184+
final Animation<double> width;
185+
186+
@override
187+
Widget build(BuildContext context) {
188+
return AnimatedBuilder(
189+
animation: controller,
190+
builder: (context, child) {
191+
return Opacity(
192+
opacity: opacity.value,
193+
child: Container(width: width.value, height: 50, color: Colors.blue),
194+
);
195+
},
196+
);
197+
}
198+
}
199+
```
200+
201+
### 8. Validate-and-Fix Loop
202+
After generating animation code, verify the following:
203+
1. Does the `State` class use `SingleTickerProviderStateMixin` (or `TickerProviderStateMixin` for multiple controllers)?
204+
2. Is `_controller.dispose()` explicitly called in the `dispose()` method?
205+
3. If using `AnimatedBuilder`, is the static widget passed to the `child` parameter rather than rebuilt inside the `builder` function?
206+
If any of these are missing, fix the code immediately before presenting it to the user.
207+
208+
## Constraints
209+
* **Strict Disposal:** You MUST include `dispose()` methods for all `AnimationController` instances to prevent memory leaks.
210+
* **No URLs:** Do not include external links or URLs in the output or comments.
211+
* **Immutability:** Treat `Tween` and `Curve` classes as stateless and immutable. Do not attempt to mutate them after instantiation.
212+
* **Performance:** Always use `AnimatedBuilder` or `AnimatedWidget` instead of calling `setState()` inside a controller's `addListener` when building complex widget trees.
213+
* **Hero Tags:** Hero tags MUST be identical and unique per route transition. Do not use generic tags like `'image'` if multiple heroes exist.

0 commit comments

Comments
 (0)