feat: TextInput component#4909
Conversation
|
Hey @wonderlul, thank you for your pull request 🤗. The documentation from this branch can be viewed here. |
146de2a to
a1a20fd
Compare
|
Here's a comment from my friend Claude about duplicate code:
|
| Block | Lines each |
|---|---|
| Props destructuring | ~13 |
isRTL extraction |
1 |
getLabelColor(...) call |
~5 |
getSupportingTextColor(...) call |
~5 |
$animatedLabelTextStyles array |
~9 |
$containerStyles array |
~4 |
$supportingTextStyles array |
~8 |
$counterStyles array |
~8 |
$prefixStyles array |
~6 |
$suffixStyles array |
~6 |
$leadingAccessoryStyles |
~3 |
$trailingAccessoryStyles |
~3 |
| Total | ~71 identical lines |
Outlined has 233 lines, filled has 280 -- so ~71/233 (30%) and ~71/280 (25%) respectively are shared logic.
What's genuinely variant: the $fieldStyles (filled adds backgroundColor), $outlineStyles (border vs bottom-bar), $animatedActiveOutlineStyles (filled-only), $disabledBackgroundStyles (undefined in outlined, overlay in filled), and the labelBackgroundColor extraction
(outlined only).
styles.ts -- low duplication (~10%)
$labelTextStyle is byte-for-byte identical in both files (5 lines). It belongs in the shared ../styles.ts.
Everything else differs meaningfully:
$fieldStyle: same keys,borderRadius(outlined) vsborderTopStart/EndRadius(filled)$outlineStyle: full-perimeter absolute (outlined) vs bottom-only absolute (filled)$containerStyle:alignItems: 'center'(outlined) vs'flex-end'(filled)$labelWrapperStyle: outlined addspaddingHorizontal, filled is empty$disabledBackgroundStyle: filled-only
constants.ts -- minimal duplication (~5%)
LABEL_START_OFFSET_WITHOUT_ACCESSORY resolves to TEXT_FIELD_INPUT_WRAPPER_PADDING_HORIZONTAL in both -- could live in shared ../constants.ts.
Everything else is variant: LABEL_PADDING_HORIZONTAL and the RTL translate constants are outlined-only; MULTILINE_PADDING_TOP is filled-only; ACTIVE_LABEL_TOP_POSITION uses different formulas; the opacity values differ (0.12 vs 0.04).
utils.ts -- 0% unifiable
Both export getOutlineColor but with incompatible signatures: outlined takes hasError: boolean, filled takes status?: 'error' | 'disabled'. Same name, different contract -- unifying them would require a signature change that ripples into both logic.ts files.
Overall
| File | Outlined lines | Filled lines | Duplicated | % |
|---|---|---|---|---|
logic.ts |
233 | 280 | ~71 | ~28% |
styles.ts |
45 | 54 | ~5 | ~10% |
constants.ts |
49 | 35 | ~2 | ~5% |
utils.ts |
39 | 34 | 0 | 0% |
| Total | 366 | 403 | ~78 | ~20% |
|
Some more issues I found:
Edit: the filled version lost the top rounded corners when in error mode and after toggling the "disabled" state. |
|
When the helper text is empty, the counter gets aligned to start. It should stay aligned to end. |
|
Error seems to take precedence over Disabled but I would expect that a disabled field is not editable even if it has "error". |
Multi-line fixed. I disagree with disallowing prefix + suffix. I think it's up to the developer to decide whether to use both, one or neither, but we shouldn't disable the option for developers. |
Yeah, this is on purpose — we’re using a clear order for those states. My thinking was: if there’s an error, the field shouldn’t really be blocked. You usually need to change the value to fix it, so disabling it felt backwards. If we’re okay with that idea, we can stick to this gradation and it keeps the code a lot simpler — less special cases. I know some developers expect disabled to always win even when error is set; if that’s what we want product-wise, we can always revisit it. Let me know. |
satya164
left a comment
There was a problem hiding this comment.
Let's keep the code style consistent with other parts of the codebase.
Avoid too many small helpers. The code is hard for me to follow because it's split between so many parts. Prefer normal hooks and components for composition instead of helper functions.
We're not using $ prefix for styles. Use StyleSheet.create API so it uses atomic styles on React Native Web. Avoid unnecessary inline styles.
Also, avoid comments like these:
// ============
// OPACITY
// ============
Use normal multiline or single-line comments when necessary and don't comment on the obvious.
In the context of hooks vs. helpers, I see it more as a matter of convention and preference. If reusable logic does not manage application state, I usually prefer helper/util terminology over hooks. The helpers I created contain reusable logic, which is why I decided to extract them. Similarly, when a piece of logic becomes relatively large (e.g. getFilledTextFieldData), I tend to move it into a helper as well. I split the logic into separate places because, from my perspective, it improves readability through clearer separation of responsibilities. Of course, this can be somewhat subjective. I think the current solution is a reasonable middle ground. I removed the filled and outlined folders and moved their logic into individual files within the main folder. Considering the complexity of the component, the current file structure feels like a good balance between readability and organization. I also added the StyleSheet API for static styles, although most of the styles in this component are dynamic. I removed the $ notation as well. As for comments, I only added them where I felt additional context improved readability — mainly around more complex functions, variables, styles, or to separate logical sections within files. Following your suggestion, I updated them to regular multiline or single-line comments. |
bf4638d to
73642ae
Compare
e3d5f56 to
daea379
Compare
satya164
left a comment
There was a problem hiding this comment.
Thanks for the PR. I left few comments.
And found few issues:
- Are inputs supposed to look this big on the web?
- Alignment seems off for outlined input on the web

