-
Notifications
You must be signed in to change notification settings - Fork 0
Widget Previews
Flutter 3.47 introduces a native widget preview system that lets you preview widgets inline in your IDE without launching a device. This page explains how to use LayrzPreviewTheme with the @Preview annotation.
The widget preview system is built into Flutter 3.47. layrz_ui provides LayrzPreviewTheme, a bridge between the Flutter preview system and the layrz_ui theme.
With previews, you can:
- See widgets render in real time as you write code
- Use multiple preview variants (light, dark, edge cases)
- Develop faster by skipping device/simulator launches for quick visual checks
Flutter 3.47's preview system uses the @Preview annotation (from package:flutter/widget_previews.dart):
import 'package:flutter/widget_previews.dart';
@Preview(
name: 'Light',
theme: LayrzPreviewTheme.light,
)
Widget previewMyWidget() {
return MyWidget();
}-
name— User-friendly label shown in the IDE (e.g., "Light", "Large Text"). -
theme— A tear-off that returns aPreviewThemeData. Must be a function reference, not a function call. For layrz_ui, always useLayrzPreviewTheme.light. -
group(optional) — Organizes multiple previews into collapsible groups. -
size(optional) — Canvas size (default is device size). -
textScaleFactor(optional) — Text scaling multiplier for accessibility testing.
import 'package:flutter/widget_previews.dart';
import 'package:layrz_ui/preview.dart';
@Preview(
name: 'Default',
theme: LayrzPreviewTheme.light,
)
Widget previewLayrzButton() {
return LayrzButton(
onTap: () {},
child: const Text('Click me'),
);
}You can define multiple @Preview annotations on different functions to show different variants:
@Preview(
name: 'Default State',
theme: LayrzPreviewTheme.light,
)
Widget previewButtonDefault() {
return LayrzButton(
onTap: () {},
child: const Text('Default'),
);
}
@Preview(
name: 'Disabled State',
theme: LayrzPreviewTheme.light,
group: 'States',
)
Widget previewButtonDisabled() {
return LayrzButton(
onTap: null, // Disabled
child: const Text('Disabled'),
);
}
@Preview(
name: 'Large Text',
theme: LayrzPreviewTheme.light,
textScaleFactor: 1.5,
group: 'A11y',
)
Widget previewButtonLargeText() {
return LayrzButton(
onTap: () {},
child: const Text('Large'),
);
}Always preview edge cases to catch layout issues early:
@Preview(
name: 'Long Text',
theme: LayrzPreviewTheme.light,
size: Size(300, 100), // Narrow canvas
)
Widget previewButtonLongText() {
return LayrzButton(
onTap: () {},
child: const Text('This is a very long button label that might wrap'),
);
}
@Preview(
name: 'Empty State',
theme: LayrzPreviewTheme.light,
)
Widget previewButtonEmpty() {
return LayrzButton(
onTap: () {},
child: const Text(''),
);
}LayrzPreviewTheme extends Flutter's PreviewThemeData interface. When you pass LayrzPreviewTheme.light to @Preview, the IDE:
- Calls
LayrzPreviewTheme.light()to get aPreviewThemeDatainstance - Calls
apply(BuildContext, Widget)to wrap your preview widget with the layrz_ui theme
Under the hood, LayrzPreviewTheme.apply() does this:
@override
Widget apply(BuildContext context, Widget child) {
return LayrzTheme(
data: data,
child: DefaultTextStyle(
style: data.textStyle,
child: IconTheme(
data: data.iconTheme,
child: ColoredBox(
color: data.backgroundColor,
child: child,
),
),
),
);
}This nesting matches what LayrzApp does, so preview widgets see the same theme context as production code.
LayrzPreviewTheme.light is the only variant. Dark mode is out of scope per decision D7. If you need to preview dark variants, implement your own DarkPreviewTheme or wait for dark mode support.
The theme: parameter must be a tear-off (function reference), not a function call:
// CORRECT
@Preview(theme: LayrzPreviewTheme.light)
// WRONG (will not compile)
@Preview(theme: LayrzPreviewTheme.light())LayrzPreviewTheme extends PreviewThemeData because the SDK declares PreviewThemeData as an abstract base class. You cannot implements a base class; you must extends it. This is a Flutter 3.47 constraint, not a layrz_ui quirk.
Do not wrap your preview widget in another widget (e.g., LayrzApp, Scaffold). The preview system and LayrzPreviewTheme.apply() handle the wrapping:
// CORRECT
@Preview(...)
Widget previewMyButton() => LayrzButton(...);
// WRONG (double-wrapped)
@Preview(...)
Widget previewMyButton() => LayrzApp(home: LayrzButton(...));- Open a Dart file with
@Previewannotations. - Click the "Preview" button above the annotation (if visible).
- A canvas opens showing the widget in real time.
- Changes to the code update the preview immediately (hot reload).
flutter test --previewThis runs all preview functions and generates a report. Useful for CI/CD pipelines to verify all previews compile.
- Add previews to all visual widgets — Helpers, data classes, and pure utilities do not need previews.
- One preview per visual state — Show default, disabled, error, loading states as separate previews.
- Test edge cases — Empty text, long text, narrow containers, high text scale.
-
Use preview groups — Organize variants with
group:parameter. - Keep preview functions simple — Avoid complex logic; show the widget with realistic props.
- Document tricky edge cases — If a preview shows a workaround or edge case, add a comment.
// lib/buttons/src/button.dart
import 'package:flutter/widget_previews.dart';
import 'package:layrz_ui/preview.dart';
class LayrzButton extends StatelessWidget {
const LayrzButton({
required this.child,
this.onTap,
});
final Widget child;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
// ...
),
);
}
}
// Preview: Default state
@Preview(
name: 'Default',
theme: LayrzPreviewTheme.light,
group: 'States',
)
Widget previewLayrzButtonDefault() {
return LayrzButton(
onTap: () {},
child: const Text('Click me'),
);
}
// Preview: Disabled state
@Preview(
name: 'Disabled',
theme: LayrzPreviewTheme.light,
group: 'States',
)
Widget previewLayrzButtonDisabled() {
return LayrzButton(
onTap: null,
child: const Text('Disabled'),
);
}
// Preview: Long text (edge case)
@Preview(
name: 'Long Text',
theme: LayrzPreviewTheme.light,
size: Size(250, 100),
group: 'Edge Cases',
)
Widget previewLayrzButtonLongText() {
return LayrzButton(
onTap: () {},
child: const Text('This is a very long button label that wraps'),
);
}
// Preview: High text scale (accessibility)
@Preview(
name: 'Large Text (200%)',
theme: LayrzPreviewTheme.light,
textScaleFactor: 2.0,
group: 'A11y',
)
Widget previewLayrzButtonLargeText() {
return LayrzButton(
onTap: () {},
child: const Text('Accessible'),
);
}-
Check Flutter version: Previews require Flutter 3.47+. Run
flutter --version. -
Verify imports: Must import
package:flutter/widget_previews.dartandpackage:layrz_ui/preview.dart. -
Check @Preview format: Ensure
theme:is a tear-off (LayrzPreviewTheme.light, notLayrzPreviewTheme.light()). - Restart IDE: Sometimes the IDE's analyzer needs to reindex. Restart VS Code or Android Studio.
This error means the Dart SDK version is too old. Previews require Dart 3.0+. Update your SDK.
-
Did you use
LayrzPreviewTheme.light()? — Remove the(). It should beLayrzPreviewTheme.light(tear-off). -
Is the preview function returning a widget? — Ensure it returns a
Widget, not void.
- Flutter Widget Previews (Official) — Official Flutter documentation.
-
Theming page — Learn about
LayrzThemeDataand theme customization. -
LayrzApp page — See how
LayrzAppsets up the theme system. - CLAUDE.md Rule #3 — Contributor rule for adding previews to new widgets.
Last updated: 2026-08-14
Related pages: Theming, LayrzApp, Getting-Started
Made with ❤️ by Golden M, Inc.