A Flutter UI catalog generated from @Preview annotations.
showfase is a Flutter port of Airbnb's
Showkase. It reads Flutter's own
@Preview widgets — the ones normally displayed in the
Widget Preview tool — and
produces a standalone Flutter app that runs on mobile, desktop, and web.
It is intended for design-system showcases, QA hand-off, and design review.
This repository is a pub workspace + melos monorepo:
| Package | Description |
|---|---|
showfase_annotation |
Anchor annotation (@ShowfaseRoot). Pure Dart. |
showfase_generator |
build_runner code generator that collects @Preview widgets. |
showfase |
Flutter runtime (ShowfaseApp, ShowfaseBrowser, preview detail screen). |
showfase_test |
Golden (snapshot) testing — renders every preview offscreen for visual regression tests. |
packages/showfase/example |
Demo app with committed generated file and golden tests. |
+-----------------+ @Preview +-----------------+
| Your widgets | ---------------> ── | showfase |
| (foo.dart) | | generator |
+-----------------+ +--------+--------+
|
| build_runner
v
+-----------------+ +-----------------+
| @ShowfaseRoot() | <----- imports ----- | showfase.g.dart|
| void main() => | | showfasePreviews()
| runApp(...) | +-----------------+
+--------+--------+
|
| flutter run -t lib/showfase.dart
v
+-----------------+
| catalog app |
| (mobile/desktop |
| /web) |
+-----------------+
The generator runs in two phases (a standard build_runner aggregating
pattern):
preview_scanner— walks every.dartlibrary in the target package, detects any element annotated with@Previewor a subclass ofMultiPreview, encodes the annotation into a.showfase.jsoncache file.showfase_builder— reacts to the single@ShowfaseRoot()declaration in the package, reads every.showfase.json, and emits one<library>.g.dartnext to it.
Annotations are re-emitted as const expressions and .transform() is
invoked at runtime, so custom Preview / MultiPreview subclasses and their
wrapper / theme / localizations callbacks are supported natively.
Add the packages to your app:
# pubspec.yaml
dependencies:
showfase: 0.1.0
showfase_annotation: 0.1.0
dev_dependencies:
build_runner: 2.15.0
showfase_generator: 0.1.0Create a catalog entry point (lib/showfase.dart):
import 'package:flutter/widgets.dart';
import 'package:showfase/showfase.dart';
import 'package:showfase_annotation/showfase_annotation.dart';
// Import each file that contains @Preview-annotated widgets so the code
// generator resolves them.
// ignore: unused_import
import 'widgets/buttons.dart';
import 'showfase.g.dart';
@ShowfaseRoot()
void main() => runApp(ShowfaseApp(previews: showfasePreviews()));Add a preview somewhere:
import 'package:flutter/material.dart';
import 'package:flutter/widget_previews.dart';
@Preview(name: 'Primary', group: 'Buttons')
Widget primaryButtonPreview() =>
FilledButton(onPressed: () {}, child: const Text('Tap me'));Run the generator and launch the catalog:
dart run build_runner build
flutter run -t lib/showfase.dartEverything Flutter's Widget Preview tool supports:
- Top-level functions returning
WidgetorWidgetBuilder - Static methods on classes
- Constructors and factories with no required parameters
- Stacked
@Preview()annotations on the same function - Custom
MultiPreviewsubclasses - All
Previewfields:group,name,size,textScaleFactor,wrapper,theme,brightness,localizations
The detail screen for each preview lets you toggle:
- Brightness (system / light / dark)
- Text scale (0.5×–3.0×)
- Right-to-left layout
- Locale (when the preview supplies
localizations)
showfase_test turns the generated catalog into golden tests: one test per
preview × device, compared against committed PNG baselines with
matchesGoldenFile.
# pubspec.yaml
dev_dependencies:
showfase_test: 0.1.0// test/showfase_test.dart
import 'package:flutter/material.dart';
import 'package:my_app/showfase.g.dart';
import 'package:showfase_test/showfase_test.dart';
Future<void> main() async {
await testShowfase(
showfasePreviews(),
devices: [SnapshotDevice.iPhone15],
builder: (preview, device) => MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: preview),
),
);
}Snapshots are written to test/snapshots/<device>/<group>/<name>.png. Two
ways to run it:
- Record-only (what this repo does): always run with
--update-goldenson a single OS (CI) and hand the PNGs to an external diff tool such as reg-suit, or review them as CI artifacts. Rendering (text anti-aliasing) differs slightly across operating systems, so baselines and comparisons must come from the same OS. - Committed goldens: record with
--update-goldens, commit the PNGs, and let plainflutter testfail on any visual diff — viable when everyone records on the same OS as CI.
See packages/showfase_test for devices, sizing
rules, and sharding.
v1 aggregates only inside the package that hosts @ShowfaseRoot. To include
previews from other workspace packages, expose a showfasePreviews() from
each and combine them manually:
import 'package:my_app/showfase.g.dart' as app;
import 'package:my_ui_kit/showfase.g.dart' as uiKit;
@ShowfaseRoot()
void main() => runApp(ShowfaseApp(previews: <ShowfasePreview>[
...app.showfasePreviews(),
...uiKit.showfasePreviews(),
]));showfase tracks Flutter's widget_previews API. Requirements:
- Flutter ≥ 3.38 (for
MultiPreview,group, andPreview.transform()) - Dart ≥ 3.11
The widget_previews library is still marked experimental by Flutter and may
change without deprecation. showfase pins Flutter's upper bound conservatively
and issues a matching release each Flutter stable.
mise use # activates pinned Flutter 3.41.6 + Dart 3.11.1
dart pub get
melos run analyze # dart analyze everywhere
melos run test # dart test everywhere
melos run test:flutter # flutter test for Flutter packages (excl. snapshots)
melos run snapshot # take catalog snapshots (record-only, CI uploads them)
melos run build # regenerate the example's showfase.g.dartApache-2.0. See LICENSE.