formless 0.1.8 copy "formless: ^0.1.8" to clipboard
formless: ^0.1.8 copied to clipboard

A Flutter package that turns any list of fields into an AI-powered conversational form. Validates answers via Groq, OpenAI, Gemini, or DeepSeek and returns a clean key->value map when all fields are collected.

Formless #

A Flutter package that turns any list of fields into an AI-powered conversational form.

Instead of a traditional form, Formless walks the user through each question one at a time in a chat UI. The LLM validates answers in natural language, asks for corrections when needed, and returns a clean key → value map when all fields are collected.

Features #

  • Chat-style UI — no traditional form widgets needed
  • Supports Groq, OpenAI, Gemini, and DeepSeek
  • Per-field validation with optional custom rules for the LLM, plus optional onValidate for your own API checks after the AI accepts an answer
  • Optional options per question for multiple choice (chips or searchable sheet for long lists; bottom field disabled for that step)
  • Optional optionsSheetPresenter to replace the default searchable bottom sheet with your own UI while still calling into Formless when the user picks a value
  • Users can edit any previous answer by long-pressing their bubble
  • Fully themeable — bubble colors, input field, send button, and more
  • Automatic JSON retry and rate-limit backoff

Free to use #

Formless works with providers that offer a free tier — you don't need a paid plan to get started:

Provider Free tier
Groq Yes — generous free tier, very fast
Gemini Yes — free tier available
OpenAI No — pay per use
DeepSeek No — pay per use

Privacy & security #

User data is sent directly from the device to the AI provider — it never passes through any third-party server, including Formless itself. The package makes HTTP calls straight to the provider's API using the key you supply, so your users' answers stay between them and the provider you choose.

Installation #

Add to your pubspec.yaml:

dependencies:
  formless: ^0.1.8

Then run:

flutter pub get

Getting started #

You need an API key from one of the supported providers:

Provider Where to get a key
Groq https://console.groq.com
OpenAI https://platform.openai.com
Gemini https://aistudio.google.com
DeepSeek https://platform.deepseek.com

Pass the key at runtime using --dart-define — never hardcode it:

flutter run --dart-define=MY_API_KEY=your_key_here

Usage #

Minimal #

import 'package:formless/formless.dart';

Formless(
  provider: AiProvider.openAi,
  apiKey: const String.fromEnvironment('MY_API_KEY'),
  onComplete: (data) {
    // data = {'name': 'Alice', 'email': 'alice@example.com', ...}
    print(data);
  },
)

This uses the built-in default questions: name, age, email, and phone.

Custom questions #

Formless(
  provider: AiProvider.groq,
  apiKey: const String.fromEnvironment('MY_API_KEY'),
  questions: [
    QuestionsModel(
      question: 'What is your full name?',
      key: 'name',
      type: QuestionFieldType.text,
    ),
    QuestionsModel(
      question: 'What is your email address?',
      key: 'email',
      type: QuestionFieldType.email,
    ),
    QuestionsModel(
      question: 'What is your monthly income?',
      key: 'income',
      type: QuestionFieldType.numeric,
      validationMessage: 'Only accept values between 1000 and 100000',
    ),
  ],
  onComplete: (data) => print(data),
)

Multiple choice (options) #

When options is a non-empty list, Formless shows tappable chips centered under the question for short lists. The bottom text field stays visible but is disabled with a “Pick an option above” hint; the user’s choice (trimmed, deduplicated labels) is sent to the LLM as their reply, and the system prompt tells the model to accept only those values.

QuestionsModel(
  question: 'Which plan do you want?',
  key: 'plan',
  type: QuestionFieldType.text,
  options: ['Free', 'Pro', 'Team'],
),

Long lists (e.g. countries): by default, if there are more than 12 options (after trim/dedupe), Formless shows a control that opens a searchable bottom sheet instead of chips. You can override with optionsPresentation and tune the threshold with optionsChipMaxCount, or set optionsSheetTitle for the sheet header.

QuestionsModel(
  question: 'Which country are you in?',
  key: 'country',
  type: QuestionFieldType.text,
  optionsSheetTitle: 'Select country',
  options: myCountryNames, // any length — use QuestionOptionsPresentation.searchableSheet to force the sheet even for short lists
  optionsPresentation: QuestionOptionsPresentation.searchableSheet, // optional: always use sheet
),

Custom options sheet (optionsSheetPresenter) #

When the step uses the searchable sheet path, you can replace the default modal with your own. Your presenter receives FormlessOptionsSheetArgs (context, options, title, questionKey, theme, select). Call args.select(value) when the user confirms a choice (then pop your route if needed). To keep the default sheet for most steps and customize one key:

