DialogContainer

A DialogContainer accepts a single Dialog as a child, and manages showing and hiding it in a modal. Useful in cases where there is no trigger element or when the trigger unmounts while the dialog is open.

installyarn add @adobe/react-spectrum
added3.4.0
usageimport {DialogContainer, Dialog} from '@adobe/react-spectrum'

Example#


function Example(props) {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <ActionButton onPress={() => setOpen(true)}>
        <Delete />
        <Text>Delete</Text>
      </ActionButton>
      <DialogContainer onDismiss={() => setOpen(false)} {...props}>
        {isOpen &&
          <AlertDialog
            title="Delete"
            variant="destructive"
            primaryActionLabel="Delete">
            Are you sure you want to delete this item?
          </AlertDialog>
        }
      </DialogContainer>
    </>
  );
}
function Example(props) {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <ActionButton onPress={() => setOpen(true)}>
        <Delete />
        <Text>Delete</Text>
      </ActionButton>
      <DialogContainer
        onDismiss={() => setOpen(false)}
        {...props}
      >
        {isOpen &&
          (
            <AlertDialog
              title="Delete"
              variant="destructive"
              primaryActionLabel="Delete"
            >
              Are you sure you want to delete this item?
            </AlertDialog>
          )}
      </DialogContainer>
    </>
  );
}
function Example(props) {
  let [isOpen, setOpen] =
    React.useState(
      false
    );

  return (
    <>
      <ActionButton
        onPress={() =>
          setOpen(true)}
      >
        <Delete />
        <Text>
          Delete
        </Text>
      </ActionButton>
      <DialogContainer
        onDismiss={() =>
          setOpen(false)}
        {...props}
      >
        {isOpen &&
          (
            <AlertDialog
              title="Delete"
              variant="destructive"
              primaryActionLabel="Delete"
            >
              Are you
              sure you
              want to
              delete this
              item?
            </AlertDialog>
          )}
      </DialogContainer>
    </>
  );
}

Dialog triggered by a menu item#


DialogContainer is useful over a DialogTrigger when your have a trigger that can unmount while the dialog is open. For example, placing a DialogTrigger around a menu item would not work because the menu closes when pressing an item, thereby unmounting the DialogTrigger. When the trigger unmounts, so does the Dialog. In these cases, it is useful to place the dialog outside the tree that unmounts, so that the dialog is not also removed.

The following example shows a MenuTrigger containing a Menu with two actions: "edit" and "delete". Each menu item opens a different dialog. This is implemented by using a DialogContainer that displays the edit dialog, delete dialog, or no dialog depending on the current value stored in local state. Pressing a menu item triggers the menu's onAction prop, which sets the state to the type of dialog to display, based on the menu item's key. This causes the associated dialog to be rendered within the DialogContainer.

This example also demonstrates the use of the useDialogContainer hook, which allows the dialog to dismiss itself when a user presses one of the buttons inside it. This triggers the DialogContainer's onDismiss event, which resets the state storing the open dialog back to null. In addition, the user can close the dialog using the Escape key (unless the isKeyboardDismissDisabled prop is set), or by clicking outside (if the isDismissable prop is set).

import {useDialogContainer} from '@adobe/react-spectrum';

function Example() {
  let [dialog, setDialog] = React.useState(null);

  return (
    <>
      <MenuTrigger>
        <ActionButton aria-label="Actions">
          <More />
        </ActionButton>
        <Menu onAction={setDialog}>
          <Item key="edit">Edit...</Item>
          <Item key="delete">Delete...</Item>
        </Menu>
      </MenuTrigger>
      <DialogContainer onDismiss={() => setDialog(null)}>
        {dialog === 'edit' &&
          <EditDialog />}
        {dialog === 'delete' &&
          (
            <AlertDialog
              title="Delete"
              variant="destructive"
              primaryActionLabel="Delete"
            >
              Are you sure you want to delete this item?
            </AlertDialog>
          )}
      </DialogContainer>
    </>
  );
}

