# ActionSheet

**Maturity:** experimental

A short list of actions presented in a floating glass panel anchored to the trigger that opened it — the iPad .confirmationDialog form.

## Import

```tsx
import { ActionSheet } from "@liquidify/react/action-sheet"
```

## Props

- `actions`: `readonly ActionSheetAction[]` (required). The action rows, each carrying a stable `id` (keying + reconciliation identity).
- `className`: `string | undefined`. Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.
- `defaultOpen`: `boolean | undefined`. Uncontrolled seed for the open state (default `false`).
- `labelProps`: `LabelProps | undefined`. Per-instance typography override spread last into the header `title` Text (ADR-0028).
- `material`: `"frosted" | "regular" | "clear" | undefined`; default `enclosing `<Material default>`, else `"regular"``. Material of the glass surface — `frosted · regular · clear` (§06). Forwarded to the engine, which resolves the chain `prop ?? <Material default> ?? "regular"`, so a bare surface inherits the enclosing provider default.
- `message`: `string | undefined`. Optional secondary message — shown beneath the title when the header is visible.
- `messageProps`: `LabelProps | undefined`. Per-instance typography override spread last into the header `message` Text (ADR-0028).
- `onOpenChange`: `((open: boolean) => void) | undefined`. Fires with the next open state on every close path (action, Escape, outside tap, focus-loss).
- `open`: `boolean | undefined`. Controlled open state. Passing it switches ActionSheet to controlled mode.
- `perf`: `"low" | "high" | undefined`; default `derived from `<Backdrop type>`, else `"low"``. Perf budget of the glass surface (ADR-0006/0007). Forwarded to the engine as `data-glass-perf`; `"high"` opts this surface into the edge-lensing filter. When omitted, derives from the enclosing `<Backdrop type>` (§04).
- `placement`: `LogicalSide | undefined`. Which side of the trigger the panel sits on. Forwarded to Popup. Defaults to `"bottom"`.
- `ref`: `Ref<HTMLDivElement> | undefined`. Forwarded ref to the rendered element — the component's native root (or the merged child element on a component that opts into {@link SlottableProps}). Generic over `E` (charter `docs/api-conventions.md` §4 rule 1, default `HTMLElement`) so a component with a polymorphic root (Link's navigation / action mode union, Text's `as`) can instantiate the concrete element per variant instead of leaving every consumer with a widened `Ref<HTMLElement>`. A component that does not narrow simply inherits the `HTMLElement` default — source-compatible with every pre-existing non-generic `extends ControlProps`.
- `title`: `string | undefined`. Optional title — shown in the header only when `titleVisibility="visible"`.
- `titleVisibility`: `ActionSheetTitleVisibility | undefined`. Whether the title/message header shows. `visible` shows it; `automatic`/`hidden` omit it.
- `trigger`: `ReactNode` (required). The element that opens the sheet — merged onto `Popup.Trigger` via `asChild`.

## SwiftUI mapping

- ``isPresented: Binding<Bool>`` → ``open` / `defaultOpen` / `onOpenChange`` (direct): presentation triad (ADR-0015), forwarded to `Popup`
- `(the modified view)` → ``trigger: ReactNode`` (direct): the anchor element, merged via `Popup.Trigger asChild`
- ``titleKey`` → ``title?: string`` (direct): shown only when `titleVisibility="visible"`
- ``titleVisibility: Visibility`` → ``titleVisibility?: "automatic" | "visible" | "hidden"`` (direct): `visible` shows the header; `automatic`/`hidden` omit it
- ``message: () -> Text`` → ``message?: string`` (direct): secondary header line (with `title`)
- ``actions: () -> View` (Buttons)` → ``actions: ActionSheetAction[]`` (direct): `{ id, label, role?, onPress?, disabled? }`; rows are keyed and roving/active-item reconciled by the stable `id` (§16.6, never `role + label`); a duplicate `id` is a dev-mode error; activation runs `onPress` then closes (mirrors `AlertAction`)
- ``Button(role: .destructive)`` → ``role: "destructive"`` (direct): red label (`--lq-color-action-destructive`)
- ``Button(role: .cancel)`` → ``role: "cancel"`` (direct): the dismiss action — rendered like `default` (`weight="regular"`, primary ink); distinguished by its label + `data-role="cancel"`, not by weight
- `(popover anchor side)` → ``placement?: "top" | "bottom" | "left" | "right"`` (direct): forwarded to `Popup`; the panel sits on that trigger edge
- `(presentation chrome)` → ``material?` / `perf?`` (direct): `PresentationalProps` — the panel's glass surface (default `frosted`); the base is tint-free (P0.B6 — no `tint` axis; rows take the role palette)
- `(header typography)` → ``labelProps?: LabelProps`` (direct): per-instance typography seam (charter §2, the shared `LabelProps` `Pick`) spread last into the header `title` `Text`
- `(message typography)` → ``messageProps?: LabelProps`` (direct): per-instance typography seam spread last into the header `message` `Text`
- `(no SwiftUI analogue)` → ``ref?: Ref<HTMLDivElement>`` (web-only): `ActionSheetProps extends PresentationalProps<HTMLDivElement>` (charter §4) — forwarded to the semantic root, the portalled menu panel (the `role="menu"` glass `<div>`); attached while mounted, `null` while closed; the internal `ref as Ref<HTMLDivElement>` cast is gone
