Building smooth, user-friendly forms is a core part of Flutter development. Whether you are building a chat app, a quick note-taking tool, or a feedback form, handling flutter multiline text correctly makes a huge difference in your app's user experience.
If you have ever tried to set up a TextField for longer responses, you might have run into common UI headaches.
How do you make a field start small and grow as the user types?
How do you restrict the total height without cutting off text?
What happens when you need a field that fills the entire remaining screen height?
In this guide, we are going to break down everything you need to know about the Flutter Multiline TextField.
We will cover how properties like flutter maxLines, minLines, and flutter expands work together to power real-world UI patterns—from auto-growing comment boxes and chat input bars to fixed scrollable text fields and simple markdown editor basics.
Let's dive right into the code and start mastering multi-line inputs in Flutter!
Ready to Go Beyond the Basics?
Learn Flutter the right way with 100+ practical lessons, real projects, and lifetime updates.
maxLines
When working with a TextField in Flutter, the maxLines property is your primary tool for controlling multi-line input. By default, a TextField has maxLines: 1, which locks it into a single line that scrolls horizontally.
To enable multi-line text input, you change maxLines to a value greater than 1, or set it to null. Understanding how maxLines behaves under the hood helps you choose the right approach for your app:
-
Fixed Height (
maxLines: 4): SettingmaxLinesto a fixed integer tells Flutter to size the field to fit exactly that number of lines immediately. The field stays that height even when empty. -
Auto-Growing (
maxLines: null): SettingmaxLinestonullallows the input field to grow dynamically without any upper limit as the user types new lines. -
Text Wrapping (
keyboardType: TextInputType.multiline): Pair multi-line configuration withkeyboardType: TextInputType.multilineso the soft keyboard shows an Enter/Return key instead of an Action/Done key.
Here is a complete, runnable example showing a fixed 4-line input field. Notice how maxLines: 4 reserves space for four lines right from the start:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Fixed maxLines Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Enter your notes below:'),
const SizedBox(height: 8),
TextField(
controller: _controller,
maxLines: 4,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
hintText: 'Type your feedback or notes here...',
border: OutlineInputBorder(),
),
),
],
),
),
);
}
}
minLines
While maxLines controls the maximum height of your input field, minLines sets the starting baseline. If you want a flutter multiline text field to look clean and expand smoothly as users type, combining minLines and maxLines is key.
When using minLines, remember these essential rules:
-
Must Pair with
maxLines: You cannot setminLinesby itself. You must also definemaxLines, andmaxLinesmust be greater than or equal tominLines(or set tonull). -
Initial Visual Height: Setting
minLines: 2forces the field to start with a height of exactly two lines, even before the user starts typing flutter long text. -
Controlled Growth: Setting
minLines: 2and flutter maxLines: 5creates a field that starts at 2 lines, expands dynamically as more text is added, and stops growing at 5 lines.
This pattern is ideal for comment boxes and feedback forms where you want to signal to the user that multi-line text is expected without taking up half the screen immediately.
Here is a complete working example showing a dynamic field that starts at 2 lines and grows up to 5 lines:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('minLines & maxLines Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Leave a comment:'),
const SizedBox(height: 8),
TextField(
controller: _controller,
minLines: 2,
maxLines: 5,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
hintText: 'Starts at 2 lines, grows up to 5 lines...',
border: OutlineInputBorder(),
),
),
],
),
),
);
}
}
expands
Sometimes you want an input field to fill all available vertical space in its parent widget—like a full-screen note-taking app or a full-page text editor. That is where flutter expands comes in.
When setting up a text field to stretch and fill space, keep these important constraints in mind:
-
Requires
maxLines: nullandminLines: null: To useexpands: true, bothmaxLinesandminLinesmust be set tonull. Leaving either property as an integer will throw a runtime assertion error. -
Needs Bounded Constraints: The
TextFieldmust be wrapped inside a widget that provides explicit vertical constraints, such asExpanded,SizedBox, orContainer. Without explicit height boundaries, Flutter won't know how far the input field should stretch. - Automatic Inner Scrolling: Once the text exceeds the available container area, the field automatically becomes a flutter multiline scroll area without any extra configuration.
Here is a complete, runnable example using expands: true inside an Expanded widget to create a full-bleed note editor:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Full Screen Note Editor')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Document Body:'),
const SizedBox(height: 8),
Expanded(
child: TextField(
controller: _controller,
expands: true,
maxLines: null,
minLines: null,
keyboardType: TextInputType.multiline,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
hintText: 'Start typing your document or long text here...',
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
),
),
],
),
),
);
}
}
Auto-growing fields
Auto-growing fields are one of the most popular UI patterns in mobile apps. Instead of locking a text field to a fixed height, an auto-expanding input starts as a single line (or a few lines) and smoothly grows as the user types more flutter long text.
To create auto-growing fields in Flutter, you combine minLines and maxLines with flexible values:
-
Uncapped Auto-Growth (
minLines: 1,maxLines: null): The input starts as a single line and grows infinitely down the screen as long as text is added. -
Capped Auto-Growth (
minLines: 1,maxLines: 5): The field grows smoothly line by line until it hits 5 lines. After reaching the cap, it stops expanding vertically and switches to flutter multiline scroll internally. -
Smart Text Wrapping: Setting
keyboardType: TextInputType.multilineensures that text automatically wraps to the next line when reaching the horizontal edge via flutter wrap text.
Capped auto-growth is the exact pattern used in modern chat input bars and messaging platforms like WhatsApp or Slack.
Here is a full working example showing how to build a clean, capped auto-growing text field:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Auto-Growing TextField')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Dynamic Description Field:'),
const SizedBox(height: 8),
TextField(
controller: _controller,
minLines: 1,
maxLines: 4,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
hintText: 'Type to watch this field grow...',
border: OutlineInputBorder(),
),
),
],
),
),
);
}
}
Long text input
Handling flutter long text inputs effectively requires paying close attention to performance, scrolling behavior, and visual layout.
When users type essays, detailed feedback, or extensive notes inside a flutter multiline field, small configuration details make a huge difference in keeping your UI smooth and responsive.
When designing fields specifically for long text input, keep these critical tips in mind:
-
Cursor Alignment: By default, Flutter centers text vertically inside a tall
TextField. For long text inputs, settextAlignVertical: TextAlignVertical.topanddecoration: InputDecoration(alignLabelWithHint: true)so the cursor and hint label start cleanly at the top-left corner. -
Controlled Scrolling: Pairing a capped
maxLinesconfiguration (likemaxLines: 8) with a explicit height box prevents long paragraphs from consuming the entire screen while maintaining smooth internal text scrolling. - Automatic Line Wrapping: Leveraging built-in flutter wrap text capabilities ensures long strings without hard line breaks wrap naturally at word boundaries without clipping off the side of the screen.
Here is a complete, working example optimized specifically for long text input, featuring aligned hint text and a fixed maximum visible line count:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Long Text Input Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Detailed Feedback / Journal Entry:'),
const SizedBox(height: 8),
TextField(
controller: _controller,
minLines: 6,
maxLines: 8,
keyboardType: TextInputType.multiline,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
hintText: 'Enter your detailed response here. The cursor starts right at the top!',
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
),
],
),
),
);
}
}
Scrollable text
When building forms for long-form content, controlling flutter multiline scroll behavior is critical to ensure a smooth user experience.
If a text field gets too tall, it can push other crucial UI elements off the screen or clash with parent scrolling views.
Understanding how to manage scrollable text inside a flutter multiline text field involves three key strategies:
-
Internal Scroll Physics: By default, when content exceeds the configured flutter maxLines or boundary container, the
TextFieldautomatically becomes scrollable internally. -
Avoiding Parent Collisions: If your
TextFieldis inside a scrollable parent (like aListVieworSingleChildScrollView), setscrollPhysics: BouncingScrollPhysics()orClampingScrollPhysics()on the input field to make inner scrolling feel smooth and natural. -
Attaching a ScrollController: You can attach a
ScrollControllerdirectly to theTextFieldto programmatically scroll to the bottom as the user types flutter long text.
Here is a complete, runnable example showing how to attach a ScrollController to keep the input area strictly capped while ensuring the internal text remains effortlessly scrollable:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_controller.dispose();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Scrollable Text Field')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Scrollable Input Area (Max 4 visible lines):'),
const SizedBox(height: 8),
TextField(
controller: _controller,
scrollController: _scrollController,
minLines: 4,
maxLines: 4,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
hintText: 'Paste or type a long paragraph here to test internal scrolling...',
border: OutlineInputBorder(),
),
),
],
),
),
);
}
}
Markdown editor basics
Building a live markdown preview is one of the most effective ways to combine a flutter multiline input field with dynamic output rendering.
By pairing a multiline TextField with Flutter's built-in RichText widget (or dedicated rendering components), you can instantly parse and preview styled text in real time.
When building flutter markdown textfield features and live editors, keep these core concepts in mind:
-
Split View or Side-by-Side Layout: Use a
ColumnorRowwithExpandedwidgets to display the raw input field and formatted preview together. -
Text Parsing with
RichText: Instead of plain text displays, useRichTextandTextSpantrees to apply custom styles like bold text, italics, and custom headers based on simple markdown syntax. -
Auto-Expanding Input: Use flutter expands or a flexible
maxLines: nullsetup so the editing area grows naturally while handling flutter long text.
Here is a working example of a simple live markdown editor that parses bold text using RichText alongside a multiline input field:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
String _inputText = '';
@override
void initState() {
super.initState();
_controller.addListener(() {
setState(() {
_inputText = _controller.text;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
// Simple parser to demonstrate RichText rendering for **bold** text
List<TextSpan> _parseMarkdown(String text) {
final List<TextSpan> spans = [];
final RegExp regExp = RegExp(r'\*\*(.*?)\*\*');
int lastMatchEnd = 0;
for (final Match match in regExp.allMatches(text)) {
if (match.start > lastMatchEnd) {
spans.add(TextSpan(text: text.substring(lastMatchEnd, match.start)));
}
spans.add(
TextSpan(
text: match.group(1),
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
lastMatchEnd = match.end;
}
if (lastMatchEnd < text.length) {
spans.add(TextSpan(text: text.substring(lastMatchEnd)));
}
return spans;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Markdown Editor Basics')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Editor (Use **bold** syntax):'),
const SizedBox(height: 8),
Expanded(
child: TextField(
controller: _controller,
minLines: null,
maxLines: null,
expands: true,
keyboardType: TextInputType.multiline,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
hintText: 'Type markdown here (e.g., Hello **World**)...',
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
),
),
const SizedBox(height: 16),
const Text('RichText Preview:'),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: Colors.grey.shade300),
),
child: RichText(
text: TextSpan(
style: const TextStyle(color: Colors.black, fontSize: 16),
children: _inputText.isEmpty
? [
const TextSpan(
text: 'Preview will appear here...',
style: TextStyle(color: Colors.grey),
),
]
: _parseMarkdown(_inputText),
),
),
),
],
),
),
);
}
}
Chat input
Creating a modern chat input bar is one of the most common applications of a flutter multiline text field.
In a real-world messaging app, the input area needs to start as a single line, grow smoothly as the user types, and integrate seamlessly with dynamic actions like send buttons.
When building a chat input bar in Flutter, keep these key techniques in mind:
-
Auto-Growing Bounds: Combine
minLines: 1and flutter maxLines: 5to let the field expand naturally up to 5 lines before switching to flutter multiline scroll. -
Flexible Layout: Wrap the
TextFieldinside anExpandedwidget within a horizontalRowso it occupies all available space next to your send or attachment action icons. - Automatic Line Wrapping: Rely on flutter wrap text behavior so long messages break neatly into new lines without overflowing the row container horizontally.
Here is a complete, runnable example showing how to build a production-grade chat input bar at the bottom of a screen:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _sendMessage() {
if (_controller.text.trim().isNotEmpty) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Sent: ${_controller.text}')));
_controller.clear();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Chat UI Input Bar')),
body: Column(
children: [
Expanded(
child: Container(
color: Colors.grey.shade50,
child: const Center(
child: Text('Chat messages list goes here...'),
),
),
),
SafeArea(
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 4,
offset: const Offset(0, -2),
),
],
),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
minLines: 1,
maxLines: 5,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
hintText: 'Type a message...',
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.0),
),
),
),
),
const SizedBox(width: 8.0),
IconButton(
icon: const Icon(Icons.send),
color: Theme.of(context).colorScheme.primary,
onPressed: _sendMessage,
),
],
),
),
),
],
),
);
}
}
Comment boxes
Designing effective comment boxes requires balancing visual structure with flexibility.
Unlike a simple single-line input or a massive full-screen text editor, comment inputs work best when they start with a predictable initial height, scale as users add content, and provide clear submission actions.
When building dynamic comment forms with a flutter multiline setup, keep these best practices in mind:
-
Starting Height with
minLines: SetminLines: 3so the input area explicitly looks like a comment field right from the start, inviting users to write more than just a word or two. -
Bounding Growth with
maxLines: Set flutter maxLines: 6to allow the field to expand for flutter long text while preventing long comments from pushing key actions completely off the viewport. - Layout Structure: Wrap the field inside a structured container complete with submission controls (like "Post Comment" or "Cancel" buttons) directly beneath the input boundary.
Here is a complete, working example showing how to build a clean social comment box card:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _submitComment() {
if (_controller.text.trim().isNotEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Comment posted: ${_controller.text}')),
);
_controller.clear();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Comment Box Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
elevation: 15,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Add a Comment'),
const SizedBox(height: 12),
TextField(
controller: _controller,
minLines: 3,
maxLines: 6,
keyboardType: TextInputType.multiline,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
hintText: 'What are your thoughts?',
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => _controller.clear(),
child: const Text('Cancel'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _submitComment,
child: const Text('Post Comment'),
),
],
),
],
),
),
),
),
);
}
}
Character limits
When collecting flutter long text or multi-line responses, adding character limits ensures users don't exceed backend constraints—like database field caps or SMS limits.
Flutter makes managing input length straightforward using the maxLength property on TextField.
When setting up character limits in a flutter multiline text field, keep these built-in behaviors and customization tips in mind:
-
Built-in Character Counter: Setting
maxLength: 250automatically displays a clean visual counter (e.g.,0/250) at the bottom-right corner of the input box. -
Strict Enforcement: By default,
maxLengthprevents the user from typing any additional characters once the limit is reached. -
Hiding the Default Counter: If you want to enforce a limit without showing the counter widget, set
buildCounter: (context, {required currentLength, required isFocused, maxLength}) => null. -
Combining with Auto-Growth: You can freely combine
maxLengthwith flutter maxLines andminLinesto create auto-expanding comment boxes or tweet-style social posts that strictly limit character counts.
Here is a complete, runnable example showing an auto-growing input field with a 150-character limit counter:
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Character Limit Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Short Review (Max 150 characters):'),
const SizedBox(height: 8),
TextField(
controller: _controller,
maxLength: 150,
minLines: 2,
maxLines: 4,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
hintText: 'Share your quick review...',
border: OutlineInputBorder(),
),
),
],
),
),
);
}
}
Related Resources & Further Reading
To master text inputs, form state management, and user interaction flows in Flutter, check out these related guides arranged from basic setup to advanced execution:
-
Foundations & Basics
- The Complete Flutter TextField Guide — Start here to understand the core mechanics of text inputs, text controllers, and initial setups.
-
Styling & User Experience
- Flutter TextField Customization — Discover how to style borders, hints, input decorations, and theme configurations to fit your design system.
-
Focus & Keyboard Control
- Flutter FocusNode Guide — Learn how to control keyboard focus, manage focus transitions, and jump between fields programmatically.
-
Handling Keyboards in Flutter — Prevent layout overflow errors, handle soft keyboards gracefully, and tune
TextInputTypeoptions.
-
Validation & Advanced Patterns
- Form Validation in Flutter — Implement real-time user input validation, error styling, and robust form submissions.
- Advanced Flutter TextField Guide — Go deeper into custom text formatters, dynamic text selection, and complex input pipelines.
-
Next Steps
- Flutter UI Engineering Course — Master production-ready layout engineering, responsive design, and advanced custom Flutter widgets.
Ready to Build Professional Flutter Apps?
Turn today’s knowledge into real-world Flutter skills with Flutter Foundations.









Top comments (0)