Przejdź do treści

Context Menu

Akcje dla konkretnego obiektu, otwierane prawym przyciskiem myszy lub długim przytrzymaniem dokładnie tam, gdzie jest kursor — skrót, nigdy jedyna droga.

Kod źródłowyBase UIRejestr
Kliknij tutaj prawym przyciskiem
import {
  ContextMenu,
  ContextMenuCheckboxItem,
  ContextMenuContent,
  ContextMenuGroup,
  ContextMenuItem,
  ContextMenuLabel,
  ContextMenuRadioGroup,
  ContextMenuRadioItem,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuTrigger,
} from "@/components/ui/context-menu"

export default function ContextMenuDemo() {
  return (
    <ContextMenu>
      <ContextMenuTrigger
        // Focusable, so Shift+F10 or the Menu key opens it from the keyboard.
        tabIndex={0}
        className="flex h-40 w-full max-w-sm items-center justify-center rounded-xl border border-dashed border-border-strong bg-card text-sm text-muted-foreground focus-ring"
      >
        Kliknij tutaj prawym przyciskiem
      </ContextMenuTrigger>
      <ContextMenuContent className="w-60">
        <ContextMenuGroup>
          <ContextMenuItem>
            Wstecz
            <ContextMenuShortcut>⌘[</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem disabled>
            Dalej
            <ContextMenuShortcut>⌘]</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem>
            Załaduj ponownie
            <ContextMenuShortcut>⌘R</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuSub>
            <ContextMenuSubTrigger>Więcej narzędzi</ContextMenuSubTrigger>
            <ContextMenuSubContent className="w-48">
              <ContextMenuGroup>
                <ContextMenuItem>Zapisz stronę jako…</ContextMenuItem>
                <ContextMenuItem>Utwórz skrót…</ContextMenuItem>
                <ContextMenuItem>Narzędzia dla deweloperów</ContextMenuItem>
              </ContextMenuGroup>
            </ContextMenuSubContent>
          </ContextMenuSub>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuGroup>
          <ContextMenuCheckboxItem defaultChecked>
            Pokaż pasek zakładek
          </ContextMenuCheckboxItem>
          <ContextMenuCheckboxItem>Pokaż pełne adresy URL</ContextMenuCheckboxItem>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuGroup>
          <ContextMenuLabel inset>Profil</ContextMenuLabel>
          <ContextMenuRadioGroup defaultValue="work">
            <ContextMenuRadioItem value="personal">
              Osobisty
            </ContextMenuRadioItem>
            <ContextMenuRadioItem value="work">Służbowy</ContextMenuRadioItem>
          </ContextMenuRadioGroup>
        </ContextMenuGroup>
      </ContextMenuContent>
    </ContextMenu>
  )
}

Instalacja

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/context-menu.json

Użycie

import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuGroup,
  ContextMenuItem,
  ContextMenuTrigger,
} from "@/components/ui/context-menu"
<ContextMenu>
  <ContextMenuTrigger className="rounded-xl border border-dashed p-10">
    Right-click here
  </ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuGroup>
      <ContextMenuItem>Rename</ContextMenuItem>
      <ContextMenuItem>Duplicate</ContextMenuItem>
    </ContextMenuGroup>
  </ContextMenuContent>
</ContextMenu>

ContextMenuTrigger wyznacza obszar, który reaguje na kliknięcie prawym przyciskiem myszy (albo długie przytrzymanie na ekranie dotykowym). Menu otwiera się w miejscu kursora. Pozycje, grupy, podmenu, pozycje z polem wyboru i pozycje radiowe działają dokładnie tak samo jak w Dropdown Menu.

Przykłady

Menu w stylu przeglądarki

Wszystko, co może zawierać menu kontekstowe: skróty, wyłączoną pozycję, podmenu, pozycje z polem wyboru i grupę radiową z etykietą.

Kliknij tutaj prawym przyciskiem
import {
  ContextMenu,
  ContextMenuCheckboxItem,
  ContextMenuContent,
  ContextMenuGroup,
  ContextMenuItem,
  ContextMenuLabel,
  ContextMenuRadioGroup,
  ContextMenuRadioItem,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuTrigger,
} from "@/components/ui/context-menu"

