# How to Implement @Mentions with Custom User Lists in the React Tiptap Editor

> Learn to add custom @mentions to your React Tiptap editor. Configure the Mention extension with a suggestion object and filter users from your list for a seamless experience.

- Repository: [Hung Hoang/reactjs-tiptap-editor](https://github.com/hunghg255/reactjs-tiptap-editor)
- Tags: how-to-guide
- Published: 2026-03-03

---

**To implement @mentions with a custom user list in the React Tiptap editor, configure the Mention extension with a `suggestion` object that defines the trigger character and an `items` function to filter your user array, which the library then renders through a React NodeView popup powered by `NodeViewMentionList`.**

The `hunghg255/reactjs-tiptap-editor` repository provides a React-centric wrapper around the official `@tiptap/extension-mention` that replaces the default suggestion UI with a fully customizable React component. This approach allows you to inject any user list—static or dynamic—into a typeahead popup that integrates seamlessly with the editor's cursor position and keyboard navigation.

## Architecture of the Mention System

The implementation relies on a thin wrapper that bridges Tiptap’s vanilla JavaScript extension system with React’s component tree.

- **[`src/extensions/Mention/Mention.ts`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/extensions/Mention/Mention.ts)**: Extends `@tiptap/extension-mention` and injects a `suggestion` configuration that delegates rendering to a React NodeView via `renderNodeViewClosure`.
- **[`src/utils/renderNodeView.ts`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/utils/renderNodeView.ts)**: Contains the `renderNodeViewClosure` helper, which creates a `ReactRenderer` from `@tiptap/react`. This mounts the suggestion list as an absolutely positioned element and syncs it with the editor’s cursor rectangle while forwarding keyboard events.
- **[`src/extensions/Mention/components/NodeViewMentionList.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/extensions/Mention/components/NodeViewMentionList.tsx)**: The React component that renders the filtered user list, manages the `selectedIndex` state, handles arrow key navigation, and invokes the `command` callback upon selection to insert the mention node into the document.

When a user types the trigger character `@`, Tiptap invokes the `items` function defined in your configuration. The returned array is passed to `NodeViewMentionList`, which displays the popup and handles the insertion command.

## Configuring @Mentions with a Custom User List

### 1. Define Your User Data Structure

Create an array of user objects that match the expected schema. The playground example in [`playground/src/App.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/playground/src/App.tsx) demonstrates this structure:

```tsx
const MOCK_USERS = [
  { id: '0', label: 'alice', avatar: { src: 'https://example.com/alice.png' } },
  { id: '1', label: 'bob', avatar: { src: 'https://example.com/bob.png' } },
  { id: '2', label: 'charlie', avatar: { src: 'https://example.com/charlie.png' } },
];

```

Each object requires at minimum an `id` and `label` property. The `avatar` field is optional and used by the default `NodeViewMentionList` component for rendering thumbnails.

### 2. Configure the Mention Extension

Import the wrapper from the library and supply a `suggestion` (or `suggestions`) configuration. In [`playground/src/App.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/playground/src/App.tsx), the extension is configured to filter the `MOCK_USERS` array based on the query string:

```tsx
import { Mention } from 'reactjs-tiptap-editor/mention';

const extensions = [
  Mention.configure({
    suggestion: {
      char: '@',
      items: async ({ query }) => {
        return MOCK_USERS.filter((user) =>
          user.label.toLowerCase().startsWith(query.toLowerCase())
        );
      },
    },
  }),
];

```

The `items` function receives an object containing the typed `query` and must return a promise or an array of items. The wrapper forwards these items to `NodeViewMentionList`, which renders the popup and manages selection state.

### 3. Initialize the Editor

Pass the configured extensions to the `useEditor` hook as standard:

```tsx
import { useEditor } from '@tiptap/react';

const editor = useEditor({
  content: '<p>Try typing @...</p>',
  extensions,
});

```

The mention functionality activates immediately when the trigger character is detected.

## Advanced Configuration Options

### Supporting Multiple Trigger Characters

The wrapper supports an array of suggestion sources via the `suggestions` property (plural). This allows you to implement `@` for users and `#` for tags within the same extension instance:

```tsx
Mention.configure({
  suggestions: [
    {
      char: '@',
      items: async ({ query }) => fetchUsers(query),
    },
    {
      char: '#',
      items: async ({ query }) => fetchTags(query),
    },
  ],
});

```

Each entry in the `suggestions` array spawns a separate `NodeViewMentionList` instance, isolated by trigger character.

### Customizing the Popup Appearance

The default styling resides in [`src/styles/mention.scss`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/styles/mention.scss). Override these CSS custom properties or classes in your own stylesheet to match your design system:

```scss
.mention-suggestion {
  background: var(--richtext-popover-bg);
  border: 1px solid var(--richtext-border);
  border-radius: 6px;
  
  .mention-item {
    padding: 8px 12px;
    
    &.is-selected {
      background-color: var(--richtext-accent);
    }
  }
}

```

The `NodeViewMentionList` component automatically applies the `is-selected` class to the active entry and scrolls it into view during keyboard navigation.

## Summary

- The **Mention extension** in [`src/extensions/Mention/Mention.ts`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/extensions/Mention/Mention.ts) wraps `@tiptap/extension-mention` to inject React-based rendering.
- The **`renderNodeViewClosure`** utility bridges Tiptap’s imperative API with React components, positioning the popup at the cursor coordinates.
- Supply a custom user list by implementing the **`items`** function in the `suggestion` configuration; it receives the typed query and returns filtered results.
- **`NodeViewMentionList`** handles all UI interactions including keyboard navigation, mouse selection, and the final `command` invocation to insert the mention node.
- Support for **multiple trigger characters** is available via the `suggestions` array configuration.

## Frequently Asked Questions

### How do I filter the user list based on the typed query?

The `items` function in your `suggestion` configuration receives an object with a `query` property containing the text typed after the trigger character. Implement your filtering logic—case-insensitive prefix matching, fuzzy search, or API calls—inside this function and return the filtered array. The playground example in [`playground/src/App.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/playground/src/App.tsx) demonstrates simple case-insensitive prefix filtering against a static `MOCK_USERS` array.

### Can I use multiple trigger characters for different entity types?

Yes. Instead of the singular `suggestion` key, use the `suggestions` array (plural) in your configuration. Each element in the array defines its own `char` (trigger character) and `items` function, allowing you to implement separate workflows for users (`@`), tags (`#`), or any other entity type. The wrapper instantiates a separate `NodeViewMentionList` for each trigger automatically.

### How do I customize the appearance of the mention nodes and suggestion popup?

The suggestion popup styling is controlled via [`src/styles/mention.scss`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/styles/mention.scss) in the source repository. You can override the default CSS classes—such as `.mention-suggestion` for the container and `.mention-item` for list entries—in your application's global styles. To customize the rendered mention node inside the document, modify the `HTMLAttributes` in the Mention extension configuration or override the `renderHTML` method in a custom extension.

### Is it possible to fetch users asynchronously from an API?

Absolutely. The `items` function supports asynchronous operations. Return a Promise that resolves to your user array, or use `async/await` syntax to fetch data from an external API based on the `query` parameter. The `ReactRenderer` will wait for the promise to resolve before updating `NodeViewMentionList` with the new items, automatically handling loading states if you implement them within your custom list component.