- Components
Context menu
The menu that opens where you right-click, and from the keyboard with the Menu key or Shift+F10.
npx shadcn add @klarhimmel/context-menu @klarhimmel/menuWhen to use
Use for
Row, tile and canvas actions for mouse and keyboard users.
Not for
The only route to an action. Touch has no right-click: repeat the actions in a visible button.
Pairs with
Menu, Icon button, tables and file grids.
Preview
Booking 4821
Right-click here, or focus it and press the Menu key or Shift+F10.
Nothing chosen yet.
context-menu.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { ContextMenu } from '@/components/klarhimmel/ui/context-menu';
import { MenuItem } from '@/components/klarhimmel/ui/menu';
export default function Example() {
return (
<Theme theme="brand">
<ContextMenu
aria-label="Booking 4821"
menu={<>
<MenuItem>Open booking</MenuItem>
<MenuItem danger>Cancel booking</MenuItem>
</>}
>
Booking 4821
</ContextMenu>
</Theme>
);
}Install
Import
import { ContextMenu } from '@/components/klarhimmel/ui/context-menu';- Lands in
- components/klarhimmel/ui/context-menu.tsx, plus cn() and the theme item if they are missing. Import theme.css after Tailwind.
- Render in
- Render it inside <Theme>, once near the root of your app. Tokens and fonts are scoped to it, so outside it the component is unstyled.
- Limit
- Overlays render into the Theme root with position: fixed. A transform on an ancestor re-parents fixed (a dialog centres in the wrapper) and overflow: hidden clips menus. Render <Theme> at the app root, outside any transformed wrapper.
Examples
Rows
- Maja Sund, room 204
- Oskar Lind, room 311
- Ida Nord, room 118
Right-click a row. The button on each row has the same actions, for touch screens.
import { useState } from 'react'; import { MoreHorizontal } from 'lucide-react'; import { ContextMenu } from '@/components/klarhimmel/ui/context-menu'; import { IconButton } from '@/components/klarhimmel/ui/icon-button'; import { Menu, MenuContent, MenuItem, MenuTrigger } from '@/components/klarhimmel/ui/menu'; const rows = ['Maja Sund, room 204', 'Oskar Lind, room 311', 'Ida Nord, room 118']; export default function ContextMenuRows() { const [last, setLast] = useState(''); const items = (who: string) => ( <> <MenuItem onSelect={() => setLast(`Check in ${who}`)}>Check in</MenuItem> <MenuItem onSelect={() => setLast(`Message ${who}`)}>Send a message</MenuItem> </> ); return ( <div className="flex min-h-72 w-[min(28rem,calc(100cqw-3rem))] flex-col gap-3"> <ul className="divide-y divide-kh-line rounded-kh-lg border border-kh-line bg-kh-surface"> {rows.map((r) => ( <li key={r} className="flex items-center gap-2 pr-2"> <ContextMenu aria-label={r} className="flex-1 px-4 py-3 text-kh-small" menu={items(r)}>{r}</ContextMenu> <Menu> <MenuTrigger asChild> <IconButton label={`Actions for ${r}`} variant="ghost"><MoreHorizontal aria-hidden className="size-4" /></IconButton> </MenuTrigger> <MenuContent align="end">{items(r)}</MenuContent> </Menu> </li> ))} </ul> <p role="status" className="text-kh-small text-kh-muted">{last || 'Right-click a row. The button on each row has the same actions, for touch screens.'}</p> </div> ); }Tiles
- rates-autumn.csv
- house-rules.pdf
- floor-plan.png
Tab to a tile and press Shift+F10.
import { useState } from 'react'; import { ContextMenu } from '@/components/klarhimmel/ui/context-menu'; import { MenuItem, MenuLabel, MenuSeparator } from '@/components/klarhimmel/ui/menu'; const files = ['rates-autumn.csv', 'house-rules.pdf', 'floor-plan.png']; export default function ContextMenuTiles() { const [last, setLast] = useState(''); return ( <div className="flex min-h-72 w-[min(30rem,calc(100cqw-3rem))] flex-col gap-3"> <ul className="grid grid-cols-3 gap-3"> {files.map((f) => ( <li key={f}> <ContextMenu aria-label={f} className="flex h-24 items-end rounded-kh-lg border border-kh-line bg-kh-surface-2 p-2 text-kh-caption [overflow-wrap:anywhere]" menu={<> <MenuLabel>{f}</MenuLabel> <MenuSeparator /> <MenuItem onSelect={() => setLast(`Download ${f}`)}>Download</MenuItem> <MenuItem onSelect={() => setLast(`Rename ${f}`)} shortcut="F2">Rename</MenuItem> </>} > {f} </ContextMenu> </li> ))} </ul> <p role="status" className="text-kh-small text-kh-muted">{last || 'Tab to a tile and press Shift+F10.'}</p> </div> ); }
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | - | Required: names the area, which is a tab stop. |
menu | ReactNode | - | Required. MenuItem, MenuLabel, MenuSeparator and the other Menu parts. |
children | ReactNode | - | What the area shows. |
className | string | - | Extra classes, merged after the component's own. |
Accessibility
| Key | Does |
|---|---|
Right-click | Opens the menu at the pointer |
Menu key / Shift+F10 | Opens it at the focused element |
Down / Up / Home / End | Move through the items |
Letters | Jump to an item that starts with them |
Escape | Closes and returns focus to the area |
It is the same Menu as a button's menu. The area is a named tab stop so keyboard users can reach it.
Accessibility