▚Modalcustom
Dimmed overlay dialog with focus on the panel
$ termino modal — press space to open
┌────────────────────────────────────────────────────┐
│ app shell — normal content here │
│ ● build passed │
└────────────────────────────────────────────────────┘
##API
Renders `null` when closed. When open, an absolute full-screen dim box centers a double-bordered panel; the panel grabs focus; esc/q closes.
properties
| prop | type | default | description |
|---|---|---|---|
| open | boolean | - | Show or hide |
| onClose | () => void | - | Called on esc/q |
| title | string | - | Optional title row |
| width / height | number | 46 / 10 | Panel size |
| borderColor | string | "#7aa2f7" | Panel border color |
##Keymap
esc / qClose the modal
(panel)Focus moves to the modal panel when opened
##Notes
- ▸The overlay uses an RGBA color with alpha — OpenTUI renders real dimming, not a static shade.
- ▸Combine with ToastProvider: confirmations push toasts, modals ask questions.
##Source
Real implementation — this is the code that runs in your terminal.
1import { createElement as h, useEffect, useRef } from "react";2import { useKeyboard } from "@opentui/react";3import type { BoxRenderable } from "@opentui/core";45export interface ModalProps {6 open: boolean;7 onClose: () => void;8 title?: string;9 children?: React.ReactNode;10 width?: number;11 height?: number;12 borderColor?: string;13}1415export function Modal({16 open,17 onClose,18 title,19 children,20 width = 46,21 height = 10,22 borderColor = "#7aa2f7",23}: Readonly<ModalProps>) {24 const panelRef = useRef<BoxRenderable | null>(null);2526 useKeyboard((key) => {27 if (!open) return;28 if (key.name === "escape" || key.name === "q") {29 onClose();30 }31 });3233 useEffect(() => {34 if (open) panelRef.current?.focus();35 }, [open]);3637 if (!open) return null;3839 return h(40 "box",41 {42 style: {43 position: "absolute",44 left: 0,45 top: 0,46 right: 0,47 bottom: 0,48 alignItems: "center",49 justifyContent: "center",50 backgroundColor: "rgba(10,12,18,0.85)",51 },52 },53 h(54 "box",55 // eslint-disable-next-line react-hooks/refs -- ref via createElement wrapper56 {57 ref: panelRef,58 style: {59 width,60 height,61 border: true,62 borderStyle: "double",63 borderColor,64 backgroundColor: "#1a1b26",65 flexDirection: "column",66 paddingLeft: 1,67 paddingRight: 1,68 paddingTop: 0,69 paddingBottom: 1,70 },71 },72 title73 ? h(74 "box",75 { flexDirection: "row", justifyContent: "space-between", width: width - 4 },76 h("text", { fg: borderColor }, title),77 h("text", { fg: "#565f89" }, "esc"),78 )79 : null,80 h("box", { style: { flexGrow: 1, justifyContent: "center" } }, children),81 ),82 );83}84