Modal trigger

Internal use only. These components are available only within Atlassian.

Every modal example must wrap its triggers in ModalContextProvider. In your application, place this provider once at the application root. Each standalone example supplies its own provider.

Basic usage

Use ModalTrigger when you need to attach a modal entry point to your own button. Pass the supplied ref to the button so the trigger can preload and open the entry point. For a standard button, consider Modal button trigger.

Select Open modal to load the content, then close the modal to return to the trigger. Supply title and loadingText so the default loading screen identifies what is opening.

import React, { type Ref } from 'react'; import Button from '@atlaskit/button/default/button'; import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider'; import { ModalTrigger } from '@atlassian/entry-points/modal-trigger'; import { modalEntryPoint } from '../fixtures/modal-entry-point'; import { RelayWrapper } from '../utils/wrapper'; export default function ModalTriggerBasic(): React.JSX.Element { return ( <RelayWrapper> <ModalContextProvider> <ModalTrigger entryPoint={modalEntryPoint} title="Example Modal" loadingText="Loading example modal" > {({ ref }) => ( <Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog"> Open modal </Button> )} </ModalTrigger> </ModalContextProvider> </RelayWrapper> ); }

The examples use a mock Relay environment through RelayWrapper. In your application, use the existing Relay environment and share one ModalContextProvider at the application root.

Default loading experience

The default loading screen shows the supplied title and loadingText while the entry point loads. Provide internationalized values so users can understand the title and loading message in their language. See Usage for translation requirements.

The example uses a loading fixture that intentionally never resolves. The loading screen stays visible and never displays the loaded content. Close the modal with its close button or Escape.

import React, { type Ref } from 'react'; import Button from '@atlaskit/button/default/button'; import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider'; import { ModalTrigger } from '@atlassian/entry-points/modal-trigger'; import { loadingModalEntryPoint } from '../fixtures/constellation/loading-modal-entry-point'; import { RelayWrapper } from '../utils/wrapper'; export default function ModalTriggerLoading(): React.JSX.Element { return ( <RelayWrapper> <ModalContextProvider> <ModalTrigger entryPoint={loadingModalEntryPoint} title="Example Modal" loadingText="Loading example modal" > {({ ref }) => ( <Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog"> Show default loading </Button> )} </ModalTrigger> </ModalContextProvider> </RelayWrapper> ); }

Default error experience

When loading modal content fails, the built-in error fallback gives people a clear message and a way to dismiss the dialog. You can replace it application-wide with GlobalModalErrorFallback through initEntryPointConfig. See Custom error experience.

The default error fallback uses title. Pass errorText and errorCloseText for its other translated strings.

import React, { type Ref } from 'react'; import { defineMessage, IntlProvider, useIntl } from 'react-intl'; import Button from '@atlaskit/button/default/button'; import { Box } from '@atlaskit/primitives/compiled/box'; import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider'; import { ModalTrigger } from '@atlassian/entry-points/modal-trigger'; import { errorModalEntryPoint } from '../fixtures/legacy/errorModal.entrypoint'; import { RelayWrapper } from '../utils/wrapper'; const triggerLabel = defineMessage({ id: 'entry-points.examples.modal.error.trigger.ai-non-final', defaultMessage: 'Open unavailable modal', description: 'Label on the example button that opens a modal whose content fails to load.', }); const title = defineMessage({ id: 'entry-points.examples.modal.error.title.ai-non-final', defaultMessage: 'Could not load issue', description: 'Title shown in the modal error dialog after the example entry point fails to load.', }); const errorText = defineMessage({ id: 'entry-points.examples.modal.error.text.ai-non-final', defaultMessage: 'The issue content is unavailable. Close this dialog and try again.', description: 'Error message shown in the modal error dialog after the example entry point fails to load.', }); const errorCloseText = defineMessage({ id: 'entry-points.examples.modal.error.close.ai-non-final', defaultMessage: 'Close', description: 'Label for the button that closes the modal error dialog after the example entry point fails to load.', }); const frenchMessages = { [triggerLabel.id]: 'Ouvrir la fenêtre indisponible', [title.id]: 'Impossible de charger le ticket', [errorText.id]: 'Le contenu du ticket est indisponible. Fermez cette fenêtre et réessayez.', [errorCloseText.id]: 'Fermer le message d’erreur', }; function TranslatedErrorModal(): React.JSX.Element { const { formatMessage } = useIntl(); return ( <ModalTrigger entryPoint={errorModalEntryPoint} title={formatMessage(title)} errorText={formatMessage(errorText)} errorCloseText={formatMessage(errorCloseText)} > {({ ref }) => ( <Button ref={ref as Ref<HTMLButtonElement>}>{formatMessage(triggerLabel)}</Button> )} </ModalTrigger> ); } export default function ModalTriggerErrorInternationalization(): React.JSX.Element { return ( <IntlProvider locale="fr" defaultLocale="en" messages={frenchMessages}> <RelayWrapper> <Box lang="fr"> <ModalContextProvider> <TranslatedErrorModal /> </ModalContextProvider> </Box> </RelayWrapper> </IntlProvider> ); }

