For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms-full.txt, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/modal.md.
  • English
  • Modal

    Display content or complete an action in a dialog.

    Edit content

    basic.tsx
    import { Button, Form, FormField, Input, Modal, Select } from '@ifhover/leaf-ui';
    
    import { useRef, useState } from 'react';
    
    export function ModalBasic() {
      const [open, setOpen] = useState(false);
      const formRef = useRef<HTMLFormElement>(null);
      return (
        <>
          <Button onClick={() => setOpen(true)}>Edit project</Button>
          <Modal
            open={open}
            title="Project settings"
            confirmText="Save"
            onClose={() => setOpen(false)}
            onConfirm={() => formRef.current?.requestSubmit()}
          >
            <Form
              ref={formRef}
              layout="vertical"
              onSubmit={(event) => {
                event.preventDefault();
                setOpen(false);
              }}
            >
              <FormField label="Project name" required>
                <Input name="name" defaultValue="Leaf UI" />
              </FormField>
              <FormField label="Owner">
                <Select
                  name="owner"
                  options={[
                    { value: 'design', label: 'Design team' },
                    { value: 'engineering', label: 'Engineering team' },
                  ]}
                />
              </FormField>
            </Form>
          </Modal>
        </>
      );
    }
    
    footer.tsx
    import { Button, Modal } from '@ifhover/leaf-ui';
    
    import { useState } from 'react';
    
    export function ModalFooter() {
      const [open, setOpen] = useState(false);
      const [extra, setExtra] = useState(false);
      return (
        <>
          <Button variant="outline" onClick={() => setOpen(true)}>
            Custom footer
          </Button>
          <Modal
            open={open}
            title="More actions"
            onClose={() => setOpen(false)}
            footer={({ cancelButton, confirmButton }) => (
              <>
                <Button
                  variant="ghost"
                  style={{ marginRight: 'auto' }}
                  onClick={() => setExtra(!extra)}
                >
                  Details
                </Button>
                {cancelButton}
                {confirmButton}
              </>
            )}
          >
            <p>Extend the footer while keeping the default actions.</p>
            {extra && <p>Additional details are shown here.</p>}
          </Modal>
        </>
      );
    }
    

    Destroying state and initial focus

    capabilities.tsx
    import { Button, Input, Modal } from '@ifhover/leaf-ui';
    
    import { useRef, useState } from 'react';
    
    export function Capabilities() {
      const [open, setOpen] = useState(false);
      const focus = useRef<HTMLInputElement>(null);
      return (
        <>
          <Button onClick={() => setOpen(true)}>{'Open editor'}</Button>
          <Modal
            open={open}
            onClose={() => setOpen(false)}
            title={'Disposable draft'}
            destroyOnClose
            initialFocus={focus}
            footer={({ cancelButton, confirmButton }) => (
              <>
                {cancelButton}
                {confirmButton}
              </>
            )}
          >
            <Input
              ref={focus}
              aria-label={'Draft title'}
              placeholder={'The draft resets after closing'}
            />
          </Modal>
        </>
      );
    }
    

    Icon and description

    Use icon and description to explain the action.

    description.tsx
    import { Button, Modal } from '@ifhover/leaf-ui';
    
    import { Save } from 'lucide-react';
    
    import { useState } from 'react';
    
    export function ModalDescription() {
      const [open, setOpen] = useState(false);
      return (
        <>
          <Button variant="outline" onClick={() => setOpen(true)}>
            Icon and description
          </Button>
          <Modal
            open={open}
            title="Save settings"
            description="Your changes take effect immediately after saving."
            icon={<Save />}
            confirmText="Save"
            onClose={() => setOpen(false)}
            onConfirm={() => setOpen(false)}
          >
            Save the current settings?
          </Modal>
        </>
      );
    }
    

    The body uses Scrollbar; long content scrolls while the heading and actions remain visible. Headers without a description and the footer use compact vertical spacing.

    PropertyTypeDefaultDescription
    initialFocusRefObject<HTMLElement | null> | (() => HTMLElement | null)—Specify initial focus after opening.
    returnFocusboolean | RefObject<HTMLElement | null> | (() => HTMLElement | null)—Restore focus on close; specify a destination or use false.
    openbooleanRequiredRequired. Whether the dialog is visible
    titleReactNode—Title / content
    descriptionReactNode—Additional description.
    iconReactNode—Decorative icon to the left of the heading.
    dividedbooleantrueSeparate the heading, content and footer with subtle borders.
    footerReactNode | ((actions: ModalFooterActions) => ReactNode)Cancel + OKCustom footer; null hides it; a function receives default buttons
    confirmTextReactNodeLocale defaultButton labels
    cancelTextReactNodeLocale defaultButton labels
    confirmLoadingbooleanfalseConfirm loading state
    confirmButtonPropsButtonProps—Customize the default buttons
    cancelButtonPropsButtonProps—Customize the default buttons
    onConfirm() => void | Promise<void>—Application controls open; rejection displays an error
    onCancel() => void—Cancel or dismiss; falls back to onClose
    widthnumber | string480Width; numbers are pixels
    closablebooleantrueClose button / backdrop dismissal / Escape dismissal
    maskClosablebooleantrueClose button / backdrop dismissal / Escape dismissal
    keyboardbooleantrueClose button / backdrop dismissal / Escape dismissal
    onClose() => void—Requests closure; update open in the application
    afterClose() => void—Called after the closing animation finishes
    forceRenderbooleanfalseMount before opening and keep hidden while closed.
    destroyOnClosebooleanfalseUnmount content after the closing transition; preserved by default.
    preserveboolean—Explicitly preserve closed content; overrides destroyOnClose.
    containerElement | DocumentFragment | (() => Element | DocumentFragment)—Custom portal or scroll container.

    ModalFooterActions

    PropertyTypeDefaultDescription
    confirmButtonReactNode—Required. Default confirm button with loading state
    cancelButtonReactNode—Required. Default cancel button

    Types

    Import types when defining reusable component props or assembling options.

    import type { ModalProps, ModalFooterActions } from '@ifhover/leaf-ui';

    Usage notes

    Focus moves into the dialog when opened and returns to the trigger on dismissal. Tab stays within the dialog. Use maskClosable and keyboard to control backdrop and Escape dismissal. Supply aria-label when omitting title.

    Customize footer actions or place a Form inside the body. Picker overlays inside a dialog retain its regional theme.

    The default footer has Cancel followed by OK. With onConfirm, update open after the action; without it, OK calls onClose. Promises show loading and prevent duplicates. Submit forms with requestSubmit() in onConfirm or a submit type and form ID in confirmButtonProps. Native validation still applies. A footer function extends default buttons; footer={null} hides the footer.

    DialogFocusOptions

    PropertyTypeDefaultDescription
    initialFocusRefObject<HTMLElement | null> | (() => HTMLElement | null)—Specify initial focus after opening.
    returnFocusboolean | RefObject<HTMLElement | null> | (() => HTMLElement | null)—Restore focus on close; specify a destination or use false.

    Surfaces and rich content

    Use description for supporting text and icon for a decorative heading icon. Divided dialogs have subtle dividers and a muted footer by default; divided={false} groups content with spacing. A footer function can add status text on the left. Group its buttons with Space so they wrap together and stay aligned to the right on narrow screens.