- Tapping on leading icon in the input seems to unfocus in the input. Since it looks like part of the input, I'd expect it to act like tapping on the input. Ideally it should be positioned absolutely on top and text input should have padding to offset it
- Measurements seems a bit off on iOS (the app doesn't build on Android, so haven't checked). The placeholder text seems larger and as a result has less space below it compared to the official example.

- The input supports multiline but leading and trailing icons stay vertically centered. I'd expect them to be aligned at the top when there is multiline text.
Also I think while we should make breaking changes to align with MD3, it'd be valuable to keep the API same where we don't need to deviate.
TextFieldmatches MD3 naming, but I thinkTextInputis better here since users already know it, and it'll be easier migration. So we should keepTextInputas the component name.- Missing props:
textColor,underlineColor,activeUnderlineColor,outlineColor,activeOutlineColor,selectionColor,cursorColor- we should at leasy support selection and cursor color. - Missing
renderprop to render a custom input. This is necessary to use an input implementation that supports masking etc.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new MD3-aligned TextField component to react-native-paper, alongside docs, example app integration, and a comprehensive test suite.
Changes:
- Added
TextFieldcomponent implementation (filled/outlined variants), including shared layout utils, animation/state hooks, constants, and styles. - Exposed
TextField(and related types) from the library entrypoint and addedTextField.Iconaccessory helper component. - Added Example app screen plus documentation wiring (screenshots + docs config) and extensive snapshots/tests.
Reviewed changes
Copilot reviewed 16 out of 19 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/index.tsx | Exports TextField and its public types from the library entrypoint. |
| src/components/TextField/utils.ts | Implements shared/variant style computation, colors, animation style objects, and accessibility helpers. |
| src/components/TextField/TextFieldIcon.tsx | Adds TextField.Icon helper component and exports accessory/icon prop types. |
| src/components/TextField/TextFieldErrorIcon.tsx | Adds default error indicator shown when error is true and no trailing accessory is provided. |
| src/components/TextField/TextField.tsx | Core TextField component: layout composition, label, accessories, supporting text, counter, render prop plumbing. |
| src/components/TextField/styles.ts | Shared + variant styles for field layout, accessories, label wrapper, outline, etc. |
| src/components/TextField/index.ts | Creates compounded export (TextField with .Icon). |
| src/components/TextField/hooks.ts | Implements state management for value/focus/flags, memoized layout derivation, and prop merging for render. |
| src/components/TextField/constants.ts | MD3 token-based constants for sizing, paddings, animation duration, label positions, etc. |
| src/components/tests/TextField.test.tsx | Adds a large test suite covering rendering, accessories, a11y props, counters, RTL, focus behavior, etc. |
| src/components/tests/snapshots/TextField.test.tsx.snap | Snapshots for the new TextField test suite. |
| jest/testSetup.js | Minor formatting change in mocked icon implementation (test infra). |
| example/src/Examples/TextFieldExample.tsx | Adds an interactive Example app screen for TextField variants and prop toggles. |
| example/src/ExampleList.tsx | Registers the new TextField example in the Example app list. |
| docs/src/data/screenshots.js | Adds TextField screenshots (filled/outlined) for docs rendering. |
| docs/src/components/PropTable.tsx | Adds type-link mappings for new TextField render prop types and adjusts formatting. |
| docs/docusaurus.config.js | Registers TextField and TextFieldIcon in the docs component pages/config and edit URL mapping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
I agree. That's what I meant by |
|
@satya164 I’d also ask you to take a look at the newly added |
BREAKING CHANGE:
TextInputhas been rewritten for Material Design 3. The public API, adornments, variant naming, and styling overrides have changed. See the migration section below for required updates.Summary
Replaces the Paper 5.x
TextInputwith a new MD3-aligned implementation. The component name staysTextInput, but most props and composition patterns have changed.The new
TextInputsupportsfilledandoutlinedvariants, floating labels,supportingText, a built-in charactercounter,prefix/suffix, leading/trailingstartAccessory/endAccessoryrender props, a customrenderprop for masked inputs, and improved accessibility. Colors and layout follow the theme; per-prop color overrides for outlines and underlines were removed.What changed
mode="flat"variant="filled"(default)mode="outlined"variant="outlined"left/rightelement propsstartAccessory/endAccessoryrender propsTextInput.Affixprefix/suffixstrings, or custom accessoriesHelperTextbelow the fieldsupportingTexton the fieldlabelasstringorReactNodelabelasstringonlydense,contentStyle,outlineStyle,underlineStylestyleandthemetextColor,underlineColor,activeUnderlineColor,outlineColor,activeOutlineColorstyleandthemedisabledblocked interactiondisabledblocks interaction; useeditable={false}for read-only fields that can still be focusedStill supported
TextInput.Icon— use insidestartAccessory/endAccessoryrender— swap the native input (e.g. masked inputs)selectionColorandcursorColor— inherited from React NativeTextInputpropsref— exposesTextInputHandles(focus,blur,clear,isFocused,setNativeProps,setSelection);clear()also syncs internal state and label animationWhat was removed
TextInput.AffixHelperTextas the recommended companion for text fields (usesupportingTextinstead)mode,left,rightdense,contentStyle,outlineStyle,underlineStyletextColor,underlineColor,activeUnderlineColor,outlineColor,activeOutlineColorMigration
Import stays the same:
Variant
Icons and adornments
left/rightelement props becomestartAccessory/endAccessoryrender functions. Spread the props TextInput passes in — they include layoutstyle, pluserror,disabled, andmultiline.Replace
TextInput.Affixwithprefix/suffixfor inline text, or a customendAccessory:Supporting text and errors
When
erroristrueand noendAccessoryis provided, a default error icon is shown on the trailing side.Disabled vs read-only
Styling and colors
Outline, underline, label, and disabled colors now come from the theme. Override input text with the standard
styleprop; override accent colors viathemeor, where applicable, inherited props:Custom input (
renderprop)Imperative handle
New props reference
variant'filled'(default) or'outlined'supportingTextcountercurrentLength/maxLength; requiresmaxLengthprefix/suffixstartAccessory/endAccessorydisablededitable={false})renderRelated
docs/docs/guides/12-migration.mdTest plan
Run the Example app and verify:
filledvariant — label animation, underline indicator, disabled overlay, error stateoutlinedvariant — outline, label notch behavior, error statestartAccessory/endAccessorywithTextInput.Iconand custom contentprefix/suffixvisibility when focused vs blurredcounterwithmaxLengthsupportingTextwith and withouterrordisabledvseditable={false}renderprop with a custom inputref.clear()on controlled and uncontrolled fieldsScreenshots
filledoutlined