super_auto_suggestion_box

Pub style: flutter_lints

A Flutter typeahead / combobox for local and remote data with rich suggestion rows, fuzzy matching, single or multi-select, advanced search, validation, recents, inline creation, paging, shadow-hint completion, and controller-driven ERP form workflows.

SuperAutoSuggestionsBox<T> keeps domain data as raw T values. A SuperAutoSuggestionBuilder<T> converts each value into searchable and renderable SuperAutoSuggestionsItem<T> metadata at the presentation boundary.

Current package version: 1.6.0.

Table of contents

Features

  • Local, asynchronous, hybrid, remote-fallback, and paged suggestion sources.
  • contains, prefix, words, and fuzzy matching strategies.
  • Relevance-ranked fuzzy results and matched-text highlighting.
  • Single-select and multi-select workflows using raw domain values.
  • Rich rows with title, description, trailing metadata, icons, groups, keywords, enabled state, and custom widgets.
  • BuildContext-aware SuperAutoSuggestionBuilder<T> for theme, localization, MediaQuery, and inherited design-system values.
  • Inline text-box search, Advanced Search, or both search surfaces.
  • Recent suggestions with caller-controlled persistence callbacks.
  • Server-side zero-based paging and infinite-scroll support.
  • Inline creation for missing values with synchronous or asynchronous creation.
  • Shadow-hint completion with keyboard-first Tab acceptance.
  • Form integration, required validation, custom validators, and configurable validation placement.
  • Read-only, disabled, fixed, bare/embed, and record-binding-friendly states.
  • Keyboard, focus, formatter, directionality, and text-input configuration for ERP identifiers and general text entry.
  • English and Arabic package localization, LTR/RTL support, and shared GeniusLink theming through super_core.

Getting started

Install

Add the package to pubspec.yaml:

dependencies:
  super_auto_suggestion_box: ^1.7.0

Then resolve dependencies using your normal Flutter workflow.

Import

import 'package:super_auto_suggestion_box/super_auto_suggestion_box.dart';

The package barrel exports the suggestion box feature, localization, super_form_field integration types used by the widget, and the shared super_core foundation used by the component.

Localization

Register the package localization delegate when the package-owned strings should follow the application's locale:

MaterialApp(
  localizationsDelegates: const [
    SuperAutoSuggestionLocalization.delegate,
  ],
  supportedLocales: SuperAutoSuggestionLocalization.supportedLocales,
);

The package also has a safe English fallback when its delegate is not installed.

Basic usage

A source and a suggestion builder are required. The source owns raw data; the builder owns searchable and visual metadata.

final units = <String>['Each', 'Box', 'Carton'];

SuperAutoSuggestionsItem<String> unitSuggestion(
  BuildContext context,
  List<String> items,
  int index,
  String unit,
) {
  return SuperAutoSuggestionsItem<String>(
    value: unit,
    titleText: unit,
  );
}

SuperAutoSuggestionsBox<String>(
  source: SuperAutoSuggestionSources.list<String>(units),
  suggestionBuilder: unitSuggestion,
  hintText: 'Type or pick...',
  onSelectionChanged: (selected) {
    final value = selected.isEmpty ? null : selected.last;
    debugPrint('Selected: $value');
  },
);

A controller is optional. Use one when the host needs imperative selection, focus, form, recents, query, or paging state.

Suggestion builder

Since 1.6.0, the canonical builder is:

typedef SuperAutoSuggestionBuilder<T> =
    SuperAutoSuggestionsItem<T> Function(
      BuildContext context,
      List<T> items,
      int index,
      T element,
    );

The active BuildContext can safely read inherited presentation values:

SuperAutoSuggestionsItem<Account> accountSuggestion(
  BuildContext context,
  List<Account> items,
  int index,
  Account account,
) {
  final colors = Theme.of(context).colorScheme;

  return SuperAutoSuggestionsItem<Account>(
    value: account,
    titleText: account.name,
    descriptionText: account.code,
    trailingText: account.balanceText,
    icon: Icon(Icons.account_balance_outlined, color: colors.primary),
    keywords: [account.code],
  );
}

