Compound Components

Control layout while the library handles state, ARIA, and keyboard navigation.

Basic Structure

import { Mentions } from "@skyastrall/mentions-react";

<Mentions triggers={triggers}>
  <Mentions.Editor placeholder="Type @..." className="my-editor" />
  <Mentions.Portal>
    <Mentions.List>
      <Mentions.Item />
    </Mentions.List>
  </Mentions.Portal>
</Mentions>
<script setup>
import {
  Mentions, MentionsEditor, MentionsPortal,
  MentionsList, MentionsItem,
} from "@skyastrall/mentions-vue";
</script>

<template>
  <Mentions :triggers="triggers">
    <MentionsEditor placeholder="Type @..." class="my-editor" />
    <MentionsPortal>
      <MentionsList>
        <MentionsItem />
      </MentionsList>
    </MentionsPortal>
  </Mentions>
</template>
<script>
import {
  Mentions, MentionsEditor, MentionsPortal,
  MentionsList, MentionsItem,
} from "@skyastrall/mentions-svelte";
</script>

<Mentions {triggers}>
  <MentionsEditor placeholder="Type @..." class="my-editor" />
  <MentionsPortal>
    <MentionsList>
      <MentionsItem />
    </MentionsList>
  </MentionsPortal>
</Mentions>

Custom Item Rendering

<Mentions.Item render={({ item, highlighted }) => (
  <div style={{
    display: "flex",
    alignItems: "center",
    gap: 8,
    fontWeight: highlighted ? 600 : 400,
  }}>
    <img src={item.avatar} width={24} height={24} style={{ borderRadius: "50%" }} />
    <div>
      <div>{item.label}</div>
      <div style={{ fontSize: 12, opacity: 0.6 }}>{item.role}</div>
    </div>
  </div>
)} />
<MentionsItem v-slot="{ item, highlighted }">
  <div :style="{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    fontWeight: highlighted ? 600 : 400,
  }">
    <img :src="item.avatar" width="24" height="24" style="border-radius: 50%" />
    <div>
      <div>{{ item.label }}</div>
      <div style="font-size: 12px; opacity: 0.6">{{ item.role }}</div>
    </div>
  </div>
</MentionsItem>
<MentionsItem>
  {#snippet children({ item, highlighted })}
    <div style:display="flex" style:align-items="center" style:gap="8px"
      style:font-weight={highlighted ? 600 : 400}>
      <img src={item.avatar} width="24" height="24" style="border-radius: 50%" />
      <div>
        <div>{item.label}</div>
        <div style="font-size: 12px; opacity: 0.6">{item.role}</div>
      </div>
    </div>
  {/snippet}
</MentionsItem>

Loading and Empty States

<Mentions.List>
  <Mentions.Loading>
    <Spinner /> Searching...
  </Mentions.Loading>
  <Mentions.Empty>
    No results found
  </Mentions.Empty>
  <Mentions.Item render={({ item }) => item.label} />
</Mentions.List>
<MentionsList>
  <MentionsLoading>
    <Spinner /> Searching...
  </MentionsLoading>
  <MentionsEmpty>
    No results found
  </MentionsEmpty>
  <MentionsItem />
</MentionsList>
<MentionsList>
  <MentionsLoading>
    <Spinner /> Searching...
  </MentionsLoading>
  <MentionsEmpty>
    No results found
  </MentionsEmpty>
  <MentionsItem />
</MentionsList>

Portal Container

By default, the dropdown renders inline inside the component. To portal elsewhere:

<Mentions.Portal container={document.body}>
  <Mentions.List>...</Mentions.List>
</Mentions.Portal>
<MentionsPortal to="body">
  <MentionsList>...</MentionsList>
</MentionsPortal>
<!-- Svelte portal renders inline with fixed positioning -->
<MentionsPortal>
  <MentionsList>...</MentionsList>
</MentionsPortal>