Custom widgets guide

Learn how to create and use custom Flutter widgets in SuperDeck presentations

Custom widgets guide

Render custom Flutter widgets from Markdown. Register widget factories in DeckOptions.widgets, then use them in slides.md.

Markdown syntax

Two equivalent forms:

  • Shorthand (recommended) - @twitter { ... }
  • Explicit - @widget { name: "twitter" ... }

All properties pass to your WidgetFactory as a Map<String, Object?>.

Create a widget factory

A WidgetFactory is a function: Widget Function(Map<String, Object?> args).

Simple: plain function

import 'package:flutter/widgets.dart';

Widget twitterWidget(Map<String, Object?> args) {
  final username = args['username'] as String? ?? '';
  final tweetId = args['tweetId'] as String? ?? '';
  return Text('Twitter: @$username ($tweetId)');
}

Recommended: StatelessWidget with typed args

Parse and validate in the constructor. Import package:superdeck_core/superdeck_core.dart when you want to use Ack for schema validation.

import 'package:flutter/widgets.dart';
import 'package:superdeck/superdeck.dart';
import 'package:superdeck_core/superdeck_core.dart';

class TwitterArgs {
  const TwitterArgs({required this.username, required this.tweetId});

  final String username;
  final String tweetId;

  static final schema = Ack.object({
    'username': Ack.string().notEmpty(),
    'tweetId': Ack.string().notEmpty(),
  });

  static TwitterArgs parse(Map<String, Object?> map) {
    schema.parse(map);
    return TwitterArgs(
      username: map['username'] as String,
      tweetId: map['tweetId'] as String,
    );
  }
}

class TwitterWidget extends StatelessWidget {
  final TwitterArgs data;

  TwitterWidget(Map<String, Object?> args, {super.key})
    : data = TwitterArgs.parse(args);

  @override
  Widget build(BuildContext context) {
    return Text('Twitter: @${data.username} (${data.tweetId})');
  }
}

Register widgets in DeckOptions

import 'package:flutter/widgets.dart';
import 'package:superdeck/superdeck.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SuperDeckApp.initialize();

  runApp(
    SuperDeckApp(
      options: DeckOptions(
        widgets: {
          'twitter': TwitterWidget.new,
        },
      ),
    ),
  );
}

Use widgets in slides.md

Shorthand:

@twitter {
  username: "flutterdev"
  tweetId: "1234567890"
}

Explicit:

@widget {
  name: "twitter"
  username: "flutterdev"
  tweetId: "1234567890"
}

Slide context

SlideConfiguration.of(context) exposes the slide index, options, and metadata if your widget needs them:

class TwitterWidget extends StatelessWidget {
  final TwitterArgs data;

  TwitterWidget(Map<String, Object?> args, {super.key})
    : data = TwitterArgs.parse(args);

  @override
  Widget build(BuildContext context) {
    final slide = SlideConfiguration.of(context);
    return Text('Slide ${slide.slideIndex + 1}: @${data.username}');
  }
}

For sizing your widget to the available block area, use Flutter's LayoutBuilder — it gives you the parent's constraints without any SuperDeck-specific API:

Widget twitterWidget(Map<String, Object?> args) {
  final username = args['username'] as String? ?? '';
  return LayoutBuilder(
    builder: (context, constraints) => SizedBox(
      width: constraints.maxWidth,
      child: Text('@$username'),
    ),
  );
}

Troubleshooting

  • "Widget not found" - Verify the name is registered in DeckOptions.widgets
  • Parse/build errors - SuperDeck renders error details with stack trace on slide

Notes

  • Built-in widgets (image, dartpad, qrcode) are always available
  • Override built-in widgets by registering the same name