Skip to content

Repository files navigation

Flow UI logo

flow_ui

pub package pub points github stars license: MIT

📚 Documentation · 🧩 Playground · 🤓 API Reference · 🗺️ Roadmap

Flow UI is an open-source Flutter UI library to build production-grade Chat & AI assistant interfaces.

The flow_ui chat surface

Important

flow_ui is pre-1.0. The API is still settling, and minor releases may carry breaking changes — pin a minor version and read the changelog when upgrading.

What's in the box

Component What it does
FlowChatScreen The full chat surface: bounded thread over a composer, centred at a readable width, with a zero state (greeting, lifted composer, starters) and a jump-to-latest button
FlowThread Scrollable conversation anchored to the newest message
FlowMessage One turn — ink-wash user bubble, plain assistant, error bubble, typed content parts
FlowStreamingText Animated text reveal while a reply arrives
FlowThinkingIndicator Turning, breathing asterisk with a shimmering label
FlowShimmerText Sweeping text highlight, static once settled
FlowMessageActions Copy / regenerate / edit / feedback row under a message
FlowComposer Multiline input with send/stop, attachments strip, and leading/trailing action slots
FlowMenu Icon-triggered menu with groups, submenus, and toggles — anchored card on desktop, bottom sheet on phones
FlowModelSelector Model picker with effort and overflow submenus, sheet on phones
FlowAttachmentGroup Image and file tiles with a type pill
FlowAttachmentPreview Full-screen image viewer with zoom and paging
FlowSuggestion / FlowSuggestionGroup Prompt starters — plain or outlined; scroll, wrap, or column layouts
FlowGreeting Zero-state headline
FlowTheme Design tokens (colors and typography) as a ThemeExtension, with light and dark presets

Getting started

dependencies:
  flow_ui: ^0.1.0

Install the theme once (optional — without it, components fall back to a preset matching the ambient brightness):

MaterialApp(
  theme: ThemeData(extensions: [FlowTheme.light()]),
  darkTheme: ThemeData(
    brightness: Brightness.dark,
    extensions: [FlowTheme.dark()],
  ),
)

The default typography ships with the package — Figtree, bundled under the SIL Open Font License — so the theme renders as designed with no font setup.

Build a chat screen

Messages are pure view models. Your app maps its own transport into FlowMessageData, and streaming is data, not streams: while a reply arrives, rebuild with copyWith carrying the grown text.

class ChatPage extends StatefulWidget {
  const ChatPage({super.key});

  @override
  State<ChatPage> createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  final ScrollController _scroll = ScrollController();
  List<FlowMessageData> _messages = const [];
  bool _generating = false;

