Context Menu
A floating right-click style menu with items, keyboard shortcuts, dividers and a danger variant.
A floating right-click style menu with items, keyboard shortcuts, dividers and a danger variant. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.
newt-context-menuimport {
ContextMenu,
ContextMenuDivider,
ContextMenuItem,
ContextMenuShortcut,
} from "@/registry/bases/newt/ui/context-menu"
export default function ContextMenuDemo() {
return (
<ContextMenu>
<ContextMenuItem>Action one</ContextMenuItem>
<ContextMenuItem>
Action two
<ContextMenuShortcut>Ctrl+K</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuDivider />
<ContextMenuItem variant="danger">Delete</ContextMenuItem>
</ContextMenu>
)
}<script setup lang="ts">
import {
ContextMenu,
ContextMenuDivider,
ContextMenuItem,
ContextMenuShortcut,
} from "~~/registry/bases/newt/ui/context-menu"
</script>
<template>
<ContextMenu>
<ContextMenuItem>Action one</ContextMenuItem>
<ContextMenuItem>
Action two
<ContextMenuShortcut>Ctrl+K</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuDivider />
<ContextMenuItem variant="danger">Delete</ContextMenuItem>
</ContextMenu>
</template>Installation
pnpm dlx newtui@latest add context-menunpx newtui@latest add context-menuyarn dlx newtui@latest add context-menubunx newtui@latest add context-menuInstall the dependencies
pnpm dlx --yes class-variance-authoritynpx --yes class-variance-authorityyarn dlx --yes class-variance-authoritybunx --yes class-variance-authorityCopy the source into your project
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"
import { cn } from "@/lib/utils"
const ContextMenu = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
role="menu"
className={cn(
"w-[188px] rounded-md border border-newt-border bg-newt-bg-floating p-1.5 shadow-elevation-high",
className
)}
{...props}
/>
))
ContextMenu.displayName = "ContextMenu"
const contextMenuItemVariants = cva(
"group flex cursor-pointer items-center justify-between rounded-sm px-2 py-1.5 text-[13px] transition-colors duration-fast ease-newt hover:text-white",
{
variants: {
variant: {
default: "text-newt-text-secondary hover:bg-newt-brand",
danger: "text-newt-dnd hover:bg-newt-dnd",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface ContextMenuItemProps
extends
React.ComponentProps<"div">,
VariantProps<typeof contextMenuItemVariants> {}
const ContextMenuItem = React.forwardRef<HTMLDivElement, ContextMenuItemProps>(
({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="menuitem"
tabIndex={-1}
className={cn(contextMenuItemVariants({ variant }), className)}
{...props}
/>
)
)
ContextMenuItem.displayName = "ContextMenuItem"
const ContextMenuShortcut = React.forwardRef<
HTMLSpanElement,
React.ComponentProps<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
className={cn(
"text-[11px] text-newt-text-muted group-hover:text-white/70",
className
)}
{...props}
/>
))
ContextMenuShortcut.displayName = "ContextMenuShortcut"
const ContextMenuDivider = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
role="separator"
className={cn("mx-1.5 my-1 h-px bg-newt-border", className)}
{...props}
/>
))
ContextMenuDivider.displayName = "ContextMenuDivider"
export {
ContextMenu,
ContextMenuItem,
ContextMenuShortcut,
ContextMenuDivider,
contextMenuItemVariants,
}<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div
role="menu"
:class="
cn(
'w-[188px] rounded-md border border-newt-border bg-newt-bg-floating p-1.5 shadow-elevation-high',
props.class
)
"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
import { contextMenuItemVariants, type ContextMenuItemVariants } from "."
const props = defineProps<{
class?: HTMLAttributes["class"]
variant?: ContextMenuItemVariants["variant"]
}>()
</script>
<template>
<div
role="menuitem"
tabindex="-1"
:class="
cn(contextMenuItemVariants({ variant: props.variant }), props.class)
"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<span
:class="
cn(
'text-[11px] text-newt-text-muted group-hover:text-white/70',
props.class
)
"
>
<slot />
</span>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div
role="separator"
:class="cn('mx-1.5 my-1 h-px bg-newt-border', props.class)"
/>
</template>import { cva, type VariantProps } from "class-variance-authority"
export { default as ContextMenu } from "./ContextMenu.vue"
export { default as ContextMenuItem } from "./ContextMenuItem.vue"
export { default as ContextMenuShortcut } from "./ContextMenuShortcut.vue"
export { default as ContextMenuDivider } from "./ContextMenuDivider.vue"
export const contextMenuItemVariants = cva(
"group flex cursor-pointer items-center justify-between rounded-sm px-2 py-1.5 text-[13px] transition-colors duration-fast ease-newt hover:text-white",
{
variants: {
variant: {
default: "text-newt-text-secondary hover:bg-newt-brand",
danger: "text-newt-dnd hover:bg-newt-dnd",
},
},
defaultVariants: {
variant: "default",
},
}
)
export type ContextMenuItemVariants = VariantProps<
typeof contextMenuItemVariants
>Update the import paths
The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.
Usage
import { ContextMenu } from "@/components/ui/context-menu"
export function Example() {
return <ContextMenu />
}<script setup lang="ts">
import { ContextMenu, ContextMenuItem, ContextMenuShortcut, ContextMenuDivider } from "@/components/ui/context-menu"
</script>
<template>
<ContextMenu />
</template>API reference
ContextMenu
children / default slot?React.ReactNode / Vue slot
Content rendered inside the component.
React.ReactNode / Vue slotclassName / class?string
Additional classes merged with the component defaults.
stringThe registry exports ContextMenu, ContextMenuItem, ContextMenuShortcut, ContextMenuDivider. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.
Accessibility
Open the menu from both pointer and keyboard input, move focus into it, support arrow-key navigation, and restore focus to the trigger when it closes.
Tokens
Context Menu reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-context-menu.