Skip to content

Bottom sheet

In review
Live · web

Detent · grip · content: set them, then open the sheet

Sheet opened 1 time. The drag handle, a scrim tap, or Esc dismisses it (no X by default; flip Close on to add the opt-in X). The island is live.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;
// this is the canonical API the published @dizyx/nockerl-react package exposes.)
//
// No `showClose` → NO X (Material canon): the drag handle + a scrim tap + Esc dismiss it.
// Actions compose the real NockerlListItem primitive (canonical rows); a form CTA composes NockerlButton
// (light-300 / UPPERCASE / -0.03em ramp), never a hand-rolled <button>.
// NockerlBottomSheet composes the shipped NockerlOverlay primitive (scrim + focus-trap + Esc); it
// supplies the pull-up panel + its slide. NockerlListItem ships today.
import { NockerlBottomSheet, NockerlListItem } from '@dizyx/nockerl-react';
export function SessionOptions({ open, stage, onDismiss }: SheetProps) {
return (
<NockerlBottomSheet open={open} onDismiss={onDismiss} stage={stage} title="nockerl-design · docs" detent="half">
<NockerlListItem primary="Share session" leadingIcon={<ShareIcon />} onSelect={share} />
<NockerlListItem primary="Rename" leadingIcon={<RenameIcon />} onSelect={rename} />
<NockerlListItem primary="Delete session" leadingIcon={<TrashIcon />} danger onSelect={remove} />
</NockerlBottomSheet>
);
}

macOS/Swift has no bottom-sheet idiom (use a popover / sheet there), so this component targets web + Android only.

Session-management views (configs, not components)

Section titled “Session-management views (configs, not components)”

Per the component-vs-view rule on the agent-console epic (task 2654), the session sheets are pure configurations of this sheet + shipped parts; none of them is a new component. Native cites them as NockerlBottomSheet call-sites already (AvatarSettingsSheet, SamplingAdvancedSettings); the settings form in the demo above is exactly this shape:

View Sheet body composes Notes
Session create / edit TextField (name) + Select (harness/model) + Switch rows + SegmentedControl the demo’s settings form, with a full-width confirm
Session switcher / picker ListItem rows (selection = the ratified wash + check, never a rail) rename / delete / fork confirms hand off to Dialog
Override controls Form layout sections over the same field primitives per-session provider overrides
Streaming target RadioGroup or ListItem selection one target, wash + check
Sampling advanced Slider + RangeSlider + Stepper + Switch the native SamplingAdvancedSettings call-site

Approval moments inside a sheet use the Approval content anatomy. Its CTA row pins in this sheet’s footer slot.

PropTypeDefaultDescription
open *booleanWhether the sheet is presented.
onDismiss *() => voidDismiss handler: scrim tap, Esc, or (when opted-in) the close button.
title *stringAccessible title shown in the header (carries the dialog name).
subtitlestringOptional supporting line under the title.
leadingIconReact.ReactNodeOptional LEADING ICON shown top-left beside the title (e.g. a settings cog next to "Settings"). A plain functional glyph on the on-plane ink, NOT a status disc, NOT cyan. It aligns to the title line; omit for the default text-only header. The header divider is unchanged either way (explicitly neutral, never cyan).
detentSheetDetent'half'Detent: a half-height peek or a near-full panel.
gripbooleantrueShow the drag grip at the top.
showClosebooleanfalseOPT-IN close (X) button. Material canon: a bottom sheet has NO X by default. The drag handle + scrim-tap + Esc ARE the dismissal. Set true only when a persistent, always-visible dismiss affordance is genuinely warranted. Default false.
stage *HTMLElement | nullThe contained stage element that gates rendering so the sheet never escapes it.
footerReact.ReactNodePINNED FOOTER-ACTION BAR (task 2653): an approve/deny row that NEVER scrolls away, rendered below the scrolling body on the sheet's own plane, with a hairline top edge and an upward scroll-under cue when more body remains beneath it. Compose the ratified CTA grammar in here (outline confirm · ghost cancel · destructive outline-red, the Dialog r5 canon). Empty = no footer, byte-identical sheet.
onBack(() => void) | undefinedNESTED-SHEET BACK NAVIGATION (task 2653): when set, the header leads with a back chevron (a real NockerlIconButton) BEFORE the title. View swapping stays with the host (swap title/children on your own state); this is the affordance + a11y.
backLabelstring'Back'Accessible name for the back affordance. Default "Back".
children *React.ReactNodeSheet body.