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. Menu
Primitives

Menu

Dropdown menu for actions, resources, labels, separators, and submenus.

Composition

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

Parts

Exported compound parts from the installed source.

Menu
├── MenuTrigger
├── MenuContent
├── MenuItem
├── MenuSeparator
└── MenuLabel

The refined source installs this primitive from src/registry/sources/refined/ui/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/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.

Control variantssrc/registry/sources/refined/control-variants.tsSupport
Surface variantssrc/registry/sources/refined/surface-variants.tsSupport
Effect utilitiessrc/registry/sources/refined/effects.cssEffect

Raw code

Primary installed primitive source

Base UI Menu slot
src/registry/sources/refined/ui/menu.tsx
"use client";
​
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
import type { ComponentProps } from "react";
import type {
MenuContentProps,
MenuItemProps,
MenuLabelProps,
MenuProps,
MenuSeparatorProps,
MenuTriggerProps,
} from "@/components/refined-ui/contracts";
import { controlSize, controlSurfaceClasses } from "@/components/refined-ui/control-variants";
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";
​
// Refined skin slot, 100% Base UI: trigger shares --radius-control/controlSize w/ Button & Select — one token set, every trigger matches.
​
type RefinedMenuTriggerProps = MenuTriggerProps & Pick<ComponentProps<typeof MenuPrimitive.Trigger>, "nativeButton" | "render">;
​
export function Menu({ children, ...props }: MenuProps) {
return <MenuPrimitive.Root {...props}>{children}</MenuPrimitive.Root>;
}
​
export function MenuTrigger({ size = "sm", className, children, disabled, ...props }: RefinedMenuTriggerProps) {
const skinClasses = skinSlot("menu-trigger", { size });
return (
<MenuPrimitive.Trigger
data-ui="agent"
data-slot="menu-trigger"
data-control="true"
data-size={size}
className={cn(
"relative isolate inline-flex shrink-0 cursor-pointer items-center justify-center overflow-visible rounded-[var(--radius-control)] font-medium outline-none transition focus-visible:ring-2 focus-visible:ring-foreground/20 disabled:cursor-not-allowed disabled:opacity-45",
controlSurfaceClasses,
controlSize({ size }),
skinClasses,
className,
)}
disabled={disabled}
{...props}
>
<span data-ui="agent" data-slot="control-content" className="relative z-[1] inline-flex items-center justify-center gap-[inherit]">
{children}
</span>
</MenuPrimitive.Trigger>
);
}
​
export function MenuContent({ className, children, ...props }: MenuContentProps) {
const skinClasses = skinSlot("menu-content", {});
return (
<MenuPrimitive.Portal>
{/* Portal escapes token-scoped ancestor — positioner re-asserts ACTIVE skin's scope (theme.css mirrors). */}
<MenuPrimitive.Positioner
data-skin={skinId()}
data-effects={skinEffects()}
side="bottom"
align="start"
sideOffset={6}
className="z-[80] outline-none"
>
<MenuPrimitive.Popup
data-ui="agent"
data-slot="menu-content"
className={cn("min-w-[max(11rem,var(--anchor-width))]", floatingListContentClasses, skinClasses, className)}
{...props}
>
{children}
</MenuPrimitive.Popup>
</MenuPrimitive.Positioner>
</MenuPrimitive.Portal>
);
}
​
export function MenuItem({ className, ...props }: MenuItemProps) {
const skinClasses = skinSlot("menu-item", { disabled: Boolean(props.disabled) });
return (
<MenuPrimitive.Item data-ui="agent" data-slot="menu-item" className={cn(floatingListItemClasses, skinClasses, className)} {...props} />
);
}
​
export function MenuSeparator({ className, ...props }: MenuSeparatorProps) {
const skinClasses = skinSlot("menu-separator", {});
return (
// -mx-[--popover-padding] bleeds rule through popup padding for edge-to-edge; rows keep inset, tracks same knob as popup.
<MenuPrimitive.Separator
data-ui="agent"
data-slot="menu-separator"
className={cn("-mx-[var(--popover-padding)] my-1 h-px bg-border", skinClasses, className)}
{...props}
/>
);
}
​
export function MenuLabel({ className, ...props }: MenuLabelProps) {
const skinClasses = skinSlot("menu-label", {});
return (
<div
data-ui="agent"
data-slot="menu-label"
className={cn("px-2 py-1 text-micro font-medium uppercase tracking-[0.08em] text-muted-foreground", skinClasses, className)}
{...props}
/>
);
}
​

On this page

  • Preview
  • Composition
  • Installation
  • Dependencies
  • Source
Dropdown menu
No action yet