---
title: "اللوحة الجانبية"
description: "تحمل محتوى تكميليًّا أو مهمة ثانوية في لوحة عند حافة النهاية، لها عنوان رئيسي وزر إغلاق وإجراءات اختيارية، بجانب المحتوى في النافذة العريضة أو فوقه في النافذة المضغوطة."
url: "https://tanrei.chnotchy.com/ar/components/side-sheet/"
last_updated: 2026-10-23T21:17:12.000Z
---

# اللوحة الجانبية

> تحمل محتوى تكميليًّا أو مهمة ثانوية في لوحة عند حافة النهاية، لها عنوان رئيسي وزر إغلاق وإجراءات اختيارية، بجانب المحتوى في النافذة العريضة أو فوقه في النافذة المضغوطة.

## نظرة عامة

> [!NOTE]
> **مواصفة معدَّلة، لم تصل بعد إلى كل منصة**
>
> تصف هذه الصفحة إجراءات اللوحة الجانبية وزر إغلاقها كما هي في المواصفة الآن، مثل إجراءات مربع الحوار. الإجراءات صف بعرض كامل من أزرار من الطراز tonal تُوضع بحسب مهمتها: كل منها معلَّم بالعلامة `confirming` أو `dismissive`، واللوحة النمطية (modal) يجب أن تعرض واحدًا على الأقل. زر الإغلاق زر صغير (small) من الطراز tonal، يُرسم ما لم تكن قيمة `closable` هي false. ويقوم فاصل تحت الترويسة دائمًا كما يقوم فوق الإجراءات، وتبقى الإجراءات فوق لوحة المفاتيح الظاهرة على الشاشة. مكوّن الويب يفعل هذا كله من الآن، وكذلك العروض؛ ويبقى معلَّمًا أعلاه بأنه متأخر إلى أن تُدقَّق المراجعة. ومنصات SwiftUI و Compose و Flutter و React Native متأخرة هي الأخرى: إلى أن تلحق تقدّم المراجعة السابقة، وفيها زر أيقونة من الطراز standard للإغلاق، والإجراءات في صف من حافة البداية بالترتيب المعطى، اختيارية في الهيئتين وبينها 8.

### الاستخدام

اللوحة الجانبية مُتحكَّم بها (controlled)، ولا تغلق نفسها أبدًا. تعرضها بضبط الخاصية `open`. وحين يطلب الشخص أن تذهب، تُصدر اللوحة الحدث `close` مع السبب، وأنت تعيد `open` إلى القيمة false. وعلى SwiftUI، حيث `open` ربط (binding)، تكتب اللوحة فيه false ثم تستدعي `onClose`. إن تركت `open` على true بعد سحب، انزلقت اللوحة عائدةً.

يحمل الحدث `close` أحد خمسة أسباب، بحالة الأحرف التي تتبعها المنصة:

| السبب | ما فعله الشخص | أي هيئة |
| --- | --- | --- |
| `close` | فعّل زر الإغلاق | القياسية والنمطية |
| `scrim` | ضغط المنطقة المعتَّمة بجانب لوحة نمطية | النمطية |
| `escape` | ضغط المفتاح Escape | النمطية |
| `back` | استخدم الرجوع في المنصة: زر الرجوع أو إيماءته في Android، وإيماءة الخروج لإمكانية الوصول في iOS | النمطية |
| `drag` | سحب اللوحة نحو حافتها أكثر من نصف عرضها، أو دفعها بسرعة في ذلك الاتجاه | النمطية |

اللوحة القياسية تتجاهل المفتاح Escape والرجوع والسحب. زر إغلاقها هو طريقة الشخص الوحيدة لإغلاقها، ما لم يفعل ذلك عنصر تحكم من عندك.

للوحة هيئتان تحددهما الخاصية `variant`، والمنتج الواحد يستخدم الاثنتين عادةً: القياسية حيث تكون النافذة عريضة بما يكفي، والنمطية حيث لا تكون.

- **القياسية** (standard)، وهي الافتراضية، منطقة تخطيط، لا تراكب. ما دامت مفتوحة تأخذ عرضها في نهاية الصف الذي تضعها فيه، ويتغير حجم المحتوى المجاور لها. لا ترسم حجابًا (scrim) ولا تحصر التركيز أبدًا.
- **النمطية** (modal) تراكب نمطي. تنزلق فوق المحتوى من حافة النهاية مع حجاب، وتُبقي التركيز في داخلها، وتعيده حين تُغلق.

ترسم اللوحة ترويسة فيها قيمة `headline`، وما دامت قيمة `closable` هي true (القيمة الافتراضية)، زر إغلاق تسمّيه الخاصية `closeLabel`. محتواك يذهب تحت الترويسة، وأزرار Tanrei في منفذ الإجراءات (actions) تذهب على امتداد الأسفل، كل منها معلَّم بالعلامة `confirming` (يُتمّ المهمة: «تطبيق»، «حفظ») أو بالعلامة `dismissive` (يتراجع: «إلغاء»). تضعها اللوحة في مواضعها بحسب هاتين العلامتين، فتعطيها بأي ترتيب، وترسم الإجراء الذي لم تضبط له `variant` بالطراز tonal. اللوحة النمطية يجب أن تعرض إجراءً واحدًا على الأقل؛ واللوحة القياسية قد لا يكون لها أي إجراء. الإجراء لا يغلق اللوحة من تلقاء نفسه أبدًا: أغلقها من معالج الإجراء.

علِّم العنصر الذي ينبغي أن ينال التركيز عند فتح اللوحة بعلامة المنصة نفسها: `autofocus` على الويب، و `.defaultFocus` أو `@AccessibilityFocusState` على SwiftUI، و `FocusRequester` يُمرَّر في `initialFocus` على Compose، و `autofocus: true` على Flutter، و `initialFocusRef` على React Native. على SwiftUI ينقل `.defaultFocus` تركيز لوحة المفاتيح، وينقل ربط `@AccessibilityFocusState` الممرَّر في `initialFocus` تركيز VoiceOver. من دون علامة يذهب التركيز إلى اللوحة نفسها، فيُقرأ عنوانها الرئيسي أولًا.

تعرض عيّنتا Web و React المواصفة الحالية. أما SwiftUI و Compose و Flutter و React Native فمتأخرة: ما زالت تقدّم المراجعة السابقة من اللوحة الجانبية، وليس فيها علامتا `confirming` و `dismissive` ولا الطراز tonal الافتراضي، ولذلك تعطي عيّناتها كل إجراء الطراز text وتضع الإجراء الرئيسي أولًا، وتعرض تلك المراجعة إلى أن تلحق بالمواصفة.

**Web**

```html
<tanrei-button id="filters" variant="outlined">عوامل التصفية</tanrei-button>

<!-- القياسية: العنصر الأخير في صف flex. هي <aside> اسمه العنوان الرئيسي. -->
<div style="display: flex">
  <main style="flex: 1">…</main>
  <tanrei-side-sheet headline="عوامل التصفية" close-label="إغلاق عوامل التصفية">
    <p>اعرض نتائج آخر 30 يومًا التي تحمل كل التسميات المحددة.</p>
    <!-- confirming يذهب إلى نهاية الصف. من دون variant، كلاهما tonal. -->
    <tanrei-button slot="actions">إعادة ضبط</tanrei-button>
    <tanrei-button slot="actions" confirming>تطبيق</tanrei-button>
  </tanrei-side-sheet>
</div>

<script type="module">
  import '@tanrei/web/components/side-sheet';
  const sheet = document.querySelector('tanrei-side-sheet');
  document.querySelector('#filters').addEventListener('click', () => (sheet.open = true));
  // اللوحة لا تغلق نفسها أبدًا. detail.reason هي close أو scrim أو escape أو back أو drag.
  sheet.addEventListener('close', (e) => (sheet.open = false));
  // في نافذة أضيق من 840px، اللوحة نفسها في صورة <dialog> نمطي فوق حجاب.
  matchMedia('(width < 840px)').addEventListener('change', (e) => (sheet.variant = e.matches ? 'modal' : 'standard'));
</script>
```

**React**

```tsx
import { Button, SideSheet } from '@tanrei/react';

<SideSheet
  variant={wide ? 'standard' : 'modal'}
  open={open}
  headline="عوامل التصفية"
  closeLabel="إغلاق عوامل التصفية"
  onClose={() => setOpen(false)}
>
  <p>اعرض نتائج آخر 30 يومًا التي تحمل كل التسميات المحددة.</p>
  <Button slot="actions" onClick={reset}>إعادة ضبط</Button>
  <Button slot="actions" confirming onClick={apply}>تطبيق</Button>
</SideSheet>
```

**SwiftUI**

```swift
import Tanrei

// القياسية: عرض في نهاية HStack. الإغلاق يكتب false في الربط (binding)، ثم يستدعي onClose.
HStack(spacing: 0) {
    ResultsView()
    TanreiSideSheet("عوامل التصفية", isPresented: $filtersShown, closable: true, closeLabel: "إغلاق عوامل التصفية") {
        FilterForm()
    } actions: {
        TanreiButton("تطبيق", variant: .text) { apply() }
        TanreiButton("إعادة ضبط", variant: .text) { reset() }
    }
}

// النمطية: مُعدِّل على العرض الذي تغطيه، تُعرض كما يُعرض tanreiDialog.
ResultsView()
    .tanreiSideSheet("عوامل التصفية", isPresented: $filtersShown, closable: true, closeLabel: "إغلاق عوامل التصفية") { … }
```

**Compose**

```kotlin
import io.github.chnotchy.tanrei.components.TanreiButton
import io.github.chnotchy.tanrei.components.TanreiSideSheet
import io.github.chnotchy.tanrei.tokens.TanreiButtonVariant
import io.github.chnotchy.tanrei.tokens.TanreiSideSheetVariant

// القياسية: الابن الأخير في Row. النمطية: تُرسم في نافذة Dialog خاصة بها.
// اضبط visible على false من onClose، الذي يستقبل السبب.
Row {
    ResultsScreen(Modifier.weight(1f))
    TanreiSideSheet(
        visible = filtersShown,
        onClose = { filtersShown = false },
        headline = "عوامل التصفية",
        variant = if (wide) TanreiSideSheetVariant.Standard else TanreiSideSheetVariant.Modal,
        closable = true,
        closeLabel = "إغلاق عوامل التصفية",
        actions = {
            TanreiButton(text = "تطبيق", onClick = apply, variant = TanreiButtonVariant.Text)
            TanreiButton(text = "إعادة ضبط", onClick = reset, variant = TanreiButtonVariant.Text)
        },
    ) {
        FilterForm()
    }
}
```

**Flutter**

```dart
import 'package:tanrei/tanrei.dart';

// القياسية: في نهاية Row. أما اللوحة النمطية فتوضع في Stack فوق الصفحة.
Row(
  children: <Widget>[
    const Expanded(child: ResultsPage()),
    TanreiSideSheet(
      open: filtersShown,
      onClose: (TanreiSheetCloseReason reason) => setState(() => filtersShown = false),
      headline: 'عوامل التصفية',
      showCloseButton: true,
      closeButtonSemanticLabel: 'إغلاق عوامل التصفية',
      actions: <Widget>[
        TanreiButton(variant: TanreiButtonVariant.text, onPressed: apply, child: const Text('تطبيق')),
        TanreiButton(variant: TanreiButtonVariant.text, onPressed: reset, child: const Text('إعادة ضبط')),
      ],
      child: const FilterForm(),
    ),
  ],
),
```

**React Native**

```tsx
import { View } from 'react-native';
import { Button, SideSheet } from '@tanrei/react-native';

// القياسية: في نهاية صف. واللوحة النمطية تُعرض في Modal شفاف.
<View style={{ flex: 1, flexDirection: 'row' }}>
  <Results style={{ flex: 1 }} />
  <SideSheet
    variant={wide ? 'standard' : 'modal'}
    open={open}
    onClose={(reason) => setOpen(false)}
    headline="عوامل التصفية"
    closable
    closeLabel="إغلاق عوامل التصفية"
    actions={
      <>
        <Button variant="text" onPress={apply}>تطبيق</Button>
        <Button variant="text" onPress={reset}>إعادة ضبط</Button>
      </>
    }
  >
    <FilterForm />
  </SideSheet>
</View>
```

## المواصفات

- Category: Containment
- Level: composed
- Listing: default
- Built from: Modal layer
- Page: https://tanrei.chnotchy.com/components/side-sheet/
- Spec: https://github.com/chnotchy/tanrei/blob/main/specs/side-sheet.spec.json

### Availability

| Design system | Has it | Notes |
| --- | --- | --- |
| Tanrei (`tanrei`) | Yes | — |
| Cupertino (Apple HIG-inspired) (`cupertino`) | Yes | — |
| M3 Expressive-like (Material 3 Expressive-inspired) (`m3-expressive-like`) | Yes | — |
| M3-like (Material 3-inspired) (`m3-like`) | Yes | — |

### Platforms

- Web: the element `<tanrei-side-sheet>`, registered by importing `@tanrei/web` (or only its own module, `@tanrei/web/components/side-sheet`).
- React: `SideSheet` from `@tanrei/react`.

| Platform | Name | Status |
| --- | --- | --- |
| Web | `tanrei-side-sheet` | lagging |
| SwiftUI | `TanreiSideSheet` | lagging |
| Jetpack Compose | `TanreiSideSheet` | lagging |
| Flutter | `TanreiSideSheet` | lagging |
| React Native | `SideSheet` | lagging |

Names a platform gives in its own idiom:

| Spec | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
| --- | --- | --- | --- | --- | --- |
| `open` (prop) | `open` | `isPresented` | `visible` | `open` | `open` |
| `closable` (prop) | `closable` | `closable` | `closable` | `showCloseButton` | `closable` |
| `closeLabel` (prop) | `closeLabel` | `closeLabel` | `closeLabel` | `closeButtonSemanticLabel` | `closeLabel` |
| `close` (event) | `close` | `onClose` | `onClose` | `onClose` | `onClose` |

Statuses: `planned` — In the inventory. Nothing shippable exists yet. `lagging` — An implementation of an earlier spec exists; it has not caught up with the current one. `alpha` — Implemented with tests. The API may still change. `beta` — Feature complete and at parity. Only fixes are expected. `stable` — Covered by semantic versioning. `deprecated` — Still shipped; scheduled for removal.

### Anatomy

- `container`: The panel at the end edge, the full height of its parent (standard) or of the window (modal). Standard: part of the layout. Modal: above the content, the corners that face the content rounded. Its color reaches under the system insets at its end, top and bottom.
- `outline`: The start edge of a standard sheet, which sets it apart from the content beside it.
- `scrim` (optional): The modal layer's scrim (MDL-1): it dims everything behind a modal sheet; a press on it asks the sheet to close.
- `header` (optional): The row at the top that holds the headline and the close button. It stays in place while the content scrolls.
- `headline` (optional): What the sheet is about ("Filters"). It is the sheet's accessible name.
- `close-button` (optional): An icon-only Button of the small size showing the close glyph at the end of the header, in the variant comp.side-sheet.close-button.variant names (tonal in the default system), as every host's close button is. The sheet places it and chooses its variant; everything else it reads from Button's own tokens.
- `content`: Whatever the product puts in the sheet. The sheet insets it, hands it a content color and scrolls it between the header and the actions; it does not lay it out.
- `divider` (optional): Hairlines across the sheet: above the actions, drawn while there are actions; and below the header, drawn whenever the header is in the default system, or only while the content is scrolled away from its top under dividerVisibility scroll.
- `actions` (optional): Tanrei Buttons at the bottom of the sheet, each marked confirming or dismissive, laid out as a full-width Button group: a row the actions share equally, or a column when they do not fit. A Button whose variant the product did not set is tonal in the default system. At least one in a modal sheet; optional in a standard one.

### Props

| Prop | Type | Default | Web attribute | Description |
| --- | --- | --- | --- | --- |
| `variant` | `enum`: `standard`, `modal` | `"standard"` | `variant` | Standard sits beside the content and takes its width in the layout (a layout region); modal slides over the content with a scrim and holds focus until it closes (a modal overlay). One product uses both: standard where the window is wide enough, modal where it is not. A design system may add values. |
| `open` | `boolean` | `false` | `open` | Whether the sheet is shown: for a standard sheet, whether it takes its place in the layout; for a modal one, whether it is presented. The product opens it, and closes it in answer to close; on SwiftUI, where it is a binding, the sheet writes false itself. |
| `headline` | `string` | `""` | `headline` | What the sheet is about, drawn in its header and used as its accessible name. Required. |
| `closable` | `boolean` | `true` | `not-closable` (turns it off) | Draws the close button at the end of the header. True by default, in both variants: a standard sheet has no other way for the person to close it, and a modal one shows a way out where the person reads. Set it to false only where the product's own control, or one of the sheet's actions, closes the sheet. |
| `closeLabel` | `string` | `""` | `close-label` | The close button's accessible name ("Close filters"). Required while closable is true, which is the default. |
| `dividerVisibility` | `enum`: `auto`, `always`, `scroll` | `"auto"` | `divider-visibility` | When the divider below the header is drawn. always: whenever the header is drawn, at rest as well as while the content is scrolled, as the divider above the actions always is. scroll: only while the content is scrolled away from its top. auto takes comp.side-sheet.divider.visibility, the design system's answer (always in the default system). |

### Events

| Event | Description |
| --- | --- |
| `close` | The sheet asks to be closed: its close button, or for a modal sheet a press on the scrim, Escape, the platform's back or a drag toward its edge. Carries which of these it was, named close, scrim, escape, back or drag (in the platform's casing). |

### Slots

| Slot | Description |
| --- | --- |
| `default` | The content, below the header. A fixture's content string is plain text, which takes sys.typescale.body in the content color (SSH-10). |
| `actions` | The action items: Tanrei Buttons, each marked confirming or dismissive; at least one in a modal sheet (SSH-16). The sheet places them (SSH-11) and gives those without a variant its own (SSH-17). A fixture fills it through its items. |

### Item: `action`

One action at the foot of the sheet: a Tanrei Button, placed by the sheet as a full-width Button group places its items and given the sheet's variant unless the product set one.

It is a Button.

Names: Web `tanrei-button`, SwiftUI `TanreiButton`, Jetpack Compose `TanreiButton`, Flutter `TanreiButton`, React Native `Button`.

| Prop | Type | Default | Web attribute | Description |
| --- | --- | --- | --- | --- |
| `variant` | `string` | `""` | `variant` | The Button's variant (filled, tonal, elevated, outlined, text, or one a system added). Empty, the default: the sheet gives it the variant comp.side-sheet.actions.variant names, tonal in the default system (SSH-17). Set it to give one action another look. |
| `disabled` | `boolean` | `false` | `disabled` | As the Button's disabled. A disabled action keeps its place and its share of the width. |
| `confirming` | `boolean` | `false` | — | The action completes the task (Apply, Save, Show results). Placed at the end of a row and at the top of a column (BGR-13, BGR-14). It decides the place only, never the look (BGR-19). |
| `dismissive` | `boolean` | `false` | — | The action backs out of the task (Cancel, Discard changes). Placed at the start of a row and at the bottom of a column (BGR-13, BGR-14). It decides the place only, never the look (BGR-19). |

| Event | Description |
| --- | --- |
| `click` | The action was activated, as the Button's click. The sheet does not close by itself (SSH-18). |

| Slot | Description |
| --- | --- |
| `label` | The Button's label, as its default slot. A fixture item's content is this label. |

### States

`enabled`

### Accessibility

- Role: `dialog`
- Labeling: The headline is the sheet's accessible name and is exposed as a heading: the modal dialog's name, or the standard sheet's complementary region's. The close button is named by closeLabel. Each action is named by its own label; confirming and dismissive are not announced, and the actions are read in the order they are placed (BGR-15). The dividers are decorative.
- Minimum target size: yes

| Keys | Action |
| --- | --- |
| Tab / Shift+Tab | Moves focus through the close button, the content's focusable elements and the actions, in that order, the actions in the order they are placed (BGR-15). In an open modal sheet it wraps and never leaves the sheet; from an open standard sheet it moves on to the content, and the sheet stays open. |
| Enter or Space | Activates the focused close button (close, reason close) or action (its click; the sheet does not close by itself, SSH-18). The sheet adds no default action to Enter. |
| Escape | Asks an open modal sheet to close (close, reason escape); with stacked overlays, only the topmost. A standard sheet ignores it. |

- The sheet has no target of its own; its close button and its actions are Tanrei Buttons, which meet the minimum target themselves (BTN-5). comp.side-sheet.actions.gap keeps each action's whole target in a column in the default system.
- A modal sheet offers at least one action (SSH-16): it holds focus until the person is done, so a person using a screen reader or a switch finds a way to finish the task where they finish reading it, not only a way out. The close button, Escape, back and the scrim still dismiss it. A standard sheet sits beside the content, which the person keeps working in, so it may have no action.
- Placement is by task, not by code order: in a row the dismissive action is at the start and the confirming one at the end; in a column the confirming one is on top. Focus and screen readers follow the placed order.
- Dragging is never the only way (WCAG 2.5.7): a modal sheet also closes with its close button, Escape, back or a press on the scrim.
- The standard form is a layout region, not an overlay: opening it moves focus in and closing it returns focus, and focus leaving it never closes it. The modal form is a modal overlay. The web presents it with a modal \<dialog> element (showModal gives inertness and the top layer; its cancel event is cancelled and becomes close with the reason escape); Compose draws it in a window of its own (a Dialog, as TanreiDialog and the modal drawer are) and Flutter as a controlled overlay in a Stack over the page, each with a scrim, trapping focus and answering back; SwiftUI presents it as the dialog is, with a full-screen cover on iOS; React Native presents it in a transparent Modal.
- Opening a modal sheet by a swipe from the screen edge is left out, as for the drawer: the product opens it from a button.

### Behaviour

Requirements in EARS form, each with its stable id.

- **SSH-1** WHILE variant is standard THE side sheet SHALL take its width in the layout at the end of the row the product places it in WHILE open is true, and no width and no presence to assistive technology or focus WHILE open is false; WHEN open changes it SHALL slide in or out at the end edge while the content beside it resizes, over comp.side-sheet.motion.enter-duration with comp.side-sheet.motion.enter-easing when opening and comp.side-sheet.motion.exit-duration with comp.side-sheet.motion.exit-easing when closing, and at once where motion is reduced. A standard sheet SHALL draw no scrim, SHALL NOT keep focus inside it, SHALL let Tab and Shift+Tab move focus out of it without closing it, and SHALL ignore Escape, the platform's back and drags (a standard sheet is a layout region, not an overlay).
- **SSH-2** WHEN open becomes true WHILE variant is modal THE side sheet SHALL slide in from the window's end edge over the content on its modal layer, fading the layer's scrim in comp.side-sheet.scrim.color and comp.side-sheet.scrim.blur in over the rest of the window in step with it (MDL-1, MDL-7), over comp.side-sheet.motion.enter-duration with comp.side-sheet.motion.enter-easing; WHEN open becomes false it SHALL slide out to the end edge from wherever it is and fade the scrim out, over comp.side-sheet.motion.exit-duration with comp.side-sheet.motion.exit-easing. WHERE motion is reduced it SHALL fade in and out without sliding, or appear and disappear at once on a platform whose reduced-motion setting removes every animation (Android's). WHILE it is open THE side sheet SHALL make everything behind it inert and keep focus inside it, as its modal layer does (MDL-2, MDL-4).
- **SSH-3** WHEN open changes from false to true THE side sheet SHALL move focus to the element in it that the product marked for initial focus (the platform's own mark, named in the platform notes) or, without one, to its container, so that assistive technology reads the headline first; WHEN it closes while focus is inside it THE side sheet SHALL return focus to the element that had it when the sheet opened or, where that element no longer exists or cannot take focus, to the platform's default. Focus SHALL move when the sheet opens or closes, not when its motion ends. This holds for both variants (a modal sheet moves and returns focus as its modal layer does, MDL-3, MDL-5); a standard sheet mounted with open true SHALL NOT move focus, and a modal sheet mounted with open true SHALL move focus in as if it had just opened, because it makes everything behind it unfocusable and focus must be somewhere it can reach.
- **SSH-4** WHILE variant is modal and the sheet is open, WHEN the user presses the scrim, presses Escape, uses the platform's back, as its modal layer answers them (MDL-6), or drags the sheet toward its end edge past half its width or flings it that way, THE side sheet SHALL emit close exactly once with the reason scrim, escape, back or drag; a drag SHALL follow the finger or pointer once it passes the platform's touch slop, and a released drag that does not ask to close SHALL slide the sheet back over comp.side-sheet.motion.enter-duration with comp.side-sheet.motion.enter-easing (at once where motion is reduced). The fling, the touch slop and the stillness after which a release is no fling are those BSH-9 names for the bottom sheet: the platform's own, or 0.5 points per millisecond, 8 points and 40 milliseconds where the platform names none.
- **SSH-5** WHILE closable is true THE side sheet SHALL draw at the end of its header an icon-only Button of the small size showing the close glyph and named by closeLabel, in the variant comp.side-sheet.close-button.variant names, given through the channel a Button reads from the component that places it, and otherwise unrestyled: it reads Button's own tokens, answers hover, focus and press as any Button does and keeps its own minimum target (BTN-5); WHEN the user activates it THE side sheet SHALL emit close exactly once with the reason close, in either variant. WHILE closable is true and closeLabel is empty it SHALL still draw the button, and in debug builds report that the button has no accessible name.
- **SSH-6** WHEN the side sheet has emitted close THE side sheet SHALL be closed through its binding where open is one (SwiftUI), and by the product setting open to false where open is a plain value (web, Compose, Flutter, React Native); a released drag that asked to close SHALL hold where it left the sheet for one frame, then slide out from there IF open has become false, or slide back IF open is still true (the product declined to close it).
- **SSH-7** THE side sheet SHALL be comp.side-sheet.container.width wide beside the end system inset — WHILE variant is modal never wider than the window less sys.target.touch, so that a strip of scrim stays pressable — and the full height of its parent (standard) or of the window (modal), filled with its variant's container color (comp.side-sheet.container.color.standard or comp.side-sheet.container.color.modal), raised by its variant's elevation (comp.side-sheet.container.elevation.standard, comp.side-sheet.container.elevation.modal), with the corners at its start edge rounded by its variant's shape (comp.side-sheet.container.shape.standard, comp.side-sheet.container.shape.modal) eased 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 square corners at its end edge, and edged at its start by comp.side-sheet.outline.width of its variant's outline color (comp.side-sheet.outline.color.standard, comp.side-sheet.outline.color.modal); right-to-left layouts SHALL put it at the left edge, mirrored.
- **SSH-8** THE side sheet SHALL lay out, top to bottom: WHILE headline is not empty or closable is true, the header, a row at least comp.side-sheet.header.height high holding the headline from comp.side-sheet.container.padding-inline after the start edge and, WHILE closable is true, the close button ending comp.side-sheet.header.padding-inline-end before the end edge, comp.side-sheet.header.gap after the headline, both centred in the row; then the content, inset by comp.side-sheet.container.padding-inline from the start and end edges, starting directly below the header or, without one, comp.side-sheet.container.padding-block below the top edge; then, WHILE the sheet has at least one action, the divider and the actions pinned to the bottom (SSH-11), or else comp.side-sheet.container.padding-block of space below the content. It SHALL extend its container under the system insets at its end, top and bottom and place the header, content and actions inside them.
- **SSH-9** WHILE headline is not empty THE side sheet SHALL draw it in comp.side-sheet.headline.typescale and comp.side-sheet.headline.color, wrapping onto more lines rather than truncating and growing the header with it, and SHALL expose it as a heading. THE side sheet SHALL set the headline's letter-spacing by the script of its text, as every title is set (PRS-20): no negative letter-spacing WHERE its language is Chinese, Japanese or Korean, and none WHERE its script joins its letters or stacks marks on them.
- **SSH-10** THE side sheet SHALL hand its content comp.side-sheet.content.color as the platform's inherited foreground (the web's inherited color, SwiftUI's foreground style, Compose's content color, Flutter's default text and icon style, React Native's Tanrei text context); plain text given as content, where the platform must style it, SHALL take sys.typescale.body. WHILE the content is taller than the room between the header and the actions THE side sheet SHALL scroll it there, keeping the header and the actions in place, and SHALL let assistive technology and keyboard focus scroll it.
- **SSH-11** WHILE the sheet has at least one action THE side sheet SHALL draw a divider of comp.side-sheet.divider.width in comp.side-sheet.divider.color across its full width below the content, whether or not the content scrolls, and below it lay out its actions as a standard Button group lays out its items (BGR-12 to BGR-19), comp.side-sheet.actions.gap apart, comp.side-sheet.container.padding-inline from each side edge and comp.side-sheet.actions.padding-block above and below them, in the order the group gives them — dismissive at the start and confirming at the end of a row, confirming on top and dismissive at the bottom of a column, unflagged actions between them in the order given — with focus and reading order following that order (BGR-15). WHILE comp.side-sheet.actions.arrangement is fill it SHALL make them a full-width group across the sheet's inner width, the actions sharing a row equally (BGR-17) or each as wide as the column; WHILE it is start it SHALL keep them at their own widths, the row aligned to the start edge, and a column as wide as its widest action and aligned to the start. It SHALL place them in a row WHILE every action's label fits its share on one line (fill) or the row at its own widths fits the inner width (start), and otherwise in a column, re-deciding when the sheet's width or the text size changes. WHEN the layout direction is right-to-left it SHALL place them as a Button group places them right to left (BGR-8). It SHALL hide the divider from assistive technology.
- **SSH-12** THE side sheet SHALL expose its container, WHILE variant is modal, as its modal layer's modal dialog (MDL-10) named by headline and, WHILE variant is standard, as a complementary region named by headline (the web's aside landmark); WHILE headline is empty it SHALL still render, and in debug builds report that it has no accessible name.
- **SSH-13** WHEN variant names a value that the active design system does not have THE side sheet SHALL render with the default value's tokens, and in debug builds report the unknown value.
- **SSH-14** WHILE the text size is enlarged THE side sheet SHALL keep its width, grow its header with the headline (its height is a minimum), scroll its content (SSH-10), stack its actions once a label no longer fits its share (SSH-11), and SHALL NOT scale the close button beyond what the Button itself does.
- **SSH-15** WHEN a dialog, a menu or another modal sheet opens from a modal side sheet THE side sheet SHALL stay below it, become inert with the rest of the window, and on the other overlay's closing receive back the focus that overlay returns, as its modal layer does (MDL-8, MDL-9); Escape SHALL dismiss only the topmost overlay.
- **SSH-16** WHILE variant is modal, IF the sheet opens with no action THE side sheet SHALL still open and lay out its other parts, and in debug builds SHALL report as an error that a modal side sheet must offer at least one action; a platform whose modal sheet has an API of its own that can require an action (a required, non-empty builder or list of actions) SHALL require it there, and one that can count its actions only when the sheet opens SHALL count them then. WHILE variant is standard THE side sheet SHALL accept having no action and report nothing, and SHALL then draw neither the divider above the actions nor the actions (SSH-8).
- **SSH-17** WHILE an action's variant is empty THE side sheet SHALL give that Button the variant comp.side-sheet.actions.variant names, through the channel a Button reads from the component that places it, so that the Button renders from that variant's tokens and states as it would with the variant set; WHILE an action's variant is set THE side sheet SHALL leave it as set. THE side sheet SHALL NOT change an action's size or tokens, and SHALL NOT change its look or emphasis for its confirming or dismissive (BGR-19).
- **SSH-18** WHEN the user activates an action THE side sheet SHALL let the Button emit its own click and SHALL NOT close, emit close or otherwise answer it itself, whether the action is confirming, dismissive or neither; the product closes the sheet from the action's handler, as it does in answer to close.
- **SSH-19** WHILE the header is drawn THE side sheet SHALL draw a divider of comp.side-sheet.divider.width in comp.side-sheet.divider.color across its full width below the header, hidden from assistive technology: WHILE its divider visibility (SSH-23) is always, at rest as well as while the content is scrolled, as the divider above the actions is drawn whether or not the content scrolls (SSH-11); WHILE it is scroll, only WHILE the content is scrolled away from its top. WHILE there is no header it SHALL draw none there. It SHALL show and hide this divider at once, without motion.
- **SSH-20** WHILE the on-screen keyboard covers the bottom of the sheet THE side sheet SHALL keep its header in place, raise its divider and actions to sit directly above the keyboard, scroll the content in the room left between them, and scroll the focused element into that room; WHEN the keyboard hides it SHALL return the actions to the bottom edge. It SHALL move them as the platform moves content for its keyboard, and at once where motion is reduced.
- **SSH-21** THE side sheet 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 side sheet is shown THE side sheet SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state and its focus.
- **SSH-22** THE side sheet SHALL lay out its headline 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 side sheet SHALL still place the headline and align its lines by the layout direction, not by the text's own.
- **SSH-23** THE side sheet SHALL draw the divider below its header (SSH-19) as its dividerVisibility names: WHILE dividerVisibility is auto, as comp.side-sheet.divider.visibility names, which follows sys.behavior.divider-visibility unless a design system sets the side sheet apart; WHILE dividerVisibility is always or scroll, as that value names, whatever the tokens say. WHEN dividerVisibility, or the token it follows, changes THE side sheet SHALL draw the other form at once, moving no part and keeping the scroll position and focus; WHEN dividerVisibility names a value the side sheet does not define THE side sheet 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, always, and state that in its README.
- **SSH-24** WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE side sheet 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 its container (SSH-12) — its dialog WHILE variant is modal, its complementary region otherwise — an id reference by the elements it names in the host's tree or a tree around it, a description after its own. WHILE headline is not empty it is the container's name, over a name given so; WHILE headline is empty, a name given so is the container's name and SHALL NOT be reported as having none in debug builds. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE side sheet SHALL take it off that element.

### Tokens

As written in the token documents (DTCG): an alias in braces names the token it takes its value from, which each design system resolves. On the web each token is a CSS custom property: `--tanrei` and the name after `tanrei`, joined by hyphens (`tanrei.comp.side-sheet.…` → `--tanrei-comp-side-sheet-…`).

