Skip to content

feat: TextField component#4904

Open
wonderlul wants to merge 1 commit intocallstack:@adrcotfas/feat/dynamic_themefrom
wonderlul:feat/TextField
Open

feat: TextField component#4904
wonderlul wants to merge 1 commit intocallstack:@adrcotfas/feat/dynamic_themefrom
wonderlul:feat/TextField

Conversation

@wonderlul
Copy link
Copy Markdown
Collaborator

Motivation

File structure
The component is split by variant (filled/ and outlined/) and a root that wires shared behavior. Each area keeps logic, styles, utils, and constants in separate files. That follows patterns already used elsewhere in the library, but goes one step further so responsibilities stay obvious: variant-specific layout and theming do not drown in shared code, and the public API file stays focused on behavior and types.

LeftAccessory / RightAccessory vs TextInput adornments
TextInput composes leading and trailing content through left and right, which are built around icons and affixes (TextInput.Icon, TextInput.Affix) and internal adornment types. TextField instead exposes LeftAccessory and RightAccessory as render props (component types). The field passes curated layout and state—notably the merged style for alignment with the field, plus status, multiline, and editable—so accessories stay aligned with the input without re-implementing field internals. That supports arbitrary leading/trailing UI (clear actions, custom buttons, non-icon content) while still inheriting the important structural styles from the component.

filled / outlined instead of flat / outlined
Material Design 3 describes text fields in terms of filled and outlined styles. The existing TextInput API uses mode: 'flat' | 'outlined', where “flat” corresponds to the filled look. The new component names variants filled and outlined so the public API matches MD3 language and is easier to map from the spec and design tools.

Style overrides
TextField is built as a small stack of clear layers, and each layer can be adjusted without fighting the rest. The outer pressable wrapper, the field shell (border, background, row that includes accessories), and the inner content wrapper (label + TextInput) each accept dedicated style props (pressableStyle, fieldStyle, containerStyle). The underlying TextInput still uses the normal style prop (and the rest of TextInputProps) for typography, padding, and input-specific layout. Label and helper text can be customized through labelProps and helperProps (including style). Leading/trailing UI uses LeftAccessory / RightAccessory, which receive a prepared style from the field so custom content stays aligned while remaining fully under your control. Together, this gives predictable “override the part you mean” behavior instead of a single opaque style that’s hard to reason about.

TextField instead of TextInput
Material Design 3 uses the term text field for this control. Exporting a second “Paper” input named TextInput would also blur the built-in React Native TextInput in imports and documentation. A dedicated TextField name keeps the design-system component clearly namespaced and aligned with MD3, while the underlying control remains React Native’s TextInput where appropriate.

Positioning relative to TextInput
TextField is intended as the modern replacement path for form text entry in react-native-paper: implementation is structured for clarity and maintainability, and it adopts MD3-oriented theming (including use of the PlatformColor API where it fits platform tokens). Compared to the legacy TextInput stack, this design aims to be easier to follow, less ad hoc in how variants and layout are split, and more efficient in how styles and state are applied—giving teams a refreshed, spec-aligned building block for new work without forcing an immediate break for existing TextInput users.

Order of merging

#4901

Related issue

#4878
#4329
#4235

Test plan

Run the Example app.

filled
text-field-filled

outlined
text-field-outlined

@callstack-bot
Copy link
Copy Markdown

callstack-bot commented Apr 22, 2026

Hey @wonderlul, thank you for your pull request 🤗. The documentation from this branch can be viewed here.

Copy link
Copy Markdown
Collaborator

@adrcotfas adrcotfas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below are some components described in the specs I see were not covered. Do we want them?

  • in all error examples from the specs there's a trailing error icon
  • the option to have leading and trailing text (called prefix and suffix) for currency, units of measurement etc; for suffix, the text field is aligned to the end instead of start;
  • character counter (aligned to the end, same style as supporting text)
  • The native filled TextField has an animation for the outline when selecting it: center to edges reveal
  • see https://m3.material.io/components/text-fields/guidelines

Some other notes:

  • Please double check the tokens from the specs. I added some comments about opacity/color for certain elements that was missing; maybe there are other inconsistencies.
  • A more complex example would be nice to cover more use cases and these elements, if we add them.

Here's a recording from this app showing the features mentioned above: https://github.com/material-components/material-components-android. I recommend installing this app to double check how TextField looks and behaves like in native.

screen-20260423-165915-1776952725646.mp4

Comment thread src/components/TextField/outlined/logic.ts
Comment thread src/components/TextField/styles.ts
Comment thread src/components/TextField/filled/constants.ts Outdated
Comment thread src/components/TextField/filled/logic.ts Outdated
Comment thread src/components/TextField/outlined/utils.ts Outdated
Comment thread src/components/TextField/filled/logic.ts Outdated
Comment thread example/src/Examples/TextFieldExample.tsx

import {
ACCESORY_SIZE,
HELPER_FONT_SIZE,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the specs, I would rename all occurrences of helper to supportingText and accessory to icon.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MD3 describes the common case as leading/trailing icons, but the slots are intentionally generic: users may pass text, badges, custom controls, or non-icon content. Naming those props *Icon would misdescribe valid usage and nudge people toward icons only. “Accessory”-style naming is a deliberate pattern for flexible slots (similar to how many form libraries avoid over-specific names).

Agreed on supportingText — that matches the MD3 wording for the area under the field and keeps the API aligned with the spec without changing behavior. I’ll rename helper / related names to supportingText (and adjust docs/tests accordingly). Thanks for the suggestion.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that makes sense but do we want to encourage users to add customization beyond what the guidelines describe like icons(icon buttons)?
The only place I found a non-icon accessory described was for the credit card example here, but we could argue that icon/image are equivalent: https://m3.material.io/components/text-fields/guidelines.

An image can help contextualize the required input text such as a credit card number.

For text there are different props we don't have yet in TextField: prefixText and suffixText which should be aligned with the actual text inside the text field, not centered vertically like the current accessories. See screen-20260423-165915-1776952725646.mp4.

In my opinion users of the library would appreciate a react-native-paper API as close as possible in naming with the MD3 specs (leadingIcon, trailingIcon) making it easier to follow the specs.
What do you think?

Copy link
Copy Markdown
Collaborator Author

@wonderlul wonderlul Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t have a strong opinion on this one — I think it’s really a product direction call: whether the public API should track MD3 as closely as possible (easier mental mapping from the spec) or stay a bit more generic so non–MD3 patterns stay natural in React Native.

Splitting things into leading/trailing icon plus prefix/suffix text (or similar) would line up better with the spec and can feel more “complete” but it also multiplies props and concepts for both authors and consumers, and we’d need clear rules for composition and order when several adornments exist. So it’s a tradeoff between spec fidelity and API surface / noise, not a purely technical right-or-wrong.

I’m fine either way the team wants to go; happy to adjust the implementation once there’s a preferred direction.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m going to consider an alternative that accounts for all the possibilities and attempts to blend an MD3-first design with flexibility. I'll let you know soon.

Comment thread src/components/TextField/filled/logic.ts
@wonderlul wonderlul reopened this Apr 28, 2026
@wonderlul wonderlul requested a review from adrcotfas April 28, 2026 08:07
// OPACITY
// ==================

export const FILLED_CONTAINER_ALPHA = 0.04;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with DISABLED_CONTAINER_OPACITY so we align with the specs token filled-text-field.disabled.container.opacity.

// OPACITY
// ============

export const OUTLINE_ALPHA = 0.12;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with DISABLED_OUTLINE_OPACITY to align with the specs outlined-text-field.disabled.outline.opacity

paddingHorizontal: 0,
};

/* Disabled tint (`onSurface @ 0.04`) is rendered as a childless overlay so its
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is duplicated in logic.ts and contains a magic number that might change. Perhaps keep the comment only here and mention the constant?

$animatedActiveOutlineStyle?: Animated.WithAnimatedObject<ViewStyle>;
};

export interface TextFieldProps extends TextInputProps {
Copy link
Copy Markdown
Collaborator

@adrcotfas adrcotfas Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to support prefixText and suffixText aligned to the actual text field like in the specs instead of accessory/icon which is centered vertically. See screen-20260423-165915-1776952725646.mp4. For suffixText there should be a way to align the text in the text field at the end - something visible in the video example.

They use the same text style as the text in the textfield with onSurfaceVariant color for all states (isError, enabled, focused, unfocused) except for disabled where it's onSurfaceVariant at 0.38 opacity.

TEXT_FIELD_ACCESSORY_MARGIN_HORIZONTAL +
TEXT_FIELD_INPUT_WRAPPER_PADDING_HORIZONTAL;

export const LABEL_LEFT_OFFSET_WITHOUT_ACCESSORY =
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to refactor all constants containing left/right to start/end to comply with RTL and MD naming.

$animatedActiveOutlineStyle?: Animated.WithAnimatedObject<ViewStyle>;
};

export interface TextFieldProps extends TextInputProps {
Copy link
Copy Markdown
Collaborator

@adrcotfas adrcotfas Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to support the counterEnabled + counterMaxLength which lives together with supportingText aligned to the end.
BTW, counter is different from suffix but could serve the same purpose.

In the MD examples I see supportingText together with the counter in the same TextField. Exceeding the limit would not modify the supportingText but apply the error style to the counter:

screen-20260428-154624-1777380377501.mp4

@@ -0,0 +1,213 @@
import * as React from 'react';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to:

  • improve the examples containing a trailing icon button to show a ripple when pressing it: now we don't have a visual feedback like in the TextInput examples
  • add other examples with counter, suffix/prefix text
  • add any other examples you might find valuable equivalent to what we showcase in TextInput since we're replacing that component with TextField

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants