Action Sheet
oge-action-sheet is the mobile list of actions that slides up from the bottom edge — "Share, Edit, Delete, Cancel".
It is a modal role="dialog" labelled by its title and moved to <body> while open, with the overlay family's shared behaviour: focus trap, ref-counted scroll lock, an inert background, the Escape stack, backdrop and swipe-down dismissal and focus restore. The actions are a WAI-ARIA APG menu with one tab stop; Cancel is a separate button. On phones the sheet spans the screen and pads its bottom edge with env(safe-area-inset-bottom); on wider screens it floats.
Basics
Each action has a label, an optional icon (SVG path data) and an optional description; destructive paints it in the danger colour — say it in the text too. ↑/↓ wrap, Home / End jump, Enter or Space choose. Choosing an action closes the sheet and focus returns to the button that opened it.
Last action: none
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeActionSheet } from '@oge-ui/overlay';
import type { OgeActionSheetItem } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeActionSheet],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- A modal role="dialog" labelled by its title, pinned to the bottom
edge and moved to <body> while open. The actions are an APG menu (one
Tab stop, ↑/↓, Home/End); Cancel is a separate button. Escape, the
backdrop and a swipe down on the handle close it, and focus returns to
the button that opened it. -->
<button type="button" (click)="sheet.open()">Photo actions</button>
<oge-action-sheet
#sheet
title="Photo"
description="Taken on 3 October in Göreme"
[items]="actions"
(itemClick)="last.set($event.item.text)"
/>
<p>Last action: {{ last() }}</p>
`,
})
export class Demo {
protected readonly last = signal('none');
protected readonly actions: OgeActionSheetItem[] = [
{ key: 'share', text: 'Share', icon: 'M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7M16 6l-4-4-4 4M12 2v13' },
{ key: 'link', text: 'Copy link' },
{ key: 'edit', text: 'Edit', icon: 'M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z' },
{ key: 'delete', text: 'Delete photo', icon: 'M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6', destructive: true },
];
}Groups, disabled actions & templates
group: 'bottom' actions render after a divider. Disabled actions stay focusable, so a screen reader user learns they exist, but never run. [ogeActionSheetItemTemplate] replaces the icon and text; the menu item button, its keyboard and its state stay with the sheet.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeActionSheet, OgeActionSheetItemTemplate } from '@oge-ui/overlay';
import type { OgeActionSheetItem } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeActionSheet, OgeActionSheetItemTemplate],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- group: 'bottom' actions render after a divider; disabled actions
stay focusable (so they are discoverable) but do nothing. The item
template replaces the icon and text; the menuitem button stays. -->
<button type="button" (click)="sheet.toggle()">File actions</button>
<oge-action-sheet #sheet title="Quarterly report.pdf" [items]="actions">
<ng-template ogeActionSheetItemTemplate let-item>
<span>
<strong>{{ item.text }}</strong>
@if (item.description) {
<small> — {{ item.description }}</small>
}
</span>
</ng-template>
</oge-action-sheet>
`,
})
export class Demo {
protected readonly actions: OgeActionSheetItem[] = [
{ key: 'download', text: 'Download', description: 'PDF, 2.4 MB' },
{ key: 'share', text: 'Share with team', description: 'Only owners can share this file', disabled: true },
{ key: 'report', text: 'Report a problem', group: 'bottom' },
{ key: 'delete', text: 'Move to trash', destructive: true, group: 'bottom' },
];
}Promise API & events
open() resolves with the chosen action, or null when the sheet was dismissed. itemClick can set keepOpen, and the cancelable closing event — with its reason: action, cancel, escape, backdrop, swipe or api — can veto a close.
Nothing chosen yet
import { ChangeDetectionStrategy, Component, signal, viewChild } from '@angular/core';
import { OgeActionSheet } from '@oge-ui/overlay';
import type { OgeActionSheetClosingEvent, OgeActionSheetItem, OgeActionSheetItemClickEvent } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeActionSheet],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- open() resolves with the chosen action, or null when the sheet was
dismissed. itemClick can keep the sheet open (keepOpen), and the
cancelable closing event can veto a close. -->
<button type="button" (click)="choose()">Choose a layout</button>
<label>
<input type="checkbox" [checked]="lock()" (change)="lock.set(!lock())" />
Veto backdrop closing
</label>
<oge-action-sheet
#sheet
title="Layout"
[items]="layouts"
(itemClick)="onItem($event)"
(closing)="onClosing($event)"
/>
<p>{{ result() }}</p>
`,
})
export class Demo {
private readonly sheet = viewChild.required<OgeActionSheet>('sheet');
protected readonly result = signal('Nothing chosen yet');
protected readonly lock = signal(false);
protected readonly layouts: OgeActionSheetItem[] = [
{ key: 'grid', text: 'Grid' },
{ key: 'list', text: 'List' },
{ key: 'preview', text: 'Preview (keeps the sheet open)' },
];
protected async choose(): Promise<void> {
const chosen = await this.sheet().open();
this.result.set(chosen ? 'Chose ' + chosen.text : 'Dismissed');
}
protected onItem(event: OgeActionSheetItemClickEvent): void {
event.keepOpen = event.item.key === 'preview';
}
protected onClosing(event: OgeActionSheetClosingEvent): void {
event.cancel = this.lock() && event.reason === 'backdrop';
}
}