# Sheet

A side-anchored panel that slides out from any screen edge to present complementary content or forms.

```slint
import { Button } from "@glint/components/button.slint";
import { Sheet } from "@glint/components/sheet.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            Button {
                text: "Edit profile";
                haspopup: true;
                clicked => { sheet.open = true; }
            }
        }
    }

    sheet := Sheet {
        width: parent.width;
        height: parent.height;
        title: "Edit profile";
        description: "Make changes to your profile details here.";

        VerticalLayout {
            spacing: 8px;

            Text {
                text: "Full Name: Ana Silva";
                color: Tokens.color-foreground;
                font-size: Tokens.typography-body-size;
            }

            Text {
                text: "Email: ana@example.com";
                color: Tokens.color-muted-foreground;
                font-size: Tokens.typography-body-sm-size;
            }
        }
    }
}
```

## Usage

```slint
import { Sheet, SheetSide } from "@glint/components/sheet.slint";

export component AppWindow inherits Window {
    in-out property <bool> show-filters: false;
    callback apply-filters();

    VerticalLayout {
        // your screen layout
    }

    // Last child of the window, sized to it: Slint has no portals, so what
    // draws on top is what comes last.
    Sheet {
        width: parent.width;
        height: parent.height;
        side: SheetSide.right;
        open <=> root.show-filters;
        title: "Filters";
        description: "Narrow down the search results.";
        dismissed => { root.apply-filters(); }
    }
}
```

A `Sheet` is a side-anchored drawer that slides into view over the current window. Its body content goes between the braces via the `@children` slot, placed between the header block and the bottom of the panel.

Mount it **last** among the window’s children, sized to `width: parent.width; height: parent.height`.

## Examples

### Sides

`side` attaches the sheet to any edge of the window: `right` (the default), `left`, `top`, or `bottom`. The slide animation automatically follows the chosen edge.

```slint
import { Button } from "@glint/components/button.slint";
import { Sheet, SheetSide } from "@glint/components/sheet.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        spacing: 12px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;
            spacing: 8px;

            Button {
                text: "Top";
                haspopup: true;
                clicked => { top-sheet.open = true; }
            }
            Button {
                text: "Right";
                haspopup: true;
                clicked => { right-sheet.open = true; }
            }
            Button {
                text: "Bottom";
                haspopup: true;
                clicked => { bottom-sheet.open = true; }
            }
            Button {
                text: "Left";
                haspopup: true;
                clicked => { left-sheet.open = true; }
            }
        }
    }

    top-sheet := Sheet {
        width: parent.width;
        height: parent.height;
        side: SheetSide.top;
        panel-extent: 180px;
        title: "Top Sheet";
        description: "Anchored to the top edge.";
    }

    right-sheet := Sheet {
        width: parent.width;
        height: parent.height;
        side: SheetSide.right;
        title: "Right Sheet";
        description: "Anchored to the right edge.";
    }

    bottom-sheet := Sheet {
        width: parent.width;
        height: parent.height;
        side: SheetSide.bottom;
        panel-extent: 180px;
        title: "Bottom Sheet";
        description: "Anchored to the bottom edge.";
    }

    left-sheet := Sheet {
        width: parent.width;
        height: parent.height;
        side: SheetSide.left;
        title: "Left Sheet";
        description: "Anchored to the left edge.";
    }
}
```

### Custom panel extent

`panel-extent` sets the cross-axis dimension of the sheet: width for left/right sheets (default 360px) or height for top/bottom sheets.

```slint
import { Button } from "@glint/components/button.slint";
import { Sheet } from "@glint/components/sheet.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            Button {
                text: "Open wide sheet";
                haspopup: true;
                clicked => { sheet.open = true; }
            }
        }
    }

    sheet := Sheet {
        width: parent.width;
        height: parent.height;
        panel-extent: 460px;
        title: "Workspace details";
        description: "A wider panel gives extra horizontal space for multi-column layouts.";
    }
}
```

### Backdrop dismissal

By default, clicking the scrim backdrop closes the sheet. Set `dismiss-on-backdrop: false` when the reader must explicitly use the close button or press `Escape`.

```slint
import { Button } from "@glint/components/button.slint";
import { Sheet } from "@glint/components/sheet.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            Button {
                text: "Review terms";
                haspopup: true;
                clicked => { sheet.open = true; }
            }
        }
    }

    sheet := Sheet {
        width: parent.width;
        height: parent.height;
        title: "Terms of service";
        description: "Clicking outside will not close this sheet.";
        dismiss-on-backdrop: false;
    }
}
```

### Restoring focus

`restore-focus` fires whenever the sheet closes, returning keyboard focus to the trigger element.

```slint
import { Button } from "@glint/components/button.slint";
import { Sheet } from "@glint/components/sheet.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger := Button {
                text: "Account settings";
                haspopup: true;
                clicked => { sheet.open = true; }
            }
        }
    }

    sheet := Sheet {
        width: parent.width;
        height: parent.height;
        title: "Account settings";
        description: "Closing will return keyboard focus to the trigger button.";
        restore-focus => { trigger.focus(); }
    }
}
```

## API Reference

### Properties

| Property              | Type           | Default           | Description                                                                     |
| --------------------- | -------------- | ----------------- | ------------------------------------------------------------------------------- |
| `open`                | `in-out bool`  | `false`           | Two-way; consumer sets true to slide in, Glint sets false on close.             |
| `side`                | `in SheetSide` | `SheetSide.right` | Edge to attach to — `top`, `right`, `bottom`, `left`.                           |
| `title`               | `in string`    | no default        | Heading inside the sheet.                                                       |
| `description`         | `in string`    | no default        | Body text under the title.                                                      |
| `close-label`         | `in string`    | `@tr("Close")`    | Accessible label for the panel's close control.                                 |
| `show-close-button`   | `in bool`      | `true`            | Whether the panel carries its top-right close control.                          |
| `dismiss-on-backdrop` | `in bool`      | `true`            | When true, clicking outside the sheet closes it.                                |
| `panel-extent`        | `in length`    | `360px`           | Cross-axis dimension of the panel: width for left/right, height for top/bottom. |

### Callbacks

| Callback          | Description                                                                                                                                                                                                                                                                                                                 |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dismissed()`     | Fired when the sheet closes itself — backdrop, Escape or the close control.                                                                                                                                                                                                                                                 |
| `restore-focus()` | Fired on EVERY close, including one the consumer drives by setting `open = false`, so keyboard focus always gets a new home: `restore-focus => { my-button.focus(); }`. Slint exposes no "previously focused element", so only the trigger's owner can name it. Same contract `Drawer`, `Dialog` and `AlertDialog` publish. |

### Enums

| Enum        | Values                           |
| ----------- | -------------------------------- |
| `SheetSide` | `top`, `right`, `bottom`, `left` |

## Accessibility

- **Role and name.** The open sheet card claims a region landmark named by `title` and described by `description`.
- **Keyboard navigation.** While open, keyboard events land in the sheet overlay so `Escape` immediately dismisses the panel and fires `dismissed()`.
- **Focus restoration.** Every close path — backdrop, Escape, close button, or setting `open = false` — fires `restore-focus()` to hand focus back to the opening trigger.
- **Off-screen tree presence.** When dismissed, the panel leaves the accessibility tree only when its slide-out motion completes and it has physically exited the window viewport.
- **Close button.** The corner close button is announced using `close-label` (defaulting to `@tr("Close")`). It can be hidden with `show-close-button: false`.
- **The trigger.** Controls that open a sheet should declare `haspopup: true`.
