قائمة الخيارات
مجموعة قصيرة من الإجراءات أو الاختيارات على سطح مؤقت يُفتح من العنصر الذي طلبه.
- الفئة
- الاختيار
- الرموز
comp.menu- المستوى
- أساسي
- يُستخدم في
- Button with menu, Menubar
- Web
tanrei-menuschedule - SwiftUI
TanreiMenuschedule - Jetpack Compose
TanreiMenuschedule - Flutter
TanreiMenuAnchorschedule - React Native
Menuschedule
مولَّد من docs/status/status.json, specs/menu.spec.json
إجراءات، وعنصر خانة اختيار، ومجموعة أزرار اختيار، وإجراء لا رجعة فيه
قائمة فرعية: عنصر يفتح مستوى آخر بجانبه
قائمة خيارات من الروابط: كل صف يذهب إلى مكان، وواحد منها هو الصفحة التي أنت فيها، وواحد يفتح تبويبًا جديدًا
عنصر الارتكاز هو زرك أنت، أو زر أيقونة أو حقل من عندك. قائمة الخيارات لا ترسمه: تقرأ موضعه، وتضع سطحها بمحاذاته، وتعيد التركيز إليه. أنت تفتح قائمة الخيارات من عنصر الارتكاز وتغلقها حين تطلب هي ذلك.
لقائمة الخيارات خمسة أنواع من العناصر: item (إجراء)، و checkbox-item، و radio-item، و submenu (عنصر يفتح مستوى آخر من قائمة الخيارات بجانبه)، و group التي تحمل عناصر من الأنواع الأخرى. تأخذها كل منصة بالطريقة التي تأخذ بها أي عناصر فرعية متكررة: عناصر فرعية على الويب، وعروض (views) في view builder على SwiftUI، واستدعاءات في lambda المحتوى على Compose، وعناصر widget في menuChildren على Flutter، ومصفوفة items من كائنات بسيطة على React Native يُفرَّق بينها بالخاصية kind. قيمة checked لعنصر خانة الاختيار ملك له. أما أي عناصر أزرار الاختيار هو المحدَّد فملك لمجموعته، بالقيمة.
اختصار لوحة المفاتيح للعنصر هو خاصيته shortcut، ويُكتب بأسماء المفاتيح المحايدة التي يستخدمها اختصار لوحة المفاتيح (mod+c). ترسمه قائمة الخيارات في نهاية الصف بمكوّن اختصار لوحة المفاتيح، في الصورة ⌘C على لوحات مفاتيح Apple و Ctrl+C في غيرها، وتعرضه للتقنيات المساعدة على أنه اختصار لوحة المفاتيح للعنصر. وهي لا تجعل المفاتيح تعمل: منتجك هو الذي يربطها. أما trailingText فلكل ما ليس اختصارًا، كعدد أو «تجريبي». الويب يرسم هذا كله، ويرسم المظهر الحالي. أما SwiftUI و Compose و Flutter و React Native فمتأخرة: إلى أن تلحق بالبقية، ليست فيها خاصية shortcut وتكتب الاختصار في trailingText، كما تفعل عيّناتها أدناه. وليست فيها قوائم فرعية ولا separation ولا iconSpace ولا markIcon كذلك، وما زالت ترسم علامة الصح علامةً خاصة بها في مربع بمقاس 16، ولا تعبّئ أي صف محدَّد، وتبدأ عنوان المجموعة حيث تبدأ التسميات.
العنصر الذي له href عنصر رابط: هو رابط المنصة نفسه، يُرسم كما يُرسم الإجراء تمامًا ويُنتقل إليه بالمفاتيح نفسها. التفعيل العادي يُصدر الحدث click الخاص بالعنصر، ثم يغلق قائمة الخيارات، ثم يفتح الوجهة. والخاصيتان target و targetLabel هما خاصيتا الزر. والخاصية current تميّز عنصر الرابط الخاص بالصفحة التي فيها الشخص: يُرسم بتعبئة خاصة به، بلا علامة صح. الويب ينفّذ عناصر الروابط؛ أما SwiftUI و Compose و Flutter و React Native فليست فيها بعد.
يمكن أن تكون قائمة الخيارات أيضًا قائمة السياق لمنطقة ما: اضبط trigger على context، فيصبح عنصر الارتكاز هو تلك المنطقة، مثل لوحة رسم أو قائمة أو صف. يطلب الشخص القائمة عند نقطة فيها بالنقر بزر الفأرة الأيمن، أو بالضغط المطوَّل، أو بالمفتاح Shift+F10 أو مفتاح ContextMenu على ما يملك التركيز. فتُطلق القائمة الحدث contextRequest ومعه النقطة، وأنت تفتحها هناك. منصة الويب تبني مُشغِّل السياق؛ أما SwiftUI و Compose و Flutter و React Native فليس فيها بعد.
<tanrei-button variant="text" id="more" label="المزيد"><span slot="icon">more_vert</span></tanrei-button>
<!-- anchor هو id العنصر الذي تُفتح منه قائمة الخيارات. -->
<tanrei-menu anchor="more">
<!-- shortcut بأسماء المفاتيح المحايدة: يُرسم ⌘C أو Ctrl+C بحسب لوحة مفاتيح الشخص. -->
<tanrei-menu-item label="نسخ" leading-icon="content_copy" shortcut="mod+c"></tanrei-menu-item>
<!-- عنوان المجموعة هو خاصيتها label، كما في <optgroup label>. -->
<tanrei-menu-group label="العرض">
<tanrei-menu-checkbox-item label="إظهار الشبكة" checked></tanrei-menu-checkbox-item>
</tanrei-menu-group>
<!-- mark-icon="check" على مجموعة، أو على عنصر واحد، يميّز العناصر المحدَّدة بعلامة صح بسيطة. -->
<tanrei-menu-group label="ترتيب حسب" value="name" mark-icon="check">
<tanrei-menu-radio-item label="الاسم" value="name"></tanrei-menu-radio-item>
<tanrei-menu-radio-item label="تاريخ التعديل" value="date"></tanrei-menu-radio-item>
</tanrei-menu-group>
<!-- قائمة فرعية: عناصرها الفرعية هي المستوى التالي. تُصدر toggle، ولا تُصدر close أبدًا. -->
<tanrei-submenu label="فتح الأخيرة" leading-icon="history">
<tanrei-menu-item label="Report.pdf"></tanrei-menu-item>
<tanrei-menu-item label="Notes.txt"></tanrei-menu-item>
</tanrei-submenu>
<tanrei-menu-item label="حذف" leading-icon="delete" destructive></tanrei-menu-item>
<!-- مع href يصير العنصر رابطًا: حدث click الخاص به، ثم الإغلاق، ثم يتبعه المتصفح. -->
<tanrei-menu-item label="اختصارات لوحة المفاتيح" href="/help/shortcuts/"></tanrei-menu-item>
<tanrei-menu-item label="المصدر" href="https://example.com/tanrei" target="new" target-label="يُفتح في تبويب جديد"></tanrei-menu-item>
</tanrei-menu>
<!-- separation="gap" يفصل المجموعات بفراغ في هذه القائمة وقوائمها الفرعية، أيًّا كان اختيار نظام التصميم. -->
<tanrei-menu anchor="more" separation="gap">…</tanrei-menu>
<!-- trigger="context": قائمة السياق للمنطقة التي معرّفها anchor. سمِّها بـ aria-label. -->
<div id="canvas">…</div>
<tanrei-menu id="canvas-menu" anchor="canvas" trigger="context" aria-label="لوحة الرسم">…</tanrei-menu>
<script type="module">
import '@tanrei/web/components/menu';
const menu = document.querySelector('tanrei-menu');
// الضغط على عنصر الارتكاز شأن عنصر الارتكاز: لا يغلق قائمة الخيارات، فبدّلها هنا.
document.querySelector('#more').addEventListener('click', () => (menu.open = !menu.open));
// close يحمل سببه: escape أو outside أو tab أو focus-out أو item.
menu.addEventListener('close', () => (menu.open = false));
// click و change و toggle تصدر على العنصر أو المجموعة وتصعد، فيكفي مستمع واحد على القائمة لالتقاطها كلها.
menu.querySelector('[label="نسخ"]').addEventListener('click', copy);
menu.querySelector('[label="إظهار الشبكة"]').addEventListener('change', (e) => setGrid(e.detail.checked));
menu.querySelector('[label="ترتيب حسب"]').addEventListener('change', (e) => sortBy(e.detail.value));
// النقر بالزر الأيمن أو الضغط المطوَّل أو Shift+F10 أو مفتاح ContextMenu في المنطقة يطلب قائمة السياق.
// في detail النقطة، والعنصر الذي طُلبت من أجله، وكيف طُلبت. افتحها، أو ألغِ الحدث
// لتترك الطلب لقائمة المتصفح نفسه.
const canvasMenu = document.querySelector('#canvas-menu');
canvasMenu.addEventListener('contextrequest', () => (canvasMenu.open = true));
canvasMenu.addEventListener('close', () => (canvasMenu.open = false));
</script>import { Button, Menu, MenuCheckboxItem, MenuGroup, MenuItem, MenuRadioItem, Submenu } from '@tanrei/react';
<Button variant="text" id="more" label="المزيد" onClick={() => setOpen(!open)}><span slot="icon">more_vert</span></Button>
<Menu anchor="more" open={open} onClose={() => setOpen(false)}>
<MenuItem label="نسخ" leadingIcon="content_copy" shortcut={['mod+c']} onClick={copy} />
<MenuGroup label="العرض">
<MenuCheckboxItem label="إظهار الشبكة" checked={grid} onChange={(e) => setGrid(e.detail.checked)} />
</MenuGroup>
<MenuGroup label="ترتيب حسب" value={sort} onChange={(e) => setSort(e.detail.value)}>
<MenuRadioItem label="الاسم" value="name" />
<MenuRadioItem label="تاريخ التعديل" value="date" />
</MenuGroup>
<Submenu label="فتح الأخيرة" leadingIcon="history">
<MenuItem label="Report.pdf" onClick={() => openFile('Report.pdf')} />
<MenuItem label="Notes.txt" onClick={() => openFile('Notes.txt')} />
</Submenu>
<MenuItem label="حذف" leadingIcon="delete" destructive onClick={remove} />
{/* عنصر رابط. الموجِّه يلغي حدث click ويذهب إلى الوجهة بنفسه؛ وقائمة الخيارات تُغلق مع ذلك. */}
<MenuItem label="اختصارات لوحة المفاتيح" href="/help/shortcuts/" onClick={(e) => { e.preventDefault(); navigate('/help/shortcuts/'); }} />
</Menu>
{/* قائمة السياق للمنطقة التي معرّفها canvas: يفتحها الطلب. */}
<Menu anchor="canvas" trigger="context" aria-label="لوحة الرسم" open={contextOpen} onContextRequest={() => setContextOpen(true)} onClose={() => setContextOpen(false)}>
<MenuItem label="لصق" onClick={paste} />
</Menu>import Tanrei
// مُعدِّل على عنصر الارتكاز، مثل .popover. الإغلاق يكتب false في isPresented.
TanreiIconButton("المزيد", action: { showMenu = true }) { Image(systemName: "ellipsis") }
.tanreiMenu(isPresented: $showMenu, placement: .bottomStart) {
TanreiMenuItem("نسخ", action: copy)
TanreiMenuSection("العرض") {
TanreiMenuToggle("إظهار الشبكة", isOn: $showGrid)
}
TanreiMenuSection("ترتيب حسب", selection: $sort) {
// طبّق .tag(_:) على TanreiMenuOption نفسه.
TanreiMenuOption("الاسم").tag("name")
TanreiMenuOption("تاريخ التعديل").tag("date")
}
TanreiMenuItem("حذف", role: .destructive, action: delete)
}import io.github.chnotchy.tanrei.components.TanreiCheckboxMenuItem
import io.github.chnotchy.tanrei.components.TanreiIconButton
import io.github.chnotchy.tanrei.components.TanreiMenu
import io.github.chnotchy.tanrei.components.TanreiMenuGroup
import io.github.chnotchy.tanrei.components.TanreiMenuItem
import io.github.chnotchy.tanrei.components.TanreiRadioMenuItem
import io.github.chnotchy.tanrei.primitives.TanreiText
// مثل DropdownMenu: استدعِ القائمة داخل Box الخاص بعنصر الارتكاز. أنت من يغلقها: onDismissRequest يحمل السبب.
Box {
TanreiIconButton(onClick = { expanded = true }, contentDescription = "المزيد") { TanreiText("more_vert") }
TanreiMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
TanreiMenuItem(text = "نسخ", onClick = ::copy, trailingText = "⌘C", leadingIcon = { TanreiText("content_copy") })
TanreiMenuGroup(title = "العرض") {
TanreiCheckboxMenuItem(text = "إظهار الشبكة", checked = showGrid, onCheckedChange = { showGrid = it })
}
TanreiMenuGroup(title = "ترتيب حسب", selectedValue = sort, onSelectedValueChange = { sort = it }) {
TanreiRadioMenuItem(text = "الاسم", value = "name")
TanreiRadioMenuItem(text = "تاريخ التعديل", value = "date")
}
TanreiMenuItem(text = "حذف", onClick = ::delete, destructive = true, leadingIcon = { TanreiText("delete") })
}
}import 'package:tanrei/tanrei.dart';
// مثل MenuAnchor: يفتحها متحكِّم (controller). حين تطلب القائمة الإغلاق تستدعي onClose مع السبب وتغلق نفسها.
final TanreiMenuController menu = TanreiMenuController();
TanreiMenuAnchor(
controller: menu,
onClose: (TanreiMenuCloseReason reason) {},
menuChildren: <Widget>[
TanreiMenuItem(
onPressed: copy,
leadingIcon: const Icon(AppSymbols.contentCopy),
trailingText: '⌘C',
child: const Text('نسخ'),
),
TanreiMenuGroup<String>(
title: 'العرض',
children: <Widget>[
TanreiCheckboxMenuItem(
value: showGrid,
onChanged: (bool value) => setState(() => showGrid = value),
child: const Text('إظهار الشبكة'),
),
],
),
TanreiMenuGroup<String>(
title: 'ترتيب حسب',
groupValue: sort,
onChanged: (String value) => setState(() => sort = value),
children: const <Widget>[
TanreiRadioMenuItem<String>(value: 'name', child: Text('الاسم')),
TanreiRadioMenuItem<String>(value: 'date', child: Text('تاريخ التعديل')),
],
),
// قيمة onPressed الفارغة (null) تعطّل العنصر.
TanreiMenuItem(onPressed: delete, destructive: true, child: const Text('حذف')),
],
builder: (BuildContext context, TanreiMenuController controller, Widget? child) =>
TanreiIconButton(onPressed: controller.open, icon: const Icon(AppSymbols.moreVert), semanticLabel: 'المزيد'),
);import { useRef, useState } from 'react';
import type { View } from 'react-native';
import { IconButton, Menu } from '@tanrei/react-native';
const more = useRef<View>(null);
const [open, setOpen] = useState(false);
// أخبر التقنيات المساعدة هل قائمة عنصر الارتكاز مفتوحة: القائمة لا تستطيع ضبط ذلك هنا.
<IconButton ref={more} accessibilityLabel="المزيد" accessibilityState={{ expanded: open }} onPress={() => setOpen(true)}>
more_vert
</IconButton>
// مُتحكَّم بها: اضبط visible على false من onDismiss الذي يستقبل السبب.
<Menu
visible={open}
anchorRef={more}
accessibilityLabel="المزيد"
onDismiss={() => setOpen(false)}
items={[
{ label: 'نسخ', leadingIcon: 'content_copy', trailingText: '⌘C', onPress: copy },
{ kind: 'group', title: 'العرض', items: [{ kind: 'checkbox-item', label: 'إظهار الشبكة', checked: grid, onCheckedChange: setGrid }] },
{ kind: 'group', title: 'ترتيب حسب', value: sort, onValueChange: setSort, items: [
{ kind: 'radio-item', label: 'الاسم', value: 'name' },
{ kind: 'radio-item', label: 'تاريخ التعديل', value: 'date' },
] },
{ label: 'حذف', leadingIcon: 'delete', destructive: true, onPress: remove },
]}
/>النص المساعد، وأيقونة النهاية، والعناصر المعطَّلة
عناوين المجموعات فوق عمودي علامة الصح والأيقونة
عمودا العلامة والأيقونة: القيمة auto للخاصية iconSpace، وهي الافتراضية، والقيمة never
العلامات: مربع النظام الافتراضي وحلقته، وعلامة صح بسيطة تسمّيها mark-icon
الفصل: فاصل بين المجموعات، وفجوة
في النظام الافتراضي يُميَّز عنصر خانة الاختيار بخانة اختيار وعنصر زر الاختيار بزر اختيار، ويُرسمان سواء أكان العنصر محدَّدًا أم لا: مربع فارغ ومربع معبّأ فيه علامة صح، وحلقة رفيعة وحلقة ثخينة مع تعبئتها. فيبقى الخيار غير المفعَّل ظاهرًا على أنه خيار. وهذه رسوم في عمود العلامات، لا عناصر تحكم: الصف هو الشيء الوحيد الذي يُضغط ويأخذ التركيز. وعلامة الصح في المربع تُرسم بالوزن الأثقل الذي لعلامة عنصر التحكم نفسه، كما في خانة الاختيار.
الخاصية markIcon، على عنصر واحد أو على مجموعة لكل عناصرها، تسمّي رسمًا (glyph) من مجموعة الأيقونات يُميَّز به العنصر المحدَّد بدلًا من ذلك. القيمة check ترسم علامة صح بسيطة بحجم أيقونة البداية المجاورة لها (24 في النظام الافتراضي)، ولا ترسم شيئًا ما دام العنصر غير محدَّد. يختار نظام التصميم علاماته: النظامان m3-like و cupertino يستخدمان علامة الصح البسيطة. وعلامة الصح لا تُعكس في التخطيط من اليمين إلى اليسار (RTL). حين تتغيّر حالة تحديد عنصر وقائمة الخيارات مفتوحة تتغيّر العلامة من دون أن تتحرك، وفورًا حيث الحركة مقلَّلة.
الصف المحدَّد يُعرَف بعلامته. في النظام الافتراضي لا يُعبَّأ عند التباين القياسي، فيكون الصف الوحيد المعبَّأ في قائمة خيارات ساكنة هو صف الصفحة التي فيها الشخص. وعند التباين المتوسط والعالي، حيث طلب الشخص إشارات أقوى، يُعبَّأ الصف المحدَّد أيضًا: طبقة من لون محتوى الصف نفسه فوق سطح قائمة الخيارات، أقوى بدرجة واحدة من الصف الذي عليه التحويم في السمة الفاتحة وفي الداكنة، فيُفرَّق على السطح المرتفع بين الصف المحدَّد والصف الذي عليه التحويم والصف الساكن. وقائمة الخيارات تقرأ التعبئة من رموزها في المخطط الذي تُرسم فيه؛ ونظام التصميم هو الذي يقررها لكل مخطط تباين. والعنصر الذي قائمته الفرعية مفتوحة يحتفظ بتعبئة التحويم ما دامت مفتوحة، وهذا يبيّن المسار إلى المستوى المستخدَم، ولا يبدو أبدًا صفًّا معبَّأً.
يبدأ عنوان المجموعة حيث يبدأ محتوى الصفوف: فوق علامة الصح أو أيقونة البداية حين ترسم قائمة الخيارات هذين العمودين، وفوق التسميات حين لا ترسم أيًّا منهما. وينتهي على المسافة نفسها من الحافة الأخرى، ويلتف، ويكبر مع النص الأكبر، وينعكس في التخطيط من اليمين إلى اليسار.
البنية
البنية
actions كما ترسمه حزمة الويب.القياسات
القياسات
| الطول | الرمز | القيمة (px) |
|---|---|---|
surface padding-inline | tanrei.comp.menu.surface.padding-inline | 4 |
actions كما ترسمه حزمة الويب.الخصائص
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
open | boolean | false | Whether the menu is shown. The product opens it from its anchor (a button, an icon-only Button, a field), or on contextRequest while trigger is context. Where open is a plain value (web, Compose, React Native) the product closes the menu by setting it to false when the menu emits close; where it is a binding or a controller (SwiftUI, Flutter) the menu closes itself through it (MNU-5). |
placement | bottom-start | bottom-end | top-start | top-end | "bottom-start" | Where the surface goes against the anchor: below or above it, with its start or end edge aligned to the anchor's. The menu flips to the other side when this side has no room. For a context menu opened at a point it is where the surface goes from the point: bottom-start puts its block-start, inline-start corner there (MNU-61). |
trigger | anchor | context | "anchor" | What the menu opens from. anchor: the product opens it from its anchor — a button, an icon-only Button, a field — and the surface is placed against the anchor's edges. context: the menu is the context menu of an area, and the anchor is that area (a canvas, a list, a row, a document's body of text); a person asks for the menu at a point in it — a secondary click, a long press of a touch or a pen, Shift+F10 or the ContextMenu key on the element of the area that has focus — and the menu emits contextRequest, the product sets open, and the surface opens at that point, or against that element for the keyboard. The items, the keys, the submenus and the close reasons are the same in both (MNU-60 to MNU-63). |
separation | auto | divider | gap | "auto" | How a group is told from what is next to it. divider: a line between them. gap: empty space and no line. Auto takes comp.menu.separation, the design system's answer (divider in the default system). It applies to the menu and to every submenu of it. |
iconSpace | auto | always | never | "auto" | Whether a row keeps the room of the mark column and of the leading icon column where it has nothing to draw there, so that the labels of a section start on one line — a section being a group, or a run of adjacent items outside any group. auto: a row keeps a column's room while another item of its section draws in that column (a checkbox or radio item its mark, an item its leading icon), so that a section without marks or icons draws its labels where the row's content starts. always: every row keeps both. never: only a row that draws in a column has it. The tree view's and the navigation drawer's iconSpace, with the same values and default. It applies to the menu and to every submenu of it (MNU-59). |
density | regular | compact | "regular" | How much room a row takes: its least height, its paddings, the gap between its columns and the size of its glyphs (the mark, the leading and the trailing icon), and never its text style. regular (a 48 row with 24 glyphs in the default system) is the menu of a product used by touch as well as by pointer. compact (a 32 row with 20 glyphs, the tree view's compact row) is for a tool used with a pointer, whose menus are long and opened often. For touch a row is never lower than the touch target in either (MNU-12). It applies to the menu and to every submenu of it (MNU-64). A design system may add values. |
الأحداث
| الحدث | الوصف |
|---|---|
close | The menu asks to be closed, every open submenu with it: Escape or the platform's back while no submenu is open, a press outside, Tab, focus leaving it, an item's activation at any level, or its anchor no longer drawn (MNU-65). Carries which of these it was, named escape, back, outside, tab, focus-out, item or anchor-hidden (in the platform's casing). Closing a submenu alone is the submenu item's toggle, not this event. |
contextRequest | While trigger is context: the person asked for the context menu in the anchor's area. Carries the point in the viewport's coordinates, the element it was asked about (the one under the pointer, or the one with focus) and how it was asked: pointer (a secondary click, or the context-menu command of assistive technology), long-press (a touch or a pen held still) or keyboard (Shift+F10, the ContextMenu key). Set open to true to show the menu there. Cancelling it leaves the request to the platform: its own context menu answers it (MNU-60). |
العناصر
أنواع العناصر التي يكررها المكوّن. يسرد كل نموذج اختبار عناصره بالترتيب، وتسمّي كل منصة كل نوع بأسلوبها.
item
An action: it does something and closes the menu. With an href it is a link: it goes somewhere, and the menu closes.
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
label | string | "" | The item's text and its accessible name. Required. |
supportingText | string | "" | A line below the label that says more about the item. |
leadingIcon | string | "" | The name of an icon (a glyph of the system's icon set) before the label. |
trailingIcon | string | "" | The name of an icon at the end of the row. |
trailingText | string | "" | Text at the end of the row that is not a keyboard shortcut, such as a count or "Beta". An item's shortcut goes in shortcut; an item that has both draws the shortcut. |
shortcut | strings | | The item's keyboard shortcut, in the keyboard shortcut component's key names: one chord per string, pressed in order (["mod+c"]). The menu draws it at the end of the row for the keyboard the person uses and exposes it as the item's keyboard shortcut (MNU-24, MNU-31). It does not make the keys work; the product binds them. |
destructive | boolean | false | Marks an action that deletes or discards something; it is drawn in the error color. |
disabled | boolean | false | Prevents activation. The item stays reachable with the arrow keys. |
href | string | "" | The destination: an address as the platform's links take one (a URL; on the web a path or a fragment as well). Not empty, it makes the item a link item (a control with a destination is a link): the platform's own link, exposed as a menu item, drawn exactly as an action is and moved to with the same keys; a plain activation emits click, closes the menu and opens the destination (MNU-53, MNU-54). Empty, the item is an action and the product acts on click. Only the item kind has one: a checkbox item, a radio item and a submenu item take no destination (MNU-57). |
target | same | new | "same" | Where the destination opens, as the Button's target. same: where the person is. new: a new context — a new tab or window on the web, outside the current screen elsewhere — which the item marks with the glyph open_in_new in its trailing icon column, unless trailingIcon names one, and says through targetLabel (MNU-55). Ignored without href. |
targetLabel | string | "" | What assistive technology reads after the link item's name while target is new ("opens in a new tab"), as the Button's targetLabel: Tanrei ships no words of its own. Required while target is new; ignored otherwise. |
current | boolean | false | Marks a link item as the destination the person is at now (the current page): exposed as the current item and drawn with the fill of the row the person is on, comp.menu.current.container.color, in every contrast scheme, with no mark and no change of weight; it stays a link (MNU-58). The product sets it; the menu never does. Ignored without href. |
| الحدث | الوصف |
|---|---|
click | The item was activated by pointer, touch, keyboard or assistive technology. A link item emits it before the menu closes and before its destination opens; a product that takes the navigation itself (a client-side router) cancels it, and the menu still closes (MNU-54). |
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| العنصر | tanrei-menu-item | TanreiMenuItem | TanreiMenuItem | TanreiMenuItem | MenuItem |
label · الخاصية | label | title | text | child | label |
supportingText · الخاصية | supportingText | supportingText | supportingText | supportingText | supportingText |
leadingIcon · الخاصية | leadingIcon | leadingIcon | leadingIcon | leadingIcon | leadingIcon |
trailingIcon · الخاصية | trailingIcon | trailingIcon | trailingIcon | trailingIcon | trailingIcon |
trailingText · الخاصية | trailingText | trailingText | trailingText | trailingText | trailingText |
shortcut · الخاصية | shortcut | shortcut | shortcut | shortcut | shortcut |
destructive · الخاصية | destructive | role | destructive | destructive | destructive |
disabled · الخاصية | disabled | disabled | enabled | onPressed | disabled |
href · الخاصية | href | destination | href | href | href |
target · الخاصية | target | target | target | target | target |
targetLabel · الخاصية | targetLabel | targetLabel | targetLabel | targetSemanticLabel | targetLabel |
current · الخاصية | current | isCurrent | current | isCurrent | current |
click · الحدث | click | action | onClick | onPressed | onPress |
| ملاحظات | With href the item's shadow root holds an <a role="menuitem" tabindex="-1"> in place of its row element, drawn through the shared link primitive (packages/web/src/primitives/link.ts) as <tanrei-list-item>'s is, with Space left among the keys that press it (MNU-54). target is the target attribute with the values same and new (the HTML spellings _self and _blank are read as those two), targetLabel the target-label attribute, and rel (to which new adds noopener) and download are web-only and forwarded to the anchor. click is the anchor's own click: cancelling it (preventDefault()) cancels the navigation and not the menu's close. The menu fires close from that click only when event.button is 0 and no modifier key is down; Enter with a modifier key is passed to the anchor, and Space calls the anchor's click(). A disabled link item is the anchor without href, with aria-disabled="true". An href attribute on tanrei-menu-checkbox-item, tanrei-menu-radio-item or tanrei-submenu is ignored and reported (MNU-57). | TanreiMenuItem(_ title:, role:, action:), as Button(role:); role: .destructive is destructive. destination: URL? = nil makes it a SwiftUI Link (the link trait in place of the button's), opened by the environment's openURL after action and after isPresented is written, which an app that routes addresses itself answers with its own OpenURLAction; target: TanreiLinkTarget = .same and targetLabel (the accessibility hint), as the Button's. | href: String? = null makes it a link: opened through LocalUriHandler.current after onClick and onDismissRequest (an app that routes addresses itself provides its own UriHandler); Compose has no link role for a clickable element, so the node drops Role.Button and carries no role; target: TanreiLinkTarget = TanreiLinkTarget.Same, and targetLabel is appended to the state description, as the Button's. | As MenuItemButton: a null onPressed disables it. href: Uri? = null makes it a link (Semantics(link: true) with the address as its linkUrl), opened by the nearest TanreiLinkOpener after onPressed and after the menu has closed; target is a TanreiLinkTarget and targetSemanticLabel the semantics hint, as the Button's. | href?: string on an entry whose kind is 'item' makes it a link (accessibilityRole="link"): onPress receives an event whose preventDefault() keeps the menu from calling Linking.openURL(href); target is "same" | "new" and targetLabel the accessibilityHint, as the Button's. An href on an entry of another kind is ignored and reported (MNU-57). |
checkbox-item
An option that is switched on or off on its own, such as "Show grid". It is marked as a checkbox unless the design system, its group or the item says otherwise.
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
label | string | "" | The item's text and its accessible name. Required. |
supportingText | string | "" | A line below the label that says more about the item. |
leadingIcon | string | "" | The name of an icon (a glyph of the system's icon set) before the label. |
trailingIcon | string | "" | The name of an icon at the end of the row. |
trailingText | string | "" | Text at the end of the row that is not a keyboard shortcut, such as a count or "Beta". An item's shortcut goes in shortcut; an item that has both draws the shortcut. |
shortcut | strings | | The item's keyboard shortcut, in the keyboard shortcut component's key names: one chord per string, pressed in order (["mod+c"]). The menu draws it at the end of the row for the keyboard the person uses and exposes it as the item's keyboard shortcut (MNU-24, MNU-31). It does not make the keys work; the product binds them. |
checked | boolean | false | Whether the option is on. It is the item's own: the menu switches it when the item is activated and emits the item's change. |
markIcon | string | "" | The name of a glyph of the system's icon set to mark this item with while it is checked, instead of the mark the design system chooses ("check" draws a plain check). Empty takes the group's markIcon, and then the design system's choice (MNU-38). |
disabled | boolean | false | Prevents activation. The item stays reachable with the arrow keys. |
| الحدث | الوصف |
|---|---|
change | The item was activated; carries its new checked. |
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| العنصر | tanrei-menu-checkbox-item | TanreiMenuToggle | TanreiCheckboxMenuItem | TanreiCheckboxMenuItem | MenuCheckboxItem |
label · الخاصية | label | title | text | child | label |
supportingText · الخاصية | supportingText | supportingText | supportingText | supportingText | supportingText |
leadingIcon · الخاصية | leadingIcon | leadingIcon | leadingIcon | leadingIcon | leadingIcon |
trailingIcon · الخاصية | trailingIcon | trailingIcon | trailingIcon | trailingIcon | trailingIcon |
trailingText · الخاصية | trailingText | trailingText | trailingText | trailingText | trailingText |
shortcut · الخاصية | shortcut | shortcut | shortcut | shortcut | shortcut |
checked · الخاصية | checked | isOn | checked | value | checked |
markIcon · الخاصية | markIcon | markIcon | markIcon | markIcon | markIcon |
disabled · الخاصية | disabled | disabled | enabled | onChanged | disabled |
change · الحدث | change | isOn | onCheckedChange | onChanged | onCheckedChange |
| ملاحظات | TanreiMenuToggle(_ title:, isOn:), as Toggle in a menu; writing the binding is change. | As CheckboxMenuButton: value is checked, a null onChanged disables it. |
radio-item
One choice of several in its group, such as "Sort by name"; its group holds which one is checked. It is marked as a radio button unless the design system, its group or the item says otherwise.
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
label | string | "" | The item's text and its accessible name. Required. |
supportingText | string | "" | A line below the label that says more about the item. |
leadingIcon | string | "" | The name of an icon (a glyph of the system's icon set) before the label. |
trailingIcon | string | "" | The name of an icon at the end of the row. |
trailingText | string | "" | Text at the end of the row that is not a keyboard shortcut, such as a count or "Beta". An item's shortcut goes in shortcut; an item that has both draws the shortcut. |
shortcut | strings | | The item's keyboard shortcut, in the keyboard shortcut component's key names: one chord per string, pressed in order (["mod+c"]). The menu draws it at the end of the row for the keyboard the person uses and exposes it as the item's keyboard shortcut (MNU-24, MNU-31). It does not make the keys work; the product binds them. |
value | string | "" | Identifies the choice: the item is checked while its group's value equals it. Unique within its group; required. |
markIcon | string | "" | The name of a glyph of the system's icon set to mark this item with while it is checked, instead of the mark the design system chooses ("check" draws a plain check). Empty takes the group's markIcon, and then the design system's choice (MNU-38). |
disabled | boolean | false | Prevents activation. The item stays reachable with the arrow keys. |
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| العنصر | tanrei-menu-radio-item | TanreiMenuOption | TanreiRadioMenuItem | TanreiRadioMenuItem | MenuRadioItem |
label · الخاصية | label | title | text | child | label |
supportingText · الخاصية | supportingText | supportingText | supportingText | supportingText | supportingText |
leadingIcon · الخاصية | leadingIcon | leadingIcon | leadingIcon | leadingIcon | leadingIcon |
trailingIcon · الخاصية | trailingIcon | trailingIcon | trailingIcon | trailingIcon | trailingIcon |
trailingText · الخاصية | trailingText | trailingText | trailingText | trailingText | trailingText |
shortcut · الخاصية | shortcut | shortcut | shortcut | shortcut | shortcut |
value · الخاصية | value | tag | value | value | value |
markIcon · الخاصية | markIcon | markIcon | markIcon | markIcon | markIcon |
disabled · الخاصية | disabled | disabled | enabled | enabled | disabled |
| ملاحظات | TanreiMenuOption(_ title:).tag(value) inside a section with a selection, as a Picker's tagged options. | As RadioMenuButton under a RadioGroup: the item takes its value and enabled, the group the selection. |
submenu · مجموعة — تحتوي عناصر
An item that opens another level of the menu beside it: it holds that level's items, of any kind, further submenus included. It is not an action and has no shortcut.
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
label | string | "" | The item's text and its accessible name. Required. |
supportingText | string | "" | A line below the label that says more about the item. |
leadingIcon | string | "" | The name of an icon (a glyph of the system's icon set) before the label. |
trailingText | string | "" | Text at the end of the row, before the submenu indicator: the current value of what the submenu sets ("English"), or a count. |
open | boolean | false | Whether the item's submenu is shown. It is the item's own: the menu sets it when the submenu is opened or closed and emits the item's toggle; the product may also set it. It shows nothing while the surface that holds the item is not shown. |
disabled | boolean | false | Prevents the submenu from opening. The item stays reachable with the arrow keys. |
| الحدث | الوصف |
|---|---|
toggle | The item's submenu was opened or closed by the person; carries its new open. |
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| العنصر | tanrei-submenu | TanreiSubmenu | TanreiSubmenu | TanreiSubmenu | MenuSubmenu |
label · الخاصية | label | title | text | child | label |
supportingText · الخاصية | supportingText | supportingText | supportingText | supportingText | supportingText |
leadingIcon · الخاصية | leadingIcon | leadingIcon | leadingIcon | leadingIcon | leadingIcon |
trailingText · الخاصية | trailingText | trailingText | trailingText | trailingText | trailingText |
open · الخاصية | open | isExpanded | expanded | controller | open |
disabled · الخاصية | disabled | disabled | enabled | enabled | disabled |
toggle · الحدث | toggle | isExpanded | onExpandedChange | onOpenChanged | onOpenChange |
| ملاحظات | <tanrei-submenu label="Open recent"> holds the submenu's items as its children, as tanrei-menu holds its own; it draws the row in its parent's surface and its own surface as a manual popover in the top layer. open reflects, and toggle fires on it and bubbles. | TanreiSubmenu(_ title:, isExpanded:) { … }, as a Menu nested in a Menu; the binding is optional, and writing it is toggle. disabled follows .disabled(_:). | TanreiSubmenu(text, expanded, onExpandedChange) { … }: the caller keeps expanded (state hoisting), and an overload without the pair keeps it itself. enabled is the inverse flag. | As SubmenuButton: TanreiSubmenu(child:, menuChildren: […], controller:, onOpenChanged:); the optional MenuController opens and closes it, as the anchor's does the menu. | { kind: 'submenu', label, items: […], open?, onOpenChange? }: uncontrolled unless open is given. |
group · مجموعة — تحتوي عناصر
A run of related items, optionally titled. The menu separates a group from what is next to it, by a divider or a gap, and a group of radio items is one set of choices.
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
title | string | "" | A short heading drawn above the group's items, which also names the group; empty draws none and leaves the group unnamed. |
value | string | null | The value of the group's checked radio item; null checks none. The group sets it when a radio item is chosen and emits its change; the product may also set it. Unused by a group without radio items. |
markIcon | string | "" | The name of a glyph of the system's icon set that marks the group's checked checkbox and radio items, for those that name none of their own ("check" draws a plain check). Empty takes the design system's choice (MNU-38). |
| الحدث | الوصف |
|---|---|
change | A radio item of the group was chosen; carries the group's new value. |
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| العنصر | tanrei-menu-group | TanreiMenuSection | TanreiMenuGroup | TanreiMenuGroup | MenuGroup |
title · الخاصية | label | title | title | title | title |
value · الخاصية | value | selection | selectedValue | groupValue | value |
markIcon · الخاصية | markIcon | markIcon | markIcon | markIcon | markIcon |
change · الحدث | change | selection | onSelectedValueChange | onChanged | onValueChange |
| ملاحظات | The title is the label attribute, as <optgroup label>: a title attribute would also draw the browser's tooltip. The group sets its own value before firing change. | TanreiMenuSection(_ title:) { … } as Section; TanreiMenuSection(_ title:, selection:) { TanreiMenuOption … } for radio items, as an inline Picker. Writing the binding is change. | TanreiMenuGroup(title, selectedValue, onSelectedValueChange) { TanreiRadioMenuItem(text, value) }; the caller keeps selectedValue. | TanreiMenuGroup<T>(title:, groupValue:, onChanged:, children:), which is also the RadioGroup of its radio items. | { kind: 'group', title?, value?, onValueChange?, items: (MenuItem | MenuCheckboxItem | MenuRadioItem)[] }. |
الحالات
السلوك
متطلبات بصيغة EARS. يُختبر تنفيذ كل منصة مقابل هذه المعرّفات. يُعرض نص المتطلبات كما ورد في ملف المواصفة، بالإنجليزية.
| المعرّف | المتطلب |
|---|---|
MNU-1 | WHEN open becomes true THE menu SHALL present its surface in the platform's overlay layer (the web's top layer through the popover API, SwiftUI's popover, Compose's Popup, Flutter's Overlay, React Native's overlay layer that TanreiProvider renders), above other content at comp.menu.surface.elevation, and SHALL move focus to its first enabled item, wherever that item stands in the surface: inside a group as well, a menu whose every item is in a group included, and a menu the product mounts with open true included; WHEN open becomes false THE menu SHALL remove the surface and, if focus was inside it, return focus to the anchor (a context menu: to the element MNU-62 names). A menu is an operated overlay, so it takes focus and returns it although it is not modal. |
MNU-2 | WHILE trigger is anchor THE menu SHALL place its surface comp.menu.surface.offset from the anchor on the side and edge that placement names (bottom-start: below the anchor with the start edges aligned; bottom-end: below with the end edges aligned; top-start and top-end: above); WHERE that side has less room than the surface needs within comp.menu.surface.viewport-margin of the edge of the visible part of the window, above the on-screen keyboard while one is shown, and the other side has more THE menu SHALL flip to the other side; it SHALL shift along the anchor to stay within that margin, SHALL limit its height to the least of the height its items need, the most height the product's own style gives the surface where it gives one (on the web, max-block-size on the surface part), and the room left, place the surface by that height and scroll its items beyond it, and SHALL keep following the anchor while it moves. WHEN the on-screen keyboard shows or hides, or the visible part of the window otherwise moves or changes size, THE menu SHALL place its surface again inside what is visible. WHEN the layout direction is right-to-left start and end SHALL mirror. |
MNU-3 | THE menu SHALL make its surface at least comp.menu.surface.min-width and at most comp.menu.surface.max-width wide — as wide as its widest item within those bounds — inset its items by comp.menu.surface.padding-block and comp.menu.surface.padding-inline, fill it with comp.menu.surface.color, round it by comp.menu.surface.shape eased by sys.shape.corner-smoothing, and clip its items to that shape. |
MNU-4 | WHEN the menu opens THE menu SHALL fade its surface in and grow it from the edge that faces the anchor over comp.menu.motion.open-duration with comp.menu.motion.open-easing, and WHEN it closes fade it out over comp.menu.motion.close-duration with comp.menu.motion.close-easing; WHERE motion is reduced it SHALL appear and disappear at once. WHEN it is mounted with open true THE menu SHALL draw its surface, and the surface of every submenu open in it (MNU-50), without that motion while the document has not yet been first painted, and with it when it is mounted after that. |
MNU-5 | WHEN the user presses Escape or the platform's back while no submenu is open, presses outside every surface of the menu and the anchor (the area of a context menu is outside, MNU-62), presses Tab or Shift+Tab at any level, or moves focus out of the menu and all of its submenus THE menu SHALL emit close once with that reason and SHALL be closed, every open submenu with it (MNU-49): through its binding or controller where open is one (SwiftUI, Flutter), and by the product setting open to false where open is a plain value (web, Compose, React Native); Tab and Shift+Tab SHALL then move focus on from the anchor as they would have from it (a context menu: from the element MNU-62 names). Whether a press outside also reaches the element under it SHALL follow the platform and be stated in its README. |
MNU-6 | WHEN the user activates an enabled item (an action) at any level by pointer, touch, Enter or Space THE menu SHALL emit the item's click once, then emit close and be closed as MNU-5 says, every open submenu with it, an item that is a link (MNU-53) opening its destination after that, and only for an activation that MNU-54 counts; WHEN the user activates an enabled checkbox item THE menu SHALL switch its checked and emit the item's change once with the new checked; WHEN the user activates an enabled radio item whose value is not its group's value THE menu SHALL set the group's value to the radio item's value, so that the group's other radio items become unchecked, and emit the group's change once; activating a radio item that is already checked SHALL emit no change. After a checkbox or radio item, a checked radio item included, the menu SHALL close as for an action when the activation came from pointer, touch or Enter, and SHALL stay open, at every level, when it came from Space. Activating a submenu item opens its submenu and closes nothing (MNU-43). |
MNU-7 | WHILE the menu is open THE menu SHALL move focus among the items of the surface that holds focus — the menu's own or one submenu's, never across levels — to the next and previous item with Down Arrow and Up Arrow, wrapping at the ends and passing through groups as if their items were the surface's, to the first and last item with Home and End, and to the next item of that surface whose label begins with the typed characters when printable characters are typed within one second of each other; disabled items SHALL be reachable but SHALL NOT activate, a submenu item SHALL take focus as any other item, and dividers, groups and group titles SHALL NOT take focus. |
MNU-8 | WHEN a pointer moves onto an item THE menu SHALL move focus to that item without showing the focus ring and render it hovered, so that the arrow keys continue from it, except while the pointer is on its way to an open submenu (MNU-45); WHEN the pointer leaves the surface focus SHALL stay on that item. |
MNU-9 | WHILE an item's disabled is true THE menu SHALL NOT emit its click or change, nor its group's change, for it and SHALL expose the disabled state to assistive technology. |
MNU-10 | WHILE an item is hovered, keyboard-focused or pressed THE menu SHALL render its container, label, supporting text, trailing text, icons, mark (a glyph's mark color, and a control look's control and control-mark colors) and opacity from the component tokens of its condition in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. |
MNU-11 | WHEN an item receives keyboard focus THE menu SHALL show the focus ring inside the item's container, which the surface would otherwise clip, without animation; focus gained by pointer or touch SHALL NOT show it. |
MNU-12 | WHERE an item is shorter than the minimum target THE menu SHALL make its container at least sys.target.touch tall for touch input, in both densities, because stacked items cannot extend a hit area over each other, and for fine pointers at least sys.target.pointer tall WHILE density is regular and as tall as its density gives WHILE density is compact (MNU-64), the one place where a row of the menu is lower than the pointer target; a platform that cannot tell the input type SHALL use sys.target.touch. The whole row SHALL be the item's hit area. |
MNU-13 | WHILE an item's disabled is true THE menu SHALL render its container, label, supporting text, trailing text, icons, mark (a glyph's mark color, and a control look's control and control-mark colors) and opacity from the disabled component tokens of its condition. |
MNU-14 | WHEN placement names a value the menu does not define THE menu SHALL render with the default value, and in debug builds report the unknown value. |
MNU-15 | WHEN the menu item is pressed by pointer, touch, Enter or Space THE menu item SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner. |
MNU-16 | WHILE pressed THE menu item SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; label, supporting text, trailing text, icons, mark, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.menu.motion.feedback-duration with comp.menu.motion.state-easing, fades over comp.menu.motion.state-duration with comp.menu.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced. |
MNU-17 | THE menu item SHALL ease its container corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show its keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README. |
MNU-18 | THE menu SHALL draw an item from the condition it is in: destructive for an item (an action) whose destructive is true, selected for a checkbox item whose checked is true and for a radio item whose value equals its group's value, current for a link item whose current is true (MNU-58), expanded for a submenu item whose submenu is open, otherwise unselected. |
MNU-19 | THE menu SHALL lay out an item as one row: comp.menu.container.padding-inline.regular from each edge, the mark column and the leading icon column, each where MNU-59 keeps it, the label with the supporting text below it taking the remaining width, then the trailing column, which holds the shortcut or the trailing text, and the trailing icon column, which holds the trailing icon or, in a submenu item, the submenu indicator (MNU-42) or, in a link item that opens a new context and names no trailing icon, its mark (MNU-55), each comp.menu.container.gap.regular from the next; an empty trailing icon column SHALL keep its width in every item of the same surface so that shortcuts, trailing text and trailing icons align, each surface — the menu's and each submenu's — reserving its own, and WHEN the layout direction is right-to-left THE menu SHALL mirror the row. |
MNU-20 | THE menu SHALL make an item at least comp.menu.container.height.regular tall, pad its content by comp.menu.container.padding-block.regular above and below, draw the label in comp.menu.label.typescale, the supporting text in comp.menu.supporting-text.typescale and the trailing text and the shortcut's legends in comp.menu.trailing-text.typescale, and grow with its content and with enlarged text rather than clip or truncate it. |
MNU-21 | THE menu SHALL draw leadingIcon at comp.menu.leading-icon.size.regular and trailingIcon at comp.menu.trailing-icon.size.regular through the icon helper, with the family and fill of the item's condition (selected for a checked item and for a current link item, otherwise unselected), sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max; an empty name SHALL draw nothing. |
MNU-22 | WHILE a checkbox or radio item is checked and its mark is a glyph (MNU-38) THE menu SHALL draw that glyph of the system's icon set — check where the design system's choice is check, the named glyph where markIcon names one — in its mark column, centered in a box of side comp.menu.mark.size.regular, in the mark color of its condition, with sys.icon.family.selected, sys.icon.fill.selected, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, and SHALL draw nothing in that column while the item is not checked; it SHALL NOT mirror the check when the layout direction is right-to-left, a glyph that markIcon names following MNU-33; THE menu SHALL draw no mark of its own (no path, shape or character of its own for the check). WHEN checked changes while the menu is open THE menu SHALL fade the glyph in or out over comp.menu.motion.state-duration with comp.menu.motion.state-easing without moving or scaling it, and at once where motion is reduced. |
MNU-23 | WHILE the menu's separation is divider (MNU-35) THE menu SHALL draw a line of comp.menu.divider.thickness in comp.menu.divider.color across the surface's inner width, inside comp.menu.surface.padding-inline on each side (the same inset as the rows, not the rows' own padding) between a group and an adjacent group or an adjacent item outside any group, with comp.menu.divider.space above and below it, and SHALL expose it as a separator that takes no focus; it SHALL draw no line before the first or after the last element of the surface. |
MNU-24 | THE menu SHALL expose the menu role, named by its anchor (a context menu as MNU-63 says); each item as a menu item, a link item included (MNU-53), each checkbox item as a menu item checkbox and each radio item as a menu item radio with its checked state, whatever its mark looks like, each with its position and the size of the surface it is in, named by label and described by supporting text, a link item that opens a new context also by its targetLabel (MNU-55), and a link item that is current with the current state (MNU-58); each submenu item as a menu item that opens a menu, with whether that menu is open (aria-haspopup with the value menu, and aria-expanded, on the web), described also by its trailing text, and each submenu's surface as a menu of its own named by its submenu item's label; shortcut SHALL be exposed as the item's keyboard shortcut in the form KBS-12 gives (aria-keyshortcuts on the web) where the platform has that property, and otherwise the text KBS-10 gives as the item's description; trailing text SHALL be exposed as the item's description only, never as a keyboard shortcut, whatever it holds; icons, the mark and the submenu indicator SHALL be hidden; each group SHALL be exposed as a group, named by its title where it has one. WHILE trigger is anchor THE menu SHALL also expose on its anchor that it opens a menu and whether the menu is open, where the platform has that property. |
MNU-25 | WHILE the menu is open THE menu SHALL NOT trap focus, SHALL NOT make the rest of the screen inert and SHALL draw no scrim: it is an operated overlay that is not modal. |
MNU-26 | WHILE an item's label is empty THE menu SHALL still render the item, and in debug builds report that it has no accessible name. |
MNU-27 | WHEN the colors of an item change between states THE menu SHALL animate them over comp.menu.motion.state-duration with comp.menu.motion.state-easing, and SHALL change them at once where motion is reduced. |
MNU-28 | WHILE a group's title is not empty THE menu SHALL draw it above the group's items in comp.menu.group-title.typescale and comp.menu.group-title.color, starting comp.menu.group-title.padding-inline.regular from the rows' start edge and ending comp.menu.group-title.padding-inline.regular from their end edge, whether or not the menu draws the mark and leading icon columns (so that, at the default values, the title starts where each row's first column starts: the mark, the leading icon or the label), with comp.menu.container.padding-block.regular above and below, wrapping and growing with enlarged text rather than clipping; WHEN the layout direction is right-to-left start and end SHALL mirror. The title SHALL take no focus and SHALL NOT be read as an item. |
MNU-29 | WHEN a radio item is not inside a group, or a group's value names none of its radio items THE menu SHALL draw every such radio item unchecked; WHEN two radio items of one group share a value THE menu SHALL check both while the group's value names it; WHEN a group holds another group THE menu SHALL draw the inner group's items as the outer group's, with no title or divider of the inner group's own; in debug builds it SHALL report each of these. |
MNU-30 | IF the icon set cannot draw a glyph the menu draws yet — the check or the named glyph of a mark, the check inside a checked box, the submenu indicator — because its font is still loading or failed to load THE menu SHALL draw nothing in the glyph's place, never the glyph's name as text, SHALL keep its column's width, SHALL still draw the box of a checkbox look and the ring of a radio look, SHALL still expose the item's checked state and whether its submenu is open, and SHALL show the glyph once the icon set can draw it, without the fade of MNU-22. |
MNU-31 | WHILE an item's shortcut is not empty THE menu SHALL draw it in the item's trailing column with the keyboard shortcut component, hosted as KBS-13 says: at size small, in the form comp.menu.shortcut.variant names, with convention auto, and disabled while the item is disabled; its legends and separators SHALL take comp.menu.trailing-text.typescale and the trailing-text color of the item's condition and state, and its caps the keyboard shortcut's own tokens. IF an item has both a shortcut and trailing text THE menu SHALL draw the shortcut, not the trailing text, and in debug builds report the item. |
MNU-32 | THE menu SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the menu is shown THE menu SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state and its focus, the surface placed again by the new direction (MNU-2). |
MNU-33 | WHERE the layout direction is right-to-left THE menu SHALL draw an item's leading icon and trailing icon, the glyph a markIcon names and the submenu indicator mirrored left to right exactly when the system's icon set marks that glyph's name as mirrored or the product forces mirroring for that use, and unmirrored when the product forbids it: the icon helper mirrors the glyph inside its own box, about the box's vertical centre line, changing neither its place, size, color and fill nor the order MNU-19 gives. THE menu SHALL NOT mirror a glyph itself, and SHALL NOT mirror content the product gives as an image or a view of its own rather than by a glyph's name. The check — the check of a mark and the check inside a checked box — is never mirrored (MNU-22, MNU-39). |
MNU-34 | THE menu SHALL lay out an item's label, supporting text and trailing text and a group's title as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE menu SHALL still place each and align its lines by the layout direction, not by the text's own. |
MNU-35 | THE menu SHALL separate a group from an adjacent group or an adjacent item outside any group as its separation names: WHILE separation is auto, as comp.menu.separation names, which follows sys.behavior.separation unless a design system sets it apart; WHILE separation is divider or gap, as that value names, whatever the tokens say; divider draws the line of MNU-23 and gap leaves the space of MNU-36. Every submenu SHALL separate its groups as its menu does. WHEN separation, or the token it follows, changes THE menu SHALL draw the other form at once, without animation, keeping its open state and its focus; WHEN separation names a value the menu does not define THE menu SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, divider, and state that in its README. |
MNU-36 | WHILE the menu's separation is gap (MNU-35) THE menu SHALL draw each group, and each run of adjacent items outside any group, as a tile of its own: a sheet of comp.menu.surface.color with the corners comp.menu.surface.shape eased by sys.shape.corner-smoothing and the elevation comp.menu.surface.elevation, its ring drawn inside the tile's edge, holding its rows inside comp.menu.surface.padding-block and comp.menu.surface.padding-inline as the surface holds them; it SHALL leave comp.menu.group.gap of empty space between two tiles, through which what is under the menu shows, and SHALL draw no sheet, edge or elevation of its own around the tiles. It SHALL draw no line between tiles and SHALL expose no separator, the groups staying exposed as groups (MNU-24) and a tile being nothing to assistive technology; it SHALL leave no space before the first or after the last tile, and a group's title SHALL keep its own padding inside its tile (MNU-28). The tiles of a menu taller than the room it has SHALL scroll together inside the menu's bounds (MNU-2), and every submenu SHALL draw its tiles the same way: the gap tells the parts of a menu apart by cutting its sheet, as the gap of a button group or of an accordion makes separate shapes, never by empty space on one sheet. |
MNU-37 | WHILE a checkbox or radio item is checked THE menu SHALL fill its row, within the corners comp.menu.container.shape gives, with the container color of the selected condition in its state (comp.menu.selected.container.color at rest), SHALL draw that fill over its surface (comp.menu.surface.color) and under the row's content, so that a container color that is not opaque lets the surface show through it and nothing else, and SHALL draw its label, supporting text, trailing text and icons in that condition's colors; checked rows next to each other SHALL each keep their own corners and SHALL NOT be joined into one shape. THE menu SHALL NOT show a choice by the fill alone: a checked item always draws its mark (MNU-38), and a design system whose selected container color is none still marks it. The mark is what tells a checked item; the fill is a second cue a design system may give or withhold, through comp.menu.selected.container.color for each contrast scheme, and the menu SHALL read that token in the scheme it is drawn in and SHALL NOT decide the fill by the scheme itself. The default system gives it at medium and high contrast, where a person has asked for stronger cues, and withholds it at standard contrast, so that there the one filled row of a menu is the row the person is on (MNU-58). |
MNU-38 | THE menu SHALL draw the mark of a checkbox or radio item as the first of these that applies: the glyph the item's markIcon names; the glyph its group's markIcon names; otherwise what the design system chooses for the item's kind — comp.menu.checkbox-item.mark for a checkbox item (checkbox: the checkbox look of MNU-39; check: the icon set's check, as MNU-22 draws it) and comp.menu.radio-item.mark for a radio item (radio: the radio look of MNU-40; check: the same check). Whatever the mark is THE menu SHALL place it in the mark column, the first column of the row (MNU-19), before the leading icon, centered in a box of side comp.menu.mark.size.regular, SHALL hide it from assistive technology, SHALL NOT make it a control of its own (it takes no focus and no press and has no role apart from its item's), and SHALL keep exposing the item as a menu item checkbox or a menu item radio with its checked state (MNU-24). A platform that cannot draw a choice SHALL fall back to the first value and state that in its README. |
MNU-39 | WHILE a checkbox item's mark is the checkbox look (MNU-38) THE menu SHALL draw, whether the item is checked or not, a box of side comp.menu.control.box.size with corners comp.menu.control.box.shape eased by sys.shape.corner-smoothing: WHILE the item is not checked the box SHALL be empty, with an outline of comp.menu.control.outline.width inside its edge in the control color of its condition and state (comp.menu.unselected.control.color at rest); WHILE it is checked the box SHALL be filled in the control color (comp.menu.selected.control.color at rest), outline and fill being one shape with no seam between them, with the glyph check of the system's icon set centered on it at comp.menu.control.mark.size in the control-mark color of its condition and state, drawn with sys.icon.family.selected, sys.icon.fill.selected, the weight comp.menu.control.mark.weight (not sys.icon.weight: the check in the box is a control's own mark, as the checkbox's is, CHK-12) and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, not clipped by the box and never mirrored; the weight SHALL change the stroke of the glyph only. THE menu SHALL draw no round state layer behind the box: the row is the item's container. |
MNU-40 | WHILE a radio item's mark is the radio look (MNU-38) THE menu SHALL draw, whether the item is checked or not, a ring of outer diameter comp.menu.control.ring.size in the control color of its condition and state, the outer diameter the same in both conditions: WHILE the item is not checked the ring SHALL be stroked at comp.menu.control.ring.width inside that diameter; WHILE it is checked it SHALL be stroked at comp.menu.control.ring.selected-width-share of the ring's radius, or at comp.menu.control.ring.width where that is wider, the stroke growing inward, and the area inside the ring's inner edge, less comp.menu.control.fill.inset all round, SHALL be filled in the same control color at comp.menu.control.fill.opacity, ring and fill being one shape where they touch; a fill whose diameter would be zero or less SHALL NOT be drawn. THE menu SHALL draw no dot and no round state layer of its own. |
MNU-41 | WHEN checked changes while the menu is open and the item's mark is a control look THE menu SHALL move the box's outline and fill, the check inside it, and the ring's stroke and fill from the old condition to the new over comp.menu.motion.state-duration with comp.menu.motion.state-easing — the check fading, the ring's stroke growing or thinning inward — without moving or resizing the box or the ring's outer diameter, and the row's fill SHALL change with it (MNU-27); WHERE motion is reduced it SHALL draw the new condition whole at once. |
MNU-42 | THE menu SHALL draw a submenu item as an item's row (MNU-19, MNU-20) with the submenu indicator in its trailing icon column: the glyph chevron_right of the system's icon set at comp.menu.trailing-icon.size.regular in the trailing-icon color of the item's condition and state, drawn through the icon helper with sys.icon.family.unselected, sys.icon.fill.unselected, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max; its trailing text, where it has one, SHALL be drawn in the trailing column before the indicator, and a submenu item SHALL have no shortcut, no trailing icon of the product's and no mark. THE menu SHALL draw no chevron of its own (no path, shape or character of its own). |
MNU-43 | WHEN the user presses Enter or Space on an enabled submenu item, presses the arrow key that points to the inline end on it (Right Arrow in a left-to-right layout, Left Arrow in a right-to-left one), presses it with a pointer or taps it THE menu SHALL open that item's submenu without delay, close any other open submenu of the same surface, move focus to the submenu's first enabled item (its first item where none is enabled), showing the focus ring only when the input was a key, set the item's open to true and emit the item's toggle once with true; it SHALL NOT emit close and SHALL close nothing else. WHEN the submenu is already open the same input SHALL move focus to its first enabled item and emit nothing. WHEN the product sets a submenu item's open to true while the surface that holds the item is shown THE menu SHALL open the submenu without moving focus and without emitting toggle. |
MNU-44 | WHEN a pointer that can hover rests on an enabled submenu item for comp.menu.motion.submenu-delay THE menu SHALL open that item's submenu, closing any other open submenu of the same surface, and SHALL leave focus on the submenu item (MNU-8) until the pointer enters the submenu or a key moves it; a pointer that leaves the item sooner SHALL open nothing. Touch has no hover: a tap opens the submenu (MNU-43). WHERE motion is reduced the delay SHALL still apply: it reads the pointer's intent and is not an animation. |
MNU-45 | WHILE a submenu is open and the pointer moves from its submenu item towards it THE menu SHALL keep the submenu open and SHALL NOT move hover or focus to the items the pointer crosses on the way (MNU-8): the pointer is on its way while it keeps moving inside the triangle whose corners are the pointer's last position on the submenu item and the two corners of the submenu's surface on the side that faces the item. WHEN the pointer leaves that triangle without entering the submenu, or rests inside it for comp.menu.motion.submenu-delay THE menu SHALL treat the item under the pointer as MNU-8 says, which closes the submenu when that item is another one (MNU-48). The triangle SHALL be taken anew each time the pointer leaves the submenu item and SHALL follow the side the submenu is placed on (MNU-46), so that it mirrors in a right-to-left layout and when the submenu is placed on the other side. |
MNU-46 | THE menu SHALL place a submenu's surface beside the surface that holds its submenu item, comp.menu.submenu.offset from that surface's inline-end edge (to the right in a left-to-right layout, to the left in a right-to-left one), with the block-start edge of its first row level with the block-start edge of the submenu item's row, and SHALL draw it above the surface it opened from; WHERE that side has less room than the surface needs within comp.menu.surface.viewport-margin of the edge of the visible part of the window, above the on-screen keyboard while one is shown, and the inline-start side has more THE menu SHALL place it on the inline-start side instead, the same distance from that edge, and a submenu opened from a surface placed that way SHALL try that side first; it SHALL shift the surface along the block axis to stay within that margin, SHALL limit its height to the least of the height its items need, the most the product's own style gives the submenu's surface (MNU-2) and the room left and scroll its items beyond it, and SHALL keep it in place against its submenu item while that item moves. WHEN the submenu item is scrolled out of the visible part of its surface THE menu SHALL close the submenu (MNU-48). |
MNU-47 | WHERE neither side of the surface that holds a submenu item has room for the submenu's surface at comp.menu.surface.min-width within comp.menu.surface.viewport-margin THE menu SHALL still present the submenu as a surface of its own, laid over the surface it opened from, on the side with more room and as far towards that side as the margin allows, narrowed down to comp.menu.surface.min-width before it covers more, and SHALL leave at least sys.target.touch of the covered surface's inline size uncovered on its other side wherever the visible part of the window is wide enough for both; THE menu SHALL NOT replace the items of a surface with its submenu's at any window size. WHILE a submenu covers part of the surface it opened from, a press on the uncovered part of that surface SHALL close the submenu as MNU-48 says and SHALL NOT activate the item under the press, so that a person without a keyboard or a back button can go back one level; keys and focus SHALL behave as in a wide window. |
MNU-48 | WHEN focus is in a submenu and the user presses the arrow key that points to the inline start (Left Arrow in a left-to-right layout, Right Arrow in a right-to-left one), Escape or the platform's back THE menu SHALL close that submenu and every submenu opened from it, and nothing else, return focus to its submenu item, showing the focus ring only when the input was a key, set the item's open to false and emit the item's toggle once with false, without emitting close. WHEN focus or hover moves to another item of the surface that holds the submenu item (MNU-7, MNU-8), WHEN the submenu item becomes disabled or is removed, or WHEN the product sets its open to false THE menu SHALL close the submenu the same way — the product's own change emitting no toggle — and focus that was inside it SHALL move to the submenu item, or to the item that took focus. Escape and the platform's back SHALL close one level for each press, and the menu itself only when no submenu is open (MNU-5). THE menu SHALL keep at most one submenu open in each surface. |
MNU-49 | WHEN the menu is closed — for a reason MNU-5 or MNU-6 names, at whatever level it arose, or because the product sets open to false — THE menu SHALL close every open submenu with it at once, emit close once and never once for each level, set each closed submenu item's open to false without emitting its toggle, and return focus to the anchor as MNU-1 says (a context menu: to the element MNU-62 names), not to a submenu item; a press inside any surface of the menu SHALL NOT count as a press outside, and focus moving between the menu's surfaces SHALL NOT count as focus leaving it. The surfaces SHALL fade out together as MNU-4 says. |
MNU-50 | WHILE a submenu is open THE menu SHALL draw its submenu item in the expanded condition (MNU-18) in whatever interaction state it is in, so that the path from the menu to the surface in use stays marked while the pointer and focus are elsewhere, drawing that condition's container color over its surface as MNU-37 draws a checked row's, and SHALL draw the submenu's surface as the menu's own (MNU-3) from the same tokens, its items following every behaviour of the menu's items, with the open and close motion of MNU-4 growing from the edge that faces its submenu item. |
MNU-51 | WHEN the user presses the arrow key that points to the inline end on an item that is not a submenu item, or the arrow key that points to the inline start in the menu's own surface THE menu SHALL do nothing and SHALL NOT let the key scroll or act on what is behind the menu. WHILE a submenu item's disabled is true THE menu SHALL NOT open its submenu by any input, SHALL close it if it is open (MNU-48), and SHALL still expose that the item opens a menu, with the disabled state (MNU-9). |
MNU-52 | THE menu SHALL let a submenu hold items of every kind, groups and further submenus included, to any depth, each level behaving as MNU-42 to MNU-51 say, and SHALL keep the radio items of a submenu apart from those of every other surface: a group's value reaches only the radio items that are its own. WHEN a submenu item lies more than two levels of submenu below the menu, or WHEN a submenu holds no item THE menu SHALL still render what it can — a submenu with no item draws its row and its indicator, takes focus and opens nothing — and in debug builds report each. WHILE open is true on a submenu item whose own surface is not shown THE menu SHALL show nothing for it until that surface is shown. |
MNU-53 | WHILE an item's href is not empty THE menu SHALL make the item a link item: the platform's own link to href (the web's anchor element; SwiftUI's Link; on Compose, Flutter and React Native an element with the platform's link semantics whose address is opened by the platform's opener), as BTN-22 says for a link button, so that whatever the platform gives a link — on the web its context menu, opening in a new tab or window, copying and dragging its address, and the address shown before it is followed — is the platform's, and not the menu's to draw or to take over. WHERE the platform has a menu item role (the web) THE menu SHALL expose a link item with that role, on the link itself, and not with the link role: a menu's own role, an item's position in it and the keys of MNU-7 hold only while everything the menu holds is a menu item, and a menu of links is operated as a menu; WHERE the platform's menu items are buttons and it has no such role THE link item SHALL carry the platform's link role or trait in place of the button's. Everything this spec says of an item (an action) other than what MNU-54 to MNU-57 say — its row and columns, its conditions, destructive included, its states, focus ring, minimum height, press answer, corners and motion, its place in the order of the arrow keys, Home, End and typed characters, and focus following the pointer (MNU-7, MNU-8, MNU-10 to MNU-21, MNU-27, MNU-31) — SHALL hold for a link item from the same tokens, with no look of its own for a link and none for a destination already visited; a link item SHALL NOT be a Tab stop, as no item of a menu is. |
MNU-54 | WHEN the user activates an enabled link item at any level as an item of a menu is activated — by a click with the primary button and no modifier key, a touch, Enter or Space with no modifier key, or the default action of assistive technology — THE menu SHALL emit the item's click exactly once, then emit close with the reason item and be closed as MNU-6 says for an action, and then open href where target names, unless the product cancelled that click, in which case it SHALL still close and SHALL open nothing: the product's own router has taken the navigation. It SHALL show the pressed state as MNU-15 and MNU-16 say and SHALL NOT hold the closing or the navigation back for sys.motion.duration.press-hold. Space SHALL activate a link item exactly as Enter does and SHALL scroll nothing: a link item is exposed and operated as a menu item (MNU-53), nothing in its look tells it from the action in the next row, Space activates every other row of the menu, and behind an open menu Space has no page to scroll (MNU-51). For every other activation the platform gives a link — on the web a click or Enter with a modifier key, a middle click and the link's context menu, on Apple platforms a long press that previews — THE menu SHALL leave the link to the platform, SHALL prevent, replace or imitate none of it, SHALL NOT emit close and SHALL stay open at every level with focus where it was, so that a person can open several of a menu's links in other tabs one after another; what MNU-5 says of a press outside and of focus leaving the menu still holds. |
MNU-55 | WHILE an item is a link item and its target is new THE menu SHALL open href in a new context — on the web a new tab or window that is given no handle to the page that opened it; on the other platforms outside the screen the person is on, as the platform's opener decides — and, WHILE the item's trailingIcon is empty, SHALL draw the glyph open_in_new of the system's icon set in the item's trailing icon column, as MNU-21 draws trailingIcon: at comp.menu.trailing-icon.size.regular, in the trailing-icon color of the item's condition and state (comp.menu.unselected.trailing-icon.color at rest, comp.menu.destructive.trailing-icon.color for a destructive item), through the icon helper, which mirrors it in a right-to-left layout because the icon set marks its name (MNU-33), hidden from assistive technology, and held to MNU-30 while the icon set cannot draw it. The mark SHALL reserve the trailing icon column in its surface as a trailing icon does (MNU-19), and the item's shortcut or trailing text SHALL keep its own column before it: the mark stands where a glyph of the product's would, not where the keys are written. WHILE trailingIcon names a glyph THE menu SHALL draw that glyph and no mark of its own: the product has chosen what that place shows. Whether a mark is drawn or not THE menu SHALL expose targetLabel as the end of the item's description, after its supporting text and whatever else MNU-24 puts there, and WHILE targetLabel is empty SHALL still render and in debug builds report that a link that opens a new context does not say so. WHILE target is same THE menu SHALL draw no mark and SHALL ignore targetLabel. |
MNU-56 | WHILE a link item's disabled is true THE menu SHALL NOT make it a link that can be followed: it SHALL render it as MNU-13 says, the mark of MNU-55 with it, SHALL expose it as MNU-53 exposes a link item, with the disabled state and no destination, SHALL keep it reachable by the arrow keys as MNU-7 keeps any disabled item, SHALL NOT emit its click or close for it, and SHALL open nothing by any means the platform gives a link (on the web the anchor carries no href, so there is no context menu entry, no drag and no new tab for it). |
MNU-57 | THE menu SHALL give a destination to the item kind alone. A checkbox item and a radio item hold a state and a submenu item opens another level, so none of them has href, target or targetLabel: a control that holds a state, or a second action, is not a destination; WHERE a platform lets one be written on them (an attribute of the web's element, a field of React Native's item object) THE menu SHALL ignore it, keep the item what it is, and in debug builds report it. WHEN a link item's target names a value the menu does not define THE menu SHALL treat it as same, and in debug builds report the unknown value. IF an item's href is only white space, or names a script to run rather than a place to go (the web's javascript: scheme), or cannot be read as an address by the platform, THE menu SHALL make the item an action without a destination, and in the last two cases report it in debug builds, as BTN-26 says for a button. WHILE an item's href is empty THE menu SHALL ignore its target and targetLabel. |
MNU-58 | WHILE a link item's current is true THE menu SHALL expose it as the current item of its set (on the web aria-current with the value page, on the item's link; on a platform without that state, the selected state) and SHALL draw its row from the current condition: a fill as MNU-37 lays one, in comp.menu.current.container.color at rest, and that condition's colors for its label, supporting text, trailing text and icons, its icons in the selected family and fill, in every state and in every contrast scheme; it SHALL draw no mark in the mark column, keep its label's own weight, and SHALL keep it a link item in every other respect (MNU-53 to MNU-56). The current destination is where the person is, not a choice the menu holds: it carries no check, and it is told from its neighbours by the fill the menu draws for the row the person is on, as a navigation drawer marks its current destination, while assistive technology is told by the state. Its fill is its own and not a checked row's: a checked row has its mark and may go without a fill (MNU-37), the current row has only its fill, so a design system that withholds the one keeps the other. WHERE the platform draws with a person's own forced colors, which remove the fill, THE menu SHALL draw a line of sys.border.thick inside the current item's row in the platform's link color. THE menu SHALL NOT set current itself on any activation: where the product is, is the product's to say. WHILE an item's href is empty THE menu SHALL ignore its current and in debug builds report it. |
MNU-59 | THE menu SHALL decide the mark column and the leading icon column for each section of a surface — a group, with the items of a group inside it (MNU-29), or a run of adjacent items outside any group: the parts MNU-36 draws as tiles — and each of the two columns by itself. WHILE iconSpace is auto a row SHALL keep a column, empty where it draws nothing in it, WHERE an item of its section draws in it — a checkbox or radio item its mark, whether checked or not and whatever the mark is (MNU-38), an item its leading icon — so that the labels of a section start on one line, and a section where no item draws in a column SHALL keep no room for it, its labels starting that much nearer the row's start; WHILE iconSpace is always every row SHALL keep both columns, whether or not any item of the surface draws in them; WHILE iconSpace is never a row SHALL have a column only where it draws in it itself. The room kept is the column's box (comp.menu.mark.size.regular for the mark, comp.menu.leading-icon.size.regular for the icon) and comp.menu.container.gap.regular after it, draws nothing and is nothing to assistive technology. A submenu item has no mark and so draws nothing in the mark column (MNU-42). Each surface SHALL be decided by itself, every submenu with the menu's iconSpace; a group's title SHALL keep its own place whatever the columns of its section are (MNU-28). WHEN an item is added or removed, or an item's kind or leadingIcon changes, THE menu SHALL decide the item's section again; WHEN iconSpace names a value the menu does not define THE menu SHALL treat it as auto and in debug builds report the unknown value. |
MNU-60 | WHILE trigger is context, WHEN the person asks for a context menu in the anchor's area — a secondary click (a right click, a Control-click, a two-finger click on a trackpad), the context-menu command of assistive technology, a press of a touch or a pen held within the platform's touch slop (8 points where the platform names none) for the platform's long-press duration (500 ms where the platform names none), or Shift+F10 or the ContextMenu key with no other modifier while focus is on the area or on an element inside it — THE menu SHALL emit contextRequest once, with the point in the viewport's coordinates (where the pointer is, where the press began, or the block-end, inline-start corner of the element that has focus), the element under the pointer or with focus, and how it was asked (pointer, long-press or keyboard), and SHALL NOT open itself: the product sets open to true (MNU-1). Unless the product cancels contextRequest, THE menu SHALL keep the platform's own context menu from answering the request, and after a long press SHALL keep the release of that press from activating what is under it; a cancelled request SHALL be left to the platform. A request that an element inside the area has already taken (on the web, a contextmenu event whose default is prevented) and one made inside the menu's own surfaces SHALL NOT be answered. WHILE trigger is anchor THE menu SHALL answer none of these. WHEN trigger names a value the menu does not define THE menu SHALL treat it as anchor and in debug builds report the unknown value. |
MNU-61 | WHILE trigger is context THE menu SHALL place its surface at the point of the last contextRequest, with no offset, as placement names it from the point (bottom-start: the surface's block-start, inline-start corner on the point; bottom-end: its block-start, inline-end corner; top-start and top-end: its block-end corners); WHERE the side below or above the point has less room than the surface needs within comp.menu.surface.viewport-margin of the edge of the visible part of the window, above the on-screen keyboard while one is shown, and the other side has more THE menu SHALL flip to the other side of the point, and WHERE the side before or after the point along the line has less room than the surface's width and the other has more SHALL flip along the line as well, each axis by itself, so that a surface asked for near a corner of the window opens away from that corner with a corner of its own on the point; it SHALL then shift to stay within that margin, limit its height as MNU-2 says and scroll its items beyond it, and keep the point where it is in the area while the area moves. FOR a request by the keyboard THE menu SHALL place the surface against the element that had focus as MNU-2 places it against an anchor, offset included. WHEN open becomes true with no request since the menu was last closed THE menu SHALL place the surface against the area as MNU-2 says. The surface SHALL grow from its edge that faces the point or the element (MNU-4). |
MNU-62 | WHILE trigger is context THE menu SHALL behave as every other menu does once it is open — the same items, focus on opening (MNU-1), keys, typeahead, submenus, activation and close reasons — and WHEN it closes SHALL return focus, where focus was inside it, to the element that had focus when the request was made (the element a keyboard request was made on), and leave focus where the platform puts it where no element had it; Tab and Shift+Tab SHALL move focus on from that element. A press anywhere outside the menu's surfaces, the area included, SHALL count as a press outside (MNU-5); a secondary click or a long press elsewhere while the menu is open SHALL therefore close it and, in the area, request it again at the new point. |
MNU-63 | WHILE trigger is context THE menu SHALL NOT expose on its area that it opens a menu, whether it is open or which element it is (no aria-haspopup, aria-expanded or aria-controls on the web), because the area is not a button: a context menu is found by the platform's own convention — a secondary click, a long press, Shift+F10, the ContextMenu key, the context-menu command of assistive technology. THE menu SHALL NOT be named by its area; it SHALL take the name the product gives it (aria-label on the web), and have none where the product gives none, as the platform's own context menus have none. |
MNU-64 | THE menu SHALL draw the rows of every surface at its density. WHILE density is regular each row SHALL take the regular value of every token of the density axis this spec names (comp.menu.container.height.regular, comp.menu.container.padding-block.regular, comp.menu.container.padding-inline.regular, comp.menu.container.gap.regular, comp.menu.leading-icon.size.regular, comp.menu.trailing-icon.size.regular, comp.menu.mark.size.regular, comp.menu.group-title.padding-inline.regular); WHILE density is compact it SHALL take the compact value of each in its place (comp.menu.container.height.compact, comp.menu.container.padding-block.compact, comp.menu.container.padding-inline.compact, comp.menu.container.gap.compact, comp.menu.leading-icon.size.compact, comp.menu.trailing-icon.size.compact, comp.menu.mark.size.compact, comp.menu.group-title.padding-inline.compact), so that a compact row is lower, set in less from its edges and draws smaller glyphs, the room a row keeps for an empty column (MNU-59) shrinking with them. The text styles, the surface and its paddings, the dividers, the group gap and the control of a checkbox or radio item SHALL be the same in both. Every submenu SHALL take the menu's density. WHEN density names a value the active design system does not have THE menu SHALL draw the default density's rows and in debug builds report the unknown value; a density a design system added SHALL be drawn from that density's tokens, as the core values are. WHEN density changes while the menu is open THE menu SHALL draw the new rows at once, without a motion, keeping focus where it is. |
MNU-65 | WHEN the element the open menu is placed against — the anchor; for a context menu its area, or the element a keyboard request was made on (MNU-61) — is no longer drawn: taken out of the page, given no box (on the web display none on it or on an ancestor), made invisible (visibility hidden) or skipped by its platform's content visibility (on the web content-visibility hidden, or auto while it is off screen), THE menu SHALL emit close once with the reason anchor-hidden and be closed as MNU-5 says, every open submenu with it (MNU-49), and SHALL NOT place its surface against that element while it is not drawn, because a surface with nothing beside it no longer says what it belongs to. WHEN it closes with focus inside it THE menu SHALL return focus as MNU-49 says and, where that element cannot take focus, SHALL NOT keep focus inside its closed surfaces. WHEN the element is drawn again while the menu is still open THE menu SHALL follow it again, and emit close again the next time it is no longer drawn. |
MNU-66 | WHERE the platform has ARIA attributes (the web), WHILE an item is a link item its role is carried by its anchor (MNU-53), and WHEN the product sets an aria- attribute on its host, by the attribute or by the ARIA property that reflects it, THE menu SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on that anchor — an id reference by the elements it names in the host's tree or a tree around it, a description after its own — except those the item owns (aria-disabled, aria-current, aria-keyshortcuts), which it SHALL move, not expose and report in debug builds; its label is its name over a name given so, and while label is empty a name given so is its name. WHEN the item stops being a link item THE menu SHALL give back to its host what it moved, as it was written, and WHEN it becomes one SHALL move what the host carries. WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE menu SHALL take it off the anchor. |
MNU-67 | WHERE the platform has ARIA attributes (the web), the hosts of the menu, of an item that is not a link item (a checkbox item, a radio item and a submenu item included) and of a group carry their roles themselves (MNU-24), so what the product writes on them is not moved, but for a name: WHILE an item's or a group's label, or the anchor that names a menu, is not empty THE menu SHALL hold the product's aria-label and aria-labelledby on that host as their data-aria- twins, so that its own is the whole name although the platform gives aria-labelledby the precedence, and WHEN it is emptied SHALL give them back as they were written; WHILE it is empty a name the product gives so is the name and an item SHALL NOT be reported as having none in debug builds (MNU-26) — a context menu is named so (MNU-63). WHILE a menu is closed, or a group is in a group (MNU-29), it exposes nothing, and THE menu SHALL hold them as well, giving them back when it is exposed again. WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it to null, THE menu SHALL take it away. |
MNU-68 | WHEN a surface of the menu is shown — the menu's own as it opens, a submenu's as its item opens it — THE menu SHALL show that surface's list from its start: a list taller than the surface (MNU-2, MNU-46) SHALL open scrolled to its top, so that its first row, its first group's title included, is in view, and WHERE the item that takes focus (MNU-1, MNU-43, or the last enabled item a menubar opens its menu on, MBR-9) is not whole in view there, scrolled just far enough to bring that item whole into view and no further. WHEN focus moves to an item by a key or by the menu's own doing (MNU-7, MNU-48) THE menu SHALL scroll the list that holds it just far enough to bring the item whole into view, and not at all where it is whole in view already — the first item of the list together with the list's start, the last together with its end, and the first item of a titled group together with that title (MNU-28); focus that follows the pointer (MNU-8) SHALL scroll nothing. Whether the item is in view SHALL be judged as the surface stands at rest: while its opening motion runs (MNU-4) the list SHALL keep its scroll position until the surface stands at rest, and then bring the item that has focus into view as this sentence says, so that no frame of the motion moves the list. |
MNU-69 | THE menu SHALL break the lines of an item's label, supporting text and trailing text and of a group's title by the rules of the text's language: a line of Japanese or Chinese SHALL neither begin with closing punctuation, a small kana, the prolonged sound mark (ー) or an iteration mark nor end with opening punctuation (the strict rules of line breaking that JIS X 4051 and the W3C's requirements for Japanese text layout set out), whether or not the product marks the text's language; and WHERE the platform finds the phrases of Japanese text (on the web, word-break: auto-phrase, which reads the language the product marks), a line of Japanese SHALL break between two phrases rather than inside one. A phrase or a word longer than its line SHALL still break rather than overflow (MNU-20). |
مطابقة الأسماء بين المنصات
التكافؤ لا يعني واجهات برمجية متطابقة. تعبّر كل منصة عن العقد نفسه بأسلوبها.
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| المكوّن | tanrei-menu | TanreiMenu | TanreiMenu | TanreiMenuAnchor | Menu |
open · الخاصية | open | isPresented | expanded | controller | visible |
placement · الخاصية | placement | placement | placement | placement | placement |
trigger · الخاصية | trigger | trigger | trigger | trigger | trigger |
separation · الخاصية | separation | separation | separation | separation | separation |
iconSpace · الخاصية | iconSpace | iconSpace | iconSpace | iconSpace | iconSpace |
density · الخاصية | density | density | density | density | density |
close · الحدث | close | isPresented | onDismissRequest | onClose | onDismiss |
contextRequest · الحدث | contextrequest | onContextRequest | onContextRequest | onContextRequest | onContextRequest |
| ملاحظات | tanrei-menu (open, placement, separation, and anchor: the id of the anchor element, as the HTML anchor attribute) holds tanrei-menu-item, tanrei-menu-checkbox-item, tanrei-menu-radio-item, tanrei-submenu and tanrei-menu-group children; item attributes are kebab-case (supporting-text, leading-icon, trailing-icon, trailing-text, mark-icon). click, change and toggle fire on the item or group and bubble, so one listener on the menu can take them all. The surface is a manual popover in the top layer, placed with CSS anchor positioning and a script fallback where the browser has none; each submenu's surface is a popover of its own, placed against its tanrei-submenu the same way. The menu implements its own light dismiss so that it can emit close with a reason, and treats every surface of its tree as inside. A press outside also reaches the element under it. It sets aria-haspopup="menu", aria-controls and aria-expanded on the anchor element, and the same three on each submenu item's row. The inline attribute, for tests, draws the open surface in place, with each open submenu beside it (MNU-19). The mark is the mark part: the box or the ring of a control look, drawn by the menu's own styles from --tanrei-comp-menu-control-*, or the icon helper's glyph (MNU-22, MNU-38). The submenu indicator is the icon helper's chevron_right in the submenu-indicator part. A group's title is inset by --tanrei-comp-menu-group-title-padding-inline-regular from the rows' edges (MNU-28). The link item is not built on this platform yet: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. A link item's current writes aria-current="page" on its anchor and draws the row from the current condition (MNU-58). Under gap each group, and each run of items outside a group, is a .tile (part tile) of its own inside the scroll box; under divider the tiles are boxes with no look (MNU-36). trigger is the trigger attribute; with context, anchor names the area. contextRequest is contextrequest, a cancelable event whose detail is { x, y, element, input }: client coordinates, the element under the pointer or with focus, and pointer, long-press or keyboard. The menu listens on the area for contextmenu (a secondary click, Android's long press, the context-menu command of assistive technology) and cancels it unless the product cancels the request; for a touch or a pen held still, which WebKit on iOS answers with no contextmenu; and for Shift+F10 and the ContextMenu key on keydown, whose own contextmenu it then cancels. After a long press the release of that press, and its click, go no further. On iOS a long press may also select text: an area whose text is not to be selected says so in its own styles (user-select, -webkit-touch-callout). | A modifier on the anchor, .tanreiMenu(isPresented:placement:) { … }, as .popover, presented with presentationCompactAdaptation(.popover) so it stays a popover on iPhone; a press outside does not reach what is under it. The content builder takes the item views. disabled follows .disabled(_:). Closing writes false to isPresented, which is close. SwiftUI's own Menu is not used: its items cannot be tokenised. Ships the previous revision today (the check stroked as a path in a 16 box, a group's title starting where the labels start). Not built on this platform yet, among the rest of this spec: the submenu item kind and everything about it (MNU-42 to MNU-52), separation (MNU-35, MNU-36), the checkbox and radio looks of the mark and markIcon (MNU-38 to MNU-41) and their 17 fixtures; the selected fill and colors arrive through the tokens, the fill of a checked row with them: none at standard contrast in the default system, as MNU-37 says. Reading comp.menu.separation, comp.menu.checkbox-item.mark and comp.menu.radio-item.mark as typed choices needs this platform's token output to write a component's choice as a typed enumeration, which it does not yet. Nor is the link item: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. Lagging behind MNU-58 (a link item's current, drawn with the selected fill) and MNU-36 (the groups as separate tiles under gap): it has neither yet. Not built yet: iconSpace (MNU-59) — this platform still keeps the mark and leading icon columns in every row of a surface where any item draws in them. Not built yet: the context trigger (MNU-60 to MNU-63) — this platform opens the menu from an anchor only. | Compose idiom, as DropdownMenu: TanreiMenu(expanded, onDismissRequest, placement) { TanreiMenuItem(text, onClick, …); TanreiMenuGroup { … } } inside the anchor's Box, shown in a Popup; the item composables are called in its content lambda. The caller closes it, so close is onDismissRequest and it carries the reason; the caller also keeps each checkbox item's checked (state hoisting). A press outside does not reach what is under it. enabled is the inverse flag. Ships the previous revision today (the check stroked as a path in a 16 box, a group's title starting where the labels start). Not built on this platform yet, among the rest of this spec: the submenu item kind and everything about it (MNU-42 to MNU-52), separation (MNU-35, MNU-36), the checkbox and radio looks of the mark and markIcon (MNU-38 to MNU-41) and their 17 fixtures; the selected fill and colors arrive through the tokens, the fill of a checked row with them: none at standard contrast in the default system, as MNU-37 says. Reading comp.menu.separation, comp.menu.checkbox-item.mark and comp.menu.radio-item.mark as typed choices needs this platform's token output to write a component's choice as a typed enumeration, which it does not yet. Nor is the link item: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. Lagging behind MNU-58 (a link item's current, drawn with the selected fill) and MNU-36 (the groups as separate tiles under gap): it has neither yet. Not built yet: iconSpace (MNU-59) — this platform still keeps the mark and leading icon columns in every row of a surface where any item draws in them. Not built yet: the context trigger (MNU-60 to MNU-63) — this platform opens the menu from an anchor only. | Flutter idiom, as MenuAnchor: TanreiMenuAnchor(controller:, placement:, menuChildren: […], builder:) shown in the Overlay; the item widgets go in menuChildren. MenuController.open() and .close() are open, onClose is close. A press outside also reaches what is under it (consumeOutsideTap: false). Ships the previous revision today (the check stroked as a path in a 16 box, a group's title starting where the labels start). Not built on this platform yet, among the rest of this spec: the submenu item kind and everything about it (MNU-42 to MNU-52), separation (MNU-35, MNU-36), the checkbox and radio looks of the mark and markIcon (MNU-38 to MNU-41) and their 17 fixtures; the selected fill and colors arrive through the tokens, the fill of a checked row with them: none at standard contrast in the default system, as MNU-37 says. Reading comp.menu.separation, comp.menu.checkbox-item.mark and comp.menu.radio-item.mark as typed choices needs this platform's token output to write a component's choice as a typed enumeration, which it does not yet. Nor is the link item: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. Lagging behind MNU-58 (a link item's current, drawn with the selected fill) and MNU-36 (the groups as separate tiles under gap): it has neither yet. Not built yet: iconSpace (MNU-59) — this platform still keeps the mark and leading icon columns in every row of a surface where any item draws in them. Not built yet: the context trigger (MNU-60 to MNU-63) — this platform opens the menu from an anchor only. | <Menu visible anchorRef placement onDismiss items={[…]}>: items is an array whose element type is the union of the five kinds, told apart by a kind field ('item', 'checkbox-item', 'radio-item', 'submenu', 'group'). Presented on the overlay layer that TanreiProvider renders (not a Modal, which would make the rest of the screen unreachable) and placed by measuring the anchor (measureInWindow); Android's back button (BackHandler) is close. A press outside closes the menu and does not reach what is under it. Controlled: the caller sets visible to false from onDismiss and keeps each checkbox item's checked and each group's value. Ships the previous revision today (the check stroked as a path in a 16 box, a group's title starting where the labels start). Not built on this platform yet, among the rest of this spec: the submenu item kind and everything about it (MNU-42 to MNU-52), separation (MNU-35, MNU-36), the checkbox and radio looks of the mark and markIcon (MNU-38 to MNU-41) and their 17 fixtures; the selected fill and colors arrive through the tokens, the fill of a checked row with them: none at standard contrast in the default system, as MNU-37 says. Reading comp.menu.separation, comp.menu.checkbox-item.mark and comp.menu.radio-item.mark as typed choices needs this platform's token output to write a component's choice as a typed enumeration, which it does not yet. Nor is the link item: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. Lagging behind MNU-58 (a link item's current, drawn with the selected fill) and MNU-36 (the groups as separate tiles under gap): it has neither yet. Not built yet: iconSpace (MNU-59) — this platform still keeps the mark and leading icon columns in every row of a surface where any item draws in them. Not built yet: the context trigger (MNU-60 to MNU-63) — this platform opens the menu from an anchor only. |
الرموز
| الرمز | يشير إلى | القيمة المحلولة (فاتح) |
|---|---|---|
--tanrei-comp-menu-checkbox-item-mark | — | 0 |
--tanrei-comp-menu-container-gap-compact | comp.tree-view.container.gap.compact | 8px |
--tanrei-comp-menu-container-gap-regular | sys.space.space150 | 12px |
--tanrei-comp-menu-container-height-compact | comp.tree-view.container.height.compact | 32px |
--tanrei-comp-menu-container-height-regular | — | 48px |
--tanrei-comp-menu-container-padding-block-compact | comp.tree-view.container.padding-block.compact | 6px |
--tanrei-comp-menu-container-padding-block-regular | — | 8px |
--tanrei-comp-menu-container-padding-inline-compact | comp.tree-view.container.padding-inline.compact | 8px |
--tanrei-comp-menu-container-padding-inline-regular | sys.space.space150 | 12px |
--tanrei-comp-menu-container-shape | — | 4px |
--tanrei-comp-menu-control-box-shape | — | 2px |
--tanrei-comp-menu-control-box-size | — | 18px |
--tanrei-comp-menu-control-fill-inset | — | 0px |
--tanrei-comp-menu-control-fill-opacity | sys.state.selected-state-layer-opacity | 0.16 |
--tanrei-comp-menu-control-mark-size | sys.icon.small | 20px |
--tanrei-comp-menu-control-mark-weight | sys.icon.mark.weight | 500 |
--tanrei-comp-menu-control-outline-width | sys.border.thick | 2px |
--tanrei-comp-menu-control-ring-selected-width-share | — | 0.5 |
--tanrei-comp-menu-control-ring-size | — | 20px |
--tanrei-comp-menu-control-ring-width | sys.border.thick | 2px |
--tanrei-comp-menu-current-container-color | sys.color.secondary-container | |
--tanrei-comp-menu-current-control-mark-color | — | |
--tanrei-comp-menu-current-control-color | — | |
--tanrei-comp-menu-current-disabled-container-color | — | |
--tanrei-comp-menu-current-disabled-control-mark-color | — | |
--tanrei-comp-menu-current-disabled-control-color | — | |
--tanrei-comp-menu-current-disabled-label-color | — | |
--tanrei-comp-menu-current-disabled-leading-icon-color | — | |
--tanrei-comp-menu-current-disabled-mark-color | — | |
--tanrei-comp-menu-current-disabled-state-layer-color | — | |
--tanrei-comp-menu-current-disabled-supporting-text-color | — | |
--tanrei-comp-menu-current-disabled-trailing-icon-color | — | |
--tanrei-comp-menu-current-disabled-trailing-text-color | — | |
--tanrei-comp-menu-current-focused-container-color | — | |
--tanrei-comp-menu-current-focused-control-mark-color | — | |
--tanrei-comp-menu-current-focused-control-color | — | |
--tanrei-comp-menu-current-focused-label-color | — | |
--tanrei-comp-menu-current-focused-leading-icon-color | — | |
--tanrei-comp-menu-current-focused-mark-color | — | |
--tanrei-comp-menu-current-focused-state-layer-color | — | |
--tanrei-comp-menu-current-focused-supporting-text-color | — | |
--tanrei-comp-menu-current-focused-trailing-icon-color | — | |
--tanrei-comp-menu-current-focused-trailing-text-color | — | |
--tanrei-comp-menu-current-hovered-container-color | — | |
--tanrei-comp-menu-current-hovered-control-mark-color | — | |
--tanrei-comp-menu-current-hovered-control-color | — | |
--tanrei-comp-menu-current-hovered-label-color | — | |
--tanrei-comp-menu-current-hovered-leading-icon-color | — | |
--tanrei-comp-menu-current-hovered-mark-color | — | |
--tanrei-comp-menu-current-hovered-state-layer-color | — | |
--tanrei-comp-menu-current-hovered-supporting-text-color | — | |
--tanrei-comp-menu-current-hovered-trailing-icon-color | — | |
--tanrei-comp-menu-current-hovered-trailing-text-color | — | |
--tanrei-comp-menu-current-label-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-current-leading-icon-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-current-mark-color | — | |
--tanrei-comp-menu-current-pressed-container-color | — | |
--tanrei-comp-menu-current-pressed-control-mark-color | — | |
--tanrei-comp-menu-current-pressed-control-color | — | |
--tanrei-comp-menu-current-pressed-label-color | — | |
--tanrei-comp-menu-current-pressed-leading-icon-color | — | |
--tanrei-comp-menu-current-pressed-mark-color | — | |
--tanrei-comp-menu-current-pressed-state-layer-color | — | |
--tanrei-comp-menu-current-pressed-supporting-text-color | — | |
--tanrei-comp-menu-current-pressed-trailing-icon-color | — | |
--tanrei-comp-menu-current-pressed-trailing-text-color | — | |
--tanrei-comp-menu-current-state-layer-color | comp.menu.current.label.color | |
--tanrei-comp-menu-current-supporting-text-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-current-trailing-icon-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-current-trailing-text-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-destructive-container-color | — | |
--tanrei-comp-menu-destructive-control-mark-color | — | |
--tanrei-comp-menu-destructive-control-color | — | |
--tanrei-comp-menu-destructive-disabled-container-color | — | |
--tanrei-comp-menu-destructive-disabled-control-mark-color | — | |
--tanrei-comp-menu-destructive-disabled-control-color | — | |
--tanrei-comp-menu-destructive-disabled-label-color | — | |
--tanrei-comp-menu-destructive-disabled-leading-icon-color | — | |
--tanrei-comp-menu-destructive-disabled-mark-color | — | |
--tanrei-comp-menu-destructive-disabled-state-layer-color | — | |
--tanrei-comp-menu-destructive-disabled-supporting-text-color | — | |
--tanrei-comp-menu-destructive-disabled-trailing-icon-color | — | |
--tanrei-comp-menu-destructive-disabled-trailing-text-color | — | |
--tanrei-comp-menu-destructive-focused-container-color | — | |
--tanrei-comp-menu-destructive-focused-control-mark-color | — | |
--tanrei-comp-menu-destructive-focused-control-color | — | |
--tanrei-comp-menu-destructive-focused-label-color | — | |
--tanrei-comp-menu-destructive-focused-leading-icon-color | — | |
--tanrei-comp-menu-destructive-focused-mark-color | — | |
--tanrei-comp-menu-destructive-focused-state-layer-color | — | |
--tanrei-comp-menu-destructive-focused-supporting-text-color | — | |
--tanrei-comp-menu-destructive-focused-trailing-icon-color | — | |
--tanrei-comp-menu-destructive-focused-trailing-text-color | — | |
--tanrei-comp-menu-destructive-hovered-container-color | — | |
--tanrei-comp-menu-destructive-hovered-control-mark-color | — | |
--tanrei-comp-menu-destructive-hovered-control-color | — | |
--tanrei-comp-menu-destructive-hovered-label-color | — | |
--tanrei-comp-menu-destructive-hovered-leading-icon-color | — | |
--tanrei-comp-menu-destructive-hovered-mark-color | — | |
--tanrei-comp-menu-destructive-hovered-state-layer-color | — | |
--tanrei-comp-menu-destructive-hovered-supporting-text-color | — | |
--tanrei-comp-menu-destructive-hovered-trailing-icon-color | — | |
--tanrei-comp-menu-destructive-hovered-trailing-text-color | — | |
--tanrei-comp-menu-destructive-label-color | sys.color.error | |
--tanrei-comp-menu-destructive-leading-icon-color | sys.color.error | |
--tanrei-comp-menu-destructive-mark-color | — | |
--tanrei-comp-menu-destructive-pressed-container-color | — | |
--tanrei-comp-menu-destructive-pressed-control-mark-color | — | |
--tanrei-comp-menu-destructive-pressed-control-color | — | |
--tanrei-comp-menu-destructive-pressed-label-color | — | |
--tanrei-comp-menu-destructive-pressed-leading-icon-color | — | |
--tanrei-comp-menu-destructive-pressed-mark-color | — | |
--tanrei-comp-menu-destructive-pressed-state-layer-color | — | |
--tanrei-comp-menu-destructive-pressed-supporting-text-color | — | |
--tanrei-comp-menu-destructive-pressed-trailing-icon-color | — | |
--tanrei-comp-menu-destructive-pressed-trailing-text-color | — | |
--tanrei-comp-menu-destructive-state-layer-color | comp.menu.destructive.label.color | |
--tanrei-comp-menu-destructive-supporting-text-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-destructive-trailing-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-destructive-trailing-text-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-disabled-container-shape | — | 4px |
--tanrei-comp-menu-disabled-opacity | — | 1 |
--tanrei-comp-menu-divider-color | sys.color.outline-variant | |
--tanrei-comp-menu-divider-space | sys.space.space50 | 4px |
--tanrei-comp-menu-divider-thickness | sys.border.hairline | 1px |
--tanrei-comp-menu-expanded-container-color | sys.color.surface-container-high | |
--tanrei-comp-menu-expanded-control-mark-color | — | |
--tanrei-comp-menu-expanded-control-color | — | |
--tanrei-comp-menu-expanded-disabled-container-color | — | |
--tanrei-comp-menu-expanded-disabled-control-mark-color | — | |
--tanrei-comp-menu-expanded-disabled-control-color | — | |
--tanrei-comp-menu-expanded-disabled-label-color | — | |
--tanrei-comp-menu-expanded-disabled-leading-icon-color | — | |
--tanrei-comp-menu-expanded-disabled-mark-color | — | |
--tanrei-comp-menu-expanded-disabled-state-layer-color | — | |
--tanrei-comp-menu-expanded-disabled-supporting-text-color | — | |
--tanrei-comp-menu-expanded-disabled-trailing-icon-color | — | |
--tanrei-comp-menu-expanded-disabled-trailing-text-color | — | |
--tanrei-comp-menu-expanded-focused-container-color | — | |
--tanrei-comp-menu-expanded-focused-control-mark-color | — | |
--tanrei-comp-menu-expanded-focused-control-color | — | |
--tanrei-comp-menu-expanded-focused-label-color | — | |
--tanrei-comp-menu-expanded-focused-leading-icon-color | — | |
--tanrei-comp-menu-expanded-focused-mark-color | — | |
--tanrei-comp-menu-expanded-focused-state-layer-color | — | |
--tanrei-comp-menu-expanded-focused-supporting-text-color | — | |
--tanrei-comp-menu-expanded-focused-trailing-icon-color | — | |
--tanrei-comp-menu-expanded-focused-trailing-text-color | — | |
--tanrei-comp-menu-expanded-hovered-container-color | — | |
--tanrei-comp-menu-expanded-hovered-control-mark-color | — | |
--tanrei-comp-menu-expanded-hovered-control-color | — | |
--tanrei-comp-menu-expanded-hovered-label-color | — | |
--tanrei-comp-menu-expanded-hovered-leading-icon-color | — | |
--tanrei-comp-menu-expanded-hovered-mark-color | — | |
--tanrei-comp-menu-expanded-hovered-state-layer-color | — | |
--tanrei-comp-menu-expanded-hovered-supporting-text-color | — | |
--tanrei-comp-menu-expanded-hovered-trailing-icon-color | — | |
--tanrei-comp-menu-expanded-hovered-trailing-text-color | — | |
--tanrei-comp-menu-expanded-label-color | sys.color.on-surface | |
--tanrei-comp-menu-expanded-leading-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-expanded-mark-color | — | |
--tanrei-comp-menu-expanded-pressed-container-color | — | |
--tanrei-comp-menu-expanded-pressed-control-mark-color | — | |
--tanrei-comp-menu-expanded-pressed-control-color | — | |
--tanrei-comp-menu-expanded-pressed-label-color | — | |
--tanrei-comp-menu-expanded-pressed-leading-icon-color | — | |
--tanrei-comp-menu-expanded-pressed-mark-color | — | |
--tanrei-comp-menu-expanded-pressed-state-layer-color | — | |
--tanrei-comp-menu-expanded-pressed-supporting-text-color | — | |
--tanrei-comp-menu-expanded-pressed-trailing-icon-color | — | |
--tanrei-comp-menu-expanded-pressed-trailing-text-color | — | |
--tanrei-comp-menu-expanded-state-layer-color | comp.menu.expanded.label.color | |
--tanrei-comp-menu-expanded-supporting-text-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-expanded-trailing-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-expanded-trailing-text-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-focused-container-shape | — | 4px |
--tanrei-comp-menu-focused-opacity | — | 1 |
--tanrei-comp-menu-group-title-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-group-title-padding-inline-compact | comp.menu.container.padding-inline.compact | 8px |
--tanrei-comp-menu-group-title-padding-inline-regular | comp.menu.container.padding-inline.regular | 12px |
--tanrei-comp-menu-group-gap | sys.space.space200 | 16px |
--tanrei-comp-menu-hovered-container-shape | — | 4px |
--tanrei-comp-menu-hovered-opacity | — | 1 |
--tanrei-comp-menu-leading-icon-size-compact | sys.icon.small | 20px |
--tanrei-comp-menu-leading-icon-size-regular | sys.icon.medium | 24px |
--tanrei-comp-menu-mark-size-compact | sys.icon.small | 20px |
--tanrei-comp-menu-mark-size-regular | sys.icon.medium | 24px |
--tanrei-comp-menu-mark-width | sys.border.thick | 2px |
--tanrei-comp-menu-motion-close-duration | sys.motion.role.appear.anchored.exit | 250ms |
--tanrei-comp-menu-motion-close-easing | sys.motion.easing.exit | cubic-bezier(0.3, 0, 1, 1) |
--tanrei-comp-menu-motion-feedback-duration | sys.motion.role.feedback | 450ms |
--tanrei-comp-menu-motion-open-duration | sys.motion.role.appear.anchored.enter | 300ms |
--tanrei-comp-menu-motion-open-easing | sys.motion.easing.enter | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-menu-motion-state-duration | sys.motion.role.state.large | 250ms |
--tanrei-comp-menu-motion-state-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-menu-motion-submenu-delay | sys.motion.duration.short4 | 200ms |
--tanrei-comp-menu-pressed-container-shape | — | 4px |
--tanrei-comp-menu-pressed-opacity | — | 1 |
--tanrei-comp-menu-radio-item-mark | — | 0 |
--tanrei-comp-menu-selected-container-color | sys.color.secondary-container | |
--tanrei-comp-menu-selected-control-mark-color | sys.color.on-primary | |
--tanrei-comp-menu-selected-control-color | sys.color.primary | |
--tanrei-comp-menu-selected-disabled-container-color | — | |
--tanrei-comp-menu-selected-disabled-control-mark-color | — | |
--tanrei-comp-menu-selected-disabled-control-color | — | |
--tanrei-comp-menu-selected-disabled-label-color | — | |
--tanrei-comp-menu-selected-disabled-leading-icon-color | — | |
--tanrei-comp-menu-selected-disabled-mark-color | — | |
--tanrei-comp-menu-selected-disabled-state-layer-color | — | |
--tanrei-comp-menu-selected-disabled-supporting-text-color | — | |
--tanrei-comp-menu-selected-disabled-trailing-icon-color | — | |
--tanrei-comp-menu-selected-disabled-trailing-text-color | — | |
--tanrei-comp-menu-selected-focused-container-color | — | |
--tanrei-comp-menu-selected-focused-control-mark-color | — | |
--tanrei-comp-menu-selected-focused-control-color | — | |
--tanrei-comp-menu-selected-focused-label-color | — | |
--tanrei-comp-menu-selected-focused-leading-icon-color | — | |
--tanrei-comp-menu-selected-focused-mark-color | — | |
--tanrei-comp-menu-selected-focused-state-layer-color | — | |
--tanrei-comp-menu-selected-focused-supporting-text-color | — | |
--tanrei-comp-menu-selected-focused-trailing-icon-color | — | |
--tanrei-comp-menu-selected-focused-trailing-text-color | — | |
--tanrei-comp-menu-selected-hovered-container-color | — | |
--tanrei-comp-menu-selected-hovered-control-mark-color | — | |
--tanrei-comp-menu-selected-hovered-control-color | — | |
--tanrei-comp-menu-selected-hovered-label-color | — | |
--tanrei-comp-menu-selected-hovered-leading-icon-color | — | |
--tanrei-comp-menu-selected-hovered-mark-color | — | |
--tanrei-comp-menu-selected-hovered-state-layer-color | — | |
--tanrei-comp-menu-selected-hovered-supporting-text-color | — | |
--tanrei-comp-menu-selected-hovered-trailing-icon-color | — | |
--tanrei-comp-menu-selected-hovered-trailing-text-color | — | |
--tanrei-comp-menu-selected-label-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-selected-leading-icon-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-selected-mark-color | sys.color.primary | |
--tanrei-comp-menu-selected-pressed-container-color | — | |
--tanrei-comp-menu-selected-pressed-control-mark-color | — | |
--tanrei-comp-menu-selected-pressed-control-color | — | |
--tanrei-comp-menu-selected-pressed-label-color | — | |
--tanrei-comp-menu-selected-pressed-leading-icon-color | — | |
--tanrei-comp-menu-selected-pressed-mark-color | — | |
--tanrei-comp-menu-selected-pressed-state-layer-color | — | |
--tanrei-comp-menu-selected-pressed-supporting-text-color | — | |
--tanrei-comp-menu-selected-pressed-trailing-icon-color | — | |
--tanrei-comp-menu-selected-pressed-trailing-text-color | — | |
--tanrei-comp-menu-selected-state-layer-color | comp.menu.selected.label.color | |
--tanrei-comp-menu-selected-supporting-text-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-selected-trailing-icon-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-selected-trailing-text-color | sys.color.on-secondary-container | |
--tanrei-comp-menu-separation | sys.behavior.separation | 0 |
--tanrei-comp-menu-shortcut-variant | — | 0 |
--tanrei-comp-menu-submenu-offset | sys.space.space50 | 4px |
--tanrei-comp-menu-surface-color | sys.color.surface-bright | |
--tanrei-comp-menu-surface-elevation | sys.elevation.level2 | 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04) |
--tanrei-comp-menu-surface-max-width | — | 320px |
--tanrei-comp-menu-surface-min-width | — | 160px |
--tanrei-comp-menu-surface-offset | sys.space.space50 | 4px |
--tanrei-comp-menu-surface-padding-block | sys.space.space50 | 4px |
--tanrei-comp-menu-surface-padding-inline | sys.space.space50 | 4px |
--tanrei-comp-menu-surface-shape | sys.shape.corner.medium | 8px |
--tanrei-comp-menu-surface-viewport-margin | sys.space.space100 | 8px |
--tanrei-comp-menu-trailing-icon-size-compact | sys.icon.small | 20px |
--tanrei-comp-menu-trailing-icon-size-regular | sys.icon.medium | 24px |
--tanrei-comp-menu-unselected-container-color | — | |
--tanrei-comp-menu-unselected-control-mark-color | — | |
--tanrei-comp-menu-unselected-control-color | sys.color.outline | |
--tanrei-comp-menu-unselected-disabled-container-color | — | |
--tanrei-comp-menu-unselected-disabled-control-mark-color | — | |
--tanrei-comp-menu-unselected-disabled-control-color | — | |
--tanrei-comp-menu-unselected-disabled-label-color | — | |
--tanrei-comp-menu-unselected-disabled-leading-icon-color | — | |
--tanrei-comp-menu-unselected-disabled-mark-color | — | |
--tanrei-comp-menu-unselected-disabled-state-layer-color | — | |
--tanrei-comp-menu-unselected-disabled-supporting-text-color | — | |
--tanrei-comp-menu-unselected-disabled-trailing-icon-color | — | |
--tanrei-comp-menu-unselected-disabled-trailing-text-color | — | |
--tanrei-comp-menu-unselected-focused-container-color | — | |
--tanrei-comp-menu-unselected-focused-control-mark-color | — | |
--tanrei-comp-menu-unselected-focused-control-color | — | |
--tanrei-comp-menu-unselected-focused-label-color | — | |
--tanrei-comp-menu-unselected-focused-leading-icon-color | — | |
--tanrei-comp-menu-unselected-focused-mark-color | — | |
--tanrei-comp-menu-unselected-focused-state-layer-color | — | |
--tanrei-comp-menu-unselected-focused-supporting-text-color | — | |
--tanrei-comp-menu-unselected-focused-trailing-icon-color | — | |
--tanrei-comp-menu-unselected-focused-trailing-text-color | — | |
--tanrei-comp-menu-unselected-hovered-container-color | — | |
--tanrei-comp-menu-unselected-hovered-control-mark-color | — | |
--tanrei-comp-menu-unselected-hovered-control-color | — | |
--tanrei-comp-menu-unselected-hovered-label-color | — | |
--tanrei-comp-menu-unselected-hovered-leading-icon-color | — | |
--tanrei-comp-menu-unselected-hovered-mark-color | — | |
--tanrei-comp-menu-unselected-hovered-state-layer-color | — | |
--tanrei-comp-menu-unselected-hovered-supporting-text-color | — | |
--tanrei-comp-menu-unselected-hovered-trailing-icon-color | — | |
--tanrei-comp-menu-unselected-hovered-trailing-text-color | — | |
--tanrei-comp-menu-unselected-label-color | sys.color.on-surface | |
--tanrei-comp-menu-unselected-leading-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-unselected-mark-color | — | |
--tanrei-comp-menu-unselected-pressed-container-color | — | |
--tanrei-comp-menu-unselected-pressed-control-mark-color | — | |
--tanrei-comp-menu-unselected-pressed-control-color | — | |
--tanrei-comp-menu-unselected-pressed-label-color | — | |
--tanrei-comp-menu-unselected-pressed-leading-icon-color | — | |
--tanrei-comp-menu-unselected-pressed-mark-color | — | |
--tanrei-comp-menu-unselected-pressed-state-layer-color | — | |
--tanrei-comp-menu-unselected-pressed-supporting-text-color | — | |
--tanrei-comp-menu-unselected-pressed-trailing-icon-color | — | |
--tanrei-comp-menu-unselected-pressed-trailing-text-color | — | |
--tanrei-comp-menu-unselected-state-layer-color | comp.menu.unselected.label.color | |
--tanrei-comp-menu-unselected-supporting-text-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-unselected-trailing-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-unselected-trailing-text-color | sys.color.on-surface-variant | |
--tanrei-comp-menu-group-title-typescale | sys.typescale.label-small | 500 12px/1.3333 |
--tanrei-comp-menu-label-typescale | sys.typescale.label | 500 14px/1.4286 |
--tanrei-comp-menu-supporting-text-typescale | sys.typescale.body-small | 400 13px/1.5385 |
--tanrei-comp-menu-trailing-text-typescale | sys.typescale.label-small | 500 12px/1.3333 |
نماذج الاختبار
عروض مرجعية. تلتقط كل المنصات لقطة لكل نموذج بالمعرّف نفسه حتى يمكن مقارنة النتائج.
density: regular (39)
actions
open: true items: 3 مرسوم وحده في نافذة بحجم 192 × 192icons-shortcut
open: true items: 3 مرسوم وحده في نافذة بحجم 193 × 192groups
open: true items: 2 مرسوم وحده في نافذة بحجم 192 × 249group-titles
open: true items: 2 مرسوم وحده في نافذة بحجم 192 × 361group-titles-icons
open: true items: 2 مرسوم وحده في نافذة بحجم 192 × 314supporting-text
open: true items: 2 مرسوم وحده في نافذة بحجم 288 × 155trailing-icon
open: true items: 2 مرسوم وحده في نافذة بحجم 253 × 145checkbox-checked
open: true items: 3 مرسوم وحده في نافذة بحجم 192 × 193checkbox-unchecked
open: true items: 2 مرسوم وحده في نافذة بحجم 192 × 145radio-checked
open: true items: 1 مرسوم وحده في نافذة بحجم 192 × 193destructive
open: true items: 2 مرسوم وحده في نافذة بحجم 192 × 145disabled
open: true items: 2 مرسوم وحده في نافذة بحجم 192 × 145checked-disabled
open: true items: 2 مرسوم وحده في نافذة بحجم 192 × 145destructive-disabled
open: true items: 2 مرسوم وحده في نافذة بحجم 192 × 145groups-divider
open: true separation: divider items: 2 مرسوم وحده في نافذة بحجم 192 × 250groups-gap
open: true separation: gap items: 2 مرسوم وحده في نافذة بحجم 192 × 265group-titles-gap
open: true separation: gap items: 2 مرسوم وحده في نافذة بحجم 192 × 377radio-none-checked
open: true items: 1 مرسوم وحده في نافذة بحجم 192 × 145radio-checked-disabled
open: true items: 1 مرسوم وحده في نافذة بحجم 192 × 145checkbox-check-mark
open: true items: 3 مرسوم وحده في نافذة بحجم 192 × 193radio-check-mark
open: true items: 1 مرسوم وحده في نافذة بحجم 192 × 193mark-icon-custom
open: true items: 3 مرسوم وحده في نافذة بحجم 192 × 193marks-icons-shortcut
open: true items: 2 مرسوم وحده في نافذة بحجم 257 × 314selected-supporting-text
open: true items: 1 مرسوم وحده في نافذة بحجم 343 × 165submenu
open: true items: 3 مرسوم وحده في نافذة بحجم 192 × 193submenu-open
open: true items: 3 مرسوم وحده في نافذة بحجم 480 × 345submenu-open-rtl
open: true direction: rtl items: 3 مرسوم وحده في نافذة بحجم 480 × 345submenu-two-levels
open: true items: 3 مرسوم وحده في نافذة بحجم 720 × 345submenu-icons-trailing-text
open: true items: 3 مرسوم وحده في نافذة بحجم 268 × 203submenu-disabled
open: true items: 3 مرسوم وحده في نافذة بحجم 192 × 193submenu-choices
open: true items: 2 مرسوم وحده في نافذة بحجم 480 × 297links
open: true items: 4 مرسوم وحده في نافذة بحجم 192 × 241links-current
open: true items: 3 مرسوم وحده في نافذة بحجم 192 × 193links-rtl
open: true direction: rtl items: 2 مرسوم وحده في نافذة بحجم 192 × 145links-trailing
open: true items: 4 مرسوم وحده في نافذة بحجم 263 × 241icon-space-auto
open: true items: 5 مرسوم وحده في نافذة بحجم 192 × 483icon-space-always
open: true iconSpace: always items: 5 مرسوم وحده في نافذة بحجم 215 × 483icon-space-never
open: true iconSpace: never items: 5 مرسوم وحده في نافذة بحجم 192 × 483japanese
open: true items: 2 مرسوم وحده في نافذة بحجم 327 × 206density: compact (1)
density-compact
open: true density: compact items: 5 مرسوم وحده في نافذة بحجم 201 × 287مولَّد من specs/menu.spec.json, tokens/comp/menu.tokens.json, design/tokens/
استخدم قائمة الخيارات للإجراءات الثانوية والإعدادات التي لا تحتاج إلى مكان على الشاشة: «المزيد» على صف، والقائمة الإضافية (overflow) لشريط أدوات، وترتيب الفرز، وخيارات عرض. يفتحها الأشخاص، ويختارون شيئًا واحدًا، ويعودون إلى ما كانوا يفعلونه.
استخدم الأزرار للإجراءات الرئيسية في الشاشة، لأن قائمة الخيارات تخفيها. استخدم قائمة (list) أو أزرار اختيار أو عنصر اختيار (select) حين يكون الاختيار هو المهمة الرئيسية في الشاشة. استخدم مربع حوار حين يجب أن يجيب الأشخاص قبل أن يتابعوا. قائمة الخيارات لا تحمل إلا عناصرها: لا حقول ولا أشرطة تمرير ولا مفاتيح تبديل، فهذه تأخذ المفاتيح التي تحتاج إليها قائمة الخيارات.
الصف الذي يذهب إلى مكان عنصر رابط: أعطِ العنصر href. استخدمه لمجموعة قصيرة من الأماكن تبقى غير ظاهرة إلى أن تُطلب، كالمستويات المطوية في مسار التنقل (breadcrumbs)، أو اختيار اللغة، أو «المزيد». أما الصفحات الرئيسية للموقع، وهي ظاهرة دائمًا، فهي تنقّل: ضعها في شريط تنقل أو درج تنقل أو قائمة (list) من الروابط.
| النوع | ما هو | بعد تفعيله | يُستخدم لـ |
|---|---|---|---|
| عنصر | إجراء، أو رابط حين يكون له href |
يُصدر حدث click الخاص به، ثم تُغلق قائمة الخيارات. ووجهة عنصر الرابط تُفتح بعد ذلك. | «نسخ»، «إعادة التسمية»، «مشاركة». اضبط destructive للإجراء «حذف» أو «تجاهل»: يُرسم بلون الخطأ. وأعطِه href لمكان يُذهب إليه. |
| عنصر خانة اختيار | خيار مفعَّل أو غير مفعَّل بمفرده | يبدّل قيمة checked الخاصة به ويُصدر حدث change الخاص به. |
«إظهار الشبكة»، «إظهار المساطر»: إعدادات لا يؤثر بعضها في بعض. |
| عنصر زر اختيار | اختيار واحد من عدة اختيارات في مجموعته | يضبط قيمة value في مجموعته وتُصدر المجموعة حدث change الخاص بها. عنصر زر الاختيار المحدَّد لا يُصدر شيئًا. |
«الترتيب حسب الاسم»، «الترتيب حسب التاريخ»: جواب واحد عن سؤال واحد. |
| قائمة فرعية | عنصر يفتح مستوى آخر بجانبه | يفتح قائمته الفرعية ويُصدر حدث toggle الخاص به. وتبقى قائمة الخيارات مفتوحة. | «فتح الأخيرة»، «مشاركة»، «نقل إلى»: مجموعة ثانية من العناصر كانت ستجعل قائمة الخيارات أطول مما ينبغي. |
| مجموعة | سلسلة من عناصر مترابطة، ولها عنوان إن شئت | لا شيء: لا تأخذ التركيز. | فصل العناصر المترابطة عن غيرها، وحمل عناصر أزرار الاختيار لسؤال واحد. |
عنصر خانة الاختيار أو زر الاختيار الذي يُفعَّل بلمسة أو نقرة أو بالمفتاح Enter يغلق قائمة الخيارات، كالإجراء. وإن فُعِّل بالمفتاح Space ترك قائمة الخيارات مفتوحة، فيستطيع مستخدم لوحة المفاتيح أن يغيّر عدة إعدادات دفعة واحدة.
- تفصل قائمة الخيارات المجموعة عمّا يجاورها أيًّا كان، ولا تفصل أبدًا عند الأعلى أو الأسفل: بفاصل، أو بفجوة. مع الفجوة تكون كل مجموعة، وكل سلسلة من العناصر خارج المجموعات، لوحًا (sheet) قائمًا بذاته، بين الألواح فراغ. لا تضع الفواصل ولا الألواح بنفسك.
- أي الاثنين يُستخدم اختيار لنظام التصميم، اختيار واحد لكل مكوّن يفصل بين أجزائه: فاصل في النظام الافتراضي. والخاصية
separationتقرر لقائمة خيارات واحدة وقوائمها الفرعية: القيمةauto(الافتراضية) تتبع نظام التصميم، والقيمةdividerأوgapتسمّي واحدًا منهما أيًّا كان اختيار النظام. مع الفجوة لا يُعرَض للتقنيات المساعدة أي فاصل، وتبقى المجموعات معروضة لها مجموعاتٍ. - المجموعة التي لها
titleترسمه عنوانًا هادئًا فوق عناصرها، يبدأ حيث يبدأ محتوى الصفوف، ويكون اسمًا لها. أعطِ مجموعة عناصر أزرار الاختيار عنوانًا يطرح السؤال، مثل «ترتيب حسب». - مكان عناصر أزرار الاختيار داخل مجموعة. عنصر زر الاختيار خارج المجموعة لا يكون محدَّدًا أبدًا، وتبلّغ عنه بنيات التنقيح (debug).
- لا تحمل المجموعة مجموعة أخرى. عناصر المجموعة الداخلية تُرسم كأنها عناصر المجموعة الخارجية.
علامة عنصر خانة الاختيار أو عنصر زر الاختيار تقع في عمود العلامة في بداية الصف، والأيقونة الأمامية في عمود الأيقونة بعده. والخاصية iconSpace تقرر أي الصفوف تحتفظ بمساحة عمود لا ترسم فيه شيئًا:
auto(القيمة الافتراضية) تقرر لكل قسم على حدة: والقسم مجموعة، أو سلسلة من العناصر المتتالية خارج أي مجموعة. يحتفظ الصف بمساحة العمود ما دام عنصر آخر من قسمه يرسم فيه، فتبدأ تسميات القسم على خط واحد. والقسم الذي ليس فيه علامة ولا أيقونة يبدأ تسمياته حيث يبدأ محتوى الصف، بلا مساحة فارغة قبلها.alwaysتحتفظ بالعمودين في كل صف، فتبدأ كل تسميات قائمة الخيارات على خط واحد.neverلا تعطي العمود إلا للصف الذي يرسم فيه، فتبدأ كل تسمية بعد علامتها أو أيقونتها مباشرة.
كل سطح يقرر لنفسه، والقائمة الفرعية تتبع iconSpace قائمتها. ويبدأ عنوان المجموعة حيث يبدأ محتوى الصفوف، أيًّا كان ما تحتفظ به صفوفها. وفي عرض الشجرة ودرج التنقل الخاصية iconSpace نفسها، بالقيم نفسها والقيمة الافتراضية نفسها.
تحدد density المساحة التي يأخذها الصف. regular (الافتراضية: صف بارتفاع 48 ورموز بحجم 24) لمنتج يُستخدم باللمس وبالمؤشر معًا. compact (صف بارتفاع 32 ورموز بحجم 20، الصف المضغوط في عرض الشجرة) لأداة تُستخدم بالفأرة، قوائمها طويلة وتُفتح كثيرًا. يبقى حجم النص كما هو في الحالتين. باللمس لا يكون الصف أبدًا أقل من هدف اللمس، فتبقى صفوف القائمة المضغوطة على الهاتف بارتفاع 48. وتأخذ كل قائمة فرعية كثافة قائمتها.
عنصر الرابط هو رابط المنصة نفسه، فتعمل معه قائمة السياق في المتصفح، والفتح في تبويب جديد، والعنوان الذي يظهر عند التحويم. وليس له مظهر خاص به: يُرسم كالإجراء الذي في الصف المجاور، في كل حالة.
- التفعيل. النقرة، أو اللمسة، أو المفتاح Enter أو Space تُصدر الحدث
clickالخاص بالعنصر، ثم تُغلق قائمة الخيارات، ثم تُفتح الوجهة. والمفتاح Space يفعّل عنصر الرابط كما يفعّل كل صف آخر في قائمة الخيارات. أما في غيرها فلا يفعّل المفتاح Space رابطًا. - الموجِّه. ألغِ الحدث
clickالخاص بالعنصر لتتولى التنقل بنفسك. وتُغلق قائمة الخيارات مع ذلك، ولا يُفتح شيء آخر. - التبويبات الأخرى. النقرة أو المفتاح Enter مع مفتاح تعديل، والنقرة بالزر الأوسط، من شأن المتصفح. لا تُصدر قائمة الخيارات الحدث
closeوتبقى مفتوحة، فيمكن فتح عدة روابط منها واحدًا بعد آخر. - سياق جديد. القيمة
target: newتفتح تبويبًا جديدًا أو نافذة جديدة. وينتهي العنصر بالرسمopen_in_newمن مجموعة الأيقونات في عمود أيقونة النهاية، ما لم تسمِّ الخاصيةtrailingIconرسمًا من عندك، وتقول الخاصيةtargetLabelذلك للتقنيات المساعدة. والاختصار أو نص النهاية يحتفظ بعموده قبل العلامة. - الصفحة التي أنت فيها. الخاصية
currentتميّز عنصر الرابط الخاص بالصفحة التي فيها الشخص. ويُعرَض للتقنيات المساعدة على أنه الصفحة الحالية (aria-current="page"على الويب)، ويُرسم بتعبئة خاصة به في كل مخططات التباين، بلا علامة صح وبلا تسمية أثقل: فهو يقول أين الشخص، لا اختيارًا اختاره. أنت تضبطها، وقائمة الخيارات لا تضبطها أبدًا. وبلاhrefتُتجاهَل. - التعطيل. عنصر الرابط المعطَّل لا يمكن اتباعه بأي وسيلة. ويبقى الوصول إليه بمفاتيح الأسهم ممكنًا، كأي عنصر معطَّل.
- النوع
itemوحده. عنصر خانة الاختيار وعنصر زر الاختيار وعنصر القائمة الفرعية لا تأخذ وجهة: فهي تحمل حالة أو تفتح مستوى آخر.
عنصر القائمة الفرعية صف ينتهي بالأيقونة chevron_right من مجموعة الأيقونات، وعناصره الفرعية هي المستوى التالي: عناصر من أي نوع، ومنها المجموعات. ليس إجراءً وليس له اختصار.
- الفتح. الضغط، أو اللمسة، أو المفتاح Enter أو Space، أو مفتاح السهم الذي يشير إلى نهاية السطر يفتح القائمة الفرعية فورًا وينقل التركيز إلى أول عنصر مفعَّل فيها. والمؤشر الذي يستقر على العنصر يفتحها بعد مهلة قصيرة ويترك التركيز على العنصر.
- في الطريق إليها. ما دام المؤشر يتحرك من العنصر نحو قائمته الفرعية المفتوحة، فالعناصر التي يعبرها لا تأخذ تحويمًا ولا تركيزًا، فلا يغلق المسار المائل القائمة الفرعية.
- إغلاق مستوى واحد. مفتاح السهم الذي يشير إلى بداية السطر، أو المفتاح Escape، يغلق المستوى الذي فيه التركيز ويعيد التركيز إلى عنصره. ولا يغلق المفتاح Escape قائمة الخيارات كلها إلا حين لا تكون أي قائمة فرعية مفتوحة. والانتقال إلى عنصر آخر في المستوى الأب يغلق القائمة الفرعية أيضًا. ولا تُفتح في السطح الواحد أكثر من قائمة فرعية واحدة.
- إغلاق كل شيء. إجراء في أي مستوى، والمفتاح Tab، وضغط خارج كل الأسطح، وخروج التركيز منها جميعًا، كلها تغلق قائمة الخيارات كلها بحدث
closeواحد. - أين توضع. بجانب السطح الذي يحمل عنصرها، من جهة النهاية، والصف الأول منها بمستوى صف العنصر؛ ومن الجهة الأخرى حيث لا تتّسع تلك الجهة. وفي نافذة أضيق من أن تتّسع لأي منهما توضع فوق سطحها الأب وتترك شريطًا منه مكشوفًا: الضغط على الشريط يغلق القائمة الفرعية. ولا تضع قائمة الخيارات أبدًا محتوى القائمة الفرعية مكان محتواها.
- الخاصية
openوالحدثtoggle. قيمةopenلعنصر القائمة الفرعية ملك له. تضبطها قائمة الخيارات ويُصدر العنصرtoggleحين يفتح الشخص القائمة الفرعية أو يغلقها. أما تغييرك أنت لقيمةopen، وإغلاق قائمة الخيارات كلها، فلا يُصدرانtoggle. - العمق. التزم مستويين من القوائم الفرعية تحت قائمة الخيارات. المستوى الثالث يُرسم مع ذلك، وتبلّغ عنه بنيات التنقيح، كما تبلّغ عن قائمة فرعية بلا عناصر.
- قيمة
valueفي المجموعة لا تصل إلا إلى عناصر أزرار الاختيار في سطحها هي، وكل سطح يقرر عمودي العلامات والأيقونات الخاصين به.
تعرض قائمة السياق الإجراءات التي تنطبق على ما يشير إليه الشخص، في المكان الذي يشير إليه. وهي القائمة نفسها، بالعناصر والمفاتيح والقوائم الفرعية نفسها، لكنها تُفتح عند نقطة من منطقة بدلًا من أن تُفتح من زر.
- المنطقة. اضبط
triggerعلىcontext، وanchorعلى معرّف المنطقة: لوحة رسم، أو قائمة، أو صف في جدول، أو مستند. المنطقة ليست زرًا، فلا تكتب القائمة عليها أي حالة لنافذة منبثقة. - كيف تُطلب. النقر بزر الفأرة الأيمن (وعلى Mac أيضًا النقر مع Control أو بإصبعين)، أو إبقاء اللمس أو القلم ثابتًا نصف ثانية، أو Shift+F10 أو مفتاح ContextMenu على العنصر الذي يملك التركيز، أو أمر قائمة السياق في قارئ الشاشة. تُطلق القائمة
contextRequestومعه النقطة، والعنصر الذي طُلبت من أجله، وكيف طُلبت:pointerأوlong-pressأوkeyboard. وأنت تضبطopen، كما تفعل عند الفتح من عنصر الارتكاز. - أين تُفتح. بإحدى زواياها على النقطة، بلا مسافة: افتراضيًا أسفل النقطة ونحو نهاية السطر، كما يحدد
placement. وقرب حافة النافذة تُفتح على الجانب الآخر من النقطة، على كل محور وحده، فلا تغطي النقطة أبدًا. وتتحرك مع المنطقة حين تُمرَّر الصفحة. وإذا طُلبت بمفتاح فإنها تُفتح بجوار العنصر الذي يملك التركيز، كما تُفتح بجوار عنصر الارتكاز. - قائمة المتصفح نفسه. تُلغي القائمة قائمة السياق الخاصة بالمتصفح في كل طلب تستجيب له. ألغِ
contextRequestلتترك طلبًا للمتصفح، مثلًا فوق حقل أو رابط أو نص محدد لا تتعامل معه قائمتك. - التركيز والإغلاق. ينتقل التركيز إلى أول عنصر مفعَّل، ويعود عند الإغلاق إلى العنصر الذي كان يملك التركيز حين طُلبت القائمة. والضغط في أي مكان خارج القائمة يغلقها، داخل المنطقة أيضًا، والنقر بالزر الأيمن في مكان آخر يغلقها ثم يطلبها من جديد هناك.
- ماذا تضع فيها. الإجراءات التي تنطبق على ما أُشير إليه فقط، والأكثر استخدامًا أولًا. وقدِّم كلًّا منها في مكان آخر من منتجك أيضًا: فمن لا يعرف كيف يطلبها لا يرى قائمة السياق أبدًا.
- على iOS قد يحدد الضغط المطوَّل النص الذي تحته أيضًا. وحيث لا يُراد تحديد نص المنطقة، صرِّح بذلك في أنماطها (
user-select: none،-webkit-touch-callout: none).
قائمة الخيارات تراكب مُشغَّل (operated overlay) غير نمطي. يعمل الأشخاص داخلها، فتأخذ التركيز وتعيده. ولا يُحجب أي شيء آخر على الشاشة.
- الفتح. أنت تضبط
openحين يُفعَّل عنصر الارتكاز. يظهر السطح في طبقة التراكب في المنصة، موضوعًا بمحاذاة عنصر الارتكاز كما تقول الخاصيةplacement: تحته افتراضيًّا، مع محاذاة حافتي البداية. ينقلب إلى الجهة الأخرى حين لا تتّسع تلك الجهة، وينزاح ليبقى داخل الشاشة، ويقبل التمرير حين يزيد طوله، ويتبع عنصر الارتكاز ما دام يتحرك. - دخول التركيز. ينتقل التركيز إلى أول عنصر مفعَّل. ثم تتنقل مفاتيح الأسهم و Home و End والبحث بالكتابة (typeahead) بين العناصر، مع الدوران عند الطرفين والمرور عبر المجموعات. والمؤشر الذي يتحرك إلى عنصر ينقل التركيز إليه أيضًا، فتتابع المفاتيح من حيث كان المؤشر.
- غير نمطية. قائمة الخيارات لا تحصر التركيز، ولا تجعل بقية الشاشة خاملة (inert)، ولا ترسم حجابًا (scrim). المفتاح Tab يغادر قائمة الخيارات ويغلقها.
- الإغلاق. تطلب قائمة الخيارات أن تُغلق وتقول لماذا. للأسباب الأسماء نفسها على كل المنصات، بحالة الأحرف التي تتبعها المنصة:
| السبب | متى |
|---|---|
escape |
ضُغط المفتاح Escape. |
back |
الرجوع في المنصة، كزر الرجوع أو إيماءة الرجوع في Android. |
outside |
وقع ضغط خارج السطح وعنصر الارتكاز. وفي قائمة السياق، في أي مكان خارج السطح، داخل المنطقة أيضًا. |
tab |
ضُغط المفتاح Tab أو Shift+Tab. ثم يتابع التركيز من عنصر الارتكاز، كما كان سيفعل منه. وفي قائمة السياق، يتابع من العنصر الذي كان يملك التركيز حين طُلبت القائمة. |
focus-out |
خرج التركيز من قائمة الخيارات بطريقة أخرى. |
item |
فُعِّل عنصر. |
- عودة التركيز. حين تُغلق قائمة الخيارات والتركيز كان داخلها، يعود التركيز إلى عنصر الارتكاز. وتعيده قائمة السياق إلى العنصر الذي كان يملك التركيز حين طُلبت، وإذا لم يكن أي عنصر يملكه، يغادر التركيز القائمة فحسب.
من يغلق قائمة الخيارات يتبع المنصة. على الويب و React Native تعيد أنت open (visible) إلى false من حدث الإغلاق. على Compose تضبط expanded على false من onDismissRequest الذي يحمل قيمة من النوع TanreiMenuCloseReason. على Flutter تستدعي قائمة الخيارات onClose مع السبب وتغلق نفسها عبر متحكِّمها. على SwiftUI تكتب قائمة الخيارات false في isPresented، فلا يُنقل السبب.
وهل يصل الضغط في الخارج إلى ما تحته أيضًا؟ هذا يتبع المنصة كذلك. على الويب و Flutter يصل. على SwiftUI و Compose و React Native يغلق الضغط قائمة الخيارات فقط.
- أبقِ التسميات قصيرة وابدأها في الإجراءات بما يدل على الفعل: «نسخ الرابط»، «حذف الملف».
- ضع اختصار لوحة المفاتيح للإجراء في
shortcut، بأسماء المفاتيح المحايدة (mod+c)، واربط المفاتيح في منتجك. أبقِtrailingTextلكل ما سوى ذلك: نص النهاية لا يُعرَض للتقنيات المساعدة اختصارًا أبدًا. - ضع الإجراء الذي لا رجعة فيه أخيرًا، مفصولًا في مجموعة خاصة به أو بعد مجموعة.
- أبقِ العنصر المعطَّل في مكانه حين يكون متاحًا أحيانًا، فلا يتغيّر شكل قائمة الخيارات. ويبقى الوصول إليه بمفاتيح الأسهم ممكنًا.
- لا تضع الإجراء الرئيسي للشاشة في قائمة خيارات.
- لا تستخدم عناصر خانات الاختيار لاختيارات يستبعد بعضها بعضًا. استخدم عناصر أزرار الاختيار في مجموعة.
- لا تصنع قائمة خيارات من عنصر واحد. اعرض الإجراء زرًّا.
- لا تضع إجراء يستخدمه الأشخاص كثيرًا في قائمة فرعية، ولا تتجاوز مستويين من القوائم الفرعية.
- لا تستخدم
chevron_rightفي نهاية إجراء. فهي علامة عنصر القائمة الفرعية. - لا تبنِ التنقل الرئيسي لموقع قائمةَ خيارات من الروابط. قائمة الخيارات تُطلب ثم تذهب.
- الدور
menu- التسمية
- The menu is named by its anchor (a context menu by the name the product gives it, and by none where it gives none), and a submenu by its submenu item's label. Each item's label is its name and its supporting text its description; its shortcut is exposed as the item's keyboard shortcut where the platform has one, and otherwise given as text in its description (KBS-10); trailing text is description only. A link item is a menu item like any other, named by its label; while it opens a new context its targetLabel ends its description. Checkbox and radio items expose their checked state, whatever their mark looks like. A submenu item exposes that it opens a menu and whether that menu is open. A group is named by its title. Icons, the mark and the submenu indicator are decorative.
- الحد الأدنى للهدف
- تستوفي مساحة اللمس الحد الأدنى لحجم الهدف حتى لو كان الحجم المرئي أصغر. minimum 32px · pointer 44px · spacing 8px · touch 48px
لوحة المفاتيح
| المفاتيح | الإجراء |
|---|---|
| Enter, Space / Down Arrow on the anchor | The product opens the menu; focus moves to the first enabled item. |
| Shift+F10 / the ContextMenu key on the area / an element inside it (trigger context) | The menu asks to be opened against that element (contextRequest); the product opens it, and focus moves to the first enabled item. |
| Down Arrow / Up Arrow | Moves focus to the next or previous item of the level that holds focus, wrapping at the ends. |
| Home / End | Moves focus to the first or last item of that level. |
| Printable characters | Moves focus to the next item of that level whose label begins with the characters typed. |
| Right Arrow (Left Arrow in a right-to-left layout) on a submenu item | Opens the submenu and moves focus to its first enabled item. |
| Left Arrow (Right Arrow in a right-to-left layout) in a submenu | Closes that submenu and returns focus to its submenu item. |
| Enter | Activates the focused item and closes the menu and its submenus, a link item's destination opening after that; on a submenu item, opens the submenu instead. |
| Space | Activates the focused item, a link item as Enter does; a checkbox or radio item leaves the menu open; on a submenu item, opens the submenu. |
| Enter with a modifier key, on a link item (the web) | What the browser does with a link: another tab or window, a download. The menu emits no close and stays open, with focus on the item (MNU-54). |
| Escape | Closes the innermost open submenu and returns focus to its submenu item; with no submenu open, closes the menu and returns focus to the anchor (a context menu: to the element that had focus when it was asked for). |
| Tab / Shift+Tab | Closes the menu and every submenu, and moves focus on from the anchor. |
ملاحظات
- The anchor is the product's own button, icon-only Button or field. The menu does not draw it; it reads its position, returns focus to it, and sets its expanded state where the platform has one (aria-haspopup and aria-expanded on the web).
- A context menu (trigger context) is the same menu opened at a point of an area instead of from a button: a secondary click, a long press, Shift+F10 or the ContextMenu key, as the WAI-ARIA menu pattern names for a context-specific menu. The area is not a button, so it carries no popup state; the menu cancels the platform's own context menu only where it answers the request, and a product that cancels contextRequest — over a field, a link, selected text it does not handle — leaves the platform's menu in place. Every action a context menu offers is also reachable elsewhere in the product, because a context menu is found only by those who know to ask for it (MNU-60 to MNU-63).
- Five item kinds: item (an action), checkbox-item, radio-item, submenu and group. A checkbox item's checked is its own; which radio item is checked is its group's, by value; a submenu item's open is its own. A group may hold items of any kind except another group; a submenu may hold items of every kind.
- A link item is the platform's own link exposed as a menu item: on the web an anchor with the menu item role. Assistive technology reads a menu by its items — how many, which one, the arrow keys — and that reading holds only while every child of the menu is a menu item; so the role stays, and what makes the row a link stays underneath it: its address, the browser's context menu and new-tab gestures, a crawler that follows it. It is the one link form that Space activates, because it is operated as a menu item and looks like the action beside it. A menu of nothing but links to a site's main pages, always in view, is navigation and belongs in a navigation landmark of links; the menu is for a short set that is out of the way until asked for (the folded levels of a trail, a choice of language, "more").
- Focus: the menu is an operated overlay that is not modal. It moves focus in when it opens and returns it to the anchor when it closes, but does not keep it: Tab leaves the menu and closes it, at whatever level focus is. A submenu takes focus when it is opened by a key or a press and returns it to its submenu item when it closes alone.
- Submenus follow the WAI-ARIA menu pattern: the arrow key that points to where the submenu opens, Enter and Space open it and move focus in; the opposite arrow key and Escape close it and return focus to its item; type-ahead, Home, End and the vertical arrow keys work inside the level that holds focus. A pointer opens a submenu by resting on its item, and may then cross other items on its way to the submenu without closing it (MNU-45); touch opens it with a tap. In a window too narrow to set two surfaces side by side the submenu is laid over its parent, never swapped in for it, so the keys, the roles and the reading order are the same at every size (MNU-47).
- Depth: one level of submenus where possible, never more than two. Each level costs a person a precise sideways movement and hides the level before it on a small screen; a set that needs more belongs in a dialog, a sheet or a page. A debug build reports a third level; nothing stops it from working (MNU-52).
- A checked item is told from an unchecked one by its mark, which differs in shape (an empty and a filled box, a thin and a heavy ring, a check or none), never by the row's fill alone: the fill supports the mark and is not held to a contrast of its own. It is still meant to be seen: in the default system a checked row's fill is a layer of the row's content color over the sheet, so it differs from the sheet and from a hovered row by the same step in every scheme (MNU-37), and the content on it keeps at least 4.5:1 in every state. The control looks are drawings in the mark column; the menu holds no checkbox and no radio button.
- Under gap no separator is exposed between groups; the groups themselves, and their titles, carry the structure to assistive technology, as they do under divider.
- A menu holds only its items. A field, slider or switch inside a menu would take arrow keys and typing that the menu needs.
- Text size does not clip an item: items grow at 200% text size, and the surface scrolls when it no longer fits the visible part of the window.
- An item's shortcut is drawn by the keyboard shortcut component (KBS-13), which writes the keys for the keyboard the person uses and only shows them: the product binds them. Trailing text is never read as a shortcut; a product that typed its shortcut there keeps the text but not the keyboard shortcut semantics until it moves the keys to shortcut.
- How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.
- WCAG 2.5.8 (target size): for touch every row is at least the touch target high in both densities (MNU-12). The compact density is lower than the pointer target for a fine pointer, by design: it is the density of a tool used with a mouse, it stays above the 24 by 24 the criterion asks, and its rows reach across the surface's whole width. A product that cannot accept that uses regular.
مولَّد من specs/menu.spec.json, tokens/sys/measure.tokens.json
- السطح يُعرَض للتقنيات المساعدة قائمةَ خيارات (menu). الإجراءات عناصر قائمة خيارات (menu items)، وعناصر خانات الاختيار عناصر من نوع خانة الاختيار (menu item checkboxes)، وعناصر أزرار الاختيار عناصر من نوع زر الاختيار (menu item radios)، ولكل منها حالة تحديده وموضعه وحجم قائمة الخيارات.
- اسم كل عنصر تسميته، ووصفه نصه المساعد. الخاصية
shortcutللعنصر تُعرَض على أنها اختصار لوحة المفاتيح له حيث تملك المنصة تلك الخاصية (aria-keyshortcutsعلى الويب)، وإلا كانت مفاتيحه مكتوبةً بأسمائها جزءًا من وصفه. أما نص النهاية فلا يكون إلا جزءًا من الوصف. والأيقونات والعلامات مخفيّة: المربع والحلقة وعلامة الصح رسوم، وحالة التحديد ملك للعنصر. - عنصر الرابط يُعرَض عنصرَ قائمة خيارات، لا رابطًا: قائمة الخيارات تُقرأ بعناصرها وعددها ومفاتيح الأسهم، ولا يصح ذلك إلا ما دام كل صف عنصر قائمة خيارات. وهو مع ذلك رابط المنصة في الأساس. وما دام يفتح سياقًا جديدًا، ينتهي وصفه بنص
targetLabelالخاص به، وتكون علامته مخفية. وعنصر الرابط الحالي يُعرَض أيضًا على أنه الصفحة الحالية. - المجموعة تُعرَض مجموعةً اسمها عنوانها. والفواصل فواصل (separators) لا تأخذ التركيز؛ وحيث تُفصل المجموعات بفجوة فلا فاصل. والعناوين لا تأخذ التركيز ولا تُقرأ على أنها عناصر.
- عنصر القائمة الفرعية يُعرَض عنصرًا يفتح قائمة خيارات، مع حالة توسّعه، حتى وهو معطَّل. وسطحه قائمة خيارات قائمة بذاتها، اسمها تسمية العنصر، وكل سطح يعدّ مواضعه هو.
- يُخبَر عنصر الارتكاز بأنه يفتح قائمة خيارات وبحالتها، مفتوحةً أو مغلقة، حيث تملك المنصة تلك الخاصية (على الويب:
aria-haspopupوaria-controlsوaria-expanded). - تترك قائمة السياق منطقتها كما هي: المنطقة ليست زرًا، ولا يُخبَر أنها تفتح قائمة. تُفتح القائمة بالنقر بالزر الأيمن، أو بالضغط المطوَّل، أو بالمفتاح Shift+F10، أو بمفتاح ContextMenu، أو بأمر قائمة السياق في التقنيات المساعدة، واسمها الوحيد هو
aria-labelالذي تعطيه إياها، كما أن قوائم السياق الخاصة بالمنصات نفسها بلا اسم. - الفتح ينقل التركيز إلى أول عنصر مفعَّل. والإغلاق يعيد التركيز إلى عنصر الارتكاز حين كان داخل قائمة الخيارات. مفتاحا السهم للأعلى والسهم للأسفل يدوران عند الطرفين، والمفتاحان Home و End يذهبان إلى الطرفين، والكتابة تنقل إلى العنصر التالي الذي تبدأ تسميته بالأحرف المكتوبة. المفتاح Enter يفعّل ويغلق. والمفتاح Space على عنصر خانة اختيار أو زر اختيار يترك قائمة الخيارات مفتوحة. المفتاح Escape يغلق ويعيد التركيز. والمفتاح Tab يغلق ويتابع من عنصر الارتكاز. في القائمة الفرعية، مفتاح السهم نحو بداية السطر والمفتاح Escape يغلقان ذلك المستوى وحده ويعيدان التركيز إلى عنصره؛ ومفاتيح الأسهم على امتداد السطر التي لا تفعل شيئًا لا تصل أبدًا إلى الصفحة خلف قائمة الخيارات.
- العناصر المعطَّلة يبقى الوصول إليها بمفاتيح الأسهم ممكنًا، لكنها لا تُفعَّل.
- حلقة التركيز تُرسم داخل العنصر، فلا يستطيع السطح أن يقصّها. وتظهر لتركيز لوحة المفاتيح فقط. والمؤشر الذي يتحرك إلى عنصر يُظهره في حالة التحويم، لا التركيز.
- ارتفاع كل عنصر لا يقل عن الحد الأدنى للهدف، والصف كله مساحة لمسه.
- تكبر العناصر مع النص الأكبر، ويقبل السطح التمرير حين لا تعود الشاشة تتّسع له.
- ما دام خط الأيقونات لا يستطيع بعد أن يرسم علامة هي رسم (glyph)، كعلامة الصح، لا ترسم قائمة الخيارات شيئًا مكانها، ولا ترسم الكلمة «check» أبدًا. تحتفظ بعرض العمود وتبقى تعرض حالة التحديد، وتُظهر العلامة حين يكتمل تحميل الخط.
- بنيات التنقيح (debug) تبلّغ عن عنصر بلا تسمية، وعنصر زر اختيار خارج مجموعة، وقيمة مجموعة لا تسمّي أيًّا من عناصر أزرار الاختيار فيها، وعنصري زر اختيار لهما القيمة نفسها، ومجموعة داخل مجموعة، وعنصر له اختصار ونص نهاية معًا، وقيمة
separationأوiconSpaceلا تعرّفها قائمة الخيارات، ومستوى ثالث من القوائم الفرعية، وقائمة فرعية بلا عناصر. وتبلّغ أيضًا عن عنصر رابط يفتح سياقًا جديدًا بلاtargetLabel، وعنhrefعلى عنصر خانة اختيار أو عنصر زر اختيار أو عنصر قائمة فرعية، وعنhrefتسمّي سكربتًا أو تتعذّر قراءتها، وعن قيمةtargetلا تعرّفها قائمة الخيارات، وعنcurrentعلى عنصر بلاhref. وتبلّغ أيضًا عنtriggerلا تعرّفه القائمة.
فجوات معروفة في المنصات:
- ليس في SwiftUI و Compose و Flutter و React Native عناصر روابط: العنصر لا يأخذ
hrefهناك بعد. - ليس في SwiftUI و Compose و Flutter و React Native مُشغِّل سياق: قوائمها تُفتح من عنصر الارتكاز فقط.
- ليس في SwiftUI و Compose و Flutter و React Native قوائم فرعية، ولا
separation، ولاiconSpace، ولاmarkIcon، ولا تعبئة على الصف المحدَّد. وهي تحتفظ بعمودي العلامة والأيقونة في كل صف من قائمة خيارات يرسم فيهما أي عنصر منها. وما زالت ترسم علامة الصح علامةً خاصة بها في مربع بمقاس 16، وتبدأ عنوان المجموعة حيث تبدأ التسميات. - موضع بداية العنوان مع
m3-likeوcupertinoيتبع قاعدة Tanrei ولم يُقَس على الأصلين. - الويب: قائمة الخيارات لا تستمع إلى طلب الإغلاق من المنصة، كإيماءة الرجوع في Android، فلا يُرسَل السبب
backأبدًا. - الويب: تُضبط
aria-haspopupوaria-expandedعلى عنصر الارتكاز. وإن كان عنصر الارتكاز عنصرًا مخصصًا (custom element)، مثل<tanrei-button>بأيقونة فقط، وُضعتا على الزر داخل shadow root الخاص به. أماaria-controlsواسم قائمة الخيارات، وهو يأتي من عنصر الارتكاز، فلا يستطيعان عبور shadow root. - الويب: يوضع السطح بسكربت، لا بخاصية CSS anchor positioning.
- الويب: لا يستطيع عنصر القائمة الفرعية أن يشير إلى سطحه بالخاصية
aria-controls، فهي لا تصل إلى داخل shadow root. وبدلًا من ذلك يكون السطح فرعًا للعنصر في شجرة إمكانية الوصول. - الويب، WebKit على macOS: عنصر القائمة الفرعية المفتوحة يحتفظ بلون التحويم تحت الضغط بدل أن يأخذ لون الضغط.
- ليس في SwiftUI دور قائمة خيارات ولا دور عنصر قائمة خيارات ولا دور فاصل، ولا خاصية للاختصار، ولا سبيل إلى القول إن عنصر الارتكاز يفتح قائمة خيارات أو إن قائمة الخيارات مفتوحة. كل عنصر زر وموضعه قيمة له («
2 of 3»، بالإنجليزية فقط)، وعنصر خانة الاختيار زر تبديل (toggle)، وعنصر زر الاختيار محدَّد (selected) ما دام مختارًا. - لا يعطي SwiftUI تركيز لوحة المفاتيح لزر معطَّل أبدًا، فتتخطى مفاتيح الأسهم العنصر المعطَّل بدل أن تصل إليه.
- يعرض SwiftUI قائمة الخيارات في عنصر
popoverالخاص بالمنصة، وهو يضع نفسه بنفسه. الرموز (tokens) تختار الجهة فقط: عنصرpopoverيحتفظ بمسافته هو عن عنصر الارتكاز وعن حواف النافذة، ويحاذي نفسه، ويُغلق بحركته هو. على iOS 26 يقصّ السطح إلى زواياه الأكبر ويرسم ظله هو. وكيف يبدو هذا على جهاز لم يُفحص بعد. - في SwiftUI: المفتاح Tab يغلق قائمة الخيارات ويعيد التركيز إلى عنصر الارتكاز، لكن التركيز لا يستطيع أن يتابع منه في ضغطة المفتاح نفسها.
- لا يقرأ SwiftUI قيمة عنصر زر الاختيار إلا من
.tag(_:)مطبَّق علىTanreiMenuOptionنفسه. - في SwiftUI: مفاتيح الأسهم، و Home، و End، والبحث بالكتابة، و Escape، و Tab مختبَرة في صورة قواعد، لا بلوحة مفاتيح حقيقية بعد.
- يرسم React Native قائمة الخيارات في طبقة التراكب التابعة للمكوّن
TanreiProvider، فلا بد منTanreiProviderفوقها. المفتاح Tab يغلق قائمة الخيارات ويعيد التركيز إلى عنصر الارتكاز، لكنه لا يستطيع أن يتابع منه في ضغطة المفتاح نفسها. ضغطة Tab التالية تتابع. - في React Native: ما يفعله قارئ الشاشة بعنصر خلف قائمة الخيارات المفتوحة، وخروج التركيز من قائمة الخيارات، لم يُفحصا بعد على جهاز.
- ليس في React Native دور لعنصر من نوع خانة الاختيار ولا لعنصر من نوع زر الاختيار، ولا خاصية للاختصار. كل عنصر عنصر قائمة خيارات، وعناصر خانات الاختيار وأزرار الاختيار تحمل حالة تحديد، ونص النهاية يُقرأ جزءًا من الوصف.
- لا يستطيع React Native أن يضبط خصائص على عنصر الارتكاز الخاص بك. أعطه
accessibilityState={{ expanded: open }}بنفسك. - لا يتبع React Native عنصر ارتكاز يتحرك وقائمة الخيارات مفتوحة. يقيس عنصر الارتكاز حين تُفتح قائمة الخيارات وحين يتغيّر حجم النافذة.
- في React Native: مفاتيح الأسهم، والبحث بالكتابة، و Escape، و Tab، و Space مقابل Enter لم تُفحص بعد على جهاز له لوحة مفاتيح فعلية.
- ليس في Compose دور قائمة خيارات ولا دور عنصر قائمة خيارات ولا دور فاصل، ولا خاصية للاختصار. السطح مجموعة (collection)، والإجراء بلا دور، وعنصر خانة الاختيار خانة اختيار وعنصر زر الاختيار زر اختيار، ونص النهاية يُقرأ جزءًا من الوصف. ولا تستطيع قائمة الخيارات أن تضبط حالة التوسّع لعنصر الارتكاز.
- في Compose: الضغط على عنصر الارتكاز وقائمة الخيارات مفتوحة يُعدّ ضغطًا في الخارج. يغلق قائمة الخيارات بالسبب
outsideولا يصل إلى عنصر الارتكاز. - في Compose: تركيز نافذة العنصر المنبثق (popup)، والضغط الحقيقي في الخارج، وزر الرجوع، وعودة التركيز إلى عنصر الارتكاز، ومتابعة Tab منه، و TalkBack على قائمة الخيارات مختبَرة تحت Robolectric فقط، لا على جهاز بعد.
- ليس في Flutter دور مجموعة ولا دور فاصل، ولا يستطيع أن يسمّي قائمة الخيارات بعنصر ارتكازها ولا أن يقول إن عنصر الارتكاز يفتح قائمة خيارات، ولا يعطي العناصر موضعًا ولا حجم مجموعة. المجموعة حاوية اسمها عنوانها. لكن عنصر الارتكاز يحصل فعلًا على حالة التوسّع.
- في Flutter: ما يعلنه TalkBack و VoiceOver وقارئ شاشة على الويب عن قائمة الخيارات، وإيماءة الرجوع في Android على قائمة خيارات مفتوحة، لم تُفحص بعد على جهاز.
- سمِّ قائمة الخيارات بعنصر ارتكازها. أعطِ عنصر الارتكاز الذي هو زر أيقونة تسمية مثل «المزيد» أو «ترتيب». على React Native، مرِّر الاسم نفسه في الخاصية
accessibilityLabelلقائمة الخيارات. - على Compose و React Native، اعرض على عنصر الارتكاز حالة قائمة الخيارات، مفتوحةً أو مغلقة. قائمة الخيارات لا تستطيع ضبط ذلك هناك.
- أغلق قائمة الخيارات حين تطلب ذلك، ولا تنقل التركيز بنفسك حين تُغلق. قائمة الخيارات تعيد التركيز إلى عنصر الارتكاز.
- سمِّ قائمة السياق بـ
aria-label، مثل «لوحة الرسم»، وقدِّم كل إجراء من إجراءاتها في مكان آخر من منتجك أيضًا. - أعطِ كل عنصر تسمية، ومنها العناصر التي تعرض أيقونة. التسمية هي الاسم وهي ما يطابقه البحث بالكتابة.
- أعطِ مجموعة عناصر أزرار الاختيار عنوانًا يقول ما موضوع الاختيار.
- أعطِ كل عنصر قائمة فرعية تسمية: فهي تسمّي العنصر والقائمة الفرعية التي يفتحها.
- أعطِ عنصر الرابط الذي يفتح تبويبًا جديدًا
targetLabelبلغة منتجك، مثل «يُفتح في تبويب جديد». ولا يقدّم Tanrei كلمات من عنده.