Widget Previews in Flutter: Using the @Preview Annotation
The Flutter Widget Previewer and @Preview annotation let you iterate on widgets without running the whole app. Setup, usage patterns, and tips.
Published on • October 15, 2026
AI Assistant

Building a widget and seeing it render shouldn’t require launching the whole app, navigating to the right screen, and faking the right state. The Flutter Widget Previewer — stable as of Flutter 3.47 — closes that loop: annotate a widget, and it renders live in a dedicated panel in your IDE or browser, isolated from the rest of your app, with hot reload and theme controls built in.
The whole workflow centers on one annotation: @Preview, from package:flutter/widget_previews.dart.
Opening the previewer
Android Studio, IntelliJ, and VS Code all start the Widget Previewer automatically on launch. Open the Flutter Widget Preview tab in the IDE sidebar to see it. From the command line, run:
flutter widget-preview start
from your project root. This launches a local server and opens the preview environment in your browser. The previewer caches project builds in a .widget_preview/ folder in your project root to speed up subsequent startups.
Annotating widgets
@Preview can be applied to:
- Top-level functions returning a
WidgetorWidgetBuilder - Static methods within a class returning a
WidgetorWidgetBuilder - Public Widget constructors and factories with no required arguments
The minimal example:
import 'package:flutter/material.dart';
import 'package:flutter/widget_previews.dart';
@Preview(name: 'My Sample Text')
Widget mySampleText() => const Text('Hello, World!');
Each preview instance gets controls for zoom in/out, reset zoom, toggling light/dark theme, and performing a hot restart for that individual preview. A global hot restart button (bottom right of the environment) restarts everything — useful when global state like a static initializer has changed.
Customizing a preview
The annotation takes several parameters for shaping the preview context:
name— a descriptive label in the previewer UIgroup— groups related previews togethersize— artificial size constraints via aSizeobjecttextScaleFactor— a custom font scalewrapper— a function wrapping your widget in a surrounding tree (e.g. injecting anInheritedWidget)theme— a function returningPreviewThemeDatafor custom theming, with sequential theme layering for matrix testingbrightness— the initial theme brightnesslocalizations— a localization configuration
A practical combination for a form widget:
@Preview(
name: 'Submit Button',
group: 'Form Controls',
size: Size(360, 200),
brightness: Brightness.dark,
)
Widget submitButtonPreview() => const SubmitButton();
You can also stack multiple @Preview annotations on a single function to generate several configurations at once — a common pattern for light/dark or text-scale variants:
@Preview(group: 'Brightness', name: 'Example - light', brightness: Brightness.light)
@Preview(group: 'Brightness', name: 'Example - dark', brightness: Brightness.dark)
Widget buttonPreview() => const ButtonShowcase();
Custom preview annotations
To cut boilerplate across a team, extend the Preview annotation class itself. The Flutter docs show a custom annotation that injects Material theming:
final class const MyCustomPreview({
super.name,
super.group,
super.size,
super.textScaleFactor,
super.wrapper,
super.brightness,
super.localizations,
}) extends Preview {
this : super(theme: PreviewMaterialThemeData.new);
}
final class const PreviewMaterialThemeData() extends PreviewThemeData {
@override
Widget apply(BuildContext context, Widget child) => Theme(
data: .from(colorScheme: .fromSeed(seedColor: Colors.lime)),
child: child,
);
}
(Note the dot shorthands — .from(...) and .fromSeed(...) — Dart’s own syntax sugar for context-typed static member access.)
For previews that need computation beyond a constant context, override Preview.transform(). It’s invoked by the previewer at runtime and returns a modified Preview built through PreviewBuilder:
@override
Preview transform() {
final originalPreview = super.transform();
final builder = originalPreview.toBuilder();
builder
..name = 'Transformed - ${originalPreview.name}'
..theme = _themeBuilder;
return builder.build();
}
For creating multiple related previews programmatically, extend MultiPreview and override its previews getter (and optionally transform()).
Searching and filtering
As projects accumulate previews, navigation matters. The previewer’s search bar filters in real time against:
- Preview name — the
nameparameter of@Preview - Group name — the
groupparameter - Containing script — the URI of the Dart file holding the preview
- Containing package — the package name
In IDEs, there’s also a Filter previews by selected file toggle at the bottom of the environment — previews update as you move between files, so the previewer shows what you’re currently editing.
Restrictions and limitations
The previewer is built on Flutter Web, which shapes what it can and can’t do:
- No native plugins or
dart:io/dart:ffi. Widgets with transitive dependencies on these libraries load fine, but any API invocation from them throws. Web plugins may work in a browser but aren’t guaranteed inside the IDE. The docs recommend structuring code with conditional imports to isolate platform-specific code. - Asset paths must be package-based. When loading resources with
fromAssetAPIs fromdart:ui, usepackages/my_package_name/assets/my_image.pngrather than a direct local path, so the previewer’s web environment can locate them. For web widgets, assets from your project’sweb/directory are synchronized automatically. - Public, constant callback arguments. All callback arguments to preview annotations must be public and constant for the previewer’s code generation to work.
- Unconstrained widgets get constrained. Widgets without explicit constraints are clamped to roughly half the previewer’s height and width. The docs note this behavior may change, so set constraints via the
sizeparameter when precision matters. - Single-project scope. The previewer currently only shows previews from one project or Pub workspace per IDE session (tracked in flutter#173550).
Workflow tips
- Treat previews as living documentation. A
@Preview(name: 'Empty state', group: 'ProductCard')next to the widget tells more than a comment. - Use
wrapperto inject real state. A preview that exercises the widget’s error or loading branch is worth ten that only show the happy path. - Group aggressively.
groupplus the search bar is how you navigate a hundred previews without pain. - Use the per-preview hot restart when iterating on a single widget, and the global one only when static/global state has changed — it’s faster.
Sources
- Flutter Widget Previewer — official guide covering setup, the
@Previewannotation, customization, and restrictions (stable as of Flutter 3.47). - Preview class API — the annotation’s full parameter reference.