feat: TextField component#4904
feat: TextField component#4904wonderlul wants to merge 1 commit intocallstack:@adrcotfas/feat/dynamic_themefrom
Conversation
|
Hey @wonderlul, thank you for your pull request 🤗. The documentation from this branch can be viewed here. |
There was a problem hiding this comment.
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
TextFieldhas 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
| borderColor: hasActiveOutline ? activeOutlineColor : inactiveOutlineColor, | ||
| }, | ||
| disabled && $disabledStyle, | ||
| $fieldStyleOverride, |
There was a problem hiding this comment.
Is this intentional? It's already part of fieldStyles above.
There was a problem hiding this comment.
Yes, this is intentional. outlineStyles are kind of additional outline to handle the focus state that has to be overridden as well.
| paddingHorizontal: TEXT_FIELD_INPUT_WRAPPER_PADDING_HORIZONTAL, | ||
| fontSize: HELPER_FONT_SIZE, | ||
| fontWeight: '400', | ||
| textAlign: 'left', |
There was a problem hiding this comment.
I think we should consider RTL instead of hardcoding left.
There was a problem hiding this comment.
Considering RTL using left is actually handling both cases depending on the settings. Do you have something else in mind?
There was a problem hiding this comment.
By considering RTL you mean outlined/logic.ts writingDirection? Doesn't the text still anchor to the left side regardless of writingDirection?
Shouldn't this be auto instead of left or just remove it?
There was a problem hiding this comment.
With auto it aligns text to the left and doesn't respect the RTL settings. I guess with textAlign: left it considers left as the start no matter if it's left or right :-). Don't even ask me how long it took me to figure that out.
| @@ -0,0 +1,20 @@ | |||
| import { | |||
| ACCESORY_SIZE, | |||
There was a problem hiding this comment.
Typo for ACCESSORY_SIZE here and in styles.ts:, filled/constants.ts, outlined/constants.ts.
| @@ -0,0 +1,167 @@ | |||
| import { Animated, StyleProp, TextStyle, ViewStyle } from 'react-native'; | |||
There was a problem hiding this comment.
The filled variant does not handle RTL as the outlined variant does. Also, some RTL tests would be useful.
|
|
||
| let inactiveOutlineColor = colors.outline; | ||
|
|
||
| if (disabled) { |
There was a problem hiding this comment.
According to specs:
Outlined text field disabled outline color -> md.sys.color.on-surface
Outlined text field disabled outline opacity -> 0.12
| { | ||
| backgroundColor: disabled ? surfaceContainerHighest : surfaceVariant, | ||
| }, | ||
| disabled && $disabledStyle, |
There was a problem hiding this comment.
The outlined variant applies disabled && $disabledStyle per element (input, label, accessories). The filled variant applies it only to the field parent.
$animatedLabelTextStyles has no disabled dimming.
According to specs:
Filled text field disabled container opacity -> 0.04
| autoCapitalize="none" | ||
| pressableStyle={styles.field} | ||
| /> | ||
| <TextField |
There was a problem hiding this comment.
No outline is visible here.
|
|
||
| import { | ||
| ACCESORY_SIZE, | ||
| HELPER_FONT_SIZE, |
There was a problem hiding this comment.
For consistency with the specs, I would rename all occurrences of helper to supportingText and accessory to icon.
| @@ -0,0 +1,167 @@ | |||
| import { Animated, StyleProp, TextStyle, ViewStyle } from 'react-native'; | |||
|
|
|||
There was a problem hiding this comment.
In the specs placeholders are hidden until the TextField is selected.
3f47684 to
d0cffca
Compare
Motivation
File structure
The component is split by variant (
filled/andoutlined/) 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/RightAccessoryvsTextInputadornmentsTextInputcomposes leading and trailing content throughleftandright, which are built around icons and affixes (TextInput.Icon,TextInput.Affix) and internal adornment types.TextFieldinstead exposesLeftAccessoryandRightAccessoryas 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/outlinedinstead offlat/outlinedMaterial Design 3 describes text fields in terms of
filledandoutlinedstyles. 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
TextInputwould also blur the built-in React NativeTextInputin imports and documentation. A dedicatedTextFieldname keeps the design-system component clearly namespaced and aligned with MD3, while the underlying control remains React Native’sTextInputwhere appropriate.Positioning relative to TextInput
TextFieldis 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 thePlatformColor APIwhere it fits platform tokens). Compared to the legacyTextInputstack, 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 existingTextInputusers.Order of merging
#4901
Related issue
#4878
#4329
#4235
Test plan
Run the Example app.
filledoutlined