It is just a simple example of how to generate an underline when text change for a general case. The output behavior on the screen still needs to be tuned if the underline position is not on the place you want.
Check more on heigh property

If you want to allocate a Fixed Lines Textfield, you can try the below widget:
style is required because it uses the fontSize & height to calculate the line number
controller use to monitor the text changed
textPainter in the initState simulates the text behavior of the text and calculates the lines of text that will be shown.
class UnderlineTextField extends StatefulWidget { const UnderlineTextField({ required this.style, this.controller, this.maxLines = 3, Key? key, }) : super(key: key); final TextEditingController? controller; final int? maxLines; final TextStyle style; @override State<UnderlineTextField> createState() => _UnderlineTextFieldState(); } class _UnderlineTextFieldState extends State<UnderlineTextField> { double maxWidth = 0; int numberOfLines = 1; @override void initState() { /// Calculate numberOfLines widget.controller?.addListener(() { final text = widget.controller?.text ?? ''; final textPainter = TextPainter( text: TextSpan(text: text, style: widget.style), maxLines: widget.maxLines, textDirection: TextDirection.ltr, ); textPainter.layout(maxWidth: maxWidth); setState(() { numberOfLines = textPainter.computeLineMetrics().length; }); }); super.initState(); } @override Widget build(BuildContext context) { final fontSize = widget.style.fontSize ?? 12; final textHeight = fontSize * (widget.style.height ?? 1); return LayoutBuilder( builder: (_, constrinat) { maxWidth = constrinat.maxWidth; return Stack( children: [ TextField( style: widget.style, controller: widget.controller, decoration: const InputDecoration( isDense: true, border: InputBorder.none, ), maxLines: widget.maxLines, ), Positioned.fill( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: List.generate( numberOfLines, (index) => Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: textHeight - 1), const Divider(height: 1, thickness: 1), ], ), ), ), ), ], ); }, ); } }