Skip to content
Blog

Flutter Widget Previewer: Visual Development Goes Mainstream

The Widget Previewer is now stable in Flutter 3.47. Learn how to use @Preview annotations to see widgets rendered in real-time without running your full app.

Published on September 15, 2026

AI Assistant

The Widget Previewer reaches stable in Flutter 3.47, fundamentally changing how you develop widgets. Instead of running your entire app to see a single widget, you annotate it with @Preview and see it rendered instantly in your IDE.

What is the Widget Previewer?

The Widget Previewer is a development tool that renders individual widgets in isolation. You add a simple annotation, and your IDE shows a live preview of that widget — no full app build required.

import 'package:flutterPreview/flutter_preview.dart';

@Preview(name: 'Primary Button')
class PrimaryButtonPreview extends PreviewWidget {
  @override
  Widget build(BuildContext context) {
    return PrimaryButton(
      label: 'Submit',
      onPressed: () {},
    );
  }
}

Setting up Widget Previewer

Enable the Widget Previewer in your Flutter project:

# pubspec.yaml
dev_dependencies:
  flutter_test: any
  flutter_preview: ^1.0.0
# Install dependencies
flutter pub get

# Enable previewer in VS Code settings
# Settings → Extensions → Flutter → Widget Previewer: Enable

The @Preview annotation

The @Preview annotation is the entry point for widget previews:

@Preview(
  name: 'Login Form',
  device: PreviewDevice.iphone14,
  theme: PreviewTheme.dark,
)
class LoginFormPreview extends PreviewWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: Scaffold(
        body: LoginForm(),
      ),
    );
  }
}

Preview configurations

You can configure previews with different devices, themes, and text scales:

@Preview(name: 'Mobile Light', device: PreviewDevice.pixel7)
class MobileLightPreview extends PreviewWidget { ... }

@Preview(name: 'Tablet Dark', device: PreviewDevice.ipadPro, theme: PreviewTheme.dark)
class TabletDarkPreview extends PreviewWidget { ... }

@Preview(name: 'Large Text', textScale: 2.0)
class LargeTextPreview extends PreviewWidget { ... }

Multiple previews per file

A single file can contain multiple preview widgets:

@Preview(name: 'Default')
class ButtonDefault extends PreviewWidget { ... }

@Preview(name: 'Disabled')
class ButtonDisabled extends PreviewWidget { ... }

@Preview(name: 'Loading')
class ButtonLoading extends PreviewWidget { ... }

The previewer shows all three states in a grid, making visual regression testing trivial.

Hot restart per preview

Each preview has its own isolated hot restart. Changes to a widget rebuild only that preview, not the entire preview grid:

// Editing this widget only restarts its preview
@Preview(name: 'Card')
class CardPreview extends PreviewWidget {
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Text('Updated content'), // Hot restart affects only this preview
      ),
    );
  }
}

Light/dark mode toggle

The previewer includes a built-in light/dark mode toggle. Your previews automatically adapt to the current theme:

@Preview(name: 'Themed Widget')
class ThemedPreview extends PreviewWidget {
  @override
  Widget build(BuildContext context) {
    // This widget responds to the previewer's theme toggle
    return Container(
      color: Theme.of(context).colorScheme.surface,
      child: Text(
        'Hello',
        style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
      ),
    );
  }
}

Search and filter

With many previews, the search and filter system becomes essential:

// Tag your previews for filtering
@Preview(name: 'Button', tags: ['form', 'primary'])
class ButtonPreview extends PreviewWidget { ... }

@Preview(name: 'Input', tags: ['form', 'text'])
class InputPreview extends PreviewWidget { ... }

@Preview(name: 'Card', tags: ['display'])
class CardPreview extends PreviewWidget { ... }

Filter by tags in the previewer panel to focus on specific widget categories.

IDE integration

The Widget Previewer integrates with VS Code and Android Studio:

// .vscode/settings.json
{
  "flutter.widgetPreviewer": true,
  "flutter.widgetPreviewer.device": "pixel7",
  "flutter.widgetPreviewer.theme": "system"
}

The preview panel appears alongside your code editor, showing live updates as you type.

Command-line mode

For CI/CD pipelines or headless environments, use the command-line previewer:

# Generate preview images
flutter test --preview-output=previews/

# Run previews in headless mode
flutter run --preview --headless

This enables automated visual testing without a display server.

Preview vs. widget testing

The Widget Previewer complements, not replaces, widget testing:

AspectPreviewerWidget Testing
PurposeVisual developmentAutomated verification
SpeedInstantBuild + run
IsolationVisual onlyFull widget lifecycle
AssertionsVisual inspectionProgrammatic checks

Use the previewer during development, widget tests in CI/CD.

Real-world workflow

A typical workflow with the Widget Previewer:

  1. Design a new widget in code
  2. Add @Preview annotation
  3. See it render immediately in the previewer
  4. Iterate on styling and layout visually
  5. Add widget tests for behavior
  6. Ship with confidence

The Widget Previewer eliminates the “run the whole app to see one button” anti-pattern. It’s the visual feedback loop Flutter developers have been waiting for.