function EditDialog() {
  // This hook allows us to dismiss the dialog when the user
  // presses one of the buttons (below).
  let dialog = useDialogContainer();

  return (
    <Dialog>
      <Heading>Edit</Heading>
      <Divider />
      <Content>
        <Form labelPosition="side" width="100%">
          <TextField autoFocus label="First Name" defaultValue="John" />
          <TextField label="Last Name" defaultValue="Smith" />
        </Form>
      </Content>
      <ButtonGroup>
        <Button variant="secondary" onPress={dialog.dismiss}>Cancel</Button>
        <Button variant="accent" onPress={dialog.dismiss}>Save</Button>
      </ButtonGroup>
    </Dialog>
  );
}
import {useDialogContainer} from '@adobe/react-spectrum';

function Example() {
  let [dialog, setDialog] = React.useState(null);

  return (
    <>
      <MenuTrigger>
        <ActionButton aria-label="Actions">
          <More />
        </ActionButton>
        <Menu onAction={setDialog}>
          <Item key="edit">Edit...</Item>
          <Item key="delete">Delete...</Item>
        </Menu>
      </MenuTrigger>
      <DialogContainer onDismiss={() => setDialog(null)}>
        {dialog === 'edit' &&
          <EditDialog />}
        {dialog === 'delete' &&
          (
            <AlertDialog
              title="Delete"
              variant="destructive"
              primaryActionLabel="Delete"
            >
              Are you sure you want to delete this item?
            </AlertDialog>
          )}
      </DialogContainer>
    </>
  );
}

function EditDialog() {
  // This hook allows us to dismiss the dialog when the user
  // presses one of the buttons (below).
  let dialog = useDialogContainer();

  return (
    <Dialog>
      <Heading>Edit</Heading>
      <Divider />
      <Content>
        <Form labelPosition="side" width="100%">
          <TextField
            autoFocus
            label="First Name"
            defaultValue="John"
          />
          <TextField
            label="Last Name"
            defaultValue="Smith"
          />
        </Form>
      </Content>
      <ButtonGroup>
        <Button
          variant="secondary"
          onPress={dialog.dismiss}
        >
          Cancel
        </Button>
        <Button variant="accent" onPress={dialog.dismiss}>
          Save
        </Button>
      </ButtonGroup>
    </Dialog>
  );
}
import {useDialogContainer} from '@adobe/react-spectrum';

function Example() {
  let [
    dialog,
    setDialog
  ] = React.useState(
    null
  );

  return (
    <>
      <MenuTrigger>
        <ActionButton aria-label="Actions">
          <More />
        </ActionButton>
        <Menu
          onAction={setDialog}
        >
          <Item key="edit">
            Edit...
          </Item>
          <Item key="delete">
            Delete...
          </Item>
        </Menu>
      </MenuTrigger>
      <DialogContainer
        onDismiss={() =>
          setDialog(
            null
          )}
      >
        {dialog ===
            'edit' &&
          <EditDialog />}
        {dialog ===
            'delete' &&
          (
            <AlertDialog
              title="Delete"
              variant="destructive"
              primaryActionLabel="Delete"
            >
              Are you
              sure you
              want to
              delete this
              item?
            </AlertDialog>
          )}
      </DialogContainer>
    </>
  );
}

function EditDialog() {
  // This hook allows us to dismiss the dialog when the user
  // presses one of the buttons (below).
  let dialog =
    useDialogContainer();

  return (
    <Dialog>
      <Heading>
        Edit
      </Heading>
      <Divider />
      <Content>
        <Form
          labelPosition="side"
          width="100%"
        >
          <TextField
            autoFocus
            label="First Name"
            defaultValue="John"
          />
          <TextField
            label="Last Name"
            defaultValue="Smith"
          />
        </Form>
      </Content>
      <ButtonGroup>
        <Button
          variant="secondary"
          onPress={dialog
            .dismiss}
        >
          Cancel
        </Button>
        <Button
          variant="accent"
          onPress={dialog
            .dismiss}
        >
          Save
        </Button>
      </ButtonGroup>
    </Dialog>
  );
}

Props#


NameTypeDefaultDescription
childrenReactNodeThe Dialog to display, if any.
type'modal''fullscreen''fullscreenTakeover''modal'The type of Dialog that should be rendered. See the visual options below for examples of each.
isDismissablebooleanWhether the Dialog is dismissable. See the Dialog docs for more details.
isKeyboardDismissDisabledbooleanWhether pressing the escape key to close the dialog should be disabled.
Events
NameTypeDescription
onDismiss() => voidHandler that is called when the 'x' button of a dismissable Dialog is clicked.

useDialogContainer#


The useDialogContainer hook can be used to allow a custom dialog component to access the type of container the dialog is rendered in (e.g. modal, popover, fullscreen, etc.), and also to dismiss the dialog programmatically. It works with both DialogContainer and DialogTrigger.

useDialogContainer(): DialogContainerValue

Visual options#


Full screen#

The type prop allows setting the type of modal to display. Set it to "fullscreen" to display a full screen dialog, which only reveals a small portion of the page behind the underlay. Use this variant for more complex workflows that do not fit in the available modal dialog sizes.

function Example(props) {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <ActionButton onPress={() => setOpen(true)}>
        <Edit />
        <Text>Edit</Text>
      </ActionButton>
      <DialogContainer
        type="fullscreen"
        onDismiss={() => setOpen(false)}
        {...props}
      >
        {isOpen &&
          <EditDialog />}
      </DialogContainer>
    </>
  );
}
function Example(props) {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <ActionButton onPress={() => setOpen(true)}>
        <Edit />
        <Text>Edit</Text>
      </ActionButton>
      <DialogContainer
        type="fullscreen"
        onDismiss={() => setOpen(false)}
        {...props}
      >
        {isOpen &&
          <EditDialog />}
      </DialogContainer>
    </>
  );
}
function Example(props) {
  let [isOpen, setOpen] =
    React.useState(
      false
    );

  return (
    <>
      <ActionButton
        onPress={() =>
          setOpen(true)}
      >
        <Edit />
        <Text>Edit</Text>
      </ActionButton>
      <DialogContainer
        type="fullscreen"
        onDismiss={() =>
          setOpen(false)}
        {...props}
      >
        {isOpen &&
          <EditDialog />}
      </DialogContainer>
    </>
  );
}

Full screen takeover#

Fullscreen takeover dialogs are similar to the fullscreen variant except that the dialog covers the entire screen.

function Example(props) {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <ActionButton onPress={() => setOpen(true)}>
        <Edit />
        <Text>Edit</Text>
      </ActionButton>
      <DialogContainer
        type="fullscreenTakeover"
        onDismiss={() => setOpen(false)}
        {...props}
      >
        {isOpen &&
          <EditDialog />}
      </DialogContainer>
    </>
  );
}
function Example(props) {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <ActionButton onPress={() => setOpen(true)}>
        <Edit />
        <Text>Edit</Text>
      </ActionButton>
      <DialogContainer
        type="fullscreenTakeover"
        onDismiss={() => setOpen(false)}
        {...props}
      >
        {isOpen &&
          <EditDialog />}
      </DialogContainer>
    </>
  );
}
function Example(props) {
  let [isOpen, setOpen] =
    React.useState(
      false
    );

  return (
    <>
      <ActionButton
        onPress={() =>
          setOpen(true)}
      >
        <Edit />
        <Text>Edit</Text>
      </ActionButton>
      <DialogContainer
        type="fullscreenTakeover"
        onDismiss={() =>
          setOpen(false)}
        {...props}
      >
        {isOpen &&
          <EditDialog />}
      </DialogContainer>
    </>
  );
}

Testing#


The DialogContainer features an overlay that transitions in and out of the page as it is opened and closed. Please see the following sections in the testing docs for more information on how to handle these behaviors in your test suite.

Timers

Please also refer to React Spectrum's test suite if you find that the above isn't sufficient when resolving issues in your own test cases.