Skip to content
  1. Keyline UI
  2. Context menu
Keyline UIComponents
  • Get started
    • Installation
    • Theming
    • Modes
    • Configuration
    • Composition
  • Actions
    • Button
    • Button group
    • Split button
    • Toggle
    • Toggle group
    • Toolbar
    • Menu
    • Menubar
    • Context menu
  • Inputs
    • Field and form
    • Checkbox
    • Checkbox group
    • Radio group
    • Switch
    • Slider
    • Segmented
    • Select
    • Native select
    • Combobox
    • Multiselect
    • Number field
    • OTP field
    • Time field
    • Date picker and calendar
    • Date range
  • Data
    • Table
    • Chart
    • Stat and meter
    • Status and badge
    • Progress
    • Skeleton
    • Empty
    • Avatar
    • Kbd
    • Marker
  • Navigation
    • Tabs
    • Breadcrumb
    • Pagination
    • Nav
    • Navigation menu
    • Command menu
  • Overlays
    • Dialog
    • Alert dialog
    • Drawer
    • Sheet
    • Popover
    • Hover card
    • Tooltip
    • Toast
    • Notice
  • Layout
    • App shell
    • Sidebar
    • Panel
    • Resizable
    • Scroll area
    • Accordion
    • Collapsible
    • Carousel
    • Inspector
    • Item
    • Direction
  • Conversation
    • Message
    • Bubble
    • Message scroller
    • Attachment
    • Questionnaire
  • Blocks
    • Shipments

Context menu

A menu opened on an object: right-click, long-press, or Shift+F10 on a focused target. While it is open, an ink keyline marks the object it acts on.

Loading the live example…

"use client"

import { useState } from "react"
import { Button } from "@/components/keyline/ui/button"
import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuTrigger,
} from "@/components/keyline/ui/context-menu"

const CLIENTS = [
  { name: "Scranton Business Park", contact: "Front desk", rep: "Dwight" },
  { name: "Lackawanna County", contact: "Procurement office", rep: "Jim" },
  { name: "Blue Cross of Pennsylvania", contact: "Accounts payable", rep: "Stanley" },
]

export function ClientList() {
  const [log, setLog] = useState("Right-click a client, or focus one and press Shift+F10.")
  const [archived, setArchived] = useState<string[]>([])
  const shown = CLIENTS.filter((c) => !archived.includes(c.name))

  return (
    <div className="grid gap-4">
      <ul className="m-0 grid list-none gap-2 p-0" aria-label="Clients">
        {shown.map((c) => (
          <li key={c.name}>
            <ContextMenu>
              <ContextMenuTrigger
                tabIndex={0}
                aria-label={`${c.name}. Actions available from the context menu.`}
                className="grid gap-0.5 border border-kl-line bg-kl-paper px-4 py-3 text-[14px] outline-offset-2 focus-visible:outline-2 focus-visible:outline-current"
              >
                <strong>{c.name}</strong>
                <span className="text-kl-ink-2">
                  {c.contact} · {c.rep}’s account
                </span>
              </ContextMenuTrigger>
              <ContextMenuContent>
                <ContextMenuItem onClick={() => setLog(`Calling ${c.name}, ${c.contact}.`)}>Call</ContextMenuItem>
                <ContextMenuItem onClick={() => setLog(`New order started for ${c.name}.`)}>New order</ContextMenuItem>
                <ContextMenuItem onClick={() => setLog(`${c.rep} will get a reminder to follow up with ${c.name} on Friday.`)}>
                  Remind {c.rep} to follow up
                </ContextMenuItem>
                <ContextMenuSeparator />
                <ContextMenuItem
                  variant="destructive"
                  onClick={() => {
                    setArchived([...archived, c.name])
                    setLog(`${c.name} archived.`)
                  }}
                >
                  Archive client
                </ContextMenuItem>
              </ContextMenuContent>
            </ContextMenu>
          </li>
        ))}
      </ul>
      {shown.length === 0 ? (
        <div className="grid justify-items-start gap-3 border border-dashed border-kl-line-soft px-4 py-4 text-[14px]">
          <p className="m-0">No clients left on the list. Michael would call this a crisis.</p>
          <Button size="compact" onClick={() => setArchived([])}>
            Restore all three
          </Button>
        </div>
      ) : null}
      <p role="status" className="m-0 min-h-5 text-[14px] text-kl-ink-2">
        {log}
      </p>
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/context-menu

Run it where your components.json is. The first time, map the @keyline-ui registry: one command, in Installation.

Usage

A context menu is a shortcut. Put every action somewhere visible too, such as a row’s Menu button, so people who never right-click can still reach it.

import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuTrigger,
} from "@/components/keyline/ui/context-menu"
<ContextMenu>
  <ContextMenuTrigger tabIndex={0} aria-label="Lackawanna County">
    Lackawanna County
  </ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuItem onClick={call}>Call</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>

Props

PropTypeDefaultDescription
renderReactElementDefault <div>On ContextMenuTrigger: the object itself, such as a row or a card.
tabIndexnumberDefault —On ContextMenuTrigger: 0 makes the object focusable, so Shift+F10 and the Menu key reach it.
containerElementDefault bodyOn ContextMenuContent: where the menu is portalled.

The rows are Menu’s, re-exported as ContextMenuItem, ContextMenuSeparator, ContextMenuSubmenu and the rest. Built on Base UI Context Menu.

Sheet 15 of 73PreviousMenubarNextField and form
On this page