This page covers the frontend modal system: the BaseModal foundation component, its composable sub-components, size and type variants, and the key modal implementations built on top of it. For the chat/playground overlay specifically, see Chat Interface. For the templates flow-creation modal UI, see Flow Management UI.
All modal dialogs in the Langflow frontend are built on a single foundation: the BaseModal component located at src/frontend/src/modals/baseModal/index.tsx BaseModal wraps Radix UI's @radix-ui/react-dialog primitive (surfaced through src/frontend/src/components/ui/dialog.tsx) and provides a composable, slot-based API: BaseModal.Header, BaseModal.Content, BaseModal.Footer, and BaseModal.Trigger. Concrete modal implementations assemble these slots and pass a size and type prop to control layout.
Architecture diagram: BaseModal and its dependencies
Sources: src/frontend/src/modals/baseModal/index.tsx1-20 src/frontend/src/components/ui/dialog.tsx1-10
BaseModal uses a static-property pattern to expose four composable slots as named sub-components. Consumers use BaseModal.Header, BaseModal.Content, BaseModal.Footer, and BaseModal.Trigger as direct children.
| Sub-component | Type name | Key props | Role |
|---|---|---|---|
BaseModal.Header | Header | children, description?, clampDescription? | Renders DialogHeader + DialogTitle + optional DialogDescription |
BaseModal.Content | Content | children, overflowHidden?, className? | Flex-column scrollable (or overflow-hidden) content area |
BaseModal.Footer | Footer | children, submit?, close?, centered?, className? | Action row; when submit is provided, renders Cancel + submit button pair |
BaseModal.Trigger | Trigger | children, asChild?, disable?, className? | Wraps DialogTrigger; hidden when children are empty or a bare Fragment |
Sources: src/frontend/src/modals/baseModal/index.tsx22-178 src/frontend/src/modals/baseModal/index.tsx342-346
The slot assignment is done at runtime by iterating React.Children.toArray(children) and matching each child's .type against the four sub-component constructors:
src/frontend/src/modals/baseModal/index.tsx238-250
BaseModalProps {
children // One or more of Content, Header, Trigger, Footer elements
open? // Controlled open state
setOpen? // Controlled open state setter
size? // One of ~22 named size tokens (default: "large")
type? // "modal" | "dialog" | "full-screen" (default: "dialog")
onSubmit? // If provided, wraps modal content in a Radix Form.Root
onChangeOpenModal? // Side-effect callback when open changes
onEscapeKeyDown? // Escape key handler forwarded to DialogContent
onOpenAutoFocus? // Auto-focus handler forwarded to DialogContent
closeButtonClassName? // CSS class for the X close button
dialogContentWithouFixed? // Boolean; switches to DialogContentWithouFixed variant
}
Sources: src/frontend/src/modals/baseModal/index.tsx179-223
The type prop selects which rendering mode BaseModal uses:
Modal rendering by type
"dialog" (default): Uses Dialog + DialogContent from dialog.tsx, rendered in a portal with overlay and an X close button via @radix-ui/react-dialog."modal": Uses the dialog-with-no-close variant; the X button is omitted."full-screen": No dialog wrapper at all; content renders inline in a div.Sources: src/frontend/src/modals/baseModal/index.tsx277-338
The size prop maps to concrete Tailwind width and height classes via switchCaseModalSize in src/frontend/src/modals/baseModal/helpers/switch-case-size.ts
| Size token | Min-width class | Height class |
|---|---|---|
"notice" | min-w-[400px] max-w-[400px] | — |
"x-small" | min-w-[20vw] | — |
"smaller" | min-w-[40vw] | h-[11rem] |
"small" | min-w-[40vw] | h-[40vh] |
"small-query" | min-w-[35vw] | h-fit |
"medium" | min-w-[60vw] max-w-[720px] | h-[60vh] |
"medium-tall" | min-w-[60vw] | h-[90vh] |
"large" | min-w-[85vw] | h-[80vh] |
"large-thin" | min-w-[65vw] | h-[90vh] |
"large-h-full" | min-w-[80vw] | — |
"x-large" | min-w-[95vw] | h-[95vh] |
"templates" | w-[97vw] max-w-[1200px] | responsive, max h-[90vh] |
"three-cards" | min-w-[1066px] | max-h-[94vh] |
"retangular" | !min-w-[900px] | min-h-[232px] |
"auth" | min-w-[600px] | — |
"small-update" | min-w-[480px] max-w-[480px] | — |
| (default/fallback) | min-w-[80vw] | h-[90vh] |
The returned minWidth and height strings are combined with className into the contentClasses string passed to DialogContent.
Sources: src/frontend/src/modals/baseModal/helpers/switch-case-size.ts1-108 src/frontend/src/modals/baseModal/index.tsx251-272
BaseModal does not call Radix UI directly. Instead it imports from two thin wrapper modules:
dialog.tsxsrc/frontend/src/components/ui/dialog.tsx re-exports all Radix Dialog primitives with Langflow-specific styling and accessibility improvements:
DialogPortal: Renders into a fixed full-screen container with z-50, preventing event propagation from the canvas (nopan nodelete nodrag noflow classes).DialogOverlay: Applies the dialogClass.dialogContent from @/customization/utils/dialog-class.DialogContent: Adds animation classes (animate-in/out, fade-in/out, zoom-in/out), auto-injects visually hidden <DialogTitle> and <DialogDescription> if absent (ARIA requirement), and renders the Cross2Icon close button with a ShadTooltip.VisuallyHidden: Utility for screen-reader-only text used for the auto-injected title/description.Sources: src/frontend/src/components/ui/dialog.tsx13-179
dialog-with-no-closeUsed when type="modal". Imported as Modal/ModalContent inside BaseModal. Omits the X close button, used when the caller wants full control over how the dialog is dismissed.
Sources: src/frontend/src/modals/baseModal/index.tsx15-17
When onSubmit is passed to BaseModal, the Header + Content + Footer block is wrapped in a @radix-ui/react-form Form.Root:
src/frontend/src/modals/baseModal/index.tsx299-334
Form.Root intercepts the native form submit event, calls event.preventDefault(), then invokes onSubmit(). This allows BaseModal.Footer's submit button to use type="submit" for proper form semantics while still being handled in React state.
The Footer sub-component handles two layouts:
submit prop: Renders the caller's children on the left plus a Cancel (DialogClose) and the named submit button on the right.close prop only: Renders a standalone "Close" DialogClose button.Sources: src/frontend/src/modals/baseModal/index.tsx118-178
Diagram: Modal implementations and their BaseModal usage
Sources: src/frontend/src/modals/promptModal/index.tsx224-342 src/frontend/src/modals/textAreaModal/index.tsx38-114 src/frontend/src/modals/templatesModal/index.tsx81-136
File: src/frontend/src/modals/promptModal/index.tsx
PromptModal provides a full-screen editor for prompt template strings. Its key behaviors:
"x-large" (95 vw × 95 vh).inputValue (the editable text), isEdit (edit vs preview toggle), and wordsHighlight (a Set<string> of detected {variable} tokens).<Textarea> (edit mode) and a SanitizedHTMLWrapper (preview mode) that syntax-highlights detected template variables using varHighlightHTML.checkVariables() runs on every input change, parses {variable} patterns (filtering invalid characters), and updates wordsHighlight.usePostValidatePrompt mutation. On success, calls setValue(inputValue) and optionally setNodeClass with the updated frontend node.wordsHighlight badges; right side has the "Check & Save" button (not the standard submit prop pattern).Props (PromptModalType):
| Prop | Type | Purpose |
|---|---|---|
field_name | string | Prompt field name sent to the validation API |
value | string | Current prompt text |
setValue | (val: string) => void | Writes back updated text |
nodeClass | APIClassType | Sent to prompt validation endpoint |
setNodeClass | (cls) => void | Applies updated node template after validation |
children | ReactNode | Trigger element |
disabled | boolean | Disables the trigger |
readonly | boolean | Disables editing |
id | string | DOM id for the textarea |
Sources: src/frontend/src/modals/promptModal/index.tsx29-343
File: src/frontend/src/modals/textAreaModal/index.tsx
Exported as ComponentTextModal, this modal provides a simple full-size textarea for any free-form text field (including password-masked fields).
"x-large".inputValue locally; syncs from value prop when modalOpen changes.password prop is truthy, an eye-toggle button in the header calls changeVisibility(). The <Textarea> receives the password prop for masked rendering.onCloseModal?.() fires in a useEffect when modalOpen becomes false.setValue(inputValue) then sets modalOpen(false).Props (textModalPropsType):
| Prop | Type | Purpose |
|---|---|---|
value | string | Current text content |
setValue | (val: string) => void | Callback to persist edited value |
children | ReactNode | Trigger element |
disabled | boolean | Disables the trigger |
readonly | boolean | Read-only textarea |
password | boolean | undefined | Enables masked display and eye toggle |
changeVisibility | () => void | Toggles password visibility |
onCloseModal | () => void | Called when modal closes |
Sources: src/frontend/src/modals/textAreaModal/index.tsx14-114
File: src/frontend/src/modals/templatesModal/index.tsx
The templates/new-flow modal. Uses size="templates" which provides responsive sizing. Internally renders a sidebar + main-content split inside BaseModal.Content.
BaseModal.Header or BaseModal.Trigger; open/setOpen are controlled externally.Nav) uses Radix Sidebar with category navigation for template filtering tabs.GetStartedComponent and TemplateContentComponent based on currentTab.BaseModal.Footer is used inside the main content area (not at the top level) to render the "Start from scratch / Blank Flow" action row.GetStartedComponent renders BaseModal.Header inline as a standalone sub-component outside of any BaseModal instance, demonstrating that the Header sub-component can also be used as a presentational component independently.Sources: src/frontend/src/modals/templatesModal/index.tsx81-136 src/frontend/src/modals/templatesModal/components/GetStartedComponent/index.tsx58-75
All concrete modals follow the same composition pattern:
<BaseModal open={...} setOpen={...} size="..." type="...">
<BaseModal.Trigger ...>
{triggerElement}
</BaseModal.Trigger>
<BaseModal.Header description="...">
{title}
</BaseModal.Header>
<BaseModal.Content overflowHidden>
{bodyContent}
</BaseModal.Content>
<BaseModal.Footer submit={{ label: "...", onClick: ... }}>
{optionalExtraContent}
</BaseModal.Footer>
</BaseModal>
BaseModal identifies each slot by comparing child.type to the statically attached sub-component constructors (Header, Content, Footer, Trigger). Order among children does not matter — the modal always renders in the fixed order: trigger (outside the dialog), then header, content, footer (inside).
Sources: src/frontend/src/modals/baseModal/index.tsx238-265
DialogContent in dialog.tsx auto-injects visually hidden DialogTitle and DialogDescription when absent, satisfying Radix's ARIA requirements.<span className="sr-only">Close</span>.DialogPortal applies nopan nodelete nodrag noflow to prevent the ReactFlow canvas from intercepting pointer events while the modal is open.Trigger detects empty/Fragment children and marks the DialogTrigger as hidden, avoiding an invisible interactive element in the DOM.Sources: src/frontend/src/components/ui/dialog.tsx64-115 src/frontend/src/modals/baseModal/index.tsx53-90
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.