
Security News
Meet Socket at Black Hat Europe and BSides London 2025
Socket is heading to London! Stop by our booth or schedule a meeting to see what we've been working on.
react-native-controlled-mentions
Advanced tools
Add to your TextInput ability to highlight mentions, hashtags or other custom patterns. It can:
TextInput componentIn addition, you can add custom styling for a regex pattern (like URLs).
Try it on Expo Snack: https://snack.expo.dev/@dabakovich/mentionsappv3?platform=ios

// with npm
npm install --save react-native-controlled-mentions
// with yarn
yarn add react-native-controlled-mentions
For instance, you have next controlled TextInput:
import { TextInput } from 'react-native';
const Mentions = () => {
const [textValue, setTextValue] = useState('');
return <TextInput value={textValue} onChangeText={setTextValue} />;
};
Now you need few simple steps:
useMentions from the react-native-controlled-mentionstextValue and setTextValue from TextInput to the just added hook as you see belowtextInputProps as new props for the TextInput component nowimport { TextInput } from 'react-native';
import { useMentions } from 'react-native-controlled-mentions';
const Mentions = () => {
const [textValue, setTextValue] = useState('');
const { textInputProps } = useMentions({
value: textValue,
onChange: setTextValue,
});
return <TextInput {...textInputProps} />;
};
Add the triggersConfig property where you can define what trigger types you want to support.
Important. Create the constant once out of functional component body, or memoize it using
useMemoto avoid unnecessary re-renders.
import { TextInput } from 'react-native';
import { useMentions, TriggersConfig } from 'react-native-controlled-mentions';
// Create config as static object out of function component
// Or memoize it inside FC using `useMemo`
const triggersConfig: TriggersConfig<'mention'> = {
mention: {
// Symbol that will trigger keyword change
trigger: '@',
// Style which mention will be highlighted in the `TextInput`
textStyle: { fontWeight: 'bold', color: 'blue' },
},
};
const Mentions = () => {
const [textValue, setTextValue] = useState('');
const { textInputProps } = useMentions({
value: textValue,
onChange: setTextValue,
// Add the config here
triggersConfig,
});
return <TextInput {...textInputProps} />;
};
Define your Suggestions functional component that
receives SuggestionsProvidedProps:
import { Pressable, View } from 'react-native';
const suggestions = [
{
id: '1',
name: 'David',
},
{
id: '2',
name: 'Mary',
},
// ...
];
const Suggestions: FC<SuggestionsProvidedProps> = ({ keyword, onSelect }) => {
if (keyword == null) {
return null;
}
return (
<View>
{suggestions
.filter((one) => one.name.toLocaleLowerCase().includes(keyword.toLocaleLowerCase()))
.map((one) => (
<Pressable key={one.id} onPress={() => onSelect(one)} style={{ padding: 12 }}>
<Text>{one.name}</Text>
</Pressable>
))}
</View>
);
};
export { Suggestions };
useMentions hook returns also triggers value that you can use as provided props for rendering suggestions.
import { TextInput } from 'react-native';
import { useMentions, TriggersConfig } from 'react-native-controlled-mentions';
import { Suggestions } from './suggestions';
const triggersConfig: TriggersConfig<'mention'> = {
mention: {
// Symbol that will trigger keyword change
trigger: '@',
// Style which mention will be highlighted in the `TextInput`
textStyle: { fontWeight: 'bold', color: 'blue' },
},
};
const Mentions = () => {
const [textValue, setTextValue] = useState('');
const { textInputProps, triggers } = useMentions({
value: textValue,
onChange: setTextValue,
triggersConfig,
});
return (
<>
<Suggestions {...triggers.mention} />
<TextInput {...textInputProps} />
</>
);
};
You're done!
The whole example is in the /example folder. You can find also class variant of using mentions, without hooks.
useMentionsReceives as parameter UseMentionsConfig<TriggerName> config.
Returns an object with two keys:
textInputPropsProps that should be provided to the TextInput components.
Be careful and don't override three required props in the
TextInput–onChangeText,onSelectionChange,children.Also, don't provide
valueto theTextInputdirectly. Now it's fully controlling by theuseMentionshook.
triggersObject with same keys that has provided triggersConfig object. Values of the triggers has SuggestionsProvidedProps type and can be used in your custom component for rendering suggestions.
UseMentionsConfig<TriggerName>An object with next keys:
value: stringResulting mention value that should be controlled externally.
onChange: (value: string) => voidCallback that will trigger external value update.
triggersConfig: TriggersConfig<TriggerName>Config that allows you to define you what trigger types will handle your input (mentions, hashtags, etc.).
It presents an object with TriggerName union type keys (for instance 'mention' | 'hashtag') and TriggerConfig values.
const triggersConfig: TriggersConfig<'mention' | 'hashtag'> = {
mention: {
trigger: '@',
},
hashtag: {
trigger: '#',
allowedSpacesCount: 0,
isInsertSpaceAfterMention: true,
textStyle: {
fontWeight: 'bold',
color: 'grey',
},
},
};
patternsConfig: PatternsConfigConfig that allows to define what other highlights should support your input (like urls, bold, italic text, etc.).
It presents an object with pattern name keys (for instance url, bold) and PatternConfig values.
const patternsConfig: PatternsConfig = {
doubleAt: {
pattern: /(@@)/gi, // ✅ Correct: wrapped in capturing group
textStyle: { color: 'blue' },
},
url: {
pattern: /(https?:\/\/[^\s]+)/gi, // ✅ Correct: wrapped in capturing group
textStyle: { color: 'blue' },
},
};
ConfigTriggerConfig| Property name | Description | Type | Required | Default |
|---|---|---|---|---|
trigger | Character that will trigger current mention type | string | true | |
pattern | Custom trigger pattern | RegExp | false | |
getTriggerData | Callback for getting TriggerData, is required when we have custom pattern | (match: string) => TriggerData | true, when pattern exists | |
getTriggerValue | Callback for getting trigger value, is required when we have custom pattern | (triggerData: TriggerData) => string | true, when pattern exists | |
allowedSpacesCount | How much spaces are allowed for mention keyword | number | false | |
isInsertSpaceAfterMention | Should we add a space after selected mentions if the mention is at the end of row | boolean | false | false |
textStyle | Text style for mentions in TextInput | StyleProp<TextStyle> | (mention: TriggerData) => StyleProp<TextStyle> | false | |
getPlainString | Function for generating custom mention text in text input | (mention: TriggerData) => string | false |
PatternConfig| Property name | Description | Type | Required | Default |
|---|---|---|---|---|
pattern | RegExp for parsing a pattern, should include global flag | RegExp | true | |
textStyle | Text style for pattern in TextInput | StyleProp<TextStyle> | (mention: TriggerData) => StyleProp<TextStyle> | false |
⚠️ Important: The
patternregex must include capturing groups for the library to work correctly. The library usesString.split()internally which requires capturing groups to preserve the matched text.✅ Correct:
/(@@)/gi,/(https?:\/\/[^\s]+)/gi,/(#\w+)/gi❌ Incorrect:
/@@/gi,/https?:\/\/[^\s]+/gi,/#\w+/gi
const patternsConfig: PatternsConfig = {
doubleAt: {
pattern: /(@@)/gi, // ✅ Correct: wrapped in capturing group
textStyle: { color: 'blue' },
},
url: {
pattern: /(https?:\/\/[^\s]+)/gi, // ✅ Correct: wrapped in capturing group
textStyle: { color: 'blue' },
},
};
SuggestionsProvidedPropskeyword: string | undefinedKeyword that will provide string between trigger character (e.g. '@') and cursor.
If the cursor is not tracking any mention typing the keyword will be undefined.
Examples where @name is just plain text yet, not mention and | is cursor position:
'|abc @name dfg' - keyword is undefined
'abc @| dfg' - keyword is ''
'abc @name| dfg' - keyword is 'name'
'abc @na|me dfg' - keyword is 'na'
'abc @|name dfg' - keyword is against ''
'abc @name |dfg' - keyword is against undefined
onSelect: (suggestion: Suggestion) => voidYou should call that callback when user selects any suggestion.
Suggestionid: string
Unique id for each suggestion.
name: string
Name that will be shown in your TextInput when user will select the suggestion.
TriggerDataFor example, we have that mention value {@}[David Tabaka](123). Then after parsing that string by triggerRegEx we will
get next properties:
original: string
The whole mention value string - {@}[David Tabaka](123)
trigger: string
The extracted trigger - @
name: string
The extracted name - David Tabaka
id: string
The extracted id - 123
triggerRegEx/({([^{^}]*)}\[([^[]*)]\(([^(^)]*)\))/i
MentionInput component propsIf you prefer to use class component without hooks the MentionInput is for you.
| Property name | Description | Type | Required | Default |
|---|---|---|---|---|
value | The same as in TextInput | string | true | |
onChange | The same as in TextInput | (value: string) => void | true | |
triggersConfig | Declare what trigger configs you want to support (mentions, hashtags) | TriggerConfig] | false | {} |
patternsConfig | Declare what pattern configs you want to support (urls, bold, italic) | PatternConfig] | false | {} |
| ...textInputProps | Other text input props | Partial | false |
For detailed migration instructions from v2 to v3, please see MIGRATION.md.
You can import RegEx that is using in the component and then extract all mentions
from textValue using your own logic.
import { triggerRegEx } from 'react-native-controlled-mentions';
Or you can use replaceTriggerValues helper to replace all mentions from textValue using
your replacer function that receives TriggerData type and returns string.
import { replaceTriggerValues } from 'react-native-controlled-mentions';
const value = 'Hello {@}[David Tabaka](5)! How are you?';
console.log(replaceTriggerValues(value, ({ id }) => `@${id}`)); // Hello @5! How are you?
console.log(replaceTriggerValues(value, ({ name }) => `@${name}`)); // Hello @David Tabaka! How are you?
If you want to parse and render your value somewhere else you can use parseValue tool which gives you array of parts
and then use your own part renderer to resolve this issue.
Here is an example:
import { Part, Config, parseValue, isTriggerConfig } from 'react-native-controlled-mentions';
/**
* Part renderer
*
* @param part
* @param index
*/
const renderPart = (part: Part, index: number) => {
// Just plain text
if (!part.config) {
return <Text key={index}>{part.text}</Text>;
}
// Mention type part
if (isTriggerConfig(part.config)) {
return (
<Text
key={`${index}-${part.data?.trigger}`}
style={part.config.textStyle}
onPress={() => console.log('Pressed', part.data)}
>
{part.text}
</Text>
);
}
// Other styled part types
return (
<Text key={`${index}-pattern`} style={part.config.textStyle}>
{part.text}
</Text>
);
};
/**
* Value renderer. Parsing value to parts array and then mapping the array using 'renderPart'
*
* @param value - `textValue` that you're getting from the `useState` hook
* @param configs - configs array that you providing to the `useMentions` hook
*/
const renderValue: FC = (value: string, configs: Config[]) => {
const { parts } = parseValue(value, configs);
return <Text>{parts.map(renderPart)}</Text>;
};
{name: ' ', value: 1})Unfortunately, common donation platforms (like GitHub Sponsors, Buy Me a Coffee, PayPal, and Stripe) are currently not available in Ukraine.
However, you can still support me and this library in other meaningful ways:
Your support in any of these forms is greatly appreciated and helps keep this project alive and growing!
FAQs
Fully controlled React Native mentions component
The npm package react-native-controlled-mentions receives a total of 15,621 weekly downloads. As such, react-native-controlled-mentions popularity was classified as popular.
We found that react-native-controlled-mentions demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket is heading to London! Stop by our booth or schedule a meeting to see what we've been working on.

Security News
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.

Research
/Security News
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.