Context menu
תפריט שנפתח בלחיצה ימנית.
- דפדפנים
- Chrome 151+Firefox 151+Safari 27+
- שכבה
- 2
- גודל חבילה דחוסה
- 4.98 kB
- מגבלת פריסה כפויה
- 0 ms
- מגבלת Axe
- 0
- תואם SSR
- כן
- חוזה מפורסם
- סביבת בדיקות
מגרש ניסויים
import { ContextMenu } from "@gunkin/uiify";
export default function Example() {
return (
<ContextMenu.Root>
<ContextMenu.Trigger className="">Right-click here</ContextMenu.Trigger>
<ContextMenu.Content className="">
<ContextMenu.Item className="">Copy</ContextMenu.Item>
<ContextMenu.Separator className="" />
<ContextMenu.Item className="">Paste</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
);
}/* Generic skin for custom core surfaces used by the demo and consumers. */
@layer components {
[data-uiify-surface] {
display: grid;
gap: var(--space-md);
inline-size: max-content;
max-inline-size: min(28rem, calc(100vw - 2 * var(--space-lg)));
padding: var(--space-lg);
border: var(--border-width) solid var(--border);
border-radius: var(--radius-xl);
background-color: var(--card);
color: var(--card-foreground);
box-shadow: var(--shadow-popover);
}
[data-uiify-surface][popover]:not(:popover-open) {
display: none;
}
@media (prefers-reduced-motion: no-preference) {
[data-uiify-surface][popover] {
transition: var(--transition-presence);
}
[data-uiify-surface][popover]:not(:popover-open) {
opacity: 0;
scale: var(--presence-scale);
}
@starting-style {
[data-uiify-surface][popover]:popover-open {
opacity: 0;
scale: var(--presence-scale);
}
}
}
}
/* Dropdown menu — a compact command surface with explicit anatomy hooks. */
@layer components {
[data-uiify-menu-bar] {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs);
border: var(--border-width) solid var(--border);
border-radius: var(--radius-lg);
background-color: var(--background);
}
:where(
[data-uiify-menu] :is([data-part="content"], [data-part="sub-content"]),
[data-uiify-menu]:is([data-part="content"], [data-part="sub-content"])
):where(:not([popover]), :popover-open) {
display: flex;
}
:where(
[data-uiify-menu] :is([data-part="content"], [data-part="sub-content"]),
[data-uiify-menu]:is([data-part="content"], [data-part="sub-content"])
) {
flex-direction: column;
min-inline-size: 12rem;
max-block-size: min(24rem, calc(100dvb - 2rem));
overflow-block: auto;
overscroll-behavior: contain;
padding: var(--space-xs);
border: var(--border-width) solid var(--border);
border-radius: var(--radius-lg);
background-color: var(--popover);
color: var(--popover-foreground);
box-shadow: var(--shadow-popover);
transform-origin: var(--transform-origin, top);
}
:where(
[data-uiify-menu] :is([data-part="content"], [data-part="sub-content"]),
[data-uiify-menu]:is([data-part="content"], [data-part="sub-content"])
)[data-state="closed"] {
display: none;
}
[data-uiify-menu]
:is(
[data-part="item"],
[data-part="checkbox-item"],
[data-part="radio-item"],
[data-part="sub-trigger"]
) {
display: flex;
align-items: center;
column-gap: var(--space-md);
padding-block: var(--space-sm);
padding-inline: var(--space-md);
border-radius: calc(var(--radius-lg) - var(--space-xs));
cursor: pointer;
user-select: none;
inline-size: 100%;
border: 0;
background: transparent;
color: inherit;
font: inherit;
font-size: var(--text-sm) !important;
line-height: var(--leading-normal);
text-align: start;
appearance: none;
}
[data-uiify-menu]
:is(
[data-part="item"],
[data-part="checkbox-item"],
[data-part="radio-item"],
[data-part="sub-trigger"]
):is(:hover, :focus-visible, [data-highlighted]) {
background-color: var(--accent);
color: var(--accent-foreground);
outline: none;
}
[data-uiify-menu]
[data-part="radio-item"]:hover[role="menuitemradio"][data-state="checked"]
[data-part="item-indicator"],
[data-uiify-menu]
[data-part="radio-item"]:focus-visible[role="menuitemradio"][data-state="checked"]
[data-part="item-indicator"],
[data-uiify-menu]
[data-part="radio-item"][data-highlighted][role="menuitemradio"][data-state="checked"]
[data-part="item-indicator"] {
border-color: var(--accent-foreground);
}
[data-uiify-menu]
:is(
[data-part="item"],
[data-part="checkbox-item"],
[data-part="radio-item"],
[data-part="sub-trigger"]
):is([aria-disabled="true"], [data-disabled]) {
opacity: 0.5;
pointer-events: none;
}
[data-uiify-menu]
:is(
[data-part="item"],
[data-part="checkbox-item"],
[data-part="radio-item"],
[data-part="sub-trigger"]
)[data-danger] {
color: var(--destructive, var(--foreground));
}
[data-uiify-menu]
:is(
[data-part="item"],
[data-part="checkbox-item"],
[data-part="radio-item"],
[data-part="sub-trigger"]
)[data-danger]:is(:hover, :focus-visible, [data-highlighted]) {
background-color: var(--destructive);
color: var(--destructive-foreground);
}
[data-uiify-menu] [data-part="item-icon"],
[data-uiify-menu] [data-part="item-indicator"] {
display: inline-grid;
place-items: center;
inline-size: 1.25rem;
block-size: 1.25rem;
flex: 0 0 1.25rem;
}
[data-uiify-menu] [data-part="item-indicator"] {
border: var(--border-width) solid var(--input);
border-radius: var(--radius-xs);
color: transparent;
}
[role="menuitemcheckbox"][data-state="checked"] [data-part="item-indicator"],
[role="menuitemcheckbox"][data-state="indeterminate"] [data-part="item-indicator"] {
border-color: var(--primary);
background: var(--primary);
color: var(--primary-foreground);
}
[role="menuitemcheckbox"][data-state="checked"] [data-part="item-indicator"]::before {
inline-size: 0.65rem;
block-size: 0.65rem;
background-color: currentcolor;
clip-path: polygon(14% 44%, 0 65%, 38% 100%, 100% 22%, 84% 6%, 38% 68%);
content: "";
}
[role="menuitemcheckbox"][data-state="indeterminate"] [data-part="item-indicator"]::before {
inline-size: 0.65rem;
block-size: 0.12rem;
background: currentcolor;
content: "";
}
[role="menuitemradio"] [data-part="item-indicator"] {
border-radius: 50%;
}
[role="menuitemradio"][data-state="checked"] [data-part="item-indicator"] {
border-width: 5px;
border-color: var(--primary);
background: var(--card);
}
[role="menuitemradio"][data-state="checked"] [data-part="item-indicator"]::before {
content: none;
}
[data-uiify-menu] [data-part="item-icon"] > svg,
[data-uiify-menu] [data-part="item-indicator"] > svg {
display: block;
inline-size: 100%;
block-size: 100%;
}
[data-uiify-menu] [data-part="item-text"] {
display: grid;
flex: 1 1 auto;
min-inline-size: 0;
}
[data-uiify-menu] [data-part="item-description"] {
color: var(--muted-foreground);
font-size: var(--text-xs);
line-height: var(--leading-normal);
}
[data-uiify-menu]
:is(
[data-part="item"],
[data-part="checkbox-item"],
[data-part="radio-item"],
[data-part="sub-trigger"]
):is(:hover, :focus-visible, [data-highlighted])
[data-part="item-description"] {
color: inherit;
opacity: 0.8;
}
[data-uiify-menu] [data-part="shortcut"] {
margin-inline-start: auto;
color: var(--muted-foreground);
font-size: var(--text-xs);
font-family: inherit;
font-weight: inherit;
line-height: 1;
white-space: nowrap;
}
[data-uiify-menu]
:is(
[data-part="item"],
[data-part="checkbox-item"],
[data-part="radio-item"],
[data-part="sub-trigger"]
):is(:hover, :focus-visible, [data-highlighted])
[data-part="shortcut"] {
color: inherit;
opacity: 0.85;
}
[data-uiify-menu] [data-part="label"] {
padding-block: var(--space-xs);
padding-inline: var(--space-md);
color: var(--muted-foreground);
font-size: var(--text-xs);
font-weight: var(--weight-semibold);
}
[data-uiify-menu] [data-part="separator"] {
block-size: var(--border-width);
margin-block: var(--space-xs);
margin-inline: calc(-1 * var(--space-xs));
background-color: var(--border);
}
[data-uiify-menu] [data-part="sub-content"] {
min-inline-size: 11rem;
}
[data-uiify-menu] [data-part="sub-content"][data-side="right"] {
--uiify-anchor-offset: 0;
inset-block-start: calc(anchor(start) - var(--space-xs));
}
@media (prefers-reduced-motion: no-preference) {
:where(
[data-uiify-menu] :is([data-part="content"], [data-part="sub-content"]),
[data-uiify-menu]:is([data-part="content"], [data-part="sub-content"])
) {
transition: var(--transition-presence);
}
:where(
[data-uiify-menu] :is([data-part="content"], [data-part="sub-content"]),
[data-uiify-menu]:is([data-part="content"], [data-part="sub-content"])
)[popover]:not(:popover-open) {
opacity: 0;
scale: var(--presence-scale);
}
@starting-style {
:where(
[data-uiify-menu] :is([data-part="content"], [data-part="sub-content"]),
[data-uiify-menu]:is([data-part="content"], [data-part="sub-content"])
):popover-open {
opacity: 0;
scale: var(--presence-scale);
}
}
}
}
@layer components {
:is([data-uiify-dropdown-menu], [data-uiify-context-menu])
[role="menuitemradio"]
[data-part="item-indicator"] {
border-radius: 50%;
}
}import { ContextMenu } from "@gunkin/uiify";
export default function Example() {
return (
<ContextMenu.Root>
<ContextMenu.Trigger
className="
grid gap-md w-max max-w-[min(28rem,calc(100vw_-_2_*_var(--space-lg)))] p-6 border border-border rounded-xl
bg-card text-card-foreground shadow-popover [&[popover]:not(:popover-open)]:hidden
motion-safe:[&[popover]]:[transition:var(--transition-presence)]
motion-safe:[&[popover]:not(:popover-open)]:opacity-0
motion-safe:[&[popover]:not(:popover-open)]:[scale:var(--presence-scale)]
motion-safe:starting:[&[popover]:popover-open]:opacity-0
motion-safe:starting:[&[popover]:popover-open]:[scale:var(--presence-scale)]
"
>
Right-click here
</ContextMenu.Trigger>
<ContextMenu.Content
className="
[&[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)]
[&_[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)]
[&[popover]:popover-open:not([data-positioning=native])]:[margin:revert]
[&_[popover]:popover-open:not([data-positioning=native])]:[margin:revert] flex-col [min-inline-size:12rem]
[max-block-size:min(24rem,calc(100dvb_-_2rem))] overflow-y-auto overscroll-contain p-xs
border-[length:var(--border-width)] border-solid border-border rounded-lg bg-popover text-popover-foreground
shadow-popover [transform-origin:var(--transform-origin,top)] data-[state=closed]:hidden
[&:where(:not([popover]),:popover-open)]:flex [&[data-part=sub-content]]:[min-inline-size:11rem]
[&[data-part=sub-content][data-side=right]]:[--uiify-anchor-offset:0]
[&[data-part=sub-content][data-side=right]]:[inset-block-start:calc(anchor(start)_-_var(--space-xs))]
motion-safe:[transition:var(--transition-presence)] motion-safe:[&:not(:popover-open)]:opacity-0
motion-safe:[&:not(:popover-open)]:[scale:var(--presence-scale)]
motion-safe:starting:[&:popover-open]:opacity-0
motion-safe:starting:[&:popover-open]:[scale:var(--presence-scale)]
"
>
<ContextMenu.Item
className="
flex items-center gap-md w-full py-sm px-md border-0 rounded-[calc(var(--radius-lg)_-_var(--space-xs))]
bg-transparent text-inherit [font-family:inherit] [font-weight:inherit] text-sm
[line-height:var(--leading-normal)] text-start cursor-pointer select-none appearance-none hover:bg-accent
hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground
focus-visible:outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground
aria-[disabled=true]:opacity-50 aria-[disabled=true]:pointer-events-none data-[disabled]:opacity-50
data-[disabled]:pointer-events-none data-[danger]:text-destructive data-[danger]:hover:bg-destructive
data-[danger]:hover:text-destructive-foreground data-[danger]:focus-visible:bg-destructive
data-[danger]:focus-visible:text-destructive-foreground data-[danger]:data-[highlighted]:bg-destructive
data-[danger]:data-[highlighted]:text-destructive-foreground
"
>
Copy
</ContextMenu.Item>
<ContextMenu.Separator
className="
[block-size:var(--border-width)] [margin-block:var(--space-xs)] [margin-inline:calc(-1_*var(--space-xs))]
bg-border
"
/>
<ContextMenu.Item
className="
flex items-center gap-md w-full py-sm px-md border-0 rounded-[calc(var(--radius-lg)_-_var(--space-xs))]
bg-transparent text-inherit [font-family:inherit] [font-weight:inherit] text-sm
[line-height:var(--leading-normal)] text-start cursor-pointer select-none appearance-none hover:bg-accent
hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground
focus-visible:outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground
aria-[disabled=true]:opacity-50 aria-[disabled=true]:pointer-events-none data-[disabled]:opacity-50
data-[disabled]:pointer-events-none data-[danger]:text-destructive data-[danger]:hover:bg-destructive
data-[danger]:hover:text-destructive-foreground data-[danger]:focus-visible:bg-destructive
data-[danger]:focus-visible:text-destructive-foreground data-[danger]:data-[highlighted]:bg-destructive
data-[danger]:data-[highlighted]:text-destructive-foreground
"
>
Paste
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
);
}API
ContextMenu.Root
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | אזור הפתיחה ותוכן התפריט. |
defaultOpen | boolean | — | מצב הפתיחה ההתחלתי של התפריט כשאינו מבוקר. |
id | string | — | המזהה המשמש לאלמנט החלונית הקופצת המכיל את התפריט. |
slotProps | ContextMenuRootSlotProps | — | מאפיינים מובנים לעוגן הווירטואלי שנוצר; סגנונות המיקום וסמני הנגישות נשארים בשליטת התפריט. |
onOpenChange | PopoverChangeHandler | — | נקראת כשהתפריט נפתח או נסגר, עם הסיבה והאירוע שגרם לכך. |
open | boolean | — | מצב הפתיחה של התפריט, ההופך אותו למבוקר. |
ContextMenu.Trigger
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | אזור שפותח את התפריט בלחיצה ימנית. |
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
render | ReactElement | ((props, state) => ReactElement) | — | החלפת האלמנט המוצג תוך שימור ההתנהגות וקישורי הנגישות. |
ContextMenu.Content
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | הפריטים, התוויות והמפרידים של התפריט. |
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
render | ReactElement | ((props, state) => ReactElement) | — | החלפת האלמנט המוצג תוך שימור ההתנהגות וקישורי הנגישות. |
ContextMenu.Item
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | תוכן הפריט. |
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
Native div props, styles, events, and ref | ComponentPropsWithRef<"div"> | — | המאפיינים, הסגנונות, האירועים וה-ref של האלמנט המובנה מועברים הלאה. |
disabled | boolean | false | משבית את הפריט ומדלג עליו בניווט במקלדת. |
onSelect | (event: MenuSelectEvent) => void | — | נקראת כשהפריט נבחר; קריאה ל-preventDefault על האירוע משאירה את התפריט פתוח. |
textValue | string | — | הטקסט המשמש להתאמה בעת חיפוש בהקלדה כשתוכן הפריט אינו טקסט פשוט. |
render | ReactElement | ((props, state) => ReactElement) | — | החלפת האלמנט המוצג תוך שימור ההתנהגות וקישורי הנגישות. |
ContextMenu.CheckboxItem
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
checked | boolean | "indeterminate" | false | מצב הסימון של הפריט. |
children | ReactNode | — | תוכן הפריט. |
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
Native div props, styles, events, and ref | ComponentPropsWithRef<"div"> | — | המאפיינים, הסגנונות, האירועים וה-ref של האלמנט המובנה מועברים הלאה. |
disabled | boolean | false | משבית את הפריט ומדלג עליו בניווט במקלדת. |
onCheckedChange | (checked: boolean) => void | — | נקראת עם מצב הסימון החדש כשהפריט נבחר. |
textValue | string | — | הטקסט המשמש להתאמה בעת חיפוש בהקלדה כשתוכן הפריט אינו טקסט פשוט. |
ContextMenu.RadioItem
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
value* | string | — | הערך שפריט זה בוחר בתפריט. |
children | ReactNode | — | תוכן הפריט. |
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
Native div props, styles, events, and ref | ComponentPropsWithRef<"div"> | — | המאפיינים, הסגנונות, האירועים וה-ref של האלמנט המובנה מועברים הלאה. |
disabled | boolean | false | משבית את הפריט ומדלג עליו בניווט במקלדת. |
textValue | string | — | הטקסט המשמש להתאמה בעת חיפוש בהקלדה כשתוכן הפריט אינו טקסט פשוט. |
ContextMenu.Label
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | טקסט כותרת הקבוצה. |
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
Native div props, styles, events, and ref | ComponentPropsWithRef<"div"> | — | המאפיינים, הסגנונות, האירועים וה-ref של האלמנט המובנה מועברים הלאה. |
ContextMenu.Separator
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
Native div props, styles, events, and ref | ComponentPropsWithRef<"div"> | — | המאפיינים, הסגנונות, האירועים וה-ref של האלמנט המובנה מועברים הלאה. |
ContextMenu.Sub
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | כפתור הפתיחה של תפריט המשנה ותוכנו. |
ContextMenu.Sub.Trigger
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | תווית כפתור הפתיחה של תפריט המשנה. |
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
Native div props, styles, events, and ref | ComponentPropsWithRef<"div"> | — | המאפיינים, הסגנונות, האירועים וה-ref של האלמנט המובנה מועברים הלאה. |
disabled | boolean | false | משבית את כפתור הפתיחה ומדלג עליו בניווט במקלדת. |
textValue | string | — | הטקסט המשמש להתאמה בעת חיפוש בהקלדה כשתוכן הפריט אינו טקסט פשוט. |
ContextMenu.Sub.Content
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | הפריטים של תפריט המשנה. |
className | string | — | המחלקה המוחלת על האלמנט המוצג. |
Native div props, styles, events, and ref | ComponentPropsWithRef<"div"> | — | המאפיינים, הסגנונות, האירועים וה-ref של האלמנט המובנה מועברים הלאה. |
מקלדת
| מקש | פעולה |
|---|---|
| Shift+F10 / Menu key | פתיחה ליד האלמנט הממוקד |
| ↑ / ↓ | מעבר בין פריטים |
| Escape | סגירה והחזרת המיקוד |
נגישות
דפוס תפריט שנפתח במיקום המצביע או במיקום שנקבע באמצעות המקלדת.
נקודות חיבור לעיצוב
data-statedata-highlighted