NakedTextField
Headless text input component with native editing behavior
Headless text input component. Preserves native text editing behavior (selection handles, keyboard shortcuts, IME). Use builder pattern for custom styling.
When to use this
- Form inputs: Text fields in forms (name, email, message, etc.)
- Search boxes: Search inputs with custom styling
- Text areas: Multi-line text input with custom appearance
- Specialized inputs: Custom styled inputs that standard TextFields can't provide
See the complete example in example/lib/api/naked_textfield.0.dart
.
Basic implementation
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
class HeadlessField extends StatefulWidget {
const HeadlessField({super.key});
@override
State<HeadlessField> createState() => _HeadlessFieldState();
}
class _HeadlessFieldState extends State<HeadlessField> {
final controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return NakedTextField(
controller: controller,
onChanged: (value) => debugPrint('Value: $value'),
builder: (context, state, editable) {
final borderColor = state.when(
focused: Colors.blue,
hovered: Colors.grey.shade400,
orElse: Colors.grey.shade300,
);
final borderWidth = state.isFocused ? 2 : 1;
return AnimatedContainer(
duration: const Duration(milliseconds: 160),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.white,
border: Border.all(
color: borderColor,
width: borderWidth,
),
boxShadow: state.isFocused
? [
BoxShadow(
color: Colors.blue.withOpacity(0.12),
blurRadius: 12,
offset: const Offset(0, 4),
),
]
: null,
),
child: editable,
);
},
);
}
}
Builder Contract
NakedTextFieldBuilder
has the signature:
typedef NakedTextFieldBuilder = Widget Function(
BuildContext context,
NakedTextFieldState state,
Widget editableText,
);
The provided editableText
widget is an EditableText
configured with all parameters from NakedTextField
. The state
parameter gives you access to interaction states like state.isFocused
, state.isHovered
, and state.when()
for conditional styling. Wrap the editable text with decoration, icons, paddings, or animations—just return the composed widget.
Constructor Highlights
const NakedTextField({
Key? key,
this.groupId = EditableText,
this.controller,
this.focusNode,
this.undoController,
this.keyboardType,
this.textInputAction,
this.textCapitalization = TextCapitalization.none,
this.textAlign = TextAlign.start,
this.textDirection,
this.readOnly = false,
this.showCursor,
this.autofocus = false,
this.obscuringCharacter = '•',
this.obscureText = false,
this.autocorrect = true,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
this.enableSuggestions = true,
this.maxLines = 1,
this.minLines,
this.expands = false,
this.maxLength,
this.maxLengthEnforcement,
this.onChanged,
this.onEditingComplete,
this.onSubmitted,
this.onAppPrivateCommand,
this.inputFormatters,
this.enabled = true,
this.cursorWidth = 2.0,
this.cursorHeight,
this.cursorRadius,
this.cursorOpacityAnimates,
this.cursorColor,
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
this.keyboardAppearance,
this.scrollPadding = const EdgeInsets.all(20.0),
this.dragStartBehavior = DragStartBehavior.start,
this.enableInteractiveSelection = true,
this.selectionControls,
this.onTap,
this.onTapAlwaysCalled = false,
this.onTapChange,
this.onTapOutside,
this.scrollController,
this.scrollPhysics,
this.autofillHints = const <String>[],
this.contentInsertionConfiguration,
this.clipBehavior = Clip.hardEdge,
this.restorationId,
this.onTapUpOutside,
this.stylusHandwritingEnabled = true,
this.enableIMEPersonalizedLearning = true,
this.contextMenuBuilder,
this.canRequestFocus = true,
this.spellCheckConfiguration,
this.magnifierConfiguration,
this.onHoverChange,
this.onFocusChange,
this.onPressChange,
this.style,
required this.builder,
this.ignorePointers,
this.semanticLabel,
this.semanticHint,
this.strutStyle,
})
Parameters mirror
EditableText
, so you can keep using the same configuration options you know from Flutter’s built-in text fields.
Styling & Pointer Control
style
lets you override the text style without wrapping the builder output in a newDefaultTextStyle
ignorePointers
temporarily disables pointer input while leaving focus/keyboard flow intact—handy when showing loading states around the same editable instancegroupId
matchesEditableText
’s grouping behaviour (set it when coordinating multiple fields for the same input method)
Event Hooks
onTap
,onTapChange
,onTapOutside
,onTapUpOutside
→ fine-grained gesture hooksonChanged
,onSubmitted
,onEditingComplete
→ forward the underlyingEditableText
callbacksonHoverChange
,onFocusChange
,onPressChange
→ interaction callbacks
Accessibility & Semantics
- Supply
semanticLabel
/semanticHint
when the visual chrome lacks textual context NakedTextField
wraps the builder result with properSemantics
for focusable text fields- Respect contrast and focus indicators in your builder to ensure the field remains discoverable
Tips
- For Material/Cupertino visual parity, wrap the builder output with your design system components
- Use
maxLines
,minLines
, andexpands
to create multi-line editors - Provide an
UndoHistoryController
when you need cross-field undo stacks - Remember to dispose any controllers you allocate alongside the widget