Customization

Custom loading experience

Pass a component to Fallback to replace the default loading content. The CustomLoading component and its styles are defined in the example below. The trigger supplies the modal shell; the fallback supplies its header, body, and actions. Use the supplied onClose callback for a cancel action. Provide internationalized text and a title in the fallback itself, because title and loadingText configure only the default loading content.

This example intentionally never finishes loading. Select Cancel, the close button, or Escape to close it.

import React, { type Ref } from 'react'; import Button from '@atlaskit/button/default/button'; import { cssMap } from '@atlaskit/css'; import FileIcon from '@atlaskit/icon/core/file'; import ModalBody from '@atlaskit/modal-dialog/modal-body'; import ModalFooter from '@atlaskit/modal-dialog/modal-footer'; import ModalHeader from '@atlaskit/modal-dialog/modal-header'; import ModalTitle from '@atlaskit/modal-dialog/modal-title'; import { Box } from '@atlaskit/primitives/compiled/box'; import { Inline } from '@atlaskit/primitives/compiled/inline'; import { Stack } from '@atlaskit/primitives/compiled/stack'; import { Text } from '@atlaskit/primitives/compiled/text'; import Spinner from '@atlaskit/spinner/spinner'; import { token } from '@atlaskit/tokens'; import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider'; import { ModalTrigger } from '@atlassian/entry-points/modal-trigger'; import { loadingModalEntryPoint } from '../fixtures/constellation/loading-modal-entry-point'; import { RelayWrapper } from '../utils/wrapper'; const styles = cssMap({ content: { paddingBlockStart: token('space.400'), paddingBlockEnd: token('space.400'), textAlign: 'center', }, indicator: { paddingBlockStart: token('space.150'), paddingBlockEnd: token('space.150'), paddingInlineStart: token('space.200'), paddingInlineEnd: token('space.200'), borderRadius: token('radius.large'), backgroundColor: token('color.background.discovery.bold'), }, }); function CustomLoading({ onClose }: { onClose?: () => void }): React.JSX.Element { return ( <> <ModalHeader hasCloseButton> <ModalTitle>Example Modal</ModalTitle> </ModalHeader> <ModalBody> <Stack space="space.300" alignInline="center" xcss={styles.content}> <Box aria-hidden="true" xcss={styles.indicator}> <Inline space="space.150" alignBlock="center"> <FileIcon label="" color={token('color.icon.inverse')} /> <Spinner size="medium" appearance="invert" label="" /> </Inline> </Box> <Stack space="space.100" alignInline="center" role="status"> <Text weight="bold">Preparing your modal</Text> <Text color="color.text.subtle"> Loading your content. You can cancel while you wait. </Text> </Stack> </Stack> </ModalBody> <ModalFooter> <Button onClick={onClose}>Cancel</Button> </ModalFooter> </> ); } export default function ModalTriggerCustomLoading(): React.JSX.Element { return ( <RelayWrapper> <ModalContextProvider> <ModalTrigger entryPoint={loadingModalEntryPoint} Fallback={CustomLoading}> {({ ref }) => ( <Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog"> Show custom loading </Button> )} </ModalTrigger> </ModalContextProvider> </RelayWrapper> ); }

Custom error experience

Pass errorFallback to customize the error experience for one trigger. The fallback in this example reports a translated error through a flag, then closes the modal. Its implementation is included in the example source.

Provide the fallback directly to the trigger:

import Button from '@atlaskit/button/new';
import type { ModalErrorFallbackProps } from '@atlassian/entry-point-config/types';

function CustomModalError({ closeModal }: ModalErrorFallbackProps) {
    return <Button onClick={closeModal}>Close</Button>;
}

<ModalTrigger entryPoint={entryPoint} errorFallback={CustomModalError}>
    {({ ref }) => <Button ref={ref}>Open</Button>}
</ModalTrigger>;

This example intentionally fails after a short loading state and never displays loaded content. The custom fallback applies only to this trigger. Reopen the trigger to try the example again.

Accessibility

Internationalization

Provide translated strings for the trigger, title, and loadingText. title identifies the default loading and error experiences. Use errorText and errorCloseText for the other default error strings, as shown in the Default error experience. This example uses react-intl with a French message catalog to populate the title and loadingText fallback props. Use your application's existing internationalization provider in production.

The entry point intentionally never resolves, so the example only shows the translated default loading experience. The loaded modal must translate and render its own title and content; title does not add a title to the UI supplied by your entry point.

import React, { type Ref } from 'react'; import { defineMessage, IntlProvider, useIntl } from 'react-intl'; import Button from '@atlaskit/button/default/button'; import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider'; import { ModalTrigger } from '@atlassian/entry-points/modal-trigger'; import { loadingModalEntryPoint } from '../fixtures/constellation/loading-modal-entry-point'; import { RelayWrapper } from '../utils/wrapper'; const triggerLabel = defineMessage({ id: 'entry-points.examples.modal.trigger.ai-non-final', defaultMessage: 'Edit issue', description: 'Label on the example button that opens the asynchronously loaded edit issue modal.', }); const modalTitle = defineMessage({ id: 'entry-points.examples.modal.title.ai-non-final', defaultMessage: 'Edit issue', description: 'Visible title on the default loading modal while the edit issue content loads.', }); const loadingMessage = defineMessage({ id: 'entry-points.examples.modal.loading.ai-non-final', defaultMessage: 'Loading issue', description: 'Message displayed while the example modal waits for its edit issue content to load.', }); const frenchMessages = { [triggerLabel.id]: 'Modifier le ticket', [modalTitle.id]: 'Modifier le ticket', [loadingMessage.id]: 'Chargement du ticket', }; function TranslatedModal(): React.JSX.Element { const { formatMessage } = useIntl(); return ( <ModalTrigger entryPoint={loadingModalEntryPoint} title={formatMessage(modalTitle)} loadingText={formatMessage(loadingMessage)} > {({ ref }) => ( // TODO: Remove once `platform_dst-a11y_modal-trigger-haspopup` is rolled out <Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog"> {formatMessage(triggerLabel)} </Button> )} </ModalTrigger> ); } export default function ModalTriggerInternationalization(): React.JSX.Element { return ( <IntlProvider locale="fr" defaultLocale="en" messages={frenchMessages}> <RelayWrapper> <ModalContextProvider> <TranslatedModal /> </ModalContextProvider> </RelayWrapper> </IntlProvider> ); }
Was this page helpful?
We use this feedback to improve our documentation.
© 2026 AtlassianTrademark, (opens new window)Privacy, (opens new window)License