sheet

Extends the Dialog component to display content that complements the main content of the screen.

PreviousNext
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';

import { ZardButtonComponent } from '@/shared/components/button';
import { ZardInputComponent } from '@/shared/components/input';
import { ZardSheetImports } from '@/shared/components/sheet/sheet.imports';
import { ZardSheetService } from '@/shared/components/sheet/sheet.service';
import { ZardTabsImports } from '@/shared/components/tabs/tabs.imports';

/** Content the service renders inside the sheet. */
@Component({
  selector: 'z-demo-sheet-preview-form',
  imports: [ReactiveFormsModule, ZardInputComponent],
  template: `
    <form [formGroup]="form" class="grid flex-1 auto-rows-min gap-6 px-4">
      <div class="grid gap-3">
        <label for="sheet-service-name" class="text-sm leading-none font-medium select-none">Name</label>
        <input z-input id="sheet-service-name" formControlName="name" />
      </div>
      <div class="grid gap-3">
        <label for="sheet-service-username" class="text-sm leading-none font-medium select-none">Username</label>
        <input z-input id="sheet-service-username" formControlName="username" />
      </div>
    </form>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  exportAs: 'zardDemoSheetPreviewForm',
})
export class ZardDemoSheetPreviewFormComponent {
  readonly form = new FormGroup({
    name: new FormControl('Pedro Duarte'),
    username: new FormControl('@peduarte'),
  });
}

@Component({
  selector: 'z-demo-sheet-preview',
  imports: [ZardButtonComponent, ZardInputComponent, ZardSheetImports, ZardTabsImports, ReactiveFormsModule],
  template: `
    <z-tab-group>
      <z-tab label="Template">
        <button type="button" z-button zType="outline" (click)="visible.set(true)">Open</button>

        <z-sheet [(zVisible)]="visible">
          <z-sheet-header>
            <z-sheet-title>Edit profile</z-sheet-title>
            <z-sheet-description>Make changes to your profile here. Click save when you're done.</z-sheet-description>
          </z-sheet-header>
          <form [formGroup]="form" class="grid flex-1 auto-rows-min gap-6 px-4">
            <div class="grid gap-3">
              <label for="sheet-name" class="text-sm leading-none font-medium select-none">Name</label>
              <input z-input id="sheet-name" formControlName="name" />
            </div>
            <div class="grid gap-3">
              <label for="sheet-username" class="text-sm leading-none font-medium select-none">Username</label>
              <input z-input id="sheet-username" formControlName="username" />
            </div>
          </form>
          <z-sheet-footer>
            <button type="button" z-button (click)="save()">Save changes</button>
            <button type="button" z-button zType="outline" z-sheet-close>Close</button>
          </z-sheet-footer>
        </z-sheet>
      </z-tab>

      <z-tab label="Service">
        <button type="button" z-button zType="outline" (click)="openSheet()">Open</button>
      </z-tab>
    </z-tab-group>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSheetPreviewComponent {
  private readonly sheetService = inject(ZardSheetService);

  readonly visible = signal(false);

  readonly form = new FormGroup({
    name: new FormControl('Pedro Duarte'),
    username: new FormControl('@peduarte'),
  });

  save() {
    console.log('Form submitted:', this.form.value);
    this.visible.set(false);
  }

  openSheet() {
    this.sheetService.create({
      zTitle: 'Edit profile',
      zDescription: `Make changes to your profile here. Click save when you're done.`,
      zContent: ZardDemoSheetPreviewFormComponent,
      zOkText: 'Save changes',
      zCancelText: 'Close',
      zOnOk: instance => {
        console.log('Form submitted:', instance.form.value);
      },
    });
  }
}

Installation

Copy
npx zard-cli@latest add sheet

Usage

import { ZardSheetImports } from '@/shared/components/sheet/sheet.imports';
Copy
<button type="button" z-button zType="outline" (click)="visible.set(true)">Open</button>

<z-sheet [(zVisible)]="visible">
  <z-sheet-header>
    <z-sheet-title>Edit profile</z-sheet-title>
    <z-sheet-description>Make changes to your profile here.</z-sheet-description>
  </z-sheet-header>
  <p class="px-4">Sheet content goes here.</p>
  <z-sheet-footer>
    <button type="button" z-button (click)="save()">Save changes</button>
    <button type="button" z-button zType="outline" z-sheet-close>Close</button>
  </z-sheet-footer>
</z-sheet>
Copy

Composition

Use the following composition to build a sheet:

z-sheet
├── z-sheet-header
│   ├── z-sheet-title
│   └── z-sheet-description
├── (your content)
└── z-sheet-footer
    └── [z-sheet-close]
Copy

Examples

side

Use zSide, as an input or an option, to set the edge of the screen where the sheet appears. Values are top, right, bottom, or left.
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button';
import type { ZardSheetSide } from '@/shared/components/sheet/sheet-panel.component';
import { ZardSheetImports } from '@/shared/components/sheet/sheet.imports';
import { ZardSheetService } from '@/shared/components/sheet/sheet.service';
import { ZardTabsImports } from '@/shared/components/tabs/tabs.imports';

const SIDES = ['top', 'right', 'bottom', 'left'] as const satisfies readonly ZardSheetSide[];

const PARAGRAPHS = Array.from({ length: 10 }).map(
  () =>
    'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
);

/** Content the service renders: a scrolling column of paragraphs. */
@Component({
  selector: 'z-demo-sheet-side-content',
  template: `
    @for (paragraph of paragraphs; track $index) {
      <p class="mb-2 leading-relaxed">{{ paragraph }}</p>
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: { class: 'no-scrollbar min-h-0 overflow-y-auto px-4' },
})
export class ZardDemoSheetSideContentComponent {
  protected readonly paragraphs = PARAGRAPHS;
}

@Component({
  selector: 'z-demo-sheet-side',
  imports: [ZardButtonComponent, ZardSheetImports, ZardTabsImports],
  template: `
    <z-tab-group>
      <z-tab label="Template">
        <div class="flex flex-wrap gap-2">
          @for (option of sides; track option) {
            <button type="button" z-button zType="outline" class="capitalize" (click)="open(option)">
              {{ option }}
            </button>
          }
        </div>

        <!-- Horizontal sheets already fill the viewport height; cap the vertical ones so the
             content scrolls instead of pushing the footer off-screen. -->
        <z-sheet [(zVisible)]="visible" [zSide]="side()" [class]="vertical() ? 'max-h-[50vh]' : ''">
          <z-sheet-header>
            <z-sheet-title>Edit profile</z-sheet-title>
            <z-sheet-description>Make changes to your profile here. Click save when you're done.</z-sheet-description>
          </z-sheet-header>
          <div class="no-scrollbar min-h-0 overflow-y-auto px-4">
            @for (paragraph of paragraphs; track $index) {
              <p class="mb-2 leading-relaxed">{{ paragraph }}</p>
            }
          </div>
          <z-sheet-footer>
            <button type="button" z-button z-sheet-close>Save changes</button>
            <button type="button" z-button zType="outline" z-sheet-close>Cancel</button>
          </z-sheet-footer>
        </z-sheet>
      </z-tab>

      <z-tab label="Service">
        <div class="flex flex-wrap gap-2">
          @for (option of sides; track option) {
            <button type="button" z-button zType="outline" class="capitalize" (click)="openSheet(option)">
              {{ option }}
            </button>
          }
        </div>
      </z-tab>
    </z-tab-group>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSheetSideComponent {
  private readonly sheetService = inject(ZardSheetService);

  protected readonly sides = SIDES;
  protected readonly paragraphs = PARAGRAPHS;

  readonly visible = signal(false);
  readonly side = signal<ZardSheetSide>('right');
  readonly vertical = computed(() => this.side() === 'top' || this.side() === 'bottom');

  open(side: ZardSheetSide) {
    this.side.set(side);
    this.visible.set(true);
  }

  openSheet(side: ZardSheetSide) {
    this.sheetService.create({
      zTitle: 'Edit profile',
      zDescription: `Make changes to your profile here. Click save when you're done.`,
      zContent: ZardDemoSheetSideContentComponent,
      zSide: side,
      zCustomClasses: side === 'top' || side === 'bottom' ? 'max-h-[50vh]' : undefined,
      zOkText: 'Save changes',
      zCancelText: 'Cancel',
    });
  }
}

no close button

Set [zClosable]="false" in the template, or zClosable: false in the options, to hide the close button; press Escape or click outside the sheet to close it.
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button';
import { ZardSheetImports } from '@/shared/components/sheet/sheet.imports';
import { ZardSheetService } from '@/shared/components/sheet/sheet.service';
import { ZardTabsImports } from '@/shared/components/tabs/tabs.imports';

@Component({
  selector: 'z-demo-sheet-no-close-button',
  imports: [ZardButtonComponent, ZardSheetImports, ZardTabsImports],
  template: `
    <z-tab-group>
      <z-tab label="Template">
        <button type="button" z-button zType="outline" (click)="visible.set(true)">Open Sheet</button>

        <z-sheet [(zVisible)]="visible" [zClosable]="false">
          <z-sheet-header>
            <z-sheet-title>No Close Button</z-sheet-title>
            <z-sheet-description>
              This sheet doesn't have a close button in the top-right corner. Click outside to close.
            </z-sheet-description>
          </z-sheet-header>
        </z-sheet>
      </z-tab>

      <z-tab label="Service">
        <button type="button" z-button zType="outline" (click)="openSheet()">Open Sheet</button>
      </z-tab>
    </z-tab-group>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSheetNoCloseButtonComponent {
  private readonly sheetService = inject(ZardSheetService);

  readonly visible = signal(false);

  openSheet() {
    this.sheetService.create({
      zTitle: 'No Close Button',
      zDescription: "This sheet doesn't have a close button in the top-right corner. Click outside to close.",
      zClosable: false,
      zHideFooter: true,
    });
  }
}

rtl

To enable RTL support in zard/ui, see the RTL configuration guide.
// You will notice this example is rendered inside a wrapper that sets the dir and data-lang attributes, and
// reads its text from a translations helper. This is because this site is not RTL by default.
// In your application you won't need either: set dir="rtl" on <html> (or use the CDK Dir directive on a subtree)
// and write the text of your own language. See the RTL guide: https://zardui.com/docs/rtl

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardSheetImports } from '@/shared/components/sheet/sheet.imports';
import { injectRtlTranslation, type ZardRtlTranslations } from '@/shared/demo-utils/rtl-translation';

interface SheetRtlStrings {
  open: string;
  editProfile: string;
  description: string;
  name: string;
  username: string;
  save: string;
  close: string;
}

const translations: ZardRtlTranslations<SheetRtlStrings> = {
  en: {
    dir: 'ltr',
    values: {
      open: 'Open',
      editProfile: 'Edit profile',
      description: "Make changes to your profile here. Click save when you're done.",
      name: 'Name',
      username: 'Username',
      save: 'Save changes',
      close: 'Close',
    },
  },
  ar: {
    dir: 'rtl',
    values: {
      open: 'فتح',
      editProfile: 'تعديل الملف الشخصي',
      description: 'قم بإجراء تغييرات على ملفك الشخصي هنا. انقر حفظ عند الانتهاء.',
      name: 'الاسم',
      username: 'اسم المستخدم',
      save: 'حفظ التغييرات',
      close: 'إغلاق',
    },
  },
  he: {
    dir: 'rtl',
    values: {
      open: 'פתח',
      editProfile: 'עריכת פרופיל',
      description: 'בצע שינויים בפרופיל שלך כאן. לחץ שמור כשתסיים.',
      name: 'שם',
      username: 'שם משתמש',
      save: 'שמור שינויים',
      close: 'סגור',
    },
  },
};

@Component({
  selector: 'z-demo-sheet-rtl',
  imports: [ZardButtonComponent, ZardInputComponent, ZardSheetImports],
  template: `
    @let t = i18n.t();
    <button type="button" z-button zType="outline" (click)="visible.set(true)">{{ t.open }}</button>

    <!-- zSide stays physical: in RTL the sheet opens from the left, as it opens from the right in LTR. -->
    <z-sheet [(zVisible)]="visible" [zSide]="i18n.dir() === 'rtl' ? 'left' : 'right'">
      <z-sheet-header>
        <z-sheet-title>{{ t.editProfile }}</z-sheet-title>
        <z-sheet-description>{{ t.description }}</z-sheet-description>
      </z-sheet-header>

      <div class="grid gap-4 px-4">
        <div class="grid gap-3">
          <label for="sheet-rtl-name" class="text-sm leading-none font-medium select-none">{{ t.name }}</label>
          <input z-input id="sheet-rtl-name" value="Pedro Duarte" />
        </div>
        <div class="grid gap-3">
          <label for="sheet-rtl-username" class="text-sm leading-none font-medium select-none">
            {{ t.username }}
          </label>
          <input z-input id="sheet-rtl-username" value="peduarte" />
        </div>
      </div>

      <z-sheet-footer>
        <button type="button" z-button z-sheet-close>{{ t.save }}</button>
        <button type="button" z-button zType="outline" z-sheet-close>{{ t.close }}</button>
      </z-sheet-footer>
    </z-sheet>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSheetRtlComponent {
  protected readonly i18n = injectRtlTranslation(translations);

  protected readonly visible = signal(false);
}

API Reference

z-sheetComponent

Root of a declarative sheet. Holds the open state and projects its content into the panel, which exposes `data-side` and `data-state` (`open` | `closed`) while the enter and leave transitions run.

PropertyDescriptionTypeDefault
[zVisible] Open state, two-way bound boolean false
[zSide] Edge of the screen the sheet slides from. Physical in RTL too: pick `left` to open from the inline-start edge of a right-to-left page 'top' | 'right' | 'bottom' | 'left' 'right'
[zSize] Preset width (left/right) or height (top/bottom). Ignored once `zWidth` or `zHeight` is set 'default' | 'sm' | 'lg' 'default'
[zWidth] Explicit width, for left/right sheets string -
[zHeight] Explicit height, for top/bottom sheets string -
[zClosable] Renders the close (X) button in the top-right corner boolean true
[zMaskClosable] Whether clicking outside the sheet (on the mask) closes it boolean true
[zDuration] How long the enter and leave transitions run, in ms number 200
[class] Custom CSS classes applied to the panel ClassValue -
(zAfterOpen) Emitted once the sheet is attached EventEmitter<void> -
(zAfterClose) Emitted once the exit transition has finished EventEmitter<void> -

z-sheet-headerComponent

Layout slot for the top of a sheet, next to `z-sheet-title` and `z-sheet-description`.

PropertyDescriptionTypeDefault
[class] Custom CSS classes to apply ClassValue -

z-sheet-footerComponent

Layout slot for the bottom of a sheet, typically the action buttons.

PropertyDescriptionTypeDefault
[class] Custom CSS classes to apply ClassValue -

z-sheet-titleComponent

Accessible name of the sheet. Wired to `aria-labelledby` automatically.

PropertyDescriptionTypeDefault
[zTitle] Title text or template, when not projecting content string | TemplateRef<void> -
[class] Custom CSS classes to apply ClassValue -

z-sheet-descriptionComponent

Supporting text. Wired to `aria-describedby` automatically.

PropertyDescriptionTypeDefault
[zDescription] Description text or template, when not projecting content string | TemplateRef<void> -
[class] Custom CSS classes to apply ClassValue -

[z-sheet-close]Component

Closes the sheet it is projected into. Works for declarative and service-opened sheets alike.

ZardSheetOptionsComponent

Configuration accepted by `ZardSheetService.create()`.

PropertyDescriptionTypeDefault
[zCancelIcon] Cancel button icon — registered icon name or inline SVG string string -
[zCancelText] Cancel button text, null to hide the button string | null 'Cancel'
[zClosable] Whether the close (X) button in the header is shown. Escape and clicking outside the mask still dismiss the sheet unless `zMaskClosable` is also set to false boolean true
[zContent] Custom content component, template, or HTML string | TemplateRef<T> | Type<T> -
[zCustomClasses] Additional CSS classes to apply ClassValue -
[zData] Data to pass to custom content components object -
[zDescription] Sheet description/body text string -
[zDuration] Exit animation duration in ms number 200
[zHeight] Custom height (e.g., '80vh', '500px') string -
[zHideFooter] Whether to hide the footer with action buttons boolean false
[zMaskClosable] Whether clicking outside the sheet (on the mask) closes it boolean true
[zOkDestructive] Whether the OK button should have destructive styling boolean false
[zOkDisabled] Whether the OK button should be disabled boolean false
[zOkIcon] OK button icon — registered icon name or inline SVG string string -
[zOkText] OK button text, null to hide the button string | null 'OK'
[zOnCancel] Cancel button click handler EventEmitter<T> | OnClickCallback<T> -
[zOnOk] OK button click handler EventEmitter<T> | OnClickCallback<T> -
[zSide] Edge of the screen where the sheet appears 'top' | 'right' | 'bottom' | 'left' 'right'
[zSize] Preset size for the sheet, relative to its side. Ignored in favor of a custom size when `zWidth` or `zHeight` is set 'default' | 'sm' | 'lg' 'default'
[zTitle] Sheet title text or template string | TemplateRef<void> -
[zViewContainerRef] View container for rendering custom content ViewContainerRef -
[zWidth] Custom width (e.g., '400px', '50%') string -

ZardSheetRefComponent

Reference returned by `ZardSheetService.create()`, used to observe and close the sheet.

PropertyDescriptionTypeDefault
[close] Closes the sheet, optionally with a result (result?: R) => void -
[isClosing] Signal that turns true once the sheet starts closing Signal<boolean> false
[result] Signal holding the result passed to close() Signal<R | undefined> undefined
[componentInstance] Signal with the instance of the component rendered as content Signal<T | null> null
github iconwhatsapp icondiscord iconX icon

Made with ❤ in Brazil. Open source and available on GitHub .