Prompt Composer
Loading preview
Starting the Flutter app
A Flutter AI prompt composer with an expanding input, attachment and voice buttons, and a send action.
import 'package:flutter/material.dart';
class PromptComposer extends StatefulWidget {
const PromptComposer({super.key});
@override
State<PromptComposer> createState() => _PromptComposerState();
}
class _PromptComposerState extends State<PromptComposer> {
final _controller = TextEditingController();
final _suggestions = const [
'Summarize this article',
'Write a product description',
'Explain like I\'m five',
'Draft a follow-up email',
];
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('New prompt')),
body: Column(
children: [
Expanded(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
Text('Try one of these',
style: theme.textTheme.labelLarge
?.copyWith(color: scheme.onSurfaceVariant)),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final s in _suggestions)
ActionChip(
avatar: const Icon(Icons.auto_awesome, size: 16),
label: Text(s),
onPressed: () => _controller.text = s,
),
],
),
],
),
),
SafeArea(
top: false,
child: Container(
margin: const EdgeInsets.all(12),
padding: const EdgeInsets.fromLTRB(12, 8, 8, 8),
decoration: BoxDecoration(
color: scheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24),
),
child: Column(
children: [
TextField(
controller: _controller,
maxLines: 5,
minLines: 1,
decoration: const InputDecoration(
hintText: 'Ask anything…',
border: InputBorder.none,
isCollapsed: true,
),
),
const SizedBox(height: 8),
Row(
children: [
IconButton(
tooltip: 'Add image',
onPressed: () => _showDemo(context, 'Add image (demo)'),
icon: const Icon(Icons.add_photo_alternate_outlined)),
IconButton(
tooltip: 'Attach file',
onPressed: () => _showDemo(context, 'Attach file (demo)'),
icon: const Icon(Icons.attach_file)),
IconButton(
tooltip: 'Voice input',
onPressed: () => _showDemo(context, 'Voice input (demo)'), icon: const Icon(Icons.mic_none)),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 4),
decoration: BoxDecoration(
border: Border.all(color: scheme.outlineVariant),
borderRadius: BorderRadius.circular(20),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('GPT-4o'),
Icon(Icons.expand_more, size: 18),
],
),
),
const SizedBox(width: 8),
IconButton.filled(
tooltip: 'Send prompt',
onPressed: () => _showDemo(context, 'Send prompt (demo)'),
icon: const Icon(Icons.arrow_upward)),
],
),
],
),
),
),
],
),
);
}
}
void _showDemo(BuildContext context, String message) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(content: Text(message)));
}
Add it to your project
Copies the Dart source into your app, so you own it. Flutter SDK only, no extra packages.
First time? Run dart pub global activate fluttertemplates_dev, or read the CLI guide.
How it works
The one live interaction is the suggestions: each ActionChip writes its string from _suggestions into _controller, and the TextField grows from one line to five. Nothing in the toolbar row is connected. The image, attachment, mic and send IconButtons show a demo SnackBar, and the ‘GPT-4o’ pill is a static Row rather than a menu.
Adapting it
- Read
_controller.textin theIconButton.filledhandler, send it to your model, then clear the field. - Replace the pill’s
Containerwith aPopupMenuButtonand hold the chosen model in state. - Hook
Icons.attach_fileandIcons.add_photo_alternate_outlinedto a file picker, and show selected files above theTextField.