Advanced Usage
Markup format and controlled state
Section titled “Markup format and controlled state”MentionsInput is typically used as a controlled component — provide a value prop and handle updates via onMentionsChange. (It can also manage its own state; see Uncontrolled mode & forms.) The value string uses a markup format to encode mentions inline with plain text.
The default markup template is @[__display__](__id__), so a value containing a mention looks like:
Hey @[Walter White](walter), are you there?The __display__ placeholder stores the visible text and __id__ stores the mention identifier. When rendered, the user sees plain text (Hey @Walter White, are you there?) while the underlying value preserves the structured mention data.
You can customize the template via the markup prop on Mention, or pass a MentionSerializer for full control (see below).
Uncontrolled mode & forms
Section titled “Uncontrolled mode & forms”When you omit the value prop, MentionsInput manages its own state. Pass defaultValue (in markup format) to set the initial content — onMentionsChange still fires on every edit if you want to observe changes:
<MentionsInput defaultValue="Hi @[Walter White](walter)!"> <Mention trigger="@" data={users} /></MentionsInput>Adding a name prop renders a hidden input carrying the markup value, so the component works with plain <form> submissions, React 19 form actions, and libraries like React Hook Form without a controlled wrapper:
<form action={sendMessage}> <MentionsInput name="message"> <Mention trigger="@" data={users} /> </MentionsInput> <button type="submit">Send</button></form>
// in sendMessage(formData):// formData.get('message') → 'Hi @[Walter White](walter)!'Notes:
- The submitted value is the markup value (with mention metadata), not the visible plain text. Use
MentionsTextorparseMentionsMarkupto work with it later. - In uncontrolled mode, a native form reset restores
defaultValue. - Providing
valueswitches back to fully controlled behavior;defaultValueis then ignored (the hidden input still works withname).
Rendering saved values
Section titled “Rendering saved values”Once a markup value has been stored (in your database, a message feed, etc.), use MentionsText to display it with mentions highlighted — the read-only counterpart to MentionsInput:
import { MentionsText } from 'react-mentions-ts'
// value: 'Hey @[Walter White](walter), are you there?';<MentionsText value={message.value} />// renders: Hey <strong data-mention-id="walter">Walter White</strong>, are you there?MentionsText accepts the same markup (a template string, a MentionSerializer, or an array of either for multi-trigger values) and displayTransform you used when creating the value, plus a renderMention callback for custom mention elements:
<MentionsText value={message.value} markup={['@[__display__](__id__)', '#[__display__](__id__)']} mentionClassName="text-primary font-medium" renderMention={(mention) => <a href={`/users/${mention.id}`}>@{mention.display}</a>}/>For non-React targets (HTML emails, notifications) or custom rendering pipelines, the lower-level helpers are also exported:
import { parseMentionsMarkup, renderMentionsToReact } from 'react-mentions-ts'
parseMentionsMarkup('Hey @[Walter White](walter)!')// [// { type: 'text', text: 'Hey ', index: 0, plainTextIndex: 0 },// { type: 'mention', id: 'walter', display: 'Walter White', markup: '@[Walter White](walter)', ... },// { type: 'text', text: '!', ... },// ]
renderMentionsToReact(value, { mentionClassName: 'mention' })// ReactNode[] — the array MentionsText renders internallymakeTriggerRegex
Section titled “makeTriggerRegex”A utility that builds a properly anchored regex from a trigger string. Useful when you need spaces in queries or accent-insensitive matching without hand-rolling a regex.
import { makeTriggerRegex } from 'react-mentions-ts'
makeTriggerRegex('@')// default — matches @query at end of string
makeTriggerRegex('@', { allowSpaceInQuery: true })// allows "@ John Doe" style multi-word queries
makeTriggerRegex('@', { ignoreAccents: true })// Unicode-aware matching for accented charactersSignature:
makeTriggerRegex( trigger?: string | RegExp, // default: '@' options?: { allowSpaceInQuery?: boolean ignoreAccents?: boolean // enables the Unicode `u` flag }): RegExpWhen trigger is already a RegExp, it is returned as-is.
createMarkupSerializer
Section titled “createMarkupSerializer”Converts a markup template string into a MentionSerializer object. Use this when you want custom markup formats without manually wiring up insertion and parsing logic.
import { createMarkupSerializer } from 'react-mentions-ts'
const serializer = createMarkupSerializer(':__id__')// serializer.insert({ id: 'wave', display: 'Wave' }) → ':wave'// serializer.findAll('Hello :wave and :smile') → [{ id: 'wave', ... }, { id: 'smile', ... }]Pass the result as the markup prop on Mention:
<Mention trigger=":" data={emojis} markup={createMarkupSerializer(':__id__')} />MentionSerializer interface
Section titled “MentionSerializer interface”For cases where createMarkupSerializer is not flexible enough, you can implement the MentionSerializer interface directly:
interface MentionSerializer { id: string insert: (input: { id: string | number; display: string }) => string findAll: (value: string) => MentionSerializerMatch[]}
interface MentionSerializerMatch { markup: string // the full matched substring index: number // position in the value string id: string // extracted mention identifier display?: string | null // extracted display text}id— a unique string identifying this serializer (used internally to distinguish multipleMentionchildren)insert— given a mention’sidanddisplay, returns the markup string to splice into the valuefindAll— scans a value string and returns every mention match with its position and extracted fields
Custom serializer for IDs containing )
Section titled “Custom serializer for IDs containing )”If your mention IDs can contain characters that would otherwise terminate a template placeholder early, write a custom serializer and encode the id before storing it in the markup.
The example below percent-encodes reserved characters so an ID like team)west is stored safely and decoded back to its original value when the controlled value is parsed:
import type { MentionSerializer } from 'react-mentions-ts'
const encodeMentionId = (id: string): string => encodeURIComponent(id).replace( /[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}` )
const decodeMentionId = (encoded: string): string => decodeURIComponent(encoded)
export const parenSafeSerializer: MentionSerializer = { id: 'paren-safe-serializer', insert: ({ id, display }) => `@[${display}](${encodeMentionId(String(id))})`, findAll: (value) => { const regex = /@\[([^\]]+)]\(([^)]+)\)/g const matches = [] let match: RegExpExecArray | null
while ((match = regex.exec(value)) !== null) { matches.push({ markup: match[0], index: match.index, display: match[1], id: decodeMentionId(match[2]), }) }
return matches },}Use it on Mention like any other serializer:
<Mention trigger="@" data={users} markup={parenSafeSerializer} />For example, selecting a mention with id: 'team)west' stores @[Team West](team%29west) in value, and findAll decodes it back to team)west.
SSR and Next.js
Section titled “SSR and Next.js”The component is SSR-compatible out of the box. It guards against missing browser globals (document) during server rendering, so it works with Next.js, Remix, and other SSR frameworks without extra configuration.
In Next.js App Router, add the 'use client' directive to any file that renders MentionsInput:
'use client'
import { MentionsInput, Mention } from 'react-mentions-ts'No dynamic imports or next/dynamic wrappers are needed.
Package & tree shaking
Section titled “Package & tree shaking”The package is published as side-effect free, and the release evidence is repeatable:
pnpm tree-shake:reportThe report command rebuilds dist, runs npx publint --pack npm, requires "sideEffects": false, inspects npm pack --dry-run --json, and bundles small Vite consumer fixtures against dist/index.js.
Current verified behavior:
- A fixture that imports only
MentionandgetSubstringIndexmust not retainLoadingIndicator,SuggestionsOverlay, or inline-suggestion markers. - A fixture that imports
MentionsInputcurrently retains overlay, loading, and inline-suggestion markers because those branches are statically imported by the orchestration shell. - The npm pack check prints the tarball filename, packed size, unpacked size, and file count so publish contents stay visible.