Refined UI

An opinionated, customizable superset of shadcn/ui

AgentsPrimitivesSkillsSkins
Guides
  • Overview
  • Get started
  • Shadcn compatibility
  • Architecture
  • Agent surface
Primitives
  • Accordion
  • Alert
  • Alert dialog
  • Aspect ratio
  • Autocomplete
  • Avatar
  • Badge
  • Button
  • Button group
  • Calendar
  • Card
  • Checkbox
  • Checkbox Group
  • Code
  • Code Diff
  • Collapsible
  • Color Picker
  • Combobox
  • Command
  • Date Picker
  • Dialog
  • Drawer
  • Empty
  • Field
  • Form
  • Gradient Editor
  • Hover card
  • Input
  • Input group
  • Input OTP
  • Item
  • Kbd
  • Markdown
  • Menu
  • Menubar
  • Meter
  • Native select
  • Navigation menu
  • Number Field
  • Pagination
  • Popover
  • Progress
  • Radio group
  • Resizable
  • Scroll area
  • Select
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Table of contents
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toolbar
  • Tooltip
  • Tree
  • Trigger Menu
  • Typography
Hooks
  • Use Chat Message
  • Use Chat Input
  • Use User Ask
  • Use Audio Recorder
  • Use Environment Variables
  • Use Copy To Clipboard
  • Use Tool Call
  • Use Sidebar Resize
Utils
  • cn
  • Skin
  • Contracts
  • Serialize
Extensions
  • Control Effects
  • View Transition
  • Send Aurora
  1. Primitives
  2. Trigger Menu
Primitives

Trigger Menu

Caret-anchored command or mention menu for text editors.

Composition

The preferred shape for composing the installed primitive from its exported parts.

Parts

Exported compound parts from the installed source.

TriggerMenu
├── TriggerMenuList
├── TriggerMenuItem
├── TriggerMenuIcon
├── TriggerMenuEmpty
├── TriggerMenuGroup
└── TriggerMenuGroupLabel

The refined source installs this primitive from src/registry/sources/refined/ui/trigger-menu.tsx. Install it on its own with the command above, or inspect the source below.

Registry command
npx shadcn@latest add http://127.0.0.1:3000/r/refined/trigger-menu.json
See registry manifest

Installed dependencies

Installed with this primitive because the slot depends on them; they are support files, not standalone sidebar items.

Trigger menu enginesrc/registry/hooks/use-trigger-menu.tsSupport
Textarea bindingsrc/registry/hooks/use-textarea-trigger-menu.tsSupport
Trigger detectionsrc/registry/lib/trigger-detect.tsSupport
Surface variantssrc/registry/sources/refined/surface-variants.tsSupport

Raw code

Primary installed primitive source

