Dialog
Dialog
Section titled “Dialog”Controlled/uncontrolled modal backed by React Native core Modal, with a real Web focus trap and Escape dismissal.
Identity
Section titled “Identity”- Category: Overlays & feedback
- Status: stable public Registry/export-map component family
- Targets: iOS · Android · Web, subject to the compatibility contract
- Source:
packages/ui/src/components/dialog.tsx
Import
Section titled “Import”import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger } from '@beemvp/beeui-ui';There is no documented deep/private source import. For source ownership from a BeeUI checkout:
pnpm beeui -- add dialogRegistry metadata: registry/registry.json.
Composition and public API
Section titled “Composition and public API”- Family exports:
DialogDialogCloseDialogContentDialogDescriptionDialogFooterDialogTitleDialogTrigger
Exported types: DialogCloseProps, DialogContentProps, DialogDescriptionProps, DialogFooterProps, DialogProps, DialogTitleProps, DialogTriggerProps
The generated API inventory is mechanically joined to packages/ui/src/index.ts, Registry metadata, and the component reference contract. For behavior details and defaults, use the canonical component behavior catalog rather than copying TypeScript declarations into a second hand-maintained table.
State and behavior contract
Section titled “State and behavior contract”Controlled/uncontrolled props, callbacks, disabled semantics, normalization/fail-safe behavior, and mount/unmount rules are defined by the public types and the canonical behavior catalog. The executable fixtures below are the source-grounded usage examples; consumers should not infer state ownership from DOM structure or another UI library.
Provider and dependencies
Section titled “Provider and dependencies”BeeUIProvideris required above this family because it participates in shared overlay/toast runtime infrastructure.- Peer/native dependencies visible to this Registry item:
react,react-native - Registry dependency closure:
button,core-cn,overlay-runtime,text,theme - Safe-area ownership remains explicit: shell surfaces touching system edges opt into
SafeArea; components do not silently invent app-shell insets. - Web consumers load the BeeUI semantic theme CSS as documented in Web onboarding.
Platform behavior
Section titled “Platform behavior”The same public family is exposed across the supported target matrix; meaningful platform differences remain governed by the compatibility contract.
- Web: live browser/keyboard behavior is verified by Web-specific checks where applicable.
- iOS / Android: package/export/native compile evidence is not described as device-runtime proof. Consult the compatibility and native-preview guides for the exact evidence class.
- Platform-specific or experimental behavior is called out in the canonical component/compatibility docs rather than hidden behind a generic parity claim.
Accessibility
Section titled “Accessibility”Use the Accessibility overview, RTL/localization, and Large text & zoom alongside this family. Roles/states, keyboard/focus behavior, announcements, Dynamic Type/Web zoom, RTL, and reduced-motion expectations remain component-specific; BeeUI does not claim universal accessibility certification from automated tests.
Styling and theming
Section titled “Styling and theming”BeeUI components consume semantic tokens and support the current typed variant/density contracts. Use Theming and Density. className is an implementation escape hatch for source-owned/application work, not a cross-engine portability guarantee.
Executable examples
Section titled “Executable examples”- Primary executable fixture:
apps/showcase/__tests__/component-contracts.test.tsx - Additional fixture:
apps/showcase/__tests__/issue-149-reduced-motion-acceptance.test.tsx - Additional fixture:
apps/showcase/__tests__/issue-68-theme-scope.test.tsx - Additional fixture:
apps/showcase/__tests__/issue-7-hardening.test.tsx
Open the matching Web runtime in Showcase. The Showcase link demonstrates Web behavior; use the native-preview guide for real simulator/emulator/device paths.
Live Web preview
Section titled “Live Web preview”This frame loads the real BeeUI Web Showcase on demand; it is not a second docs-only implementation. It proves browser behavior only. Use native preview for iOS/Android simulator, emulator or device paths.
Composition anatomy
Section titled “Composition anatomy”- Family root / primary export:
Dialog- Public composition parts / helpers:
DialogCloseDialogContentDialogDescriptionDialogFooterDialogTitleDialogTrigger
- Exported type surface:
DialogClosePropsDialogContentPropsDialogDescriptionPropsDialogFooterPropsDialogPropsDialogTitlePropsDialogTriggerProps
- Public composition parts / helpers:
The tree above is ordinary document structure so it remains readable with keyboard and assistive technology; it is derived from the real public export family rather than a canvas-only diagram.
Verified example source
Section titled “Verified example source”The following is the exact typechecked runtime Showcase fixture selected for this live preview: apps/showcase/component-gallery/component-gallery.tsx. Runtime gallery/pattern sources are preferred over test harnesses, and the displayed source and executable source are the same file; there is no separately maintained demo snippet.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AlertBanner, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogTitle, AlertDialogTrigger, AppHeader, Avatar, Badge, BeeThemeScope, BottomActionBar, Box, Breadcrumb, BreadcrumbItem, Button, Card, Checkbox, Chip, ChipGroup, Collapsible, CollapsibleContent, CollapsibleTrigger, DescriptionItem, DescriptionList, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState, ErrorState, Field, FormGroup, HStack, IconButton, Input, Link, ListGroup, ListGroupHeader, ListItem, OTPInput, Pagination, PaginationItem, PasswordInput, Popover, PopoverClose, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger, Progress, Radio, RadioGroup, SafeArea, Screen, SearchInput, Section, SegmentedControl, SegmentedControlItem, Separator, SettingsItem, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetTitle, SheetTrigger, Skeleton, Spinner, Stack, Stat, StatHelpText, StatLabel, StatValue, Stepper, StepperItem, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Text, Textarea, Timeline, TimelineItem, Tooltip, TooltipContent, TooltipTrigger, useToast, VStack,} from '@beemvp/beeui-ui';import * as React from 'react';import { ScrollView, StatusBar } from 'react-native';import { Uniwind, useUniwind } from 'uniwind';import { DatePickerShowcase } from './date-picker-showcase';import { DateTimePickerShowcase } from './date-time-picker-showcase';import { SelectShowcase } from './select-showcase';import { TableShowcase } from './table-showcase';
function ThemeToggle() { const { hasAdaptiveThemes, theme } = useUniwind(); const activeTheme = hasAdaptiveThemes ? 'system' : theme; const nextTheme = activeTheme === 'system' ? 'light' : activeTheme === 'light' ? 'dark' : 'system';
return ( <Button accessibilityLabel={`Theme ${activeTheme}. Switch to ${nextTheme}`} onPress={() => Uniwind.setTheme(nextTheme)} size="sm" testID="component-gallery-theme-toggle" variant="outline" > {`Theme: ${activeTheme}`} </Button> );}
function PlaygroundHeading({ children, description }: { children: string; description: string }) { return ( <VStack gap="xs"> <Text variant="heading">{children}</Text> <Text tone="muted">{description}</Text> </VStack> );}
function PlacementPopover({ placement }: { placement: 'top' | 'right' | 'bottom' | 'left' }) { return ( <Popover> <PopoverTrigger size="sm" variant="outline"> {placement} </PopoverTrigger> <PopoverContent placement={placement} testID={`popover-${placement}-content`}> <PopoverTitle>{`${placement[0].toUpperCase()}${placement.slice(1)} placement`}</PopoverTitle> <PopoverDescription> This surface is positioned by the shared anchored-overlay geometry kernel. </PopoverDescription> {placement === 'bottom' ? ( <Field label="Note"> <Input accessibilityLabel="Popover note" testID="popover-demo-input" /> </Field> ) : null} <PopoverClose size="sm" variant="ghost"> Close </PopoverClose> </PopoverContent> </Popover> );}
// Consumer React context declared below BeeUIProvider. Overlay content must// resolve the provided value, not the default — the web regression asserts this.const OverlayConsumerContext = React.createContext('overlay-context-default');
function OverlayContextValue({ testID }: { testID: string }) { return <Text testID={testID}>{`context: ${React.useContext(OverlayConsumerContext)}`}</Text>;}
// CASE C — scope-ordering proof for real-browser Escape. A single control opens,// with independent open states, a root-scope Popover AND a Dialog whose nested// DropdownMenu is also open. They live in different overlay scopes, so their// registration order is irrelevant by construction — Escape routes to the active// modal scope (the menu), never the root Popover behind it. (Strict "root opened// after menu" ordering with real timing is covered deterministically in the jest// CASE B/D contract tests; a click inside the modal cannot be used here because// the open menu's dismiss layer intercepts pointer events.)function CaseCScopeOrdering() { const [dialogOpen, setDialogOpen] = React.useState(false); const [menuOpen, setMenuOpen] = React.useState(false); const [rootOpen, setRootOpen] = React.useState(false); return ( <> <Button onPress={() => { setDialogOpen(true); setMenuOpen(true); setRootOpen(true); }} testID="overlay-context-casec-open" variant="outline" > Open CASE C </Button>
<Popover onOpenChange={setRootOpen} open={rootOpen}> <PopoverTrigger testID="overlay-context-casec-root-trigger" variant="outline"> CASE C root </PopoverTrigger> <PopoverContent placement="bottom"> <OverlayContextValue testID="overlay-context-casec-root-value" /> </PopoverContent> </Popover>
<Dialog onOpenChange={setDialogOpen} open={dialogOpen}> <DialogTrigger testID="overlay-context-casec-dialog-trigger">CASE C dialog</DialogTrigger> <DialogContent> <DialogTitle>CASE C dialog</DialogTitle> <DropdownMenu onOpenChange={setMenuOpen} open={menuOpen}> <DropdownMenuTrigger testID="overlay-context-casec-menu-trigger" variant="outline"> CASE C menu </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>CASE C menu</DropdownMenuLabel> <OverlayContextValue testID="overlay-context-casec-menu-value" /> </DropdownMenuContent> </DropdownMenu> </DialogContent> </Dialog> </> );}
function ConsumerContextOverlays() { const [dialogMenuAction, setDialogMenuAction] = React.useState('none'); return ( <OverlayConsumerContext.Provider value="preserved"> <VStack gap="sm"> <Popover> <PopoverTrigger testID="overlay-context-popover-trigger" variant="outline"> Popover context </PopoverTrigger> <PopoverContent placement="bottom"> <PopoverTitle>Popover consumer context</PopoverTitle> <OverlayContextValue testID="overlay-context-popover-value" /> </PopoverContent> </Popover>
<DropdownMenu> <DropdownMenuTrigger testID="overlay-context-menu-trigger" variant="outline"> Menu context </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>Menu consumer context</DropdownMenuLabel> <OverlayContextValue testID="overlay-context-menu-value" /> </DropdownMenuContent> </DropdownMenu>
<Tooltip> <TooltipTrigger testID="overlay-context-tooltip-trigger" variant="outline"> Tooltip context </TooltipTrigger> <TooltipContent> <OverlayContextValue testID="overlay-context-tooltip-value" /> </TooltipContent> </Tooltip>
<Dialog> <DialogTrigger testID="overlay-context-dialog-trigger">Dialog context</DialogTrigger> <DialogContent> <DialogTitle>Dialog with a nested overlay</DialogTitle> <Popover> <PopoverTrigger testID="overlay-context-dialog-popover-trigger" variant="outline"> Popover in dialog </PopoverTrigger> <PopoverContent placement="bottom"> <OverlayContextValue testID="overlay-context-dialog-popover-value" /> </PopoverContent> </Popover>
<Tooltip> <TooltipTrigger testID="overlay-context-dialog-tooltip-trigger" variant="outline"> Tooltip in dialog </TooltipTrigger> <TooltipContent> <OverlayContextValue testID="overlay-context-dialog-tooltip-value" /> </TooltipContent> </Tooltip>
<DropdownMenu> <DropdownMenuTrigger testID="overlay-context-dialog-menu-trigger" variant="outline"> Menu in dialog </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>Menu in dialog</DropdownMenuLabel> <OverlayContextValue testID="overlay-context-dialog-menu-value" /> <DropdownMenuItem onSelect={() => setDialogMenuAction('selected')} testID="overlay-context-dialog-menu-item" > Select in dialog </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> <Text testID="overlay-context-dialog-menu-action">{`menu action: ${dialogMenuAction}`}</Text> </DialogContent> </Dialog>
<CaseCScopeOrdering /> </VStack> </OverlayConsumerContext.Provider> );}
// #68 — real-browser proof that a BeeThemeScope declared around an overlay// trigger is still resolved for that overlay's portaled content on web (the// `ReactDOM.createPortal` transport). The Playwright regression// (apps/visual-regression/tests/overlay-context.spec.ts) asserts these testIDs// against the real Uniwind runtime, the same way issue #35 proves consumer// context survives the same transport.function ThemeScopeValue({ testID }: { testID: string }) { const { theme } = useUniwind(); return <Text testID={testID}>{`theme: ${theme}`}</Text>;}
function ThemeScopeOverlays() { return ( <VStack gap="sm"> <BeeThemeScope appearance="dark" brand="violet"> <VStack gap="sm"> <ThemeScopeValue testID="theme-scope-root-value" />
<Popover> <PopoverTrigger testID="theme-scope-popover-trigger" variant="outline"> Popover in scope </PopoverTrigger> <PopoverContent placement="bottom"> <PopoverTitle>Popover in a BeeThemeScope</PopoverTitle> <ThemeScopeValue testID="theme-scope-popover-value" /> </PopoverContent> </Popover>
<DropdownMenu> <DropdownMenuTrigger testID="theme-scope-menu-trigger" variant="outline"> Menu in scope </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>Menu in a BeeThemeScope</DropdownMenuLabel> <ThemeScopeValue testID="theme-scope-menu-value" /> </DropdownMenuContent> </DropdownMenu>
<Dialog> <DialogTrigger testID="theme-scope-dialog-trigger">Dialog in scope</DialogTrigger> <DialogContent> <DialogTitle>Dialog inside a BeeThemeScope</DialogTitle> <ThemeScopeValue testID="theme-scope-dialog-value" /> <DialogClose size="sm" variant="ghost"> Close </DialogClose> </DialogContent> </Dialog>
<BeeThemeScope appearance="light" brand="bee"> <ThemeScopeValue testID="theme-scope-nested-value" /> </BeeThemeScope> </VStack> </BeeThemeScope>
<ThemeScopeValue testID="theme-scope-sibling-value" /> </VStack> );}
function ToastPlayground() { const toast = useToast(); const [lastAction, setLastAction] = React.useState('No Toast action yet');
return ( <Card className="gap-4" variant="raised"> <Section description="Provider-scoped transient notifications. Up to three are visible; overflow waits FIFO. Close visible items to inspect queue promotion." title="Toast notifications" > <HStack gap="sm" wrap> <Button onPress={() => toast.show({ title: 'Saved', description: 'Default toast with a 5 second timeout.' })} size="sm" variant="outline" > Default </Button> <Button onPress={() => toast.show({ title: 'Published', description: 'Changes are live.', variant: 'success' })} size="sm" variant="outline" > Success </Button> <Button onPress={() => toast.show({ title: 'Check settings', description: 'One option needs attention.', variant: 'warning' })} size="sm" variant="outline" > Warning </Button> <Button onPress={() => toast.show({ title: 'Save failed', description: 'Try the operation again.', variant: 'destructive' })} size="sm" variant="outline" > Error </Button> <Button onPress={() => toast.show({ title: 'Item archived', description: 'You can undo this action.', variant: 'info', action: { label: 'Undo', onPress: () => setLastAction('Undo pressed') }, })} size="sm" variant="outline" > Action </Button> <Button onPress={() => toast.show({ title: 'Persistent notice', description: 'Dismiss this explicitly.', duration: 'persistent', variant: 'info' })} size="sm" variant="outline" > Persistent </Button> <Button onPress={() => { for (let index = 1; index <= 6; index += 1) { toast.show({ title: `Queued toast ${index}`, description: index <= 3 ? 'Initially visible.' : 'Promotes FIFO after a visible toast closes.', duration: 'persistent', variant: index % 2 === 0 ? 'info' : 'neutral', }); } }} size="sm" variant="secondary" > Queue stress </Button> <Button onPress={toast.dismissAll} size="sm" variant="ghost">Dismiss all</Button> </HStack> <Text tone="muted" variant="caption">{lastAction}</Text> </Section> </Card> );}
export function ComponentGallery({ onBack }: { onBack: () => void }) { const { theme } = useUniwind(); const [accepted, setAccepted] = React.useState(false); const [notifications, setNotifications] = React.useState(true); const [plan, setPlan] = React.useState<'starter' | 'pro'>('starter'); const [tab, setTab] = React.useState('overview'); const [otp, setOtp] = React.useState(''); const [filters, setFilters] = React.useState<string[]>(['mobile']); const [viewMode, setViewMode] = React.useState('list'); const [page, setPage] = React.useState(2); const [step, setStep] = React.useState(3); const [menuToolbar, setMenuToolbar] = React.useState(true); const [menuDensity, setMenuDensity] = React.useState('comfortable'); const [menuAction, setMenuAction] = React.useState('No action yet');
return ( <Screen testID="component-gallery"> <StatusBar barStyle={theme === 'dark' ? 'light-content' : 'dark-content'} /> <SafeArea className="bg-surface" edges={['top', 'left', 'right']}> <AppHeader description="Interactive React Native component playground built entirely from the public BeeUI API." testID="component-gallery-header" leading={ <HStack gap="sm"> <Button accessibilityLabel="Back to Showcase home" testID="component-gallery-back" onPress={onBack} size="sm" variant="ghost" > Back </Button> <Avatar accessibilityLabel="BeeUI" fallback="BU" /> </HStack> } title="Component Gallery" trailing={<ThemeToggle />} /> </SafeArea>
{/* #281 — `min-h-24` is a rem-scaled floor (matches the row/text scaling contract, so it keeps pace at any zoom) that stops this flex:1 scroll region from being squeezed toward zero when the pinned AppHeader above and BottomActionBar below both grow at large text / zoom. Without it, once combined header+bar height leaves less room than a single row needs, `scrollIntoView` can only partially reveal that row, and hit-testing at its (unclipped) geometric center lands on the BottomActionBar's real on-screen rect — reported as the bar "intercepting pointer events" for content that's actually just been squeezed out of its own viewport. The floor keeps the scroll region a real, usable size; on the rare device/zoom combination where total content still exceeds the viewport, the page falls back to ordinary document-level scrolling instead of corrupting hit-testing for in-view content. */} <SafeArea className="flex-1 min-h-24" edges={['left', 'right']}> <ScrollView contentContainerStyle={{ paddingBottom: 120 }}> <Box className="mx-auto w-full max-w-3xl gap-10 px-5 py-8"> <AlertBanner description="Everything below is rendered from the public @beemvp/beeui-ui API. Switch themes, open overlays, change form state, and resize the web window to exercise the same contracts used on native." title="Hands-on playground" variant="info" />
<PlaygroundHeading description="Buttons, semantic surfaces, feedback, and loading states."> Foundation </PlaygroundHeading>
<Card className="gap-4" variant="raised"> <Section action={<IconButton accessibilityLabel="Add item" variant="outline">+</IconButton>} description="Variants, disabled state, and loading behavior." title="Actions" > <Box className="gap-3"> <Button>Primary action</Button> <Button variant="secondary">Secondary action</Button> <Button variant="outline">Outline action</Button> <Button variant="ghost">Ghost action</Button> <Button variant="destructive">Destructive action</Button> <Button disabled>Disabled action</Button> <Button loading>Loading action</Button> </Box> </Section> </Card>
<Card className="gap-4" variant="muted"> <Text variant="heading">Status and feedback</Text> <Box className="flex-row flex-wrap gap-2"> <Badge>Primary</Badge> <Badge variant="secondary">Secondary</Badge> <Badge variant="success">Success</Badge> <Badge variant="warning">Warning</Badge> <Badge variant="destructive">Error</Badge> <Badge variant="info">Info</Badge> </Box> <Separator /> <Box className="flex-row items-center gap-5"> <Spinner /> <Spinner tone="success" /> <Spinner tone="warning" /> <Spinner tone="destructive" /> </Box> <Progress accessibilityLabel="Profile completion" value={72} /> </Card>
<ToastPlayground />
<Card className="gap-4"> <Text variant="heading">Loading and state surfaces</Text> <Box className="flex-row items-center gap-3"> <Skeleton className="h-12 w-12" variant="circle" /> <Box className="flex-1 gap-2"> <Skeleton className="w-2/3" variant="text" /> <Skeleton className="w-full" variant="text" /> </Box> </Box> <Skeleton className="h-24 w-full" /> <Separator /> <EmptyState action={<Button size="sm">Create record</Button>} description="Create your first record to get started." title="No records yet" /> <Separator /> <ErrorState action={<Button size="sm" variant="outline">Try again</Button>} description="The server could not load this section." /> </Card>
<PlaygroundHeading description="Text-entry composition plus explicit group semantics for related choices."> Forms </PlaygroundHeading>
<Card className="gap-4"> <Field description="Used only for account notifications." label="Email" required> <Input autoCapitalize="none" placeholder="you@example.com" /> </Field> <Field label="Search"> <SearchInput onSearch={() => undefined} placeholder="Search projects" /> </Field> <Field label="Password"> <PasswordInput placeholder="Enter password" /> </Field> <Field description="Six numeric digits." label="Verification code"> <OTPInput accessibilityLabel="Verification code" onValueChange={setOtp} value={otp} /> </Field> <Field error="Enter a valid project name." invalid label="Project name"> <Input placeholder="Invalid value" /> </Field> <Field disabled label="Managed field"> <Input placeholder="Disabled by field context" /> </Field> <Field description="Optional long-form content." label="Notes"> <Textarea placeholder="Long-form notes" /> </Field> <Separator /> <Checkbox checked={accepted} label="Accept terms" onCheckedChange={setAccepted} /> <FormGroup description="The group owns legend/guidance metadata while each radio stays independently discoverable." legend="Subscription plan" required > <RadioGroup onValueChange={(value) => setPlan(value as 'starter' | 'pro')} value={plan} > <Radio label="Starter plan" value="starter" /> <Radio label="Pro plan" value="pro" /> </RadioGroup> </FormGroup> <Box className="flex-row items-center justify-between gap-4"> <Text>Notifications</Text> <Switch accessibilityLabel="Notifications" onValueChange={setNotifications} value={notifications} /> </Box> </Card>
<PlaygroundHeading description="Persistent value selection with anchored overlay transport, keyboard behavior on web, and modal-local nesting."> Select </PlaygroundHeading>
<SelectShowcase />
<PlaygroundHeading description="Composable primitives (no owned data/columns) — real HTML table semantics on web, keyboard-reachable sort, and caller-owned row selection."> Table </PlaygroundHeading>
<TableShowcase />
<PlaygroundHeading description="Field-integrated trigger; Web opens Calendar in a Popover, native delegates to the system picker."> DatePicker </PlaygroundHeading>
<DatePickerShowcase />
<PlaygroundHeading description="Field-integrated trigger; composes DatePicker's date part with an Input/SegmentedControl time control on Web, native delegates to the system picker (chained date+time steps on Android)."> DateTimePicker </PlaygroundHeading>
<DateTimePickerShowcase />
<PlaygroundHeading description="Modal and anchored overlays now have real public APIs you can click through here."> Overlay playground </PlaygroundHeading>
<Card className="gap-5" variant="raised"> <Section description="Centered modal-class overlays use React Native core Modal." title="Dialog and AlertDialog" > <HStack gap="sm" wrap> <Dialog> <DialogTrigger>Open Dialog</DialogTrigger> <DialogContent> <DialogTitle>Project settings</DialogTitle> <DialogDescription> This is BeeUI's centered modal surface. Backdrop, accessibility escape, and native request-close follow the Dialog contract. </DialogDescription> <Field label="Project name"> <Input defaultValue="BeeUI" /> </Field> <DialogFooter> <DialogClose variant="outline">Cancel</DialogClose> <DialogClose>Save changes</DialogClose> </DialogFooter> </DialogContent> </Dialog>
<AlertDialog> <AlertDialogTrigger variant="destructive">Delete project</AlertDialogTrigger> <AlertDialogContent> <AlertDialogTitle>Delete this project?</AlertDialogTitle> <AlertDialogDescription> The backdrop cannot dismiss this confirmation. Choose an explicit action instead. </AlertDialogDescription> <AlertDialogFooter> <AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogAction>Delete permanently</AlertDialogAction> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> </HStack> </Section>
<Separator />
<Section description="Bottom-sheet surface (#159): BeeUI's own Web overlay/focus primitives — Escape, backdrop press, Tab focus-trap, and focus restoration — with no native Modal and no gorhom on Web (ADR-006)." title="Sheet" > <Sheet> <SheetTrigger testID="sheet-demo-trigger">Open Sheet</SheetTrigger> <SheetContent overlayTestID="sheet-demo-overlay" testID="sheet-demo-content"> <SheetTitle>Filters</SheetTitle> <SheetDescription>Refine results by category and price.</SheetDescription> <Field label="Search"> <Input accessibilityLabel="Sheet search" testID="sheet-demo-input" /> </Field> <SheetFooter> <SheetClose testID="sheet-demo-close" variant="outline"> Close </SheetClose> </SheetFooter> </SheetContent> </Sheet> </Section>
<Separator />
<Section description="Popover uses the shared non-Modal host, window-coordinate measurement, collision handling, and topmost dismissal stack." title="Popover placements" > <HStack gap="sm" wrap> <PlacementPopover placement="top" /> <PlacementPopover placement="right" /> <PlacementPopover placement="bottom" /> <PlacementPopover placement="left" /> </HStack> </Section>
<Separator />
<Section description="Menu items reuse the anchored runtime with normal, checkbox, radio, disabled, and keyboard-selection contracts." title="DropdownMenu" > <VStack gap="sm"> <DropdownMenu> <DropdownMenuTrigger variant="outline">Workspace menu</DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>Workspace</DropdownMenuLabel> <DropdownMenuItem onSelect={() => setMenuAction('Edit project')}> Edit project </DropdownMenuItem> <DropdownMenuItem disabled>Archive unavailable</DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuCheckboxItem checked={menuToolbar} onCheckedChange={setMenuToolbar} > Show toolbar </DropdownMenuCheckboxItem> <DropdownMenuSeparator /> <DropdownMenuLabel>Density</DropdownMenuLabel> <DropdownMenuRadioGroup onValueChange={setMenuDensity} value={menuDensity} > <DropdownMenuRadioItem value="compact">Compact</DropdownMenuRadioItem> <DropdownMenuRadioItem value="comfortable"> Comfortable </DropdownMenuRadioItem> </DropdownMenuRadioGroup> </DropdownMenuContent> </DropdownMenu> <Text tone="muted"> {`Last action: ${menuAction} · toolbar ${menuToolbar ? 'on' : 'off'} · ${menuDensity}`} </Text> </VStack> </Section>
<Separator />
<Section description="Non-interactive contextual disclosure (#152/#153/#154): hover or focus reveals a short description after a delay; the trigger's own press action is never intercepted, Escape dismisses without moving focus, and content is never a Tab stop." title="Tooltip" > <HStack gap="sm" wrap> <Tooltip> <TooltipTrigger testID="tooltip-demo-trigger" variant="outline"> Autosave </TooltipTrigger> <TooltipContent testID="tooltip-demo-content"> Saved automatically every 30 seconds </TooltipContent> </Tooltip> </HStack> </Section>
<Separator />
<Section description="Open the parent, then the child. Outside press / Escape / accessibility escape dismisses child-first." title="Nested Popover" > <Popover> <PopoverTrigger variant="secondary">Open parent</PopoverTrigger> <PopoverContent align="start" placement="bottom"> <PopoverTitle>Parent Popover</PopoverTitle> <PopoverDescription> The child registers above this overlay in the shared dismiss stack. </PopoverDescription> <Popover> <PopoverTrigger size="sm" variant="outline">Open child</PopoverTrigger> <PopoverContent align="start" placement="right"> <PopoverTitle>Child Popover</PopoverTitle> <PopoverDescription> Dismiss me first; the parent remains mounted underneath. </PopoverDescription> <PopoverClose size="sm">Done</PopoverClose> </PopoverContent> </Popover> </PopoverContent> </Popover> </Section>
<Separator />
<Section description="This trigger is aligned to the container edge. Shrink the web viewport or rotate a device to make flip/shift collision handling obvious." title="Collision edge case" > <Box className="items-end"> <Popover> <PopoverTrigger variant="outline">Near right edge</PopoverTrigger> <PopoverContent align="start" placement="right" sideOffset={12}> <PopoverTitle>Collision-aware placement</PopoverTitle> <PopoverDescription> Preferred placement is right; the resolver flips or shifts only when the viewport requires it. </PopoverDescription> <PopoverClose size="sm" variant="ghost">Close</PopoverClose> </PopoverContent> </Popover> </Box> </Section>
<Separator />
<Section description="Consumer React context declared below BeeUIProvider resolves inside overlay content on web, native, and inside a Dialog." title="Consumer context" > <ConsumerContextOverlays /> </Section>
<Separator />
<Section description="BeeThemeScope (#68) is a thin typed wrapper over Uniwind's own ScopedTheme. The scoped brand/appearance below resolves for Popover, DropdownMenu, and Dialog content declared inside it, nests independently of an inner scope, and never leaks to the sibling value outside it." title="Scoped theme (BeeThemeScope)" > <ThemeScopeOverlays /> </Section> </Card>
<PlaygroundHeading description="Selections, tabs, disclosure, paging, and application-level composition stay router-neutral."> Navigation and composition </PlaygroundHeading>
<Card className="gap-4"> <Section description="Dependency-free filters, view selection, and paging." title="Selection and navigation"> <Text variant="label">Filters</Text> <ChipGroup onValueChange={(value) => setFilters(Array.isArray(value) ? value : [value])} selectionMode="multiple" value={filters} > <Chip value="mobile">Mobile</Chip> <Chip value="web">Web</Chip> <Chip value="design">Design</Chip> </ChipGroup> <Text variant="label">View</Text> <SegmentedControl onValueChange={setViewMode} value={viewMode}> <SegmentedControlItem value="list">List</SegmentedControlItem> <SegmentedControlItem value="grid">Grid</SegmentedControlItem> </SegmentedControl> <Text variant="label">Page</Text> <Pagination onPageChange={setPage} page={page} pageCount={4}> <PaginationItem type="previous" /> <PaginationItem page={1} /> <PaginationItem page={2} /> <PaginationItem page={3} /> <PaginationItem page={4} /> <PaginationItem type="next" /> </Pagination> </Section> </Card>
<Card className="gap-4"> <Text variant="heading">Tabs and disclosure</Text> <Tabs onValueChange={setTab} value={tab}> <TabsList> <TabsTrigger value="overview">Overview</TabsTrigger> <TabsTrigger value="details">Details</TabsTrigger> </TabsList> <TabsContent value="overview"> <Text tone="muted">Overview content is mounted for the active tab.</Text> </TabsContent> <TabsContent value="details"> <Text tone="muted">Details content is mounted only when selected.</Text> </TabsContent> </Tabs> <Separator /> <Collapsible> <CollapsibleTrigger>Advanced options</CollapsibleTrigger> <CollapsibleContent> <Text tone="muted">Hidden until expanded.</Text> </CollapsibleContent> </Collapsible> <Accordion defaultValue="account"> <AccordionItem value="account"> <AccordionTrigger>Account</AccordionTrigger> <AccordionContent> <Text tone="muted">Account preferences and identity.</Text> </AccordionContent> </AccordionItem> <AccordionItem value="billing"> <AccordionTrigger>Billing</AccordionTrigger> <AccordionContent> <Text tone="muted">Invoices and payment settings.</Text> </AccordionContent> </AccordionItem> </Accordion> </Card>
<Card className="gap-5"> <Section description="Layout, history, and application composition without router ownership." title="Application composition" > <Breadcrumb accessibilityLabel="Project breadcrumb"> <BreadcrumbItem onPress={() => undefined}>Projects</BreadcrumbItem> <BreadcrumbItem current>BeeUI</BreadcrumbItem> </Breadcrumb>
<Stack gap="lg"> <HStack gap="lg" wrap> <Stat className="min-w-32 flex-1"> <StatLabel>API surface</StatLabel> <StatValue>Public</StatValue> <StatHelpText>Only @beemvp/beeui-ui exports</StatHelpText> </Stat> <Stat className="min-w-32 flex-1"> <StatLabel>Verification</StatLabel> <StatValue>Cross-platform</StatValue> <StatHelpText>Web, Android, and iOS gates</StatHelpText> </Stat> </HStack>
<Stepper currentStep={step} onStepChange={setStep}> <StepperItem step={1} title="Foundation" /> <StepperItem step={2} title="Application patterns" /> <StepperItem step={3} title="Overlays" /> </Stepper>
<ListGroup> <ListGroupHeader description="Composition reuses existing row behavior." title="Workspace" /> <ListItem description="Portable component system" onPress={() => undefined} title="BeeUI" /> </ListGroup>
<Timeline> <TimelineItem description="Core layout, form, navigation, and modal contracts established." meta="v0.1" status="success" title="Foundation" /> <TimelineItem description="Packed packages bundle in Expo and bare React Native; Android APK compiles in CI." meta="CI verified" status="success" title="Native portability" /> <TimelineItem description="Public Popover and DropdownMenu exercise the shared anchored overlay geometry/runtime kernels." meta="Current" status="primary" title="Anchored overlays" /> </Timeline> </Stack> </Section> </Card>
<Card className="gap-4"> <Section description="Read-only application information patterns." title="Metadata and rows"> <DescriptionList> <DescriptionItem label="Runtime" value="React Native 0.86.2" /> <DescriptionItem label="Styling" value="Uniwind 1.10.1" /> <DescriptionItem description="Generated and compiled in CI" label="Native verification" value="Expo + bare RN" /> </DescriptionList> <Separator /> <ListItem description="Open your profile" onPress={() => undefined} title="Profile" trailing={<Badge variant="success">Active</Badge>} /> <SettingsItem description="Changes app appearance" onPress={() => undefined} title="Appearance" value={theme} /> <SettingsItem description="Native preference control" title="Push notifications" trailing={ <Switch accessibilityLabel="Push notifications" onValueChange={setNotifications} value={notifications} /> } /> </Section> </Card>
<VStack gap="xs"> <Text tone="muted" variant="caption"> Navigation remains application-owned. This showcase intentionally has no router or docs-site framework. </Text> <Link onPress={() => undefined}>Open documentation</Link> </VStack> </Box> </ScrollView> </SafeArea>
<SafeArea className="bg-surface" edges={['bottom', 'left', 'right']}> <BottomActionBar> <Button size="sm" variant="ghost">Cancel</Button> <Button size="sm">Save changes</Button> </BottomActionBar> </SafeArea> </Screen> );}Use the code block’s copy affordance to copy the exact fixture. For a smaller app-specific example, start from the public imports shown above and keep only the state your screen owns.
Limitations
Section titled “Limitations”Controlled open requires onOpenChange.