Repository navigation
[android][TextInput] Placeholder is not respecting custom font family and weight #50137
Description
Description
On some Android devices user can change default font to e.g. Cool jazz.
In the app we don't want to allow user to change font family, so we apply fontFamily and fontWeight.
Example:
<TextInput style={{
height: 60,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 20,
fontFamily: 'Roboto',
fontWeight: '400',
fontSize: 20,
}} placeholder={"Search..."} />We are expecting the placeholder will be using the same fontFamilyas set in styles. However it's not a case. Placeholder will use device's default font instead.
To workaround this I manage to find this solution:
import React, { forwardRef, useRef, useCallback } from 'react';
import {Platform, StyleSheet, TextInput, TextInputProps} from 'react-native';
const CustomTextInput = forwardRef<TextInput, TextInputProps>((props, ref) => {
const inputRef = useRef<TextInput | null>(null);
const setRef = useCallback(
(node: TextInput | null) => {
if (node) {
inputRef.current = node;
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
const mutableRef = ref as React.MutableRefObject<TextInput | null>;
mutableRef.current = node;
}
// Workaround for Android to apply style before setting placeholder
// to apply font family correctly
if (Platform.OS === 'android') {
node.setNativeProps({
style: StyleSheet.flatten([{
fontFamily: 'Roboto',
fontWeight: '400',
}, props.style ?? {}]),
});
}
}
},
[ref],
);
return <TextInput ref={setRef} {...props} />;
});
export default CustomTextInput;so the fontFamily is applied correctly.
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
paddingHorizontal: safePadding,
paddingTop: safePadding,
paddingBottom: safePadding,
}}>
<TextInput style={{
height: 60,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 20,
fontFamily: 'Roboto',
fontWeight: '400',
fontSize: 20,
}} placeholder={"Search..."} />
</View>
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
paddingHorizontal: safePadding,
paddingTop: safePadding,
paddingBottom: safePadding,
}}>
<CustomTextInput style={{
height: 60,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 20,
fontFamily: 'Roboto',
fontWeight: '400',
fontSize: 20,
}} placeholder={"Custom search..."} />
</View>
As you can see the first text input has wrong fontFamily and the second one uses the correct one.
However expected behaviour is that the custom font will be applied based on style.
All React-Native versions are affected.
Steps to reproduce
Set custom font into TextInput and set placeholder.
Change Font Style on device.
You will see that placeholder is not respecting the custom font.
React Native Version
0.78.1
Affected Platforms
Runtime - Android
Output of npx @react-native-community/cli info
System:
OS: macOS 15.3.2
CPU: (12) arm64 Apple M2 Pro
Memory: 218.94 MB / 32.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 22.13.1
path: /usr/local/bin/node
Yarn:
version: 1.22.22
path: /usr/local/bin/yarn
npm:
version: 10.9.2
path: /usr/local/bin/npm
Watchman:
version: 2025.02.17.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.16.2
path: /Users/tomaszlorenc/.rbenv/shims/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 24.2
- iOS 18.2
- macOS 15.2
- tvOS 18.2
- visionOS 2.2
- watchOS 11.2
Android SDK:
API Levels:
- "28"
- "31"
- "34"
- "35"
Build Tools:
- 30.0.3
- 34.0.0
- 35.0.0
- 35.0.1
System Images:
- android-26 | Google APIs ARM 64 v8a
- android-28 | Google ARM64-V8a Play ARM 64 v8a
- android-34 | Google APIs ARM 64 v8a
- android-34 | Google Play ARM 64 v8a
- android-35 | Google APIs ARM 64 v8a
- android-35 | Google Play ARM 64 v8a
Android NDK: Not Found
IDEs:
Android Studio: 2024.3 AI-243.22562.218.2431.13114758
Xcode:
version: 16.2/16C5032a
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.14
path: /usr/bin/javac
Ruby:
version: 3.3.0
path: /Users/tomaszlorenc/.rbenv/shims/ruby
npmPackages:
"@react-native-community/cli":
installed: 15.0.1
wanted: 15.0.1
react:
installed: 19.0.0
wanted: 19.0.0
react-native:
installed: 0.78.1
wanted: 0.78.1
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: Not found
newArchEnabled: false
Stacktrace or Logs
No logs.
Reproducer
https://github.com/lorenc-tomasz/react-native-text-input-placeholder-font-issue-reproducer
Screenshots and Videos
No response
Activity
lorenc-tomasz commentedon Mar 19, 2025
Hi @cortinico,
could you look into this, please? I tried to find native solution but nothing worked for me. That's way at the moment I'm using the mentioned workaround.
cortinico commentedon Mar 19, 2025
I don't have a device that allows this so I can't reproduce at the moment
lorenc-tomasz commentedon Mar 19, 2025
Just to let you know that any new Samsung Android phone allows that. It's interesting that some users use e.g.
Comic Sanson device and it's funny how apps look with that font, and even Google Maps doesn't manage it well :Dnarekmalk commentedon Mar 19, 2025
Hi, can I take this issue?
lorenc-tomasz commentedon Mar 20, 2025
@narekmalk Do you have an idea how to fix it on native side?
cortinico commentedon Mar 20, 2025
Please do
narekmalk commentedon Mar 24, 2025
@cortinico, @lorenc-tomasz, I tried several physical devices and android emulators, but wasn't able to find the feature of changing system font
I think would be better to assign this issue to someone else
Apologies
mensonones commentedon Mar 25, 2025
Hey, @cortinico and @lorenc-tomasz
I found out that the cause may be related to autoCapitalize
If you pass autoCapitalize="none" the problem does not occur
You must run Android again to get the changes related to this case. hot reload does not always work with this change
lorenc-tomasz commentedon Mar 26, 2025
Hi @mensonones,
I have checked your workaround. And it seems to work with 0.78.1. However when I tried the same solution on our production app that still uses 0.76.x, it doesn't fixed the issue. Only the workaround with ref is working fine.
lorenc-tomasz commentedon Mar 28, 2025
Hi @cortinico,
Could you unassign @narekmalk, as he doesn't have a device to look into the issue, please?
lorenc-tomasz commentedon Apr 2, 2025
Hi @cortinico, @narekmalk and @mensonones,
I have pushed changed into
patchbranch. No idea about quality of the solution but this works for me:What do you think?
cortinico commentedon Apr 2, 2025
@lorenc-tomasz can you send this as a Pull Request and we'll look into it?
lorenc-tomasz commentedon Apr 2, 2025
@cortinico
I take different approach to fix it (branch patch2 on reproducer):
Will try to create PR today :)
lorenc-tomasz commentedon Apr 5, 2025
Final patch based on PR:
lorenc-tomasz commentedon Apr 16, 2025
Bump the issue
dilanbopanna commentedon May 3, 2025
I encountered a similar issue: the placeholder text in TextInput was not applying the custom font. After setting autoCapitalize="none", it started working.
However, for Input fields with type 'password', the problem persists even after setting autoCapitalize="none".
sayan-avantai commentedon Nov 6, 2025
Any update?
olegmelnyk123 commentedon Aug 5, 2026
any solution to this ?
rifadhkp-circles commentedon Sep 24, 2026
Any update here?
ziishaned commentedon Oct 16, 2026
I can reproduce this on a Samsung SM-S938W (Android 17, One UI 9) with a custom font picked in Settings, on RN 0.86.2 with the New Architecture. I think I found the root cause, and it also explains why it can't be reproduced on emulators or Pixels.
Root cause: Samsung changes
TextView.getTypeface()When a custom font is on, Samsung's
TextView.getTypeface()does not return the view's typeface. This is from/system/framework/framework.jarpulled off the device and disassembled withdexdump:So with a custom font on,
getTypeface()always returns the system font in the view's style, no matter what was passed tosetTypeface(). The view still draws with the typeface that was set; only reading it back is wrong. Swapping the system font on an emulator doesn't change this method, which is why the bug only shows up on Samsung.How it reaches the placeholder
ReactEditText.setInputTypesaves and restores the typeface aroundsuper.setInputType(for the password monospace case):In 0.86,
onAfterUpdateTransactioncallsmaybeUpdateTypeface(), which sets the custom font, and thencommitStagedInputType(), which callssetInputTypeand swaps it for the system font. Typed text keeps the right font because it's drawn withCustomStyleSpan. The hint has no span, so it uses the view's typeface.This fits the workarounds above:
autoCapitalize="none"likely helps because the staged input type then matches the current one, sosetInputTypenever runs. It doesn't help password fields, as reported above.setNativeProps({ style })sends the font again in a later transaction, after the input type is committed. It breaks again on the next input type change, such as togglingsecureTextEntry.On main / 0.88
#57805 (in 0.88.0) moved
maybeUpdateTypeface()aftercommitStagedInputType(), which probably fixes the placeholder on first render. ButsetInputTypestill readssuper.getTypeface(), andmaybeUpdateTypeface()only reapplies the font when a font prop changes. So a later input type change, like asecureTextEntrytoggle or akeyboardTypechange, should still drop the font. I haven't tested 0.88.Suggested fix
Don't read the typeface back from the view. Remember the one
ReactEditTextlast set, and restore that:Restoring
paint.typefaceinstead wouldn't be safe: with Android 12+'s Bold text setting, the paint holds a weight-adjusted copy, and applying it again would bold it twice.We can't patch the prebuilt ReactAndroid in our app, so we ship this as a
ReactEditTextsubclass. AReactTextInputManagersubclass serves it, registered in an on-demand package ahead ofMainReactPackageso it wins theAndroidTextInputlookup. On the Samsung, placeholders on email, password, multiline and search fields now all keep the app font.Happy to open a PR if this direction looks right.