  void _send(String text) async {
    final id = DateTime.now().microsecondsSinceEpoch.toString();
    setState(() {
      _messages = [
        ..._messages,
        FlowMessageData.text(id: id, role: FlowMessageRole.user, text: text),
        // An empty pending reply renders the thinking indicator.
        FlowMessageData(
          id: '$id-reply',
          role: FlowMessageRole.assistant,
          status: FlowMessageStatus.pending,
        ),
      ];
      _generating = true;
    });

    // Feed chunks from your backend as they arrive.
    var streamed = '';
    await for (final chunk in myBackend.reply(text)) {
      streamed += chunk;
      setState(() {
        _messages = [
          ..._messages.sublist(0, _messages.length - 1),
          _messages.last.copyWith(
            parts: [FlowTextPart(streamed)],
            status: FlowMessageStatus.streaming,
          ),
        ];
      });
    }

    setState(() {
      _messages = [
        ..._messages.sublist(0, _messages.length - 1),
        _messages.last.copyWith(status: FlowMessageStatus.complete),
      ];
      _generating = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FlowChatScreen(
        empty: _messages.isEmpty,
        greeting: const FlowGreeting(
          icon: Icons.wb_twilight,
          text: 'Good afternoon',
        ),
        suggestions: FlowSuggestionGroup(
          layout: FlowSuggestionLayout.column,
          suggestions: [
            FlowSuggestion(
              label: 'Write an essay about life and enjoyment',
              icon: Icons.edit_note,
              onTap: () => _send('Write an essay about life and enjoyment'),
            ),
            FlowSuggestion(
              label: 'Create a Monday briefing from my tasks',
              icon: Icons.event_available,
              onTap: () => _send('Create a Monday briefing from my tasks'),
            ),
          ],
        ),
        thread: FlowThread(
          messages: _messages,
          controller: _scroll,
          thinkingLabel: 'Thinking…',
        ),
        threadController: _scroll,
        jumpToLatestTooltip: 'Jump to latest',
        composer: FlowComposer(
          placeholder: 'How can I help you today?',
          isStreaming: _generating,
          onSend: _send,
          onStop: myBackend.stop,
        ),
      ),
    );
  }
}

FlowChatScreen is body-only — it builds no Scaffold and no app bar, so your app keeps the chrome, the background, and the keyboard inset. See example/lib/main.dart for a complete runnable version of this page, and the live playground for a demo of every component with variants and code snippets.

Composer accessories

The composer takes leading and trailing action slots. Drop in a FlowMenu (attachments, toggles) and a FlowModelSelector — both render an anchored card on wide layouts and a bottom sheet on phones:

FlowComposer(
  onSend: _send,
  leadingActions: [
    FlowMenu(
      icon: Icons.add,
      tooltip: 'Add to chat',
      entries: const [
        FlowMenuOption(id: 'files', icon: Icons.attach_file, label: 'Add files'),
        FlowMenuDivider(),
        FlowMenuOption(id: 'web', icon: Icons.public, label: 'Web search', selected: true),
      ],
      onSelected: _handleMenu,
    ),
  ],
  trailingActions: [
    FlowModelSelector(
      models: const [
        FlowModelOption(id: 'fast', label: 'Fast', description: 'Quick answers'),
        FlowModelOption(id: 'smart', label: 'Smart', description: 'Hard problems'),
      ],
      selectedId: _modelId,
      onSelected: (id) => setState(() => _modelId = id),
    ),
  ],
)

Message content is typed parts

A message holds an ordered list of sealed FlowMessageParts — FlowTextPart, FlowAttachmentPart, and FlowCustomPart for anything the package doesn't know about. Custom parts render through a builder you supply, so hosts can inject arbitrary widgets (tool cards, citations, charts) without forking the message renderer:

FlowThread(
  messages: _messages,
  customPartBuilder: (context, message, part) {
    return switch (part.type) {
      'order-card' => OrderCard(order: part.data as Order),
      _ => null, // unknown parts are skipped
    };
  },
)

Attachments carry an ImageProvider, so network, file, memory, and asset images all work — the package never loads anything itself:

FlowMessageData(
  id: 'm1',
  role: FlowMessageRole.user,
  parts: [
    FlowAttachmentPart([
      FlowAttachment(id: 'a1', thumbnail: NetworkImage(url), kind: 'JPG', label: 'sunset.jpg'),
    ]),
    FlowTextPart('What do you think of this shot?'),
  ],
)

Theming

FlowTheme carries two token sets — colors and typography. Role names follow Material 3's ColorScheme, so an existing scheme maps across, with one addition: the design draws content at three ink levels (onSurface, onSurfaceVariant, onSurfaceMuted) where M3 names two. Start from a preset and override what your brand needs:

FlowTheme(
  colors: FlowColors.dark.copyWith(primary: const Color(0xFF6C5CE7)),
  typography: FlowTypography.standard,
)

Spacing and corner radii are deliberately not tokens. Following Material's structure, each component bakes its own metrics from the Flow UI design file and exposes per-widget overrides (padding:, borderRadius:) where hosts retheme. Strings shown to the user (tooltips, placeholders, labels) are host-supplied, so localization stays in your app — the one exception, the model selector's effortLabel and moreModelsLabel English defaults, is overridable the same way.

Docs & playground

Full documentation lives at flowui.stac.dev, and every component has a stage in the live playground — variant pills and code snippets included. The playground is also in the repo to run locally:

cd playground && flutter run -d chrome

License

Code is released under the MIT License. The bundled Figtree font is licensed separately under the SIL Open Font License.

About

Flutter UI library for AI Chat Interfaces

Topics

Resources

Stars

8 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages