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, over the page's bars.
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. It flips to the opposite side when its own has no room.
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,BottomSheet,Dropdown,Popconfirm,SelectandTooltiprender 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.
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, which Portal fills in a CSR build.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.
presentation"dialog" | "sheet" | "auto" = "dialog"
sheet fills the screen; auto is a full-screen sheet below md and a dialog above.placement"center" | "right" = "center"
right makes it a full-height side sheet from md up. Every modal surface (Modal, Dialog.Modal, the Model modals, Model.New) takes it and presentation through the exported ModalSurfaceProps.className / bodyClassNamestring
Classes for the window and for its scrolling body.
- A dialog does not move. It has no transition and no gesture, so content the user is reading never animates.
LegacyModalkeeps the previous spring skin, withopenandonCancelrequired and notriggerorcloseButton. - A sheet fits a phone. It pads for the safe area, keeps the title and actions in place while the body scrolls, and closes on a drag down on its handle or title. Type a wrapper's props with
ModalPresentationandModalPlacement. - 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?, presentation?, placement?, className?, bodyClassName? }
The window
Modal draws, dialog or sheet. 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 callsonCancelonce; mounting never does. - It sits above the page. The sheet opens over the frame's bars, or inside the modal it was opened from, pads for the safe area and stops the page from scrolling while open.
Usage
Tooltip
A hint that appears on hover or keyboard focus, drawn on the overlay layer. 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"
The side the bubble prefers. It flips to the opposite one when this side has no room.
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.
- Not in the page until it opens. The bubble is drawn on the overlay layer only while shown, so a scrolling list neither clips it nor grows wider for it.
- A conditional hint needs no wrapper. Pass an empty
contentand only the trigger renders. The bubble shows after 300 ms of hover (never on touch) or at once on keyboard focus, and closes on leave, blur or Escape. - Need one that 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. In a CSR build 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, looked up after the first render: nothing renders before then, on the server included.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. On an SSR page the Layout components draw their chrome into the server HTML without a portal, and after hydration that same DOM moves into the slot, so text typed into it before hydration survives. A host of your own is filled only after hydration.
- 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