export default function ContextMenuDemo() {
  return (
    <ContextMenu>
      <ContextMenuTrigger
        // Focusable, so Shift+F10 or the Menu key opens it from the keyboard.
        tabIndex={0}
        className="flex h-40 w-full max-w-sm items-center justify-center rounded-xl border border-dashed border-border-strong bg-card text-sm text-muted-foreground focus-ring"
      >
        Kliknij tutaj prawym przyciskiem
      </ContextMenuTrigger>
      <ContextMenuContent className="w-60">
        <ContextMenuGroup>
          <ContextMenuItem>
            Wstecz
            <ContextMenuShortcut>⌘[</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem disabled>
            Dalej
            <ContextMenuShortcut>⌘]</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem>
            Załaduj ponownie
            <ContextMenuShortcut>⌘R</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuSub>
            <ContextMenuSubTrigger>Więcej narzędzi</ContextMenuSubTrigger>
            <ContextMenuSubContent className="w-48">
              <ContextMenuGroup>
                <ContextMenuItem>Zapisz stronę jako…</ContextMenuItem>
                <ContextMenuItem>Utwórz skrót…</ContextMenuItem>
                <ContextMenuItem>Narzędzia dla deweloperów</ContextMenuItem>
              </ContextMenuGroup>
            </ContextMenuSubContent>
          </ContextMenuSub>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuGroup>
          <ContextMenuCheckboxItem defaultChecked>
            Pokaż pasek zakładek
          </ContextMenuCheckboxItem>
          <ContextMenuCheckboxItem>Pokaż pełne adresy URL</ContextMenuCheckboxItem>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuGroup>
          <ContextMenuLabel inset>Profil</ContextMenuLabel>
          <ContextMenuRadioGroup defaultValue="work">
            <ContextMenuRadioItem value="personal">
              Osobisty
            </ContextMenuRadioItem>
            <ContextMenuRadioItem value="work">Służbowy</ContextMenuRadioItem>
          </ContextMenuRadioGroup>
        </ContextMenuGroup>
      </ContextMenuContent>
    </ContextMenu>
  )
}

Elementy listy

Daj każdemu wierszowi własny ContextMenu, żeby akcje zawsze dotyczyły obiektu pod kursorem. Gdy menu jest otwarte, jego wyzwalacz dostaje atrybut data-popup-open — użyj go, żeby wiersz pozostał wyróżniony i było widać, na czym działa menu.

Brand guidelines.pdf2,4 MB · Edytowano dziś
Q3 roadmap.fig18,1 MB · Edytowano wczoraj
Launch checklist.md12 KB · Edytowano 4 marca
import {
  CopyIcon,
  DownloadIcon,
  FileTextIcon,
  FolderInputIcon,
  LinkIcon,
  PencilIcon,
  Share2Icon,
  StarIcon,
  Trash2Icon,
} from "lucide-react"

import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuGroup,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuTrigger,
} from "@/components/ui/context-menu"

const files = [
  { name: "Brand guidelines.pdf", meta: "2,4 MB · Edytowano dziś" },
  { name: "Q3 roadmap.fig", meta: "18,1 MB · Edytowano wczoraj" },
  { name: "Launch checklist.md", meta: "12 KB · Edytowano 4 marca" },
]

export default function ContextMenuFileItem() {
  return (
    <div className="flex w-full max-w-md flex-col gap-1 rounded-xl border bg-card p-1.5 shadow-raised">
      {files.map((file) => (
        <ContextMenu key={file.name}>
          <ContextMenuTrigger
            tabIndex={0}
            className="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm focus-ring transition-colors duration-fast hover:bg-accent data-popup-open:bg-accent"
          >
            <span className="flex size-9 shrink-0 items-center justify-center rounded-lg border bg-surface text-muted-foreground">
              <FileTextIcon className="size-4" />
            </span>
            <span className="flex min-w-0 flex-col">
              <span className="truncate font-medium text-foreground">
                {file.name}
              </span>
              <span className="text-body-xs text-muted-foreground">
                {file.meta}
              </span>
            </span>
          </ContextMenuTrigger>
          <ContextMenuContent className="w-56">
            <ContextMenuGroup>
              <ContextMenuItem>
                <PencilIcon />
                Zmień nazwę
                <ContextMenuShortcut>↵</ContextMenuShortcut>
              </ContextMenuItem>
              <ContextMenuItem>
                <CopyIcon />
                Duplikuj
                <ContextMenuShortcut>⌘D</ContextMenuShortcut>
              </ContextMenuItem>
              <ContextMenuItem>
                <StarIcon />
                Oznacz gwiazdką
              </ContextMenuItem>
              <ContextMenuItem>
                <FolderInputIcon />
                Przenieś do…
              </ContextMenuItem>
            </ContextMenuGroup>
            <ContextMenuSeparator />
            <ContextMenuGroup>
              <ContextMenuSub>
                <ContextMenuSubTrigger>
                  <Share2Icon />
                  Udostępnij
                </ContextMenuSubTrigger>
                <ContextMenuSubContent className="w-44">
                  <ContextMenuGroup>
                    <ContextMenuItem>
                      <LinkIcon />
                      Kopiuj link
                    </ContextMenuItem>
                    <ContextMenuItem>
                      <DownloadIcon />
                      Pobierz
                    </ContextMenuItem>
                  </ContextMenuGroup>
                </ContextMenuSubContent>
              </ContextMenuSub>
            </ContextMenuGroup>
            <ContextMenuSeparator />
            <ContextMenuGroup>
              <ContextMenuItem variant="destructive">
                <Trash2Icon />
                Przenieś do kosza
                <ContextMenuShortcut>⌘⌫</ContextMenuShortcut>
              </ContextMenuItem>
            </ContextMenuGroup>
          </ContextMenuContent>
        </ContextMenu>
      ))}
    </div>
  )
}

Obiekty na płótnie

W narzędziach projektowych, edytorach i mapach polecenia dotyczące pojedynczych obiektów opierają się na menu kontekstowym. Używaj dokładnie tego samego słownictwa co w głównych menu aplikacji i pokazuj te same skróty.

Ramka 1
import {
  BringToFrontIcon,
  ClipboardPasteIcon,
  CopyIcon,
  GroupIcon,
  LockIcon,
  ScissorsIcon,
  SendToBackIcon,
} from "lucide-react"

import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuGroup,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuTrigger,
} from "@/components/ui/context-menu"

export default function ContextMenuCanvas() {
  return (
    <ContextMenu>
      <ContextMenuTrigger
        // Focusable, so Shift+F10 or the Menu key opens it from the keyboard.
        tabIndex={0}
        className="relative flex h-52 w-full max-w-md items-center justify-center overflow-hidden rounded-xl border bg-surface focus-ring"
      >
        <div className="flex size-28 items-center justify-center rounded-lg border bg-card text-sm font-medium shadow-floating">
          Ramka 1
        </div>
      </ContextMenuTrigger>
      <ContextMenuContent className="w-56">
        <ContextMenuGroup>
          <ContextMenuItem>
            <ScissorsIcon />
            Wytnij
            <ContextMenuShortcut>⌘X</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem>
            <CopyIcon />
            Kopiuj
            <ContextMenuShortcut>⌘C</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem>
            <ClipboardPasteIcon />
            Wklej tutaj
            <ContextMenuShortcut>⌘V</ContextMenuShortcut>
          </ContextMenuItem>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuGroup>
          <ContextMenuItem>
            <BringToFrontIcon />
            Przenieś na wierzch
            <ContextMenuShortcut>]</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem>
            <SendToBackIcon />
            Przenieś na spód
            <ContextMenuShortcut>[</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem>
            <GroupIcon />
            Grupuj zaznaczenie
            <ContextMenuShortcut>⌘G</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem>
            <LockIcon />
            Zablokuj
            <ContextMenuShortcut>⇧⌘L</ContextMenuShortcut>
          </ContextMenuItem>
        </ContextMenuGroup>
      </ContextMenuContent>
    </ContextMenu>
  )
}

Wytyczne

Kiedy używać

  • Aby przyspieszyć akcje na obiektach, którymi użytkownicy manipulują bezpośrednio: plikach, wierszach, warstwach na płótnie, wiadomościach.
  • W gęstych narzędziach obsługiwanych głównie myszą, w których widoczne przyciski akcji zagraciłyby interfejs.

Kiedy nie używać

  • Gdy ma być jedynym sposobem dotarcia do akcji. Kliknięcie prawym przyciskiem trudno odkryć, na większości urządzeń dotykowych w ogóle nie istnieje, a z klawiatury wywołuje się je niewygodnie. Każda pozycja musi być dostępna także z widocznej kontrolki — Dropdown Menu pod przyciskiem ⋯, paska narzędzi albo widoku szczegółów obiektu.
  • Tam, gdzie ludzie oczekują menu przeglądarki — np. na tekście, który chcą skopiować, albo na linkach, które chcą otworzyć w nowej karcie.

Odzwierciedlaj widoczne menu

Menu kontekstowe to skrót do poleceń, które już istnieją. Używaj tych samych etykiet, kolejności i skrótów co w menu ⋯ danego wiersza albo w pasku menu aplikacji, żeby ludzie uczyli się tego słownictwa tylko raz. Najczęstsze akcje umieszczaj na początku, a destrukcyjne — na końcu, za separatorem.

Wytyczne marki.pdf
DobrzeTe same akcje są o jedno kliknięcie stąd, pod widocznym przyciskiem.
Wytyczne marki.pdf(opcje pod prawym przyciskiem myszy)
ŹleAkcje dostępne tylko pod prawym przyciskiem myszy są dla większości ludzi niewidoczne.

Im krócej, tym lepiej

Menu kontekstowe czyta się przy kursorze i w pośpiechu. Staraj się zmieścić w mniej niż dziesięciu pozycjach w dwóch–trzech grupach, unikaj więcej niż jednego poziomu podmenu i nigdy nie umieszczaj w menu dłuższych treści.

Dostępność

Context Menu opiera się na komponencie Context Menu z Base UI, który po otwarciu realizuje wzorzec WAI-ARIA menu (APG opisuje menu i paski menu na jednej stronie).

KlawiszDziałanie
ShiftF10
Gdy fokus jest w obszarze wyzwalacza, otwiera menu — to systemowy klawiaturowy odpowiednik kliknięcia prawym przyciskiem. To samo robi klawisz menu kontekstowego (ContextMenu) na klawiaturach dla Windows.
↓↑
Przenosi fokus między pozycjami, zapętlając się na końcach listy.
HomeEnd
Przenosi fokus na pierwszą lub ostatnią pozycję.
→←
Otwiera lub zamyka podmenu.
EnterSpacja
Aktywuje pozycję z fokusem.
A–Z
Przenosi fokus na następną pozycję zaczynającą się od wpisanych znaków.
Esc
Zamyka menu.
  • Dotyk. Długie przytrzymanie wyzwalacza otwiera menu, więc te same akcje działają na telefonach i tabletach.
  • Równoważność. Menu kontekstowe trudno odkryć za pomocą technologii wspomagających, dlatego widoczna alternatywa nie jest opcjonalna — to dzięki niej akcje są w ogóle dostępne.

Dokumentacja API

ContextMenu

Komponent główny. Przyjmuje propsy ContextMenu.Root z Base UI.

PropTypDomyślnie
open

Kontrolowany stan otwarcia.

booleanBrak wartości domyślnej
defaultOpen

Początkowy stan otwarcia w trybie niekontrolowanym.

booleanfalse
onOpenChange

Wywoływana, gdy menu się otwiera lub zamyka.

(open: boolean, details) => voidBrak wartości domyślnej
loopFocus

Czy fokus przesuwany strzałkami zapętla się na końcach listy.

booleantrue
disabled

Wyłącza menu; zamiast niego pojawia się menu przeglądarki.

booleanfalse

ContextMenuTrigger

Obszar, który nasłuchuje kliknięcia prawym przyciskiem i długiego przytrzymania. Renderuje <div> z klasą select-none; stylizuj go jak każdy kontener. Gdy jego menu jest otwarte, dostaje atrybut data-popup-open.

ContextMenuContent

PropTypDomyślnie
side

Preferowana strona względem kursora.

"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""right"
align

Wyrównanie względem pozycji kursora.

"start" | "center" | "end""start"
sideOffset

Odstęp od kursora w px.

number0
alignOffset

Przesunięcie wzdłuż osi wyrównania w px.

number4

ContextMenuItem

PropTypDomyślnie
variant

Pozycje destrukcyjne mają kolor danger.

"default" | "destructive""default"
inset

Dodaje odstęp na początku, żeby wyrównać pozycję z pozycjami, które mają ikony.

booleanfalse
onClick

Wywoływana po aktywowaniu pozycji.

(event) => voidBrak wartości domyślnej
closeOnClick

Czy aktywowanie pozycji zamyka menu.

booleantrue
disabled

Przygasza pozycję i ignoruje kliknięcia oraz Enter. Strzałki nadal do niej docierają, więc czytnik ekranu może ogłosić ją jako niedostępną.

booleanfalse
label

Tekst do wyszukiwania przez wpisywanie, gdy treść pozycji nie jest zwykłym tekstem.

stringBrak wartości domyślnej

Pozostałe części

ContextMenuCheckboxItem, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut i ContextMenuGroup mają takie samo API jak odpowiadające im części Dropdown Menu. ContextMenuPortal jest eksportowany na potrzeby własnych kompozycji; ContextMenuContent renderuje go już sam.