#### `tanrei.comp.side-sheet`

| Token | Type | Value |
| --- | --- | --- |
| `comp.side-sheet.container.color.standard` | `color` | `{tanrei.sys.color.surface}` |
| `comp.side-sheet.container.color.modal` | `color` | `{tanrei.sys.color.surface-container-low}` |
| `comp.side-sheet.container.elevation.standard` | `shadow` | `{tanrei.sys.elevation.level0}` |
| `comp.side-sheet.container.elevation.modal` | `shadow` | `{tanrei.sys.elevation.level2}` |
| `comp.side-sheet.container.shape.standard` | `dimension` | `{tanrei.sys.shape.corner.none}` |
| `comp.side-sheet.container.shape.modal` | `dimension` | `{tanrei.sys.shape.corner.large}` |
| `comp.side-sheet.container.width` | `dimension` | `320px` |
| `comp.side-sheet.container.padding-inline` | `dimension` | `{tanrei.sys.space.space300}` |
| `comp.side-sheet.container.padding-block` | `dimension` | `{tanrei.sys.space.space300}` |
| `comp.side-sheet.outline.color.standard` | `color` | `{tanrei.sys.color.outline-variant}` |
| `comp.side-sheet.outline.color.modal` | `color` | `{"colorSpace":"srgb","components":[0,0,0],"alpha":0,"hex":"#000000"}` |
| `comp.side-sheet.outline.width` | `dimension` | `{tanrei.sys.border.hairline}` |
| `comp.side-sheet.scrim.color` | `color` | `{tanrei.comp.modal-layer.scrim.color}` |
| `comp.side-sheet.scrim.blur` | `dimension` | `{tanrei.comp.modal-layer.scrim.blur}` |
| `comp.side-sheet.header.height` | `dimension` | `64px` |
| `comp.side-sheet.header.gap` | `dimension` | `{tanrei.sys.space.space100}` |
| `comp.side-sheet.header.padding-inline-end` | `dimension` | `{tanrei.sys.space.space150}` |
| `comp.side-sheet.close-button.variant` | `number` | `1` |
| `comp.side-sheet.headline.color` | `color` | `{tanrei.sys.color.on-surface}` |
| `comp.side-sheet.headline.typescale` | `typography` | `{tanrei.sys.typescale.heading-5}` |
| `comp.side-sheet.content.color` | `color` | `{tanrei.sys.color.on-surface}` |
| `comp.side-sheet.divider.color` | `color` | `{tanrei.sys.color.outline-variant}` |
| `comp.side-sheet.divider.width` | `dimension` | `{tanrei.sys.border.hairline}` |
| `comp.side-sheet.divider.visibility` | `number` | `{tanrei.sys.behavior.divider-visibility}` |
| `comp.side-sheet.actions.gap` | `dimension` | `{tanrei.sys.space.space200}` |
| `comp.side-sheet.actions.arrangement` | `number` | `0` |
| `comp.side-sheet.actions.variant` | `number` | `1` |
| `comp.side-sheet.actions.padding-block` | `dimension` | `{tanrei.sys.space.space200}` |
| `comp.side-sheet.motion.enter-duration` | `duration` | `{tanrei.sys.motion.role.appear.sheet.enter}` |
| `comp.side-sheet.motion.enter-easing` | `cubicBezier` | `{tanrei.sys.motion.easing.enter}` |
| `comp.side-sheet.motion.exit-duration` | `duration` | `{tanrei.sys.motion.role.appear.sheet.exit}` |
| `comp.side-sheet.motion.exit-easing` | `cubicBezier` | `{tanrei.sys.motion.easing.exit}` |

## الإرشادات

### متى يُستخدم

استخدم اللوحة الجانبية لمحتوى يساند ما يعمل عليه الشخص من دون أن يحل محله: عوامل تصفية لقائمة، تفاصيل صف محدَّد، خصائص كائن محدَّد. في النافذة العريضة تقع بجانب المحتوى، فيستطيع الشخص أن يغيّر عامل تصفية ويرى النتائج تتغير. وفي نافذة مضغوطة (Compact) تنزلق اللوحة نفسها فوق المحتوى، ويُنهي الشخص عمله فيها أو يغلقها قبل أن يعود.

