Sheet

Slide a panel up from the bottom of the screen for secondary actions or a short form. Users close it by tapping the backdrop or dragging it down.

Installation

npx @blotch/dolan add sheet
import {
  UiSheet,
  UiSheetHeader,
  UiSheetTitle,
  UiSheetDescription,
  UiSheetFooter,
} from '../components/ui/sheet';

This also adds Bottom Sheet, which handles the sliding, dragging and backdrop.

Usage

import { Component, signal } from '@angular/core';
import {
  UiSheet,
  UiSheetHeader,
  UiSheetTitle,
  UiSheetDescription,
  UiSheetFooter,
} from '../components/ui/sheet';
import { UiButton } from '../components/ui/button';

@Component({
  selector: 'app-settings-sheet',
  imports: [
    UiSheet,
    UiSheetHeader,
    UiSheetTitle,
    UiSheetDescription,
    UiSheetFooter,
    UiButton,
  ],
  template: `
    <ui-button (pressed)="open.set(true)">Settings</ui-button>

    <ui-sheet [(open)]="open">
      <ui-sheet-header>
        <ui-sheet-title>Settings</ui-sheet-title>
        <ui-sheet-description>Configure your preferences.</ui-sheet-description>
      </ui-sheet-header>
      <ui-sheet-footer>
        <ui-button (pressed)="open.set(false)">Save changes</ui-button>
        <ui-button variant="outline" (pressed)="open.set(false)">
          Cancel
        </ui-button>
      </ui-sheet-footer>
    </ui-sheet>
  `,
})
export class SettingsSheet {
  protected readonly open = signal(false);
}
  • [(open)] two-way binds a signal. Set it to true to slide the sheet up and false to slide it down.
  • A backdrop tap sets open back to false.
  • The bar at the top is a drag handle. Dragging it down more than 100px closes the sheet, and a shorter drag springs it back. The backdrop lightens as it moves.
  • The sheet pads its content and keeps it clear of the home indicator at the bottom of the screen.
  • <ui-sheet-title> and <ui-sheet-description> render <text>, so put plain text in them. The footer stacks its buttons full width.

Other content

Put any elements between the header and footer. Import LYNX_ELEMENTS in your component for them.

<ui-sheet [(open)]="open">
  <ui-sheet-header>
    <ui-sheet-title>Filters</ui-sheet-title>
  </ui-sheet-header>
  <view class="flex flex-col gap-3 py-4">
    <text class="text-sm text-foreground">Notifications: on</text>
  </view>
</ui-sheet>

The sheet grows with its content. For long content, put a <scroll-view> with a fixed height inside, because a scroll view needs one to scroll.

Run code after it closes

closed emits once the slide-down animation has finished. It fires from a timer, outside any tap handler, so it's a safe place to navigate. To navigate straight from a button instead, wrap the navigation in setTimeout (see the setTimeout rule).

<ui-sheet [(open)]="open" (closed)="onClosed()">…</ui-sheet>

API

UiSheet

InputTypeDefaultDescription
openbooleanfalseWhether the sheet is shown. Supports [(open)].
classstring''Extra Tailwind classes for the content area inside the panel. Default padding is px-6 pb-6.
OutputTypeDescription
openChangebooleanEmits false when a backdrop tap or a drag closes the sheet. Part of [(open)].
closedvoidEmits after the close animation finishes.

All sub-components (UiSheetHeader, UiSheetTitle, UiSheetDescription, UiSheetFooter) accept class.