YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
CLI Reference▾
AkanJS Reference▾
Overlays UI
The overlay components in
akanjs/ui: modal windows, confirmations, bottom sheets, menus, hints and a copy action. Start with Modal, and compose the headless Dialog only when you need your own layout.Words used on this page
TermDescription
portal
Drawing an element elsewhere in the DOM, here at the end of
document.body, so no parent clips it.trigger
The element the user clicks to open the overlay, passed as
trigger or as children.controlled
You pass
open and set it back in onCancel. Left out, the component keeps its own open state.override slot
A name in
_overrides.tsx that swaps a component for one route subtree.scrim
A see-through layer behind a popover that catches the click outside it.
Pick a component
Component
Portalled
document.body
At trigger
Override slot
_overrides.tsx
Windows over the page
Modal
✓
✓
A centred window with title, body and footer slots. The default for a modal flow.
Dialog
✓
The headless parts
Modal is built from, for a custom layout or an agent-named dialog.BottomSheet
A mobile panel that slides up from the bottom edge. Drawn in place, fixed to the screen.
Anchored to a trigger
Popconfirm
✓
✓
✓
A small OK/cancel popover before a destructive action.
Dropdown
✓
✓
✓
A short action menu, such as the actions on a list row.
Tooltip
✓
A hover or focus hint in pure CSS. At the screen edge it is clipped, not moved.
Navigation and helpers
Menu
✓
A navigation menu built from an
items tree, for a sidebar or a top bar.Portal
Renders its children into a host element named by
id.Copy
Copies text to the clipboard and shows a success toast.
✓Does itDoes not
- A portalled overlay is never clipped.
Modal,Dropdown,PopconfirmandSelectrender atdocument.body, so a scrolling modal body or a table's overflow container cannot cut them off. Portalis the same idea with a name. It renders into a host element you pick byidinstead of at the end of the body.Tooltipdoes none of this on purpose. It is pure CSS, so it costs almost nothing, and near the screen edge it is clipped rather than moved.
Related pages
Forms UI→
Select, the fourth control that portals its list and anchors it to the field.Override Slots→
Swap
Modal, Popconfirm, Dropdown, Tooltip or Menu for one route subtree.Core UI→
Layout.Navbar and the other frame slots that Portal fills.In-Page Agent→
How the tools a component publishes let the agent drive the screen.
Modal
A centred window with title, body and footer slots, built on the headless
Dialog. Reach for it first; compose Dialog only when you need a layout of your own.Props / API
openboolean
Controlled open state. May be left out when
trigger is given.onCancel() => void
Called when the modal closes itself: the close button, a backdrop click or Escape.
triggerReactNode
Element that opens the modal. With it, the modal keeps its own open state.
titlestring | ReactNode
The header row. Left out, no header is drawn.
actionReactNode
The footer row, right-aligned. Usually buttons.
closeButtonReactNode | false
The corner close control, wired by its slot, so a replacement needs no handler.
false draws none.confirmCloseboolean = false
Asks with the browser's confirm dialog before closing.
className / bodyClassNamestring
Classes for the window and for its scrolling body.
- Nothing moves. The window has no transition and no gesture, so content the user is reading never animates.
LegacyModalkeeps the previous spring skin, withopenandonCancelrequired and notriggerorcloseButton. - Focus and scroll are handled. Opening moves focus into the window and locks the page scroll; closing returns focus to where it was.
- It is the Modal override slot. A replacement bound in
_overrides.tsxreaches every<Modal>, including the onesModel.*draws.LegacyModalis not overridable.
Usage
Dialog
The headless compound parts that
Modal is built from. Compose them when Modal's fixed layout does not fit, or when the in-page agent should be able to open and close the dialog.Props / API
Dialog{ open?, defaultOpen? = false, namespace?, className? }
The root that holds the open state.
open is followed whenever it changes.namespacestring
Names the dialog for the in-page agent. Without it, the dialog publishes no tool.
Dialog.Trigger{ className?, children }
Opens the dialog when anything inside it is clicked.
Dialog.Modal{ onCancel?, confirmClose?, closeButton?, className?, bodyClassName? }
The plain window
Modal draws. Escape, a backdrop click and the corner button close it.Dialog.LegacyModal{ onCancel?, confirmClose?, className?, bodyClassName? }
The previous window: spring open/close and drag-to-dismiss on touch.
Dialog.Title / Dialog.Action{ children }
Draw nothing where written; they hand their children to the header and footer rows.
Dialog.Content{ className?, children }
The body, a full-width block.
- A namespace publishes three names.
namespace="share"gives the agentopenDialogInShare,closeDialogInShareand thedialogInSharestate. Two dialogs on one screen need different names. - The agent closes it the way a person does. Its close goes through the window's own dismissal, so
confirmCloseandonCancelstill run.Modaltakes nonamespace, so it publishes nothing.
Usage
Popconfirm
A small OK/cancel popover that stands in front of a destructive or irreversible action. Wrap the trigger in it and pass the action as
onConfirm.Props / API
titleReactNode
The question, in bold.
descriptionReactNode
Optional detail under the title.
onConfirm() => void
Called when the user presses OK. The popover closes first.
okText / cancelTextReactNode
Button labels. The defaults are the
base.ok and base.cancel dictionary entries.okButtonProps / cancelButtonPropsButtonHTMLAttributes & { loading? }
Attributes spread onto the two default buttons.
iconReactNode | false
The mark beside the message, a warning icon by default.
false draws none.actionsReactNode
Replaces the whole footer. The replacement owns both the confirm and the dismiss.
triggerClassName / decoClassNamestring
Classes for the trigger wrapper and for the pointer.
decoClassName also takes over its position.- Never clipped. The popover portals to
document.bodyand sits under the trigger's end edge. With no room below it flips above, and the pointer follows. - The scrim takes the outside click. Clicking outside, or Escape, only cancels the popover. A
Dropdownor modal that opened it stays open. - Removing a model record?
Model.RemoveWrapperalready draws this popover and publishes the removal as an agent tool.
Usage
Dropdown
A compact action menu under a trigger button. It is the usual home for row actions, comment menus and other context actions in a list.
Props / API
valueReactNode
Content of the default trigger, a ghost button.
triggerReactNode
Your own trigger instead of the button. It is cloned, so it must forward className, onClick, aria-*.
contentReactNode
The menu rows. They render inside a
<ul>, so write <li> items.align"start" | "end" = "end"
The trigger edge the menu lines up with. A
left-0 class cannot change it.namespacestring
Names the menu for the in-page agent. Without it, the menu publishes no tool.
className / buttonClassName / dropdownClassNamestring
Classes for the wrapper, the trigger button and the menu panel.
data-dropdown-keep-openattribute
Put on a row with its own interaction, such as a switch, so clicking it keeps the menu open.
- Never clipped. The menu portals to
document.bodyand is placed against its trigger, so a modal, a scrolling modal body or a table's scroll container cannot cut it off. - A row may open a Modal. A closed menu is hidden, not unmounted, so the modal survives. Clicks inside an overlay this menu opened are not outside clicks; any other overlay still closes it.
- Clicking a row closes the menu, unless the row carries
data-dropdown-keep-open(also exported asDROPDOWN_KEEP_OPEN_ATTR). A customtrigger's ownonClickruns first; callingpreventDefault()there keeps the menu from toggling.
Usage
BottomSheet
The mobile overlay: a panel that slides up from the bottom edge.
type decides almost everything; like Modal, it runs controlled or from its own trigger.Props / API
type"full" | "half"
Required.
half is 90% tall with a grab handle; full covers the screen with a close row.open / onCancelboolean / () => void
Controlled state. Left out, the sheet keeps its own and opens from
trigger or the ref.triggerReactNode
Element that opens the sheet.
header / handle / closeReactNode
header replaces the whole top row; handle and close replace only the mark inside it.className / bodyClassNamestring
Classes for the sheet surface and for its scrolling body.
refBottomSheetRef
{ open, close }, an imperative handle for opening the sheet without a trigger.- Four ways to close it: drag a
halfsheet's handle down past a third of its height, tap the backdrop, press Escape, or use afullsheet's close row. Each one callsonCancel.
Usage
Tooltip
A hint that appears on hover or keyboard focus, drawn in pure CSS. It is for hints only: content that must be read does not belong in a tooltip.
Props / API
contentReactNode
The hint. Empty,
null or undefined renders children alone.childrenReactNode
The trigger the bubble is anchored to.
side"top" | "right" | "bottom" | "left" = "top"
Which side of the trigger the bubble sits on.
variant"default" | "primary" | "info" = "default"
The bubble's colour.
Field.Label uses info for the help icon beside a field description.classNamestring
Classes for the bubble.
- Light, but it never moves. No state, no portal and no position pass, so it works in the server-rendered HTML. The cost: near the viewport edge the bubble is clipped rather than flipped.
- A conditional hint needs no wrapper. Pass an empty
contentand only the trigger renders. The bubble shows after 300 ms of hover, or at once on keyboard focus. - Need one that flips or follows the pointer?
Tooltipis an override slot: bind your own in_overrides.tsxand every existing call site follows.
Usage
Menu
A navigation menu built from data rather than markup: you pass an
items tree and it draws the rows, the submenus and the active state. mode picks a sidebar or a top bar.Props / API
items{ key, label, icon?, children?, type? }[]
The tree of
MenuItems. A children array turns the row into a submenu.mode"horizontal" | "inline" = "inline"
inline for a sidebar. horizontal for a top bar, folding what does not fit into a … menu.selectedKeys / defaultSelectedKeysstring[]
Selected keys.
selectedKeys is controlled; defaultSelectedKeys sets the start, first key only.onClick(item: MenuItem) => void
Receives the clicked item. In
inline mode a row with children only expands.inlineCollapsedboolean
Hides the labels, leaving only the icons.
activeStyle"bordered" | "active" = "bordered"
How the active row is marked: a bottom border, or a
bg-border fill.renderItem(item, active) => ReactNode
Draws one item's body. The row, its click and any submenu stay the framework's.
ulClassName / liClassName / labelClassNamestring / string / (isActive) => string
The list, each row and each label.
className reaches the outer wrapper.Menuis not aDropdown.Menuis a navigation structure andDropdowna short-lived action list. They look alike but are not interchangeable: row actions on a list go in aDropdown.
Usage
Portal
Renders
children into an element the page already has, named by id. It is the wiring behind Layout.Navbar: a component deep in a route fills the top bar without either one knowing about the other.Props / API
idstring
The host element's
id. Nothing renders until that element is mounted.childrenReactNode
What is rendered into the host.
- Fill the frame's slots through their Layout component.
Layout.Navbar,Layout.TopInset,Layout.TopLeftActionandLayout.BottomInsetpick the right host in a CSR build, and all butTopLeftActionreserve the slot's height. - Frame slots are in the first HTML. During SSR their content is written into the shell instead of appearing after hydration. A host of your own is filled in the browser.
- It is not a way out of a clipping parent.
Modalalready portals todocument.body, andDropdown,PopconfirmandSelectalso place themselves against their trigger.
Usage
Copy
Wraps a trigger so that clicking it copies
text to the clipboard and shows a success toast.Props / API
textstring = ""
Text written to the clipboard.
copyMessagestring
The success toast. Defaults to "Copied" in the reader's language.
childrenReactNode
The trigger. An element keeps its own
onClick, which runs before the copy.- Works without the Clipboard API. Where
navigator.clipboardis missing it falls back to a hidden text area. The toast goes through the store'sshowMessage.
Usage