TextInput
TextInput edits one line of text. Use it for a search box, a name field, or
the composer in a list. For several lines, use TextArea.
import 'package:noir/noir.dart';Minimal working field
This complete widget owns the controller, reads the text on submit, and
releases the controller in dispose:
class SearchField extends StatefulWidget {
const SearchField({required this.onSearch, super.key});
final ValueChanged<String> onSearch;
@override
State<SearchField> createState() => _SearchFieldState();
}
class _SearchFieldState extends State<SearchField> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => TextInput(
controller: _controller,
autofocus: true,
placeholder: 'package name',
onSubmit: () => widget.onSearch(_controller.text.trim()),
);
}Properties you will reach for first
| Property | Default | Behavior |
|---|---|---|
controller | null | The TextEditingController that owns the text, selection, and caret. |
value | null | Text the field owns for you. It sets the starting text, and a new value from a rebuild replaces the text. Passing both controller and value throws. |
placeholder | null | Dimmed hint text shown while the field is empty. |
onChanged | null | Called with the raw text after the reader edits it. Programmatic controller changes do not call it. |
onSubmit | null | Called when the reader presses Enter. |
maxLength | 100 | Maximum accepted characters. Raise it for a long line. |
focusNode | null | Supply one when a parent must request or observe focus. |
autofocus | false | Requests primary focus when the field first mounts. |
obscureText / obscuringCharacter | false / '*' | Paints each grapheme as the obscuring character. The stored text stays raw. |
cursorStyle | CursorStyle.block | Shape drawn for the caret. |
color, backgroundColor, cursorColor | Theme values | Override only for a deliberate exception to the Theme. |
Keyboard and pointer behavior
- Printable input inserts at the caret while the field has focus.
- Backspace and Delete remove one grapheme cluster.
- Left, Right, Home, and End move the caret. Ctrl+Home and Ctrl+End jump to the start and end of the text.
- Enter calls
onSubmit. The field never inserts a newline;TextAreadoes. - Tab is not consumed, so focus traversal keeps working.
- A primary click takes focus. It does not move the caret to the clicked column. A field that gains focus with no usable selection puts the caret at the end of its text.
Who owns what
The code that creates a TextEditingController or a FocusNode also disposes
it. TextInput creates an internal focus node only when you do not pass one,
and it disposes what it created. It never disposes a controller or node you
supplied.
Read controller.text inside the callback that needs it. Do not keep a second
copy of the text in State and try to keep the two in sync.
onChanged reports the reader’s edits: typing, deleting, and pasting. Changing
controller.text from your own code updates the field and repaints it, but it
does not call onChanged. When a filter or a search must also react to a
programmatic change, listen to the controller instead:
_controller.addListener(() => _applyFilter(_controller.text));Remove that listener, or dispose the controller, in the same State that added
it. This is why clearing a search field in code does not rerun a filter that is
attached only to onChanged.
Common mistake
Passing both controller and value throws an assertion. Choose one:
controller when your code must read or change the text and selection, value
when the parent supplies the text and onChanged is enough.
Related
- Handle input and focus — event order, focus ownership, and shortcuts.
- Manage state and resources —
useTextEditingControllerkeeps the same ownership without aStateclass. - Widget catalog — the other interactive controls.
- Generated signature
— every parameter for the version in your
pubspec.lock.