titleText is the canonical searchable, displayed, and committed title. keywords extend the searchable haystack without changing the visible title.

Suggestion sources

Context-aware fetch callbacks

Starting with 1.7.0, remote fetch callbacks receive the active BuildContext before the query arguments. This lets the request read inherited values such as locale or application-scoped configuration at the moment the request starts.

final source = SuperAutoSuggestionSources.async<Account>(
  (context, query) async {
    final locale = Localizations.localeOf(context);
    return repository.searchAccounts(query, locale: locale);
  },
);

Hybrid and remote-fallback sources use fetch: (context, query). Paged sources use (context, query, page). Local list, strings, and fuzzy sources remain synchronous.

Use SuperAutoSuggestionSources for normal source construction.

Factory Use case Concrete source
list In-memory data SuperAutoListSuggestionsSource<T>
strings In-memory String values SuperAutoListSuggestionsSource<String>
fuzzy Ranked fuzzy in-memory search SuperAutoListSuggestionsSource<T>
async Remote or repository search SuperAutoAsyncSuggestionsSource<T>
hybrid Local results plus remote expansion SuperAutoHybridSuggestionsSource<T>
remoteFallback Local-first search with sparse-result fallback SuperAutoRemoteFallbackSuggestionsSource<T>
paged Server-side paging / infinite scroll SuperAutoPagedSuggestionsSource<T>

Local list

final source = SuperAutoSuggestionSources.list<Account>(
  accounts,
  match: AutoSuggestionMatch.contains,
  caseSensitive: false,
);

For plain strings:

final source = SuperAutoSuggestionSources.strings(
  ['Aden', 'Amman', 'Cairo', 'Dubai'],
  match: AutoSuggestionMatch.prefix,
);

The widget still receives a suggestionBuilder, including when strings() is used.

Fuzzy

final source = SuperAutoSuggestionSources.fuzzy<Account>(accounts);

Fuzzy results are ranked by match quality rather than preserving only insertion order.

Async

final source = SuperAutoSuggestionSources.async<Account>(
  (context, query) => repository.searchAccounts(query),
  initialItems: initiallyKnownAccounts,
);

SuperAutoSuggestionsBox<Account>(
  source: source,
  suggestionBuilder: accountSuggestion,
  debounce: const Duration(milliseconds: 300),
);

initialItems seed value resolution; they are not automatically returned as the result of every async query.

Hybrid

Hybrid search always lets local matches contribute to the final result and fetches remotely when the local result count is below the configured threshold.

final source = SuperAutoSuggestionSources.hybrid<Account>(
  initialItems: recentAccounts,
  fetch: repository.searchAccounts,
  remoteThreshold: 3,
  remoteMinChars: 2,
);

The remote condition is localMatches.length < remoteThreshold.

When the source is used by SuperAutoSuggestionsBox, prefer the widget's minResult to control the progressive remote-fetch threshold. remoteThreshold remains relevant to direct source query/progressive calls.

Remote fallback

Use remoteFallback when remote data should primarily extend sparse local results:

final source = SuperAutoSuggestionSources.remoteFallback<Account>(
  initialItems: recentAccounts,
  fetch: repository.searchAccounts,
  remoteThreshold: 5,
  remoteMinChars: 2,
);

The remote condition is inclusive: localMatches.length <= remoteThreshold.

Inside SuperAutoSuggestionsBox, minResult is the widget-level inclusive threshold for progressive remote fetching. The source-level remoteThreshold is retained for direct source calls.

Paged

The page callback uses zero-based page indexes. Page 0 is the first page.

final source = SuperAutoSuggestionSources.paged<Account>(
  (context, query, page) async {
    final response = await repository.searchAccountsPage(
      query: query,
      page: page,
    );

    return SuperSuggestionsPage<Account>(
      items: response.items,
      hasMore: response.hasMore,
    );
  },
  resolveFrom: initiallySelectedAccounts,
);