import 'package:formless/formless.dart';

Formless(
  provider: AiProvider.groq,
  apiKey: const String.fromEnvironment('MY_API_KEY'),
  optionsSheetPresenter: (args) async {
    if (args.questionKey == 'country') {
      await showModalBottomSheet<void>(
        context: args.context,
        builder: (ctx) => MyCountryPicker(
          onPick: (v) {
            Navigator.pop(ctx);
            args.select(v);
          },
        ),
      );
      return;
    }
    await showDefaultFormlessOptionPickerSheet(args);
  },
  questions: [...],
  onComplete: (data) => print(data),
)

Lower-level access: showOptionsSearchSheet opens the package default sheet with explicit parameters.

Post-AI validation (onValidate) #

The LLM first decides whether the answer fits the question and any validationMessage / field type. After it accepts, you can run your own validation on the user’s text — for example calling your API to check that a nickname is not already taken.

onValidate is optional on each QuestionsModel. It runs only when the AI has already accepted the answer. Return null to keep the answer, or a non-empty String to reject it; that string is shown to the user so they can try again.

QuestionsModel(
  question: 'What nickname would you like?',
  key: 'nickname',
  type: QuestionFieldType.text,
  onValidate: (answer) async {
    final taken = await myBackend.isNicknameTaken(answer);
    if (taken) {
      return 'That nickname is already taken — please choose another.';
    }
    return null;
  },
),

Use this for uniqueness checks, database rules, or any logic you control on the device or via your own services — anything the model cannot reliably verify on its own.

Custom theme #

Formless(
  provider: AiProvider.openAi,
  apiKey: const String.fromEnvironment('MY_API_KEY'),
  theme: FormlessTheme(
    userBubbleColor: Colors.blue.shade700,
    botBubbleColor: Colors.grey.shade100,
    sendButtonColor: Colors.blue.shade700,
    inputDecoration: InputDecoration(
      hintText: 'Type your answer...',
      filled: true,
      fillColor: Colors.grey.shade100,
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
      ),
    ),
  ),
  onComplete: (data) => print(data),
)

Override the model #

Formless(
  provider: AiProvider.openAi,
  apiKey: const String.fromEnvironment('MY_API_KEY'),
  model: 'gpt-4o',
  onComplete: (data) => print(data),
)

API reference #

Formless #

Parameter Type Required Description
provider AiProvider Yes Which LLM API to use
apiKey String Yes API key for the chosen provider
questions List<QuestionsModel>? No Fields to collect; defaults to name/age/email/phone
model String? No Override the provider's default model
theme FormlessTheme? No Colors and input field styling
backgroundColor Color? No Background behind the chat padding
sendIcon Widget? No Custom icon inside the send button
optionsSheetPresenter FormlessOptionsSheetPresenter? No Custom UI for the searchable options sheet; default uses showDefaultFormlessOptionPickerSheet
onComplete Function(Map<String, dynamic>)? No Called with collected data when done
onError Function(String)? No Called on network or API errors

QuestionsModel #

Parameter Type Required Description
question String Yes The question shown to the user
key String Yes Key used in the onComplete data map
type QuestionFieldType? No Drives validation rules (email, phone, numeric, etc.)
validationMessage String? No Custom rule the LLM must strictly follow
options List<String>? No Multiple choice; chips or searchable sheet; restricts valid answers for that field
optionsPresentation QuestionOptionsPresentation? No automatic (default): chips if ≤12 options, else sheet; or chips / searchableSheet
optionsChipMaxCount int? No When automatic, max count for chips before using the sheet (default 12)
optionsSheetTitle String? No Title on the searchable sheet and on the open button
onValidate Future<String?> Function(String)? No After the AI accepts, run custom checks (e.g. API); return null or an error message for the user

FormlessTheme #

Parameter Description
userBubbleColor User message bubble background
botBubbleColor Bot message bubble background
userTextColor Text color in user bubbles
botTextColor Text color in bot bubbles
sendButtonColor Send button background
typingIndicatorColor Animated typing dots color
inputDecoration Full InputDecoration override for the text field
inputTextStyle Text style for what the user types
inputBorderColor Border color (ignored when inputDecoration is set)
inputHintText Hint text (ignored when inputDecoration is set)

License #

MIT

4
likes
150
points
244
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter package that turns any list of fields into an AI-powered conversational form. Validates answers via Groq, OpenAI, Gemini, or DeepSeek and returns a clean key->value map when all fields are collected.

Repository (GitHub)
View/report issues

Topics

#form #ai #llm #chat #validation

License

MIT (license)

Dependencies

flutter, http

More

Packages that depend on formless