# Sheet

> Side-over panel that slides from the inline-start, inline-end or bottom edge, with header, scrolling body and footer. Wraps Base UI Dialog.

Source: https://docs.nasaqui.com/components/sheet

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/sheet.json
```

A panel that slides in from an edge of the screen. Use it for editing a record, filters, or a notification
list, where a centred dialog would be too small. Sides are **logical**: `end` is the right edge in LTR and
the left edge in RTL. It is built on Base UI `Dialog`, so it is modal with a focus trap.

## When to use

- A form with several fields, a detail view, filters, or a notifications list.
- Mobile action panels (`side="bottom"`).

## When not to use

- A short confirmation or one field: use [`Dialog`](https://docs.nasaqui.com/components/dialog).
- A list of actions: use [`DropdownMenu`](https://docs.nasaqui.com/components/dropdown-menu).
- Permanent side content: use the layout in [`AppShell`](https://docs.nasaqui.com/components/app-shell).

## Import

```tsx
import {
  Sheet, SheetTrigger, SheetClose, SheetContent,
  SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter,
} from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import {
  Button, Field, FieldLabel, Input, Sheet, SheetBody, SheetClose, SheetContent,
  SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,
} from "@fadymondy/nasaq/web";

export function EditProject() {
  return (
    <Sheet>
      <SheetTrigger render={<Button />}>Edit project</SheetTrigger>
      <SheetContent side="end">
        <SheetHeader>
          <SheetTitle>Edit project</SheetTitle>
          <SheetDescription>Changes save when you press Save.</SheetDescription>
        </SheetHeader>
        <SheetBody className="flex flex-col gap-4 p-4">
          <Field>
            <FieldLabel>Name</FieldLabel>
            <Input defaultValue="Nasaq" />
          </Field>
        </SheetBody>
        <SheetFooter className="justify-end">
          <SheetClose render={<Button variant="ghost" />}>Cancel</SheetClose>
          <SheetClose render={<Button variant="primary" />}>Save</SheetClose>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
```

## Anatomy

```
Sheet                            Base UI Dialog.Root
├─ SheetTrigger                  Base UI Dialog.Trigger
└─ SheetContent                  data-slot="sheet-content", data-side="end|start|bottom"
   ├─ SheetBackdrop              data-slot="sheet-backdrop" (rendered for you)
   ├─ SheetHeader                data-slot="sheet-header"
   │  ├─ SheetTitle              data-slot="sheet-title"
   │  └─ SheetDescription        data-slot="sheet-description"
   ├─ SheetBody                  data-slot="sheet-body"  (scrolls)
   ├─ SheetFooter                data-slot="sheet-footer"
   │  └─ SheetClose              Base UI Dialog.Close
   └─ close (×) button           data-slot="sheet-close" (when showClose)
```

## API

### `Sheet`, `SheetTrigger`, `SheetClose`

Aliases of Base UI `Dialog.Root`, `Dialog.Trigger`, `Dialog.Close`. `Sheet` takes `open`, `defaultOpen`,
`onOpenChange`, `modal`, `disablePointerDismissal`. `SheetTrigger` and `SheetClose` accept `render`.

### `SheetContent`

`SheetContentProps` extends Base UI `Dialog.Popup` props and the `side` variant.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side?` | `"end" \| "start" \| "bottom"` | `"end"` | Edge the sheet slides from. Logical, so it mirrors in RTL. |
| `showClose?` | `boolean` | `true` | Renders the × button at the top inline-end corner. |
| `closeLabel?` | `string` | `"Close"` / `"إغلاق"` by locale | `aria-label` for the × button. Defaults from the Nasaq locale (`ar*` gives Arabic); pass your own to override. |
| `className?` | `string` | none | Merged onto the popup. |

Sizes: `start` / `end` are `h-dvh`, `w-[min(24rem,100vw)]`. `bottom` is full width, `max-h-[85dvh]`, with rounded top corners.

### `SheetBackdrop`

Takes Base UI `Dialog.Backdrop` props. Rendered by `SheetContent`; exported for custom compositions.

### `SheetHeader`, `SheetBody`, `SheetFooter`

`ComponentProps<"div">`. The header has a bottom border and `pe-12` for the close button. The body is
`flex-1 overflow-y-auto overscroll-contain` with no padding (add your own). The footer is a bordered row
with `gap-2`; add `justify-end` to align actions.

### `SheetTitle`, `SheetDescription`

Base UI `Dialog.Title` / `Dialog.Description`, styled with `text-label` and `text-caption`.

## Examples

### Each side

```tsx
import {
  Button, Sheet, SheetBody, SheetContent, SheetDescription,
  SheetHeader, SheetTitle, SheetTrigger,
} from "@fadymondy/nasaq/web";

export function Sides() {
  return (
    <div className="flex gap-3">
      {(["end", "start", "bottom"] as const).map((side) => (
        <Sheet key={side}>
          <SheetTrigger render={<Button />}>Open {side}</SheetTrigger>
          <SheetContent side={side}>
            <SheetHeader>
              <SheetTitle>Filters</SheetTitle>
              <SheetDescription>Narrow the list.</SheetDescription>
            </SheetHeader>
            <SheetBody className="p-4">Content</SheetBody>
          </SheetContent>
        </Sheet>
      ))}
    </div>
  );
}
```

### Arabic, controlled

```tsx
import { Button, Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function NotificationsAr() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Button onClick={() => setOpen(true)}>الإشعارات</Button>
      <Sheet open={open} onOpenChange={setOpen}>
        <SheetContent closeLabel="إغلاق">
          <SheetHeader>
            <SheetTitle>الإشعارات</SheetTitle>
          </SheetHeader>
          <SheetBody className="p-4">لا توجد إشعارات جديدة.</SheetBody>
        </SheetContent>
      </Sheet>
    </>
  );
}
```

## Accessibility

Same as Dialog (Base UI): `role="dialog"`, modal, focus trapped and restored, background inert.

| Key | Action |
| --- | --- |
| `Esc` | Closes the sheet. |
| `Tab` / `Shift+Tab` | Cycles focus inside the sheet. |
| `Enter` / `Space` | Activates the focused button. |

- Always render a `SheetTitle` (accessible name).
- The × button uses `aria-label={closeLabel}` (default "Close" / "إغلاق" by Nasaq locale).

## RTL & i18n

- `end` and `start` use `end-0` / `start-0`, logical borders, and a reversed slide offset under `rtl:`, so the sheet enters from the correct edge in both directions.
- `bottom` does not depend on direction.
- The only built-in string is the default `closeLabel`: "Close" in English, "إغلاق" when the Nasaq locale starts with `ar`.

## Styling & tokens

- Surface level 3 (`bg-popover`), `shadow-floating`, `border-border`, `rounded-t-floating` (bottom).
- Motion: 200ms translate (2rem) plus opacity. Backdrop `bg-nq-fg/10`, dark `bg-nq-bg/60`.
- Target `[data-slot=sheet-content][data-side=end]`, `[data-slot=sheet-backdrop]`.

## Do / Don't

- **Do** use the header / body / footer parts so long content scrolls while actions stay visible.
- **Do** prefer `end` for detail and edit panels, `bottom` for mobile.
- **Don't** hard-code `left` / `right`; use `start` / `end`.
- **Don't** use a sheet for a one-line confirmation.

## Related

- [Dialog](https://docs.nasaqui.com/components/dialog) · [Button](https://docs.nasaqui.com/components/button) · [AppShell](https://docs.nasaqui.com/components/app-shell)

## Lab

https://docs.nasaqui.com/?path=/docs/components-overlays-sheet--docs

## Code

### React

```tsx
import {
  Button, Field, FieldLabel, Input, Sheet, SheetBody, SheetClose, SheetContent,
  SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,
} from "@fadymondy/nasaq/web";

export function EditProject() {
  return (
    <Sheet>
      <SheetTrigger render={<Button />}>Edit project</SheetTrigger>
      <SheetContent side="end">
        <SheetHeader>
          <SheetTitle>Edit project</SheetTitle>
          <SheetDescription>Changes save when you press Save.</SheetDescription>
        </SheetHeader>
        <SheetBody className="flex flex-col gap-4 p-4">
          <Field>
            <FieldLabel>Name</FieldLabel>
            <Input defaultValue="Nasaq" />
          </Field>
        </SheetBody>
        <SheetFooter className="justify-end">
          <SheetClose render={<Button variant="ghost" />}>Cancel</SheetClose>
          <SheetClose render={<Button variant="primary" />}>Save</SheetClose>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
```

### shadcn

```tsx
import { Button } from "@/components/ui/button";
import { Field, FieldLabel, Input } from "@/components/ui/field";
import { Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";

export function EditProject() {
  return (
    <Sheet>
      <SheetTrigger render={<Button />}>Edit project</SheetTrigger>
      <SheetContent side="end">
        <SheetHeader>
          <SheetTitle>Edit project</SheetTitle>
          <SheetDescription>Changes save when you press Save.</SheetDescription>
        </SheetHeader>
        <SheetBody className="flex flex-col gap-4 p-4">
          <Field>
            <FieldLabel>Name</FieldLabel>
            <Input defaultValue="Nasaq" />
          </Field>
        </SheetBody>
        <SheetFooter className="justify-end">
          <SheetClose render={<Button variant="ghost" />}>Cancel</SheetClose>
          <SheetClose render={<Button variant="primary" />}>Save</SheetClose>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import {
  NqButton, NqField, NqFieldLabel, NqInput, NqSheet, NqSheetBody, NqSheetClose, NqSheetContent,
  NqSheetDescription, NqSheetFooter, NqSheetHeader, NqSheetTitle, NqSheetTrigger,
} from "@fadymondy/nasaq/vue";
import { ref } from "vue";

const name = ref("Nasaq");
</script>

<template>
  <NqSheet>
    <NqSheetTrigger as-child><NqButton>Edit project</NqButton></NqSheetTrigger>
    <NqSheetContent side="end">
      <NqSheetHeader>
        <NqSheetTitle>Edit project</NqSheetTitle>
        <NqSheetDescription>Changes save when you press Save.</NqSheetDescription>
      </NqSheetHeader>
      <NqSheetBody class="flex flex-col gap-4 p-4">
        <NqField>
          <NqFieldLabel>Name</NqFieldLabel>
          <NqInput v-model="name" />
        </NqField>
      </NqSheetBody>
      <NqSheetFooter class="justify-end">
        <NqSheetClose as-child><NqButton variant="ghost">Cancel</NqButton></NqSheetClose>
        <NqSheetClose as-child><NqButton variant="primary">Save</NqButton></NqSheetClose>
      </NqSheetFooter>
    </NqSheetContent>
  </NqSheet>
</template>
```

### Blade

```blade
<x-nq::sheet>
    <x-nq::sheet.trigger>Edit project</x-nq::sheet.trigger>
    <x-nq::sheet.content side="end">
        <x-nq::sheet.header>
            <x-nq::sheet.title>Edit project</x-nq::sheet.title>
            <x-nq::sheet.description>Changes save when you press Save.</x-nq::sheet.description>
        </x-nq::sheet.header>
        <x-nq::sheet.body class="flex flex-col gap-4 p-4">
            <x-nq::field>
                <x-nq::field.label>Name</x-nq::field.label>
                <x-nq::field.input value="Nasaq" />
            </x-nq::field>
        </x-nq::sheet.body>
        <x-nq::sheet.footer class="justify-end">
            <x-nq::sheet.close variant="ghost">Cancel</x-nq::sheet.close>
            <x-nq::sheet.close variant="primary">Save</x-nq::sheet.close>
        </x-nq::sheet.footer>
    </x-nq::sheet.content>
</x-nq::sheet>
```

### HTML + Alpine

```html
<div data-slot="sheet" x-data="nqDialog(false)" x-modelable="open" x-id="['nq-dialog']" class="contents">
    <button data-slot="sheet-trigger"
     type="button"                         aria-haspopup="dialog" x-on:click="show()" :aria-expanded="open" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Edit project</button>
    <template x-teleport="body">
    <div data-slot="sheet-portal">
        <div data-slot="sheet-backdrop" x-nq-presence="open" x-on:click="close()" class="fixed inset-0 z-50 bg-nq-fg/10 transition-opacity duration-200 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0 dark:bg-nq-bg/60"></div>
        <div data-slot="sheet-content" data-side="end" x-bind="popup" x-nq-presence="open" x-trap.noscroll="open"
            class="fixed z-50 flex flex-col bg-popover text-popover-foreground outline-none shadow-floating transition-[translate,opacity] duration-200 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0 inset-y-0 end-0 h-dvh w-[min(24rem,100vw)] border-s border-border data-starting-style:translate-x-8 data-ending-style:translate-x-8 rtl:data-starting-style:-translate-x-8 rtl:data-ending-style:-translate-x-8">
            <div data-slot="sheet-header" class="flex flex-col gap-1 border-b border-border px-4 py-3.5 pe-12"><h2 data-slot="sheet-title" :id="$id('nq-dialog', 'title')" class="text-label text-foreground">Edit project</h2>
            <p data-slot="sheet-description" :id="$id('nq-dialog', 'description')" class="text-caption text-muted-foreground">Changes save when you press Save.</p></div>
        <div data-slot="sheet-body" class="min-h-0 flex-1 overflow-y-auto overscroll-contain flex flex-col gap-4 p-4"><div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Name</label>
                <input data-slot="input" type="text"
                    value="Nasaq" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control">
</div></div>
        <div data-slot="sheet-footer" class="flex items-center gap-2 border-t border-border px-4 py-3 justify-end"><button data-slot="sheet-close"
     type="button"                         x-on:click="close()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Cancel</button>
            <button data-slot="sheet-close"
     type="button"                         x-on:click="close()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        Save</button></div>
                            <button type="button" data-slot="sheet-close" x-on:click="close()" aria-label="Close"
                    class="absolute end-3 top-3 inline-flex size-8 items-center justify-center rounded-control text-muted-foreground transition-colors duration-150 hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
                    </div>
    </div>
</template>
</div>
```