Fetched pages are retained for value resolution. The paged source does not de-duplicate rows across pages, so APIs that can repeat rows should be normalized in the repository or fetch callback.

Matching strategies

AutoSuggestionMatch supports:

Strategy Behavior
contains Query appears anywhere in the searchable text.
prefix Searchable text starts with the query.
words Every whitespace-separated query token must appear.
fuzzy Query characters appear in order with gaps allowed.

Example:

final source = SuperAutoSuggestionSources.list<Account>(
  accounts,
  match: AutoSuggestionMatch.words,
);

Use the box's highlightMatch and highlightMatches options to control row highlighting behavior.

Search modes

SuperAutoSuggestionsMode controls which search surface is exposed:

  • textBox: editable field with anchored suggestions.
  • advanceView: field-like launcher for the Advanced Search view.
  • both: editable field plus Advanced Search access.
SuperAutoSuggestionsBox<Account>(
  source: source,
  suggestionBuilder: accountSuggestion,
  mode: SuperAutoSuggestionsMode.both,
);

When mode is omitted, desktop platforms default to textBox; Android, iOS, and Fuchsia default to advanceView.

A custom Advanced Search surface can be supplied with advancedSearchBuilder.

Multi-select

Initial selected values belong to the controller. Multi-select behavior belongs to the widget.

final controller = SuperAutoSuggestionsController<Account>(
  initialSelected: initialAccounts,
);

SuperAutoSuggestionsBox<Account>(
  controller: controller,
  source: source,
  suggestionBuilder: accountSuggestion,
  multiSelect: true,
  onSelectionChanged: (selected) {
    debugPrint('Selected ${selected.length} accounts');
  },
);

Read the current values from controller.selectedItems.

Recent suggestions

SuperAutoSuggestionsBox<Account>(
  source: source,
  suggestionBuilder: accountSuggestion,
  showRecents: true,
  maxRecents: 8,
  initialRecents: persistedRecents,
  onRecentsChanged: persistRecents,
);

Recents are most-recent-first. The package owns runtime ordering while the host can persist changes through onRecentsChanged.

Inline create

When onCreate is set and a non-empty query has no match, the component can show a create action in both inline and Advanced Search surfaces.

SuperAutoSuggestionsBox<Account>(
  source: source,
  suggestionBuilder: accountSuggestion,
  onCreate: (query) async {
    return repository.createAccount(name: query);
  },
  createLabelBuilder: (query) => 'Create "$query"',
);

Return the created raw T to commit it, or null to cancel creation.

Shadow-hint completion

Shadow hints render the untyped suffix of the highlighted prefix match without changing the actual editable value.

SuperAutoSuggestionsBox<String>(
  source: source,
  suggestionBuilder: referenceSuggestion,
  showShadowHint: true,
  completeShadowHintOnTab: true,
);

Forward Tab accepts a visible shadow hint. Shift+Tab keeps reverse focus traversal. If no shadow hint is visible, Tab keeps normal focus traversal or uses onTabNext when supplied.

Forms and validation

SuperAutoSuggestionsBox<T> participates in Flutter forms with the committed raw T? value.

final formKey = GlobalKey<FormState>();
final fieldKey = GlobalKey<FormFieldState<Account>>();

final controller = SuperAutoSuggestionsController<Account>(
  formFieldKey: fieldKey,
);

Form(
  key: formKey,
  child: SuperAutoSuggestionsBox<Account>(
    controller: controller,
    source: source,
    suggestionBuilder: accountSuggestion,
    required: true,
    validator: (account) {
      if (account != null && account.isBlocked) {
        return 'Blocked accounts cannot be selected';
      }
      return null;
    },
    autovalidateMode: AutovalidateMode.onUserInteraction,
    validationPosition: ValidationPosition.underBox,
  ),
);

