---
title: NakedTextField
description: Headless text input component with native editing behavior
keywords: [flutter, textfield, editabletext, headless, input]
---

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

<Info>
  See the complete example in [`packages/example/lib/api/naked_textfield.0.dart`](https://github.com/btwld/naked_ui/blob/main/packages/example/lib/api/naked_textfield.0.dart).
</Info>

## Basic implementation

```dart
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:

```dart
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

```dart
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.error = false,
  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.semanticErrorText,
  this.strutStyle,
  this.excludeSemantics = false,
})
```

> 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 new `DefaultTextStyle`
- `ignorePointers` temporarily disables pointer input while leaving focus/keyboard flow intact—handy when showing loading states around the same editable instance
- `groupId` matches `EditableText`’s grouping behaviour (set it when coordinating multiple fields for the same input method)

## Event Hooks

- `onTap`, `onTapChange`, `onTapOutside`, `onTapUpOutside` → fine-grained gesture hooks
- `onChanged`, `onSubmitted`, `onEditingComplete` → forward the underlying `EditableText` callbacks
- `onHoverChange`, `onFocusChange`, `onPressChange` → interaction callbacks

## Accessibility & Semantics

- Supply `semanticLabel` and `semanticHint` when the visual chrome lacks textual context
- Pair `semanticErrorText` with `error: true`; when `error` is false, the error text is not announced and the field is not a live region
- Set `excludeSemantics` to hide the field and its descendants from accessibility services
- `NakedTextField` wraps the builder result with proper `Semantics` for focusable text fields
- Respect contrast and focus indicators in your builder to ensure the field remains discoverable

For a validation error, derive both parameters from the same state:

```dart
NakedTextField(
  controller: emailController,
  error: emailError != null,
  semanticLabel: 'Email',
  semanticErrorText: emailError,
  builder: (context, state, editableText) => editableText,
);
```

## Tips

- For Material/Cupertino visual parity, wrap the builder output with your design system components
- Use `maxLines`, `minLines`, and `expands` 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
