If you have ever tried to build an Instagram or TikTok style story editor in React Native, you probably started the same way I did.
<Text style={{ backgroundColor: '#FF3D7F', color: 'white', fontSize: 32 }}>
golden hour in lisbon
</Text>
And you got a flat pink rectangle. No rounded corners, no padding around each line, and when the text wrapped to a second line, the box just grew into one big block. That is not the story look. The story look is a rounded background that hugs every line separately, with a little breathing room around the glyphs, and it grows line by line while you type.
I needed exactly that in an app I was building and could not find a library that did it well on both platforms. So I built one, react-native-highlight-text-view.
Why plain backgroundColor cannot do this
The usual tricks all break somewhere.
-
backgroundColoron<Text>paints the whole text box. You get one rectangle, andborderRadiusrounds that rectangle, not each line. -
Nested
<Text>with a background paints behind the glyphs of each run, but you cannot add padding to an inline span, and the corners stay square. The background also stops tight at the glyph edges, so lines look cramped. -
Splitting the text into one
<Text>per line works for a fixed caption, but you have to guess where the line breaks are. The moment the font, size or screen width changes, your breaks are wrong. -
Editing makes all of this worse. A
TextInputdoes its own line wrapping on the native side, and you only learn about it after the fact. Trying to mirror the layout in JS on every keystroke leads to flicker and backgrounds that lag a frame behind the cursor.
The real problem is that the line breaks are only known inside the native text layout engine. So that is where the background has to be drawn.
The approach: draw it in the native text layout
The library is a Fabric component (New Architecture) with a native implementation on each platform.
-
iOS: the view wraps a
UITextViewwhose layout manager is a customNSLayoutManagersubclass. It overridesdrawBackgroundForGlyphRange:atPoint:, so after the system has laid out and wrapped the text, it draws rounded, padded backgrounds for the glyphs that are actually on each line. -
Android: the view is a custom
EditTextthat draws the rounded highlights itself inonDraw(), using theLayoutthe system already computed, and then letsEditTextdraw the text, cursor and selection on top. Neighbouring character backgrounds are filled as one shape, so a line reads as one continuous rounded block instead of separate boxes per word.
Because both sides read the platform's own line layout, the highlight follows wrapping automatically, redraws as you type, and keeps working when you change the font, size, colors or alignment at runtime without remounting. As of 1.1.0, Android uses the same units as iOS (points), so values you tune on one platform give the same result on the other.
Install
npm install react-native-highlight-text-view
# or
yarn add react-native-highlight-text-view
For the React Native CLI on iOS, run cd ios && pod install afterwards.
On Expo:
npx expo install react-native-highlight-text-view
npx expo prebuild # or build with EAS
This package contains native code, so it works in Expo development builds and EAS builds, but not in Expo Go. No config plugin is needed.
Requirements: React Native 0.76 or newer with the New Architecture (it is a Fabric only component), or Expo SDK 52 or newer in a dev build. Android minSdk is 24.
Quick start
Here is a controlled story input. It behaves like a regular TextInput: text plus onChange, a placeholder, keyboard props, and a ref with focus(), blur(), clear() and setText().
import { useRef, useState } from 'react';
import {
HighlightTextView,
type HighlightTextViewRef,
} from 'react-native-highlight-text-view';
export default function StoryText() {
const [text, setText] = useState('');
const ref = useRef<HighlightTextViewRef>(null);
return (
<HighlightTextView
ref={ref}
text={text}
onChange={(e) => setText(e.nativeEvent.text)}
placeholder="Tap to type"
color="#FF3D7F"
textColor="white"
fontSize={32}
fontWeight="800"
textAlign="center"
verticalAlign="center"
paddingLeft={10}
paddingRight={10}
paddingTop={4}
paddingBottom={4}
highlightBorderRadius={10}
returnKeyType="done"
onSubmitEditing={() => ref.current?.blur()}
style={{ width: '100%', height: 240 }}
/>
);
}
A few things worth knowing:
-
coloris the highlight background andtextColoris the text. Both accept any React Native color string, like"#FF3D7F","rgb(255, 204, 0)","hsl(158, 64%, 52%)"or"teal". - Numeric props take numbers (
fontSize={32}) or strings (fontSize="32"). - The view is sized by its
style, like aTextInput. The highlight is drawn only behind the text inside it. - Setting
returnKeyTypeto anything other than'default'makes Return fireonSubmitEditinginstead of inserting a new line. Leave it unset if you want multi-line typing. - With no
verticalAlign, iOS draws the text at the top and Android centers it. SetverticalAlignto get the same result on both.
Three recipes
The example app in the repo has a story editor (live typing, color swatches, alignment, font size, ref methods) and a recipes screen. Here are three of them. Read-only views just use editable={false}.
1. Story text
The classic look. A \n in the text gives you a manual break, and long lines wrap on their own.
<HighlightTextView
editable={false}
text={'golden hour\nin lisbon'}
color="#FF3D7F"
textColor="#FFFFFF"
fontSize={30}
fontWeight="800"
textAlign="center"
verticalAlign="center"
paddingLeft={10}
paddingRight={10}
paddingTop={4}
paddingBottom={4}
highlightBorderRadius={10}
style={{ height: 130 }}
/>
2. Marker highlight
A soft color plus a large backgroundInsetTop paints only the lower part of each line, like a highlighter pen across a note.
<HighlightTextView
editable={false}
text="Ship the demo before Friday"
color="#FFE066"
textColor="#1D1D1F"
fontSize={24}
fontWeight="600"
paddingLeft={3}
paddingRight={3}
backgroundInsetTop={14}
highlightBorderRadius={3}
verticalAlign="center"
style={{ height: 92 }}
/>
3. Tag chips
One small read-only view per tag. Give each chip a width. The example app measures the label with a hidden <Text> and uses that.
<HighlightTextView
editable={false}
text="#reactnative"
color="#E0F2FE"
textColor="#075985"
fontSize={15}
fontWeight="700"
textAlign="center"
verticalAlign="center"
paddingLeft={10}
paddingRight={10}
paddingTop={6}
paddingBottom={6}
highlightBorderRadius={14}
style={{ width: 130, height: 40 }}
/>
Tips from tuning it
Padding grows the background, insets shrink it. The padding props (padding, which defaults to 4, plus paddingTop, paddingBottom, paddingLeft, paddingRight) expand the colored area outward from the text. The backgroundInset props (backgroundInsetTop, backgroundInsetBottom, backgroundInsetLeft, backgroundInsetRight, all default 0) pull it inward from the font's line box. Some fonts, like Eczar or Georgia, have tall built-in ascenders and descenders, so the highlight looks too tall. Insets fix that without touching the font size.
<HighlightTextView
fontFamily="Eczar"
fontSize={32}
paddingLeft={8}
paddingRight={8}
paddingTop={4}
paddingBottom={4}
backgroundInsetTop={6}
backgroundInsetBottom={6}
text="Tight Background"
/>
Touching line backgrounds. If you want the lines to join into one smooth vertical shape, combine lineHeight with insets. Start with lineHeight around fontSize + 4 to fontSize + 8, then raise backgroundInsetTop and backgroundInsetBottom until the backgrounds touch.
<HighlightTextView
fontSize={32}
lineHeight={36}
paddingLeft={8}
paddingRight={8}
paddingTop={4}
paddingBottom={4}
backgroundInsetTop={14}
backgroundInsetBottom={14}
highlightBorderRadius={4}
text="Multiple lines with touching backgrounds create smooth vertical flow"
/>
Change fonts freely. fontFamily, fontSize and fontWeight can change at any time. The view re-measures and redraws the highlight. The old key={fontFamily} remount trick from before 1.0 is no longer needed.
Open the keyboard on mount with autoFocus, which is handy for a "create story" screen.
Try it
- GitHub: https://github.com/aqeelkhokhar/react-native-highlight-text
- npm: https://www.npmjs.com/package/react-native-highlight-text-view
The current version is 1.1.0, fully typed, MIT licensed. Clone the repo and run yarn then yarn example ios (or yarn example android) to play with the story editor.
If it saves you a weekend of fighting backgroundColor, a star on GitHub helps other developers find it. And if something renders differently from what you expect on a font or device, open an issue with a screenshot. That kind of report is how the Android and iOS output got matched in 1.1.0.


Top comments (0)