Platform Blocks Feedback & Overlays
Everything that appears over or about the current screen in
@platform-blocks/ui — notifications, modals, anchored dropdowns, inline
messages, and loading state. All imports are from the package root:
import {
ToastProvider, useToast, Toast,
Dialog, DialogProvider, useDialog, useSimpleDialog,
Popover, Menu, MenuDropdown, MenuItem, Tooltip, ContextMenu,
Alert, Overlay, LoadingOverlay, Loader, Progress, Ring, Skeleton,
useDisclosure,
} from '@platform-blocks/ui';
Four reference files sit alongside this one:
references/api.md— the curated API: how the pieces compose, the union types, the defaults that bite. Read this first.references/props.md— generated, exhaustive prop tables for every component in this skill. Look here for the complete surface of a single prop.references/icons.md— generated, everynamethe built-inIconregistry accepts. Check it before writing<Icon name="…">; unlisted names render nothing.references/patterns.md— complete copy-paste screens.
Pick the right component
| Need | Use |
|---|---|
| Transient "it worked" / "it failed" message | useToast() |
| Blocking decision or a form over the screen | Dialog (or useSimpleDialog) |
| Persistent message inside the page flow | Alert |
| Rich content anchored to a trigger | Popover |
| A list of actions anchored to a trigger | Menu |
| Short text hint on hover/focus | Tooltip |
| Right-click / long-press actions | ContextMenu |
| Block a region while it loads | LoadingOverlay |
| Placeholder while content loads | Skeleton |
| Determinate progress | Progress (bar) or Ring (circular) |
| Indeterminate spinner | Loader |
| A bare dimming layer you position yourself | Overlay |
Providers — what is and is not mounted for you
PlatformBlocksProvider mounts OverlayProvider + OverlayRenderer for you
(controlled by withOverlays, default true). So Popover, Menu,
Tooltip and ContextMenu work with no extra setup.
ToastProvider and DialogProvider are not mounted automatically. Add the
ones you use, inside PlatformBlocksProvider:
<SafeAreaProvider>
<PlatformBlocksProvider>
<ToastProvider>
<DialogProvider>
<App />
</DialogProvider>
</ToastProvider>
</PlatformBlocksProvider>
</SafeAreaProvider>
Toast
useToast() returns the toast API. Every method returns the toast's id:
const toast = useToast();
toast.success('Saved'); // string shortcut
toast.error({ title: 'Upload failed', message: 'Try again.' });
toast.info('Synced'); // also: warning, warn (alias), info
const id = toast.show({ title: 'Custom', sev: 'info', autoHide: 0 });
toast.update(id, { title: 'Updated' });
toast.hide(id);
toast.hideAll();
toast.promise(promise, { pending, success, error }) drives one toast through a
promise's lifecycle; success and error may be functions of the resolved
value / thrown error. toast.batch([...]) shows several at once and returns
their ids; groupId + toast.hideGroup(groupId) dismisses a set together.
Key ToastOptions: title, message (or children), sev
('info' | 'success' | 'warning' | 'error'), variant
('light' | 'filled' | 'outline'), color, autoHide (ms, 0 = never),
persistent, position ('top' | 'bottom' | 'left' | 'right'),
actions: { label, onPress, color }[], dismissOnTap, priority, groupId.
ToastProvider sets the defaults: defaultPosition, limit (max per
position), autoHide, defaultVariant, defaultSize.
Outside React — there is no public module-level toast API. useToast()
is the only supported entry point. To raise toasts from a service module or an
HTTP interceptor, capture the API once from inside the tree and export a
module-level handle — full pattern in references/patterns.md.
Dialog
Two ways to open one.
1. Controlled component — you own the state. visible is required.
const [opened, { open, close }] = useDisclosure(false);
<Dialog visible={opened} title="Delete project" variant="modal">
<Text>This cannot be undone.</Text>
</Dialog>
variant: 'modal' | 'bottomsheet' | 'fullscreen'. Other useful props:
closable, backdrop, backdropClosable, showHeader, w/h, radius,
transitionDuration (0 = instant), autoFocus, trapFocus (web, default
true), bottomSheetSwipeZone ('container' | 'handle' | 'none').
2. Imperative — needs DialogProvider. useDialog() gives
openDialog(config) => id, closeDialog(id), closeAllDialogs().
useSimpleDialog() wraps it with modal, bottomSheet, fullScreen,
confirm, close, closeAll:
const dialog = useSimpleDialog();
const id = dialog.modal(<Text>Body</Text>, { title: 'Details' });
dialog.close(id);
See pitfall 4 before using dialog.confirm().
Anchored overlays — note the two different shapes
Popover is a namespace compound and needs an explicit Popover.Target.
Menu uses flat sibling exports and treats its first child as the
trigger — there is no Menu.Target, and no Menu.Item:
// Popover — namespaced, explicit target
<Popover position="bottom" withArrow>
<Popover.Target><Button>Open</Button></Popover.Target>
<Popover.Dropdown><Text>Anything</Text></Popover.Dropdown>
</Popover>
// Menu — flat exports, first child is the trigger
import { Menu, MenuDropdown, MenuItem, MenuDivider, MenuLabel } from '@platform-blocks/ui';
<Menu>
<Button variant="outline">Actions</Button>
<MenuDropdown>
<MenuLabel>Account</MenuLabel>
<MenuItem startSection={<Icon name="user" size="sm" />}>Profile</MenuItem>
<MenuDivider />
<MenuItem color="danger" out</MenuItem>
</MenuDropdown>
</Menu>
Tooltip wraps a single element and takes label:
<Tooltip label="Copy to clipboard" position="top" withArrow openDelay={300}>
<IconButton icon="copy" />
</Tooltip>
ContextMenu is a render-prop, not a wrapper — it hands you the handlers to
spread onto your trigger:
<ContextMenu items={[{ id: 'rename', label: 'Rename', onSelect: rename }]}>
{({ onContextMenu, onPressIn }) => (
<Pressable
<Text>Right-click me</Text>
</Pressable>
)}
</ContextMenu>
Alert
Inline, in-flow message — not an overlay. sev sets color and icon together:
<Alert sev="error" title="Payment failed" withCloseButton
Your card was declined.
</Alert>
variant: 'light' (default) | 'filled' | 'outline' | 'subtle'.
icon accepts a node or an Icon registry name string. Only icon={false}
removes it — null/undefined falls back to the sev icon.
Notice is a deprecated alias for Alert.
Loading and progress
<Loader size="md" variant="oval" /> {/* 'oval' | 'dots' | 'bars' */}
<Skeleton shape="text" w="80%" /> {/* text|chip|avatar|button|card|circle|rectangle|rounded */}
<Progress value={62} striped animate /> {/* 0–100; orientation="vertical" fills bottom-up */}
<Ring value={62} size={120} thickness={12} /> {/* circular; showValue prints the % */}
<Block style={{ position: 'relative' }}>
<LoadingOverlay visible={isLoading} />
{content}
</Block>
LoadingOverlay fills its nearest positioned ancestor — give that container
position: 'relative'. Forward props with overlayProps / loaderProps, or
replace the spinner entirely with loader={<YourThing />}.
Accessibility
- Toasts announce automatically; still give
actionsreal labels. Dialogtraps Tab focus on web (trapFocus, defaulttrue) and restores focus to the trigger on close. UseautoFocusto move focus in on open.Popover/Menu/Tooltip/ContextMenuclose on Escape by default (closeOnEscape);Popoveralso takestrapFocusandreturnFocus.Tooltipalone is never an accessible name — putaccessibilityLabelon the trigger too, and enableevents.focus(off by default) so keyboard users can see it at all.- Set
accessibilityLabelonLoader/Progressregions that convey status.
Pitfalls (verified against source)
ToastProviderandDialogProviderare not mounted byPlatformBlocksProvider— onlyOverlayProvideris. Without themuseToast()falls back to the module-level queue (calls are buffered and never rendered) anduseDialog()has nothing to render into.Menuhas noMenu.Item/Menu.Target. ImportMenuItem,MenuDropdown,MenuDivider,MenuLabel,MenuSubas separate named exports, and pass the trigger asMenu's first child.Popoveris the opposite —Popover.TargetandPopover.Dropdownare namespaced and the target is required.Dialog'svisibleprop is required and it is fully controlled — it will not open from an internal default. Pair it withuseDisclosure().useSimpleDialog().confirm()does not close itself. Its built-in buttons callcloseDialog('')with an empty id (an unfinished TODO in the source), which matches no dialog, so the prompt stays open after Confirm or Cancel. The callbacks do fire. Build confirms withopenDialogand your own buttons, capturing the returned id — seereferences/patterns.md.ContextMenutakes a render function, not children. Passing an element renders nothing.LoadingOverlayneeds a positioned ancestor. Withoutposition: 'relative'on the wrapper it covers the wrong box.Progressvalueis 0–100, not 0–1.Ringnormalizes againstmin/max(0/100 by default) instead.Toast'sautoHideis milliseconds;0disables it (it does not mean "hide immediately"). Usepersistentfor toasts the user must dismiss.toasts.promise()silently no-ops without a provider — unlike the other standalone methods it is not queued; it just returns the promise untouched.Tooltipdoes not open on keyboard focus by default. Theeventsdefault is{ hover: true, focus: false, touch: true }— keyboard users get nothing. Passevents={{ hover: true, focus: true, touch: true }}on any tooltip carrying information, and give the trigger a realaccessibilityLabelregardless.Tooltipalso needs a single element child that can take a ref.
Anything this skill does not cover
This skill covers notifications, modals, anchored overlays, inline messages, and loading state. Platform Blocks is much larger — 97 components, 25 charts, and 18 hooks. Do not guess an API for something outside this scope; fetch the generated docs instead:
| What you need | Where |
|---|---|
| Index of every page, one line each | https://platform-blocks.com/llms.txt |
| One component or chart | https://platform-blocks.com/llms/components/<Name>.md |
| One hook | https://platform-blocks.com/llms/hooks/<useName>.md |
| Guides | https://platform-blocks.com/llms/guides/{getting-started,accessibility,localization}.md |
| Everything in one file (~1.3 MB) | https://platform-blocks.com/llms-full.txt |
<Name> is the exact PascalCase export name — .../llms/components/DataTable.md,
.../llms/components/AreaChart.md. Each page carries the component's full prop
table (type, required, default, description) plus runnable examples, generated
from the source, so it is authoritative where memory is not. When you are unsure
whether something exists or what it is called, read llms.txt first — it lists
every page with a one-line summary.
Import paths: components come from the package root (import { X } from '@platform-blocks/ui'). The exceptions are subpath-only: FormLayout
(@platform-blocks/ui/FormLayout), AudioPlayer
(@platform-blocks/ui/AudioPlayer), and the whole Navigation module —
NavigationContainer, createStackNavigator, createDrawerNavigator,
Screen, useNavigation, useRoute (@platform-blocks/ui/Navigation). A few
utilities also live on subpaths (e.g. validationRules on
@platform-blocks/ui/Input). A docs page existing does not guarantee a root
export — HoverCard, for instance, is internal and has no page and no export.
Notably outside this skill:
- Navigation components that also use overlays —
Spotlight(command palette),Tabs,Stepper,Pagination,Breadcrumbs→ theplatform-blocks-navigationskill. - Tables, lists and other data display —
Table,DataTable,Tree,Timeline,Accordion,Badge,Chip,Avatar→ theplatform-blocks-data-displayskill. - Form inputs and validation →
platform-blocks-forms. Install and provider wiring →platform-blocks-setup. Theme tokens and variant colors →platform-blocks-theming. Screen layout →platform-blocks-layout.