على الهاتف تكون [اللوحة السفلية](https://tanrei.chnotchy.com/ar/components/bottom-sheet/) أنسب في الغالب: ترتفع من الحافة التي يبلغها الإبهام. استخدم [مربع الحوار](https://tanrei.chnotchy.com/ar/components/dialog/) لقرار لا تستطيع المهمة أن تستمر من دونه، و[درج التنقل](https://tanrei.chnotchy.com/ar/components/navigation-drawer/)، عند حافة البداية، لوجهات المنتج.

### قياسية أم نمطية

| الطراز | ما هو | يُستخدم لـ |
| --- | --- | --- |
| القياسية (الافتراضية) | منطقة تخطيط: لوحة في نهاية الصف، يفصلها عن المحتوى خط رفيع عند حافة بدايتها. يتغير حجم المحتوى المجاور لها حين تُفتح وتُغلق. لا ترسم حجابًا، وتبقى مفتوحة والشخص يعمل في المحتوى. | النوافذ العريضة، حيث تستطيع اللوحة أن تبقى مفتوحة بجانب المحتوى. |
| النمطية | تراكب نمطي: لوحة مرفوعة زواياها التي تلي المحتوى مدوَّرة، تنزلق من حافة النهاية فوق حجاب. كل ما هو خلفها خامل (inert)، ويبقى التركيز في داخلها إلى أن تُغلق. | النوافذ المضغوطة، والمهام التي يجب إنهاؤها أو إغلاقها قبل أن يمضي الشخص. |

اللوحة القياسية جزء من التخطيط، مثل درج التنقل القياسي أو شريط التطبيق: خروج التركيز منها لا يغلقها أبدًا، لأن إغلاق لوحة كلما مضى المفتاح Tab يجعلها غير قابلة للاستخدام من لوحة المفاتيح. أما هيئتها النمطية فتراكب، وكل ما يعد به التراكب النمطي يسري عليها.

اللوحة النمطية لا تكون أبدًا بعرض نافذة ضيقة. تبقى أضيق منها بهدف لمس واحد على الأقل، فيبقى دائمًا شريط من الحجاب يُضغط عليه. ويجوز لنظام تصميم أن يضيف طُرُزًا.

### الأجزاء

| الجزء | يُضبط بـ | ملاحظات |
| --- | --- | --- |
| العنوان الرئيسي | `headline` | مطلوب. ما تدور حوله اللوحة، مثل «عوامل التصفية». وهو الاسم المتاح للتقنيات المساعدة للوحة وعنوان (heading)، ويلتف ولا يُقتطع. |
| زر الإغلاق | `closable`، `closeLabel` | زر صغير (small) بأيقونة فقط، يحمل الأيقونة `close` في نهاية الترويسة، ومن الطراز tonal في النظام الافتراضي، مثل كل أزرار الإغلاق في Tanrei (ورمز بلا خلفية في m3-like). يُرسم افتراضيًّا؛ اجعل قيمة `closable` هي false (السمة `not-closable`) حين يغلق اللوحةَ عنصرُ تحكم من عندك أو أحد الإجراءات. ترسمه اللوحة وتربطه بالحدث `close`؛ وأنت تعطيه تسمية. |
| المحتوى | المنفذ الافتراضي | كل ما وُجدت اللوحة من أجله. تزيحه اللوحة عن الحواف إلى الداخل، وتعطيه لون محتوى، وتمرّره بين الترويسة والإجراءات. |
| الإجراءات | منفذ الإجراءات (actions) | أزرار Tanrei، كل منها معلَّم بالعلامة `confirming` أو `dismissive`، تحت فاصل. مطلوبة في اللوحة النمطية، اختيارية في القياسية. تضعها اللوحة في مواضعها وترسم ما لا طراز له منها بالطراز tonal؛ ولا تغيّر مظهرها بغير ذلك أبدًا. |

تتقاسم الإجراءات العرض الداخلي للوحة بالتساوي، وبينها 16. في الصف، يكون إجراء التراجع (dismissive) في البداية وإجراء التأكيد (confirming) في النهاية، والإجراءات غير المعلَّمة بينهما بالترتيب المعطى. وحين تكاد تسمية تلتف في حصتها، تُرصّ في عمود وإجراء التأكيد في الأعلى. التركيز وقارئات الشاشة تتبع ترتيب المواضع. اضبط `variant` لزر واحد لتعطي ذلك الإجراء مظهرًا آخر؛ والمنتج الذي يريد مظهرًا آخر لكل اللوحات يضبطه مرة واحدة في طبقته (`actions.variant`، و `actions.arrangement` لصف مضغوط عند البداية، أو `close-button.variant` لزر الإغلاق).

تبقى الترويسة والإجراءات في مكانها حين يُمرَّر المحتوى. الفاصل فوق الإجراءات يُرسم دائمًا، وكذلك في النظام الافتراضي فاصل مماثل تحت الترويسة: فكلاهما حدّ للوحة تبقى بجانب الصفحة. اضبط `dividerVisibility` على `scroll` (`divider-visibility` على الويب) ليُرسم فاصل الترويسة فقط ما دام المحتوى ممرَّرًا بعيدًا عن أعلاه؛ ويختار `cupertino` و `m3-like` ذلك لكل اللوحات. وما دامت لوحة المفاتيح الظاهرة على الشاشة تغطي أسفل اللوحة، ترتفع الإجراءات لتستقر فوقها مباشرة، ويُمرَّر المحتوى في الحيّز الباقي. لون اللوحة يمتد تحت هوامش النظام (insets) عند نهايتها وأعلاها وأسفلها، وترويستها ومحتواها وإجراءاتها تبقى داخل تلك الهوامش. وفي التخطيط من اليمين إلى اليسار تقع اللوحة عند الحافة اليسرى، معكوسةً.

### الفتح والتركيز والإغلاق

- **فتح لوحة قياسية.** تنزلق داخلةً عند حافة النهاية والمحتوى المجاور لها يفسح لها. ينتقل التركيز إلى العنصر الذي علّمته، أو إلى اللوحة، فيُقرأ عنوانها الرئيسي أولًا. واللوحة المفتوحة من البداية لا تنقل تركيزًا.
- **العمل بجانب لوحة قياسية.** المفتاح Tab ينتقل من اللوحة إلى المحتوى، وتبقى اللوحة مفتوحة. والمفتاح Escape والرجوع والسحب لا تفعل بها شيئًا.
- **فتح لوحة نمطية.** تنزلق داخلةً من حافة النهاية والحجاب يظهر تدريجيًّا فوق بقية النافذة. ينتقل التركيز إلى الداخل كما في اللوحة القياسية ويبقى فيه: المفتاح Tab من آخر عنصر يقبل التركيز يذهب إلى الأول، و Shift+Tab من الأول يذهب إلى الأخير. كل ما هو خلفها خامل. واللوحة النمطية المفتوحة من البداية تنقل التركيز إلى الداخل هي الأخرى، لأن لا شيء خلفها يستطيع أن يأخذ التركيز.
- **الإغلاق.** تنزلق اللوحة خارجةً إلى حافة النهاية، وفي اللوحة النمطية يختفي الحجاب تدريجيًّا. وإن كان التركيز في داخلها، عاد إلى العنصر الذي كان عليه حين فُتحت اللوحة.
- **سحب لوحة نمطية.** تتبع الإصبع حالما يتجاوز هامش اللمس (touch slop) في المنصة. الإفلات بعد أكثر من نصف عرضها نحو حافة النهاية، أو دفعة سريعة (fling) في ذلك الاتجاه، يطلب منها أن تُغلق بالسبب `drag`. والسحب الأقصر والأبطأ يعيدها منزلقةً. والدفعة السريعة وهامش اللمس هما ما تستخدمه [اللوحة السفلية](https://tanrei.chnotchy.com/ar/components/bottom-sheet/).
- **التكدّس.** مربع الحوار أو قائمة الخيارات أو لوحة نمطية أخرى تُفتح من لوحة جانبية نمطية تتكدّس فوقها. المفتاح Escape يغلق الأعلى فقط.
- **لا فتح بسحب من الحافة.** تُفتح اللوحة من زر، لا من حركة سحب سريعة (swipe) عند حافة الشاشة أبدًا.
- حيث تُقلَّل الحركة، تُفتح اللوحة القياسية وتُغلق فورًا، وتظهر اللوحة النمطية وتختفي تدريجيًّا من دون انزلاق.

### افعل

- اكتب عنوانًا رئيسيًّا قصيرًا يسمّي محتوى اللوحة: «عوامل التصفية»، «التفاصيل»، «الخصائص».
- أبقِ زر الإغلاق في اللوحة القياسية، وهو يُرسم افتراضيًّا، ما لم يغلقها عنصر تحكم من عندك، مثل زر «عوامل التصفية» يُبدَّل.
- استخدم المحتوى نفسه في الهيئتين، ليتنقل المنتج بينهما بعرض النافذة وحده.
- علِّم كل إجراء بالعلامة `confirming` أو `dismissive`، ودع اللوحة تضعها في مواضعها.
- أعطِ اللوحة النمطية إجراءً يُنهي المهمة، مثل «تطبيق»، لا مخارج فقط.

### لا تفعل

- لا تستخدم لوحة جانبية نمطية في نافذة عريضة بما يكفي للوحة قياسية.
- لا تضع تنقل المنتج في لوحة جانبية. استخدم [درج التنقل](https://tanrei.chnotchy.com/ar/components/navigation-drawer/).
- لا تغيّر مظهر زر الإغلاق ولا الأزرار التي في منفذ الإجراءات بما يتجاوز `variant`، ولا ترتّب الإجراءات بيدك.
- لا تفتح لوحة جانبية لم يطلبها الشخص.

## إمكانية الوصول

### ما يتكفل به المكوّن

- تُعرَّف اللوحة النمطية للتقنيات المساعدة مربعَ حوار نمطيًّا اسمه عنوانها الرئيسي. واللوحة القياسية منطقة تكميلية (complementary) اسمها عنوانها الرئيسي، وهي على الويب مَعلم (landmark) من النوع aside.
- يُعرَّف العنوان الرئيسي عنوانًا (heading). والفاصل مخفي عن التقنيات المساعدة.
- الفتح ينقل التركيز إلى العنصر الذي علّمته، أو إلى اللوحة. والإغلاق يعيد التركيز إلى العنصر الذي كان عليه حين فُتحت اللوحة. وهذا يصح في الهيئتين.
- في اللوحة النمطية، المفتاحان Tab و Shift+Tab يدوران داخلها، وكل ما هو خلفها خامل ومخفي عن التقنيات المساعدة، والمفتاح Escape والرجوع والضغط على الحجاب تطلب منها أن تُغلق. ومع التراكبات المتكدّسة، لا يُغلق إلا الأعلى.
- من اللوحة القياسية، يمضي المفتاح Tab إلى المحتوى، وتبقى اللوحة مفتوحة. وهي تتجاهل المفتاح Escape والرجوع.
- زر الإغلاق زر Tanrei بأيقونة فقط تسمّيه الخاصية `closeLabel`، والإجراءات أزرار Tanrei. كل منها يستوفي الحد الأدنى للهدف بنفسه، والمسافة 16 بين الإجراءات المرصوصة تُبقي كل هدف كاملًا.
- التركيز وقارئات الشاشة تلقى الإجراءات بترتيب مواضعها، وهو الترتيب نفسه الذي يراه الشخص المبصر. تبقى الإجراءات فوق لوحة المفاتيح الظاهرة على الشاشة، فيبقى الوصول إليها ممكنًا والشخص يكتب في اللوحة.
- المفتاحان Enter و Space يفعّلان زر الإغلاق أو الإجراء الذي عليه التركيز. اللوحة لا تضيف إجراءً افتراضيًّا إلى المفتاح Enter.
- السحب ليس الطريقة الوحيدة للإغلاق أبدًا: زر الإغلاق والمفتاح Escape والرجوع والحجاب تفعل ذلك أيضًا.
- النص الأكبر يُبقي عرض اللوحة، ويكبّر ترويستها مع العنوان الرئيسي، ويمرّر المحتوى.
- تبلّغ بنيات التنقيح (debug) عن لوحة بلا عنوان رئيسي، وعن زر إغلاق بلا `closeLabel`، وعن لوحة نمطية فُتحت بلا إجراء، وعن طراز ليس في نظام التصميم.

فجوات معروفة في المنصات:

- على SwiftUI و Compose و Flutter و React Native: الخاصية `dividerVisibility` تنتظر أن تلحق تلك المنصات. فهي ترسم الفاصل تحت الترويسة فقط ما دام المحتوى ممرَّرًا، كما تفعل `scroll`.
- الويب: في Firefox تغادر اللوحة النمطية فورًا بدل أن تنزلق خارجةً، كما يفعل مربع الحوار. والسحب باللمس وهوامش المنطقة الآمنة الحقيقية لا يغطيها اختبار.
- ليس في SwiftUI دور لمربع الحوار ولا للمنطقة التكميلية: اللوحة حاوية اسمها عنوانها الرئيسي، وتحمل الصفة النمطية (modal trait) ما دامت نمطية. ولا يستطيع SwiftUI أن يعطي تركيز لوحة المفاتيح للوحة نفسها: من دون علامة ينتقل VoiceOver إلى العنوان الرئيسي ويبقى تركيز لوحة المفاتيح حيث كان. والإغلاق لا يعيد تركيز لوحة المفاتيح إلى شيء؛ وعلى iOS يعيد الغطاء النمطي تركيز VoiceOver، واللوحة القياسية تتركه. اللوحة القياسية لا تُقصّ وهي تنزلق، فضعها عند حافة النهاية من صف النافذة. وعلى macOS تغطي اللوحة النمطية العرض الذي أُلحق به المُعدِّل فقط، فألحقه بجذر النافذة.
- على SwiftUI: النص المجرّد في المحتوى يأخذ خط المتن من دون ارتفاع سطره، فلا يوضع تمامًا حيث تضعه المنصات الأخرى. والمفتاح Escape وإيماءة الخروج في iOS موصولان لكن لا اختبار يضغطهما.
- على Compose: حين تُغلق لوحة نمطية، تحتفظ نافذتها بالتركيز إلى أن تنتهي حركة الخروج. وإلى أن تلحق، تُستدعى الإجراءات في `RowScope`، لكن اللوحة ترتّب تخطيطها بنفسها، فلا يفعل `weight` ولا المحاذاة شيئًا. وعمليات السحب فيها متحقَّق منها تحت Robolectric فقط، لا على جهاز.
- على Flutter: اللوحة القياسية حاوية مسمّاة، لا مَعلم تكميلي، لأن Flutter 3.32 ليس فيه دور كهذا. واللوحة لم تُفحص بعد مع TalkBack و VoiceOver على جهاز.
- لا يستطيع React Native أن يعرف أي عنصر كان عليه التركيز، فحين تُغلق اللوحة يذهب التركيز حيث تضعه المنصة، لا إلى العنصر الذي فتحها. والمكوّن Modal والسحب وحركة العرض والهوامش متحقَّق منها في Jest فقط، لا على جهاز قط.
- مع الإعداد `m3-like`، تحتفظ اللوحة بعرض النظام الافتراضي وبحافة الخط الرفيع التي للهيئة القياسية: قيم Material تنتظر أن يغطي مرجع المطابقة اللوحة الجانبية.

### ما عليك فعله

- أعطِ كل لوحة قيمة للخاصية `headline`. هي اسم اللوحة.
- أعطِ زر الإغلاق، الذي يُرسم افتراضيًّا، قيمة للخاصية `closeLabel` تقول ما الذي يغلقه، مثل «إغلاق عوامل التصفية».
- أغلق اللوحة بنفسك: اضبط `open` على false استجابةً لطلب الإغلاق (الحدث `close` على الويب، و `onClose` على Compose و Flutter و React Native). وعلى SwiftUI تكتب اللوحة false في الربط عنك.
- أعطِ اللوحة القياسية طريقة لإغلاقها: زر إغلاقها، أو عنصر تحكم من عندك له تسمية.
- أعطِ اللوحة النمطية إجراءً واحدًا على الأقل، وعلِّم كل إجراء بالعلامة `confirming` أو `dismissive`، وأغلق اللوحة من معالجه.
- افتح اللوحة من زر مرئي له تسميته الخاصة.
- علِّم التركيز الأولي حيث يفيد، مثل عامل التصفية الأول.
