Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 67 additions & 137 deletions skills/accessibility/references/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,11 @@ import 'package:flutter/material.dart';

/// A rating bar that provides a single semantic description
/// instead of exposing individual star icons.
class AccessibleRatingBar extends StatelessWidget {
const AccessibleRatingBar({
required this.rating,
required this.maxRating,
super.key,
});

final int rating;
final int maxRating;

class const AccessibleRatingBar({
required final int rating,
required final int maxRating,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Semantics(
Expand Down Expand Up @@ -53,18 +48,12 @@ Cupertino widgets ship with weaker semantic defaults than their Material equival
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class AccessibleCupertinoSwitch extends StatelessWidget {
const AccessibleCupertinoSwitch({
required this.label,
required this.value,
required this.onChanged,
super.key,
});

final String label;
final bool value;
final ValueChanged<bool> onChanged;

class const AccessibleCupertinoSwitch({
required final String label,
required final bool value,
required final ValueChanged<bool> onChanged,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Semantics(
Expand All @@ -78,22 +67,14 @@ class AccessibleCupertinoSwitch extends StatelessWidget {
}
}

class AccessibleCupertinoSlider extends StatelessWidget {
const AccessibleCupertinoSlider({
required this.label,
required this.value,
required this.min,
required this.max,
required this.onChanged,
super.key,
});

final String label;
final double value;
final double min;
final double max;
final ValueChanged<double> onChanged;

class const AccessibleCupertinoSlider({
required final String label,
required final double value,
required final double min,
required final double max,
required final ValueChanged<double> onChanged,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Semantics(
Expand Down Expand Up @@ -173,11 +154,8 @@ import 'package:flutter/material.dart';

enum UploadStatus { idle, uploading, success, error }

class UploadStatusIndicator extends StatelessWidget {
const UploadStatusIndicator({required this.status, super.key});

final UploadStatus status;

class const UploadStatusIndicator({required final UploadStatus status, super.key})
extends StatelessWidget {
@override
Widget build(BuildContext context) {
final icon = switch (status) {
Expand Down Expand Up @@ -226,18 +204,12 @@ import 'package:flutter/material.dart';

/// Wraps any small widget in a minimum 48x48 touch target.
/// 48 is the VGV recommended minimum. The WCAG 2.2 AA floor (2.5.8) is 24.
class AccessibleTapTarget extends StatelessWidget {
const AccessibleTapTarget({
required this.onTap,
required this.semanticLabel,
required this.child,
super.key,
});

final VoidCallback onTap;
final String semanticLabel;
final Widget child;

class const AccessibleTapTarget({
required final VoidCallback onTap,
required final String semanticLabel,
required final Widget child,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Semantics(
Expand Down Expand Up @@ -265,16 +237,11 @@ AccessibleTapTarget(
### Dismissible With a Non-Drag Alternative (WCAG 2.2 2.5.7)

```dart
class AccessibleDismissibleListItem extends StatelessWidget {
const AccessibleDismissibleListItem({
required this.item,
required this.onDelete,
super.key,
});

final Item item;
final ValueChanged<Item> onDelete;

class const AccessibleDismissibleListItem({
required final Item item,
required final ValueChanged<Item> onDelete,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Dismissible(
Expand All @@ -298,16 +265,11 @@ class AccessibleDismissibleListItem extends StatelessWidget {
### Reorderable List With Up/Down Buttons (WCAG 2.2 2.5.7)

```dart
class AccessibleReorderableList extends StatelessWidget {
const AccessibleReorderableList({
required this.items,
required this.onReorder,
super.key,
});

final List<Item> items;
final void Function(int oldIndex, int newIndex) onReorder;

class const AccessibleReorderableList({
required final List<Item> items,
required final void Function(int oldIndex, int newIndex) onReorder,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReorderableListView.builder(
Expand Down Expand Up @@ -349,24 +311,15 @@ class AccessibleReorderableList extends StatelessWidget {
Material's `Slider` already responds to arrow keys. Custom slider widgets must do the same.

```dart
class AccessibleSlider extends StatelessWidget {
const AccessibleSlider({
required this.label,
required this.value,
required this.onChanged,
this.min = 0,
this.max = 100,
this.step = 1,
super.key,
});

final String label;
final double value;
final double min;
final double max;
final double step;
final ValueChanged<double> onChanged;

class const AccessibleSlider({
required final String label,
required final double value,
required final ValueChanged<double> onChanged,
final double min = 0,
final double max = 100,
final double step = 1,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
Expand Down Expand Up @@ -450,16 +403,11 @@ class AccessibleForm extends StatelessWidget {
### Focus Not Obscured: ensureVisible on Sticky-Header Layouts (WCAG 2.2 2.4.11)

```dart
class FocusEnsureVisibleField extends StatefulWidget {
const FocusEnsureVisibleField({
required this.label,
required this.stickyHeaderHeight,
super.key,
});

final String label;
final double stickyHeaderHeight;

class const FocusEnsureVisibleField({
required final String label,
required final double stickyHeaderHeight,
super.key,
}) extends StatefulWidget {
@override
State<FocusEnsureVisibleField> createState() => _FocusEnsureVisibleFieldState();
}
Expand Down Expand Up @@ -507,16 +455,11 @@ class _FocusEnsureVisibleFieldState extends State<FocusEnsureVisibleField> {
### Focus Appearance at AAA (WCAG 2.2 2.4.13)

```dart
class FocusAppearanceWrap extends StatefulWidget {
const FocusAppearanceWrap({
required this.onPressed,
required this.child,
super.key,
});

final VoidCallback onPressed;
final Widget child;

class const FocusAppearanceWrap({
required final VoidCallback onPressed,
required final Widget child,
super.key,
}) extends StatefulWidget {
@override
State<FocusAppearanceWrap> createState() => _FocusAppearanceWrapState();
}
Expand Down Expand Up @@ -592,10 +535,8 @@ ThemeData buildAccessibleTheme() {
### Status Indicators Without Color Dependency

```dart
class AccessibleStatusBadge extends StatelessWidget {
const AccessibleStatusBadge({required this.status, super.key});
final TaskStatus status;

class const AccessibleStatusBadge({required final TaskStatus status, super.key})
extends StatelessWidget {
@override
Widget build(BuildContext context) {
final (icon, label, color) = switch (status) {
Expand Down Expand Up @@ -627,16 +568,11 @@ enum TaskStatus { pending, active, complete, error }
### Adaptive Card Layout (Holds at 2x Android, 3x iOS)

```dart
class AdaptiveInfoCard extends StatelessWidget {
const AdaptiveInfoCard({
required this.title,
required this.description,
super.key,
});

final String title;
final String description;

class const AdaptiveInfoCard({
required final String title,
required final String description,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
Expand Down Expand Up @@ -694,12 +630,8 @@ class AccessiblePageRoute<T> extends MaterialPageRoute<T> {
### Hero Animation With Reduced-Motion Support

```dart
class AccessibleHero extends StatelessWidget {
const AccessibleHero({required this.tag, required this.child, super.key});

final Object tag;
final Widget child;

class const AccessibleHero({required final Object tag, required final Widget child, super.key})
extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (MediaQuery.of(context).disableAnimations) {
Expand Down Expand Up @@ -741,10 +673,8 @@ class CheckoutEmailField extends StatelessWidget {
### Accessible Authentication: Allow Paste, Support Password Managers (WCAG 2.2 3.3.8)

```dart
class AccessiblePasswordField extends StatelessWidget {
const AccessiblePasswordField({required this.controller, super.key});
final TextEditingController controller;

class const AccessiblePasswordField({required final TextEditingController controller, super.key})
extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TextField(
Expand Down
8 changes: 3 additions & 5 deletions skills/animations/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Apply these standards to ALL animation work:
- **Use `SingleTickerProviderStateMixin` for one controller** — use `TickerProviderStateMixin` only when the widget owns multiple controllers
- **Keep animated subtrees small** — wrap only the widgets that change inside the animation builder, not entire widget trees
- **Never animate a layout-triggering property** — `width`, `height`, `padding` and `SizedBox` dimensions force a fresh layout pass on every frame, in a one-child tree as much as in a deep one. Animate a `Transform` instead, `Transform.scale` for size and `Transform.translate` for position, or `Opacity` for fade, since those run on the compositing layer and skip layout
- **Dart 3.13 primary constructors** — on the Dart 3.13+ baseline (`very_good_core` 1.6 / `very_good_analysis` 11), declare widget fields as primary-constructor declaring parameters (`class const LabelChip({required final String label, super.key}) extends StatelessWidget`) rather than `this.field`; keep the classic form only below 3.13

---

Expand Down Expand Up @@ -118,11 +119,8 @@ Use implicit animations when the widget rebuilds with new target values. The fra
Compose one `AnimatedFoo` per property when several move together. This is the entry-animation shape — a widget hidden until its data arrives, then fading in and sliding into place:

```dart
class SummaryCard extends StatelessWidget {
const SummaryCard({required this.summary, super.key});

final Summary? summary;

class const SummaryCard({required final Summary? summary, super.key})
extends StatelessWidget {
@override
Widget build(BuildContext context) {
final hasData = summary != null;
Expand Down
17 changes: 5 additions & 12 deletions skills/animations/references/explicit-animations.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,11 @@ Do not start animations in `build()`. Use `initState` for initial playback and `
Expose an optional controller parameter to allow tests to drive the animation directly:

```dart
class PulsingDot extends StatefulWidget {
const PulsingDot({
required this.isActive,
super.key,
@visibleForTesting this.controller,
});

final bool isActive;

@visibleForTesting
final AnimationController? controller;

class const PulsingDot({
required final bool isActive,
super.key,
@visibleForTesting final AnimationController? controller,
}) extends StatefulWidget {
@override
State<PulsingDot> createState() => _PulsingDotState();
}
Expand Down
7 changes: 2 additions & 5 deletions skills/animations/references/implicit-animations.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ entry-animation shape — a widget hidden until its data arrives, then fading in
into place:

```dart
class SummaryCard extends StatelessWidget {
const SummaryCard({required this.summary, super.key});

final Summary? summary;

class const SummaryCard({required final Summary? summary, super.key})
extends StatelessWidget {
@override
Widget build(BuildContext context) {
final hasData = summary != null;
Expand Down
6 changes: 1 addition & 5 deletions skills/animations/references/page-transitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,7 @@ abstract class AppPageTransitions {
path: 'details/:id',
)
@immutable
class DetailsPageRoute extends GoRouteData {
const DetailsPageRoute({required this.id});

final String id;

class const DetailsPageRoute({required final String id}) extends GoRouteData {
@override
Page<void> buildPage(BuildContext context, GoRouterState state) {
return AppPageTransitions.slideFade(
Expand Down
Loading
Loading