@danielivanovz/mention
Recipes

Controlled value

Integrate with form libraries by treating the textarea value as state you own.

The compound <Mention.Root> doesn't expose value / onValueChange directly — those land on the textarea. Use <Mention.Input>'s standard textarea props for controlled flows, or useMention() for the most flexible integration.

Plain controlled textarea

import { useState } from "react";
import { Mention } from "@danielivanovz/mention";

export function MessageInput() {
  const [value, setValue] = useState("");

  return (
    <Mention.Root
      items={users}
      getKey={(u) => u.id}
      getLabel={(u) => u.name}
      onSelect={(u) => console.log(u)}
    >
      <Mention.Input
        value={value}
        onChange={(e) => setValue(e.currentTarget.value)}
        aria-label="Message"
      />
      <Mention.Popover>
        <Mention.List>
          {(u) => <Mention.Item value={u}>{u.name}</Mention.Item>}
        </Mention.List>
      </Mention.Popover>
    </Mention.Root>
  );
}

The library composes its own onChange (for trigger detection) on top of yours. Both run; you don't lose any keystrokes.

react-hook-form

<Mention.Input> registers like any textarea — register("message") via the name attribute pattern, or Controller for explicit value flow:

import { Controller, useForm } from "react-hook-form";
import { Mention } from "@danielivanovz/mention";

export function CommentForm() {
  const { control, handleSubmit } = useForm({
    defaultValues: { body: "" },
  });

  return (
    <form onSubmit={handleSubmit((d) => console.log(d))}>
      <Mention.Root
        items={users}
        getKey={(u) => u.id}
        getLabel={(u) => u.name}
        onSelect={(u) => console.log(u)}
      >
        <Controller
          name="body"
          control={control}
          render={({ field }) => (
            <Mention.Input
              {...field}
              aria-label="Comment"
            />
          )}
        />
        <Mention.Popover>
          <Mention.List>
            {(u) => <Mention.Item value={u}>{u.name}</Mention.Item>}
          </Mention.List>
        </Mention.Popover>
      </Mention.Root>
      <button type="submit">Post</button>
    </form>
  );
}

The field.ref from Controller forwards to the underlying textarea, so RHF's focus-on-error works out of the box.

Formik

<Field as={Mention.Input}> works for most cases — Formik forwards value + onChange, and the library composes on top:

<Field as={Mention.Input} name="body" aria-label="Comment" />

For onBlur-driven validation, pair with useField + an explicit <Mention.Input> wired to field.value / field.onChange.

Reading commits with getInsertText

onSelect notifies after a commit but doesn't tell you what got inserted. Use getInsertText to control the inserted string and persist a structured representation in parallel:

const [mentionedIds, setMentionedIds] = useState<number[]>([]);

<Mention.Root
  items={users}
  getKey={(u) => u.id}
  getLabel={(u) => u.name}
  getInsertText={(u) => `@${u.username}`}
  onSelect={(u) => setMentionedIds((prev) => [...prev, u.id])}
>

</Mention.Root>

Storing the structured form (ids) alongside the rendered string keeps the round-trip lossless: server-side, render @username back to a styled mention chip using the persisted id list.

On this page