Skip to content

Widget Previews

Kenny Mochizuki Escalona edited this page Aug 14, 2026 · 1 revision

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.


Overview

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

The @Preview Annotation

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();
}

Parameters

  • name — User-friendly label shown in the IDE (e.g., "Light", "Large Text").
  • theme — A tear-off that returns a PreviewThemeData. Must be a function reference, not a function call. For layrz_ui, always use LayrzPreviewTheme.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.

Using LayrzPreviewTheme

Basic Usage

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'),
  );
}

Multiple Previews

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'),
  );
}

Edge Cases

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(''),
  );
}

How LayrzPreviewTheme Works

LayrzPreviewTheme extends Flutter's PreviewThemeData interface. When you pass LayrzPreviewTheme.light to @Preview, the IDE:

  1. Calls LayrzPreviewTheme.light() to get a PreviewThemeData instance
  2. 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.


Important Notes

1. Light Theme Only

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.

2. Tear-Off, Not Function Call

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())

3. extends, Not implements

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.

4. Return the Widget Directly

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(...));

Running Previews

In VS Code (Flutter Extension)

  1. Open a Dart file with @Preview annotations.
  2. Click the "Preview" button above the annotation (if visible).
  3. A canvas opens showing the widget in real time.
  4. Changes to the code update the preview immediately (hot reload).

Via Command Line

flutter test --preview

This runs all preview functions and generates a report. Useful for CI/CD pipelines to verify all previews compile.


Best Practices

  1. Add previews to all visual widgets — Helpers, data classes, and pure utilities do not need previews.
  2. One preview per visual state — Show default, disabled, error, loading states as separate previews.
  3. Test edge cases — Empty text, long text, narrow containers, high text scale.
  4. Use preview groups — Organize variants with group: parameter.
  5. Keep preview functions simple — Avoid complex logic; show the widget with realistic props.
  6. Document tricky edge cases — If a preview shows a workaround or edge case, add a comment.

Example: Complete Widget with Previews

// 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'),
  );
}

Troubleshooting

Preview Does Not Appear in IDE

  • Check Flutter version: Previews require Flutter 3.47+. Run flutter --version.
  • Verify imports: Must import package:flutter/widget_previews.dart and package:layrz_ui/preview.dart.
  • Check @Preview format: Ensure theme: is a tear-off (LayrzPreviewTheme.light, not LayrzPreviewTheme.light()).
  • Restart IDE: Sometimes the IDE's analyzer needs to reindex. Restart VS Code or Android Studio.

Build Error: "Cannot find declaration of base class"

This error means the Dart SDK version is too old. Previews require Dart 3.0+. Update your SDK.

Preview Shows Blank or Wrong Theme

  • Did you use LayrzPreviewTheme.light()? — Remove the (). It should be LayrzPreviewTheme.light (tear-off).
  • Is the preview function returning a widget? — Ensure it returns a Widget, not void.

Related Documentation


Last updated: 2026-08-14
Related pages: Theming, LayrzApp, Getting-Started

Clone this wiki locally