Validation is selection-driven. Use FormState.validate() for form-level validation and controller state for the selected value.

If autovalidateMode is omitted, the field inherits the nearest Form setting and otherwise defaults to AutovalidateMode.disabled.

Debounce and minResult

Local matching is never debounced. On every query change, data that is already available in memory is matched immediately and can be rendered without waiting for the debounce window.

SuperAutoSuggestionsBox.debounce applies only to work that reaches an external source: remote fetch callbacks, progressive loadMore work, and the first request of a paged source. Version 1.7.0 uses easy_debounce, so rapid keystrokes reset only the pending remote request.

SuperAutoSuggestionsBox<Account>(
  source: SuperAutoSuggestionSources.async<Account>(
    (context, query) => repository.searchAccounts(query),
    initialItems: cachedAccounts,
  ),
  debounce: const Duration(milliseconds: 300),
  minResult: 0,
  suggestionBuilder: accountSuggestion,
);

In this example, cachedAccounts are matched immediately. If the local match count allows remote loading, the repository request starts after 300ms. Changing the debounce duration never delays local filtering.

The built-in sources behave as follows:

  • list / strings / fuzzy: local-only matching; debounce is ignored.
  • async: initialItems and previously cached results are matched immediately; remote fetch is debounced when more data is needed.
  • hybrid / remoteFallback: local matching is immediate; only the remote augmentation step is debounced.
  • paged: page requests are external work and remain debounced; resolveFrom continues to be a resolution cache rather than a local search collection.

SuperAutoSuggestionSources.async accepts match and caseSensitive for its local cache matching:

final source = SuperAutoSuggestionSources.async<Account>(
  (context, query) => repository.searchAccounts(query),
  initialItems: cachedAccounts,
  match: AutoSuggestionMatch.contains,
  caseSensitive: false,
);

Use Duration.zero when remote requests should start immediately. Values such as 300ms or 800ms reduce external request churn without making local search feel slower.

minResult

minResult is evaluated against the immediate local result count. Remote work is eligible when the local count is less than or equal to minResult. The default is 0, so a remote request is needed only when local matching returns no rows.

SuperAutoSuggestionsBox<Account>(
  source: SuperAutoSuggestionSources.hybrid<Account>(
    initialItems: recentAccounts,
    fetch: (context, query) => repository.searchAccounts(query),
    remoteMinChars: 2,
  ),
  minResult: 3,
  debounce: const Duration(milliseconds: 300),
  suggestionBuilder: accountSuggestion,
);

With minResult: 3, 0, 1, 2, or 3 local rows are shown immediately and the remote fetch is then scheduled through debounce. With 4 or more local matches, no remote request is started. Source-specific minimum query-length rules still apply.

A pure async source with an empty local cache naturally has 0 local results, so its remote fetch proceeds after debounce. As remote results are cached, later queries can match those cached values immediately before deciding whether another external request is necessary.

Controller

Create a SuperAutoSuggestionsController<T> when the host needs direct state or commands:

final controller = SuperAutoSuggestionsController<Account>(
  initialValue: initialAccount,
  allowFreeText: false,
  focusNode: accountFocusNode,
);

Common reads:

controller.query;
controller.results;
controller.suggestions;
controller.selected;
controller.selectedItems;
controller.recents;
controller.isOpen;
controller.isLoading;
controller.isLoadingMore;
controller.isPaged;
controller.hasMore;
controller.error;

Common commands:

controller.open();
controller.close();
controller.toggle();
controller.setText('1001');
controller.refresh();
controller.selectByValue(account);
controller.setSelectedItems(accounts);
controller.clearSelection();
controller.setRecents(accounts);
controller.clearRecents();
controller.loadNextPage();
controller.clear();

The widget owns the source, builder, query timing, recents configuration, result limit, and multi-select configuration; the controller is the runtime state/command surface.

Rich and custom rows

Use SuperAutoSuggestionsItem<T> for default rich rows:

SuperAutoSuggestionsItem<Account>(
  value: account,
  titleText: account.name,
  descriptionText: account.code,
  trailingText: account.balanceText,
  group: account.typeLabel,
  iconData: Icons.account_balance_outlined,
  keywords: [account.code, account.alias],
  enabled: account.isActive,
);

Custom supporting widgets can be supplied directly:

SuperAutoSuggestionsItem<Account>(
  value: account,
  titleText: account.name,
  description: Text(account.code),
  trailing: Chip(label: Text(account.statusLabel)),
  icon: const Icon(Icons.account_balance_outlined),
);

For complete row control, use itemBuilder:

SuperAutoSuggestionsBox<Account>(
  source: source,
  suggestionBuilder: accountSuggestion,
  itemBuilder: (context, account, suggestion, highlighted) {
    return ListTile(
      selected: highlighted,
      title: Text(suggestion.displayText),
      subtitle: Text(account.code),
    );
  },
);

Other presentation hooks include emptyBuilder and loadingBuilder.

Field states and embedding

The box supports several distinct states:

  • enabled: false: disables the standard field interaction.
  • disabled: true: dims the field and suppresses interaction and validation.
  • readOnly: true: keeps normal contrast while blocking editing and opening.
  • allowFixed: true: exposes a lock/unlock action backed by controller.isFixed.
  • bare: true: removes the outer border/fill and tightens padding for embedded surfaces such as editable table cells.

Keyboard-oriented embedders can use onEscape, onTabNext, and onTabPrev. Use restoreOnBlur to control whether uncommitted typing reverts to the last committed selection.

Input configuration

The widget exposes Flutter text-input controls needed for ERP and structured identifiers, including:

  • keyboardType, textInputAction, and textCapitalization.
  • inputFormatters, maxLength, and maxLengthEnforcement.
  • textDirection, textAlign, and textAlignVertical.
  • autocorrect, enableSuggestions, and enableIMEPersonalizedLearning.
  • autofillHints, smart dashes/quotes, cursor options, and selection controls.
  • focusNode, autofocus, canRequestFocus, and scrollOnFocus.

Example for an exact numeric account code:

SuperAutoSuggestionsBox<Account>(
  source: source,
  suggestionBuilder: accountSuggestion,
  keyboardType: TextInputType.number,
  enableSuggestions: false,
  autocorrect: false,
  inputFormatters: [FilteringTextInputFormatter.digitsOnly],
  textDirection: TextDirection.ltr,
);

Theming

Use SuperAutoSuggestionsBoxThemeData for component styling and SuperAutoSuggestionsBoxFocusedStyle for focused-state overrides.

A field-level theme can override the ambient package theme:

SuperAutoSuggestionsBox<Account>(
  source: source,
  suggestionBuilder: accountSuggestion,
  theme: SuperAutoSuggestionsBoxThemeData.light.copyWith(
    borderFocus: Colors.indigo,
  ),
);

The package integrates with the shared GeniusLink design-system foundation from super_core, including light/dark themes, spacing, typography, and RTL-aware presentation.

Documentation

The example/ application contains runnable scenarios for local and remote search, Advanced Search modes, validation placement, autovalidation, rich rows, and newer package behavior.

Changelog

See CHANGELOG.md for new features, fixes, and breaking changes.

Issues

Use the GitHub issue tracker for bug reports and feature requests.

Libraries

localizations/generated/l10n
localizations/generated/l10n_ar
localizations/generated/l10n_en
super_auto_suggestion_box
Super Auto Suggestion Box — a GeniusLink design-system Flutter package that ships the SuperAutoSuggestionsBox typeahead / combobox: local + remote sources, prefix / contains / words / fuzzy-ranked matching, single- and multi-select, free-text entry, a local-first progressive remoteFallback source, server-side paged infinite-scroll, recently-used suggestions, inline create, shadow-hint completion, a trailing meta column, record binding + read-only mode, an advanced-search overlay, and a bare embedding mode.