Trigger menu slot
src/registry/sources/refined/ui/trigger-menu.tsx
"use client";
​
import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
import type {
TriggerMenuEmptyProps,
TriggerMenuGroupLabelProps,
TriggerMenuGroupProps,
TriggerMenuIconProps,
TriggerMenuItemProps,
TriggerMenuListProps,
TriggerMenuProps,
} from "@/components/refined-ui/contracts";
import { cn } from "@/components/refined-ui/lib/cn";
import { skinEffects, skinId, skinSlot } from "@/components/refined-ui/skin";
import { floatingListContentClasses, floatingListItemClasses } from "@/components/refined-ui/surface-variants";
​
// VIEW half of trigger-menu primitive: Base UI Popover controlled by headless engine (open + virtual anchor at caret rect), not a trigger button.
// Rides shared popover token set (--radius-popover, shadow-pop); rows match menu/select rows (--control-h-xs, --radius-popup-item).
// initialFocus/finalFocus={false} keep focus in editor so arrow/Enter keep flowing to caret while menu is up.
​
export function TriggerMenu({
open,
onOpenChange,
anchorRect,
side = "top",
align = "start",
sideOffset = 8,
className,
children,
}: TriggerMenuProps) {
return (
<PopoverPrimitive.Root open={open} onOpenChange={(next, eventDetails) => onOpenChange?.(next, eventDetails)} modal={false}>
<PopoverPrimitive.Portal>
{/* Portals land outside token-scoped subtree (and ChatScene's overflow clip); positioner re-asserts skin scope itself. Anchor = virtual element at caret rect engine reported. */}
<PopoverPrimitive.Positioner
data-skin={skinId()}
data-effects={skinEffects()}
anchor={anchorRect === null ? undefined : () => ({ getBoundingClientRect: () => anchorRect })}
side={side}
align={align}
sideOffset={sideOffset}
className="z-[80] outline-none"
>
<PopoverPrimitive.Popup
data-ui="agent"
data-slot="trigger-menu"
initialFocus={false}
finalFocus={false}
className={cn(
"max-h-[min(18rem,var(--available-height))] w-64 max-w-[var(--available-width)] overflow-y-auto",
floatingListContentClasses,
skinSlot("trigger-menu", {}),
className,
)}
>
{children}
</PopoverPrimitive.Popup>
</PopoverPrimitive.Positioner>
</PopoverPrimitive.Portal>
</PopoverPrimitive.Root>
);
}
​
export function TriggerMenuList({ className, ...props }: TriggerMenuListProps) {
return (
<div
data-ui="agent"
data-slot="trigger-menu-list"
role="listbox"
className={cn("flex flex-col gap-0.5", skinSlot("trigger-menu-list", {}), className)}
{...props}
/>
);
}
​
export function TriggerMenuItem({ className, active = false, disabled = false, onMouseDown, ...props }: TriggerMenuItemProps) {
return (
<div
data-ui="agent"
data-slot="trigger-menu-item"
role="option"
// Listbox options driven by editor's keyboard, not tab focus; -1 keeps them out of tab order while satisfying focusable-interactive contract.
tabIndex={-1}
aria-selected={active}
aria-disabled={disabled || undefined}
data-highlighted={active ? "" : undefined}
data-disabled={disabled ? "" : undefined}
// Keep focus in the editor/textarea when a row is clicked so the insertion still targets the caret.
onMouseDown={(event) => {
event.preventDefault();
onMouseDown?.(event);
}}
className={cn(floatingListItemClasses, skinSlot("trigger-menu-item", { highlighted: active, disabled }), className)}
{...props}
/>
);
}
​
export function TriggerMenuIcon({ className, ...props }: TriggerMenuIconProps) {
return (
<span
data-ui="agent"
data-slot="trigger-menu-icon"
className={cn(
"flex size-5 shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-4",
skinSlot("trigger-menu-icon", {}),
className,
)}
{...props}
/>
);
}
​
export function TriggerMenuEmpty({ className, ...props }: TriggerMenuEmptyProps) {
return (
<div
data-ui="agent"
data-slot="trigger-menu-empty"
className={cn("px-[calc(var(--padding-x)*0.5)] py-2 text-body text-muted-foreground", skinSlot("trigger-menu-empty", {}), className)}
{...props}
/>
);
}
​
export function TriggerMenuGroup({ className, ...props }: TriggerMenuGroupProps) {
return (
<div
data-ui="agent"
data-slot="trigger-menu-group"
className={cn("flex flex-col gap-0.5", skinSlot("trigger-menu-group", {}), className)}
{...props}
/>
);
}
​
export function TriggerMenuGroupLabel({ className, ...props }: TriggerMenuGroupLabelProps) {
return (
<div
data-ui="agent"
data-slot="trigger-menu-group-label"
className={cn(
"px-[calc(var(--padding-x)*0.5)] pb-0.5 pt-1 text-micro font-medium uppercase tracking-[0.08em] text-muted-foreground",
skinSlot("trigger-menu-group-label", {}),
className,
)}
{...props}
/>
);
}
​

On this page

  • Preview
  • Composition
  • Installation
  • Dependencies
  • Source