Mention Guard

Says how many people @channel, @everyone or a group mention will notify as you type, and asks for an explicit confirm before a mass ping.

Stops@channel pinged 1,240 people

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/mention-guard.json

The files land in your project. They're yours to edit.

Usage

mention-guard-usage.tsx
import { MentionGuard, type MentionContext, type MentionPerson } from "@/components/ui/mention-guard"

<MentionGuard
  conversation="#launch"
  label="Reply in #launch"
  context={context}
  defaultValue="@channel the launch review moved to 4 pm IST. @rahul can you bring the deck?"
  onInvite={(person) => setPeople((list) => list.map((p) => (p.handle === person.handle ? { ...p, inConversation: true } : p)))}
  onSend={(text) => new Promise<void>((resolve) => setTimeout(() => (setSent(text), resolve()), 500))}
/>

What it catches

  • Broadcast mentions over a threshold, with the count
  • Recipients in other time zones
  • Mentioned people who aren't in the conversation or can't open it
  • Double press on Send: the confirm can't be triggered by it
  • Ignores emails (a@here.com) and code spans

Props

MentionGuard props
PropTypeDefault
value

Controlled message text.

string—
defaultValue

Initial text when uncontrolled.

string""
onValueChange

Called with the text on every change.

(value: string) => void—
onSend* (required)

Sends the message. Return a promise to show a pending state; the text clears when it resolves.

(text: string) => void | Promise<void>—
onInvite

Invites a mentioned person into the conversation. Without it, only "Remove mention" is offered.

(person: MentionPerson) => void—
conversation* (required)

Where the message goes, e.g. "#launch".

string—
context* (required)

Who each

MentionContext—
threshold

Audiences at or above this size need a confirm before sending. Slack uses 6.

number6
softerHandle

Handle to suggest instead of a broadcast, e.g. "here" (only people online).

string"here"
sendOnEnter

Enter sends and Shift+Enter adds a line. Off: only Ctrl/⌘+Enter sends.

booleantrue
label

Visible label.

string"Message"
placeholder

Textarea placeholder.

string—
broadcastText

Templates. `{handle}`, `{count}`, `{zones}`, `{name}` and `{conversation}` are filled in.

string"@{handle} notifies {count} people"
zonesText

Template for how many are in other time zones.

string", {zones} in other time zones"
outsiderText

Shown for a mentioned person not in the conversation.

string"{name} isn't in {conversation} and won't be notified."
noAccessText

Shown when a mentioned person can't open the conversation.

string"{name} can't see {conversation}, so they won't see this."
softerLabel

Button text to swap for the softer handle.

string"Use @{handle} ({count})"
silenceLabel

Button text to mention without notifying.

string"Don't notify"
inviteLabel

Button text to invite a mentioned person.

string"Invite {name}"
removeLabel

Button text to remove a mention.

string"Remove mention"
sendLabel

Send button text.

string"Send"
confirmLabel

Confirm button template for a broadcast.

string"Notify {count} people"
confirmOutsiderLabel

Confirm button text when outsiders are mentioned.

string"Send anyway"
cancelLabel

Cancel button text while armed.

string"Edit"
sendingLabel

Button text while sending.

string"Sending…"
armedHint

Read to screen readers when Send asks for a second press.

string"Check the warnings, then press Send again. Escape to kee…

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Visible label; textarea is described by a polite notice region
  • Escape cancels the armed confirm; IME composition doesn't trigger send
  • Confirm pulse is skipped under reduced motion
  • Fixes are buttons; icons are aria-hidden

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Rate-limit and permission-check broadcast mentions on the server.

  • Audience sizes come from your context prop
  • Doesn't stop sending through other clients or the API
  • Mentions are parsed with simple rules; custom mention syntax needs adapting

What it stops

@channel pinged 1,240 people

Also known as

@channel · @everyone · @here · mentions · slack · discord · notify · group mention

Field type

Dependencies

Pairs well with