Modal
Modal dialogs are used to inform users about a task and can contain critical information, require decisions, or involve multiple tasks. Modal dialogs disable app functionality when they appear, and remain on screen until confirmed, dismissed, or a required action has been taken.
Import
Modal
: AProvider
component that provides the context to the components it wraps.ModalOverlay
: The overlay of the modal.ModalContent
: The content of the modal.ModalHeader
: The header of the modal.ModalBody
: The body of the modal.ModalFooter
: The footer of the modal.
import {Modal,ModalOverlay,ModalContent,ModalHeader,ModalBody,ModalFooter,} from '@trendmicro/react-styled-ui';
Modal components
Below is a static modal example, including modal header (optional), modal body (required when padding is necessary), and modal footer (optional).
You can also compose your modal components when customization is needed.
EDITABLE EXAMPLEconst SkeletonContent = (props) => (<Flex {...props}><Flex flex="none" mr="4x" align="center"><Skeleton variant="circle" width="10x" height="10x" /></Flex><Box flex="auto"><Skeleton /><Skeleton /><Skeleton /></Box></Flex>);function Example() {return (<Stack direction="column" spacing="4x"><ModalContent ml={0} width={480}><ModalHeader>Modal Title</ModalHeader><ModalBody><Alert variant="outline" severity="warning" mb="4x"><Text>This is a warning alert</Text></Alert><Text mb="4x">Modal body text goes here.</Text><SkeletonContent /></ModalBody><ModalFooter><Button variant="primary" minWidth="20x">Save Changes</Button><Space width="2x" /><Button minWidth="20x">Cancel</Button></ModalFooter></ModalContent><ModalContent ml={0} width={480}><ModalBody><Text mb="4x">Modal body text goes here.</Text><SkeletonContent /></ModalBody><ModalFooter><Button variant="primary" minWidth="20x">Save Changes</Button><Space width="2x" /><Button minWidth="20x">Cancel</Button></ModalFooter></ModalContent><ModalContent ml={0} width={480}><ModalBody><Text mb="4x">Modal body text goes here.</Text><SkeletonContent /></ModalBody></ModalContent><ModalContent ml={0} width={480}><Box px="4x" py="4x">You can create a custom modal with any sort of content.</Box></ModalContent></Stack>);}render(<Example />);
Usage
Click the button below to toggle a modal. The modal will show up in the center of the screen.
EDITABLE EXAMPLEconst SelectableButton = ({ selected, ...props }) => {const [colorMode] = useColorMode();const { colors } = useTheme();const focusColor = colors['blue:60'];let _selectedColor = {dark: 'blue:60',light: 'blue:60',}[colorMode];_selectedColor = colors[_selectedColor];const getSelectedProps = {bg: _selectedColor,borderColor: _selectedColor,color: 'white:emphasis',cursor: 'default',pointerEvents: 'none',zIndex: 1,css: {'&::before': {backgroundColor: _selectedColor,},'&:focus': {':not(:active)': {borderColor: focusColor,boxShadow: `inset 0 0 0 1px ${focusColor}`,},'&::before': {backgroundColor: focusColor,},}},_hover: {bg: _selectedColor,},_active: {bg: _selectedColor,},};return (<Button{...(selected && getSelectedProps)}{...props}/>);};const useSelection = (defaultValue) => {const [value, setValue] = React.useState(defaultValue);const changeBy = (value) => () => setValue(value);return [value, changeBy];};const useToggle = (defaultValue) => {const [value, setValue] = React.useState(defaultValue);const toggle = () => setValue(value => !value);return [value, toggle];};const FormGroup = (props) => (<Box mb="4x" {...props} />);const modalWithBodyScrollLockCode = `<Modal><Globalstyles={css\`body {overflow: hidden;}\`}/><ModalOverlay /><ModalContent /></Modal>`.trim();function Example() {const initialFocusRef = React.useRef();const [colorMode] = useColorMode();const [colorStyle] = useColorStyle({ colorMode });const iconColor = {dark: 'white:tertiary',light: 'black:tertiary',}[colorMode];const { isOpen, onOpen, onClose } = useDisclosure();const [ensureFocus, toggleEnsureFocus] = useToggle(true);const [autoFocus, toggleAutoFocus] = useToggle(true);const [closeOnEsc, toggleCloseOnEsc] = useToggle(true);const [closeOnOutsideClick, toggleCloseOnOutsideClick] = useToggle(true);const [isClosable, toggleIsClosable] = useToggle(true);const [isOverlayVisible, toggleIsOverlayVisible] = useToggle(true);const [isHeaderVisible, toggleIsHeaderVisible] = useToggle(true);const [isBodyVisible, toggleIsBodyVisible] = useToggle(true);const [isFooterVisible, toggleIsFooterVisible] = useToggle(true);const [isAlertVisible, toggleIsAlertVisible] = useToggle(true);const [size, changeSizeBy] = useSelection('sm');const [scrollBehavior, changeScrollBehaviorBy] = useSelection('inside');const [height, changeHeightBy] = useSelection('default');const [enableBodyScrollLock, toggleBodyScrollLock] = useToggle(true);const [enableModalOverflowBehavior, toggleModalOverflowBehavior] = useToggle(true);return (<><Box><Button onClick={onOpen}>Launch modal</Button></Box><Divider my="4x" /><Box mb="4x"><Text fontSize="lg" lineHeight="lg">Modal props</Text></Box><FormGroup><Box mb="2x"><TextLabel>size</TextLabel></Box><ButtonGroupvariant="secondary"css={{'> *:not(:first-of-type)': {marginLeft: -1}}}>{['auto', 'xs', 'sm', 'md', 'lg', 'xl', 'full'].map(value => (<SelectableButtonkey={value}selected={value === size}onClick={changeSizeBy(value)}minWidth="15x">{value}</SelectableButton>))}</ButtonGroup></FormGroup><FormGroup><Box mb="2x"><TextLabel>scrollBehavior</TextLabel></Box><ButtonGroupvariant="secondary"css={{'> *:not(:first-of-type)': {marginLeft: -1}}}>{['inside', 'outside'].map(value => (<SelectableButtonkey={value}selected={value === scrollBehavior}onClick={changeScrollBehaviorBy(value)}minWidth="15x">{value}</SelectableButton>))}</ButtonGroup></FormGroup><FormGroup><Box mb="2x"><TextLabel>height</TextLabel></Box><ButtonGroupvariant="secondary"css={{'> *:not(:first-of-type)': {marginLeft: -1}}}>{['default', '100%'].map(value => (<SelectableButtonkey={value}selected={value === height}onClick={changeHeightBy(value)}minWidth="15x">{value}</SelectableButton>))}</ButtonGroup></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkboxchecked={ensureFocus}onChange={toggleEnsureFocus}/><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">ensureFocus</Text></TextLabel></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkboxchecked={autoFocus}disabled={!ensureFocus}onChange={toggleAutoFocus}/><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">autoFocus</Text></TextLabel></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkboxchecked={closeOnEsc}disabled={!isClosable && !closeOnOutsideClick}onChange={toggleCloseOnEsc}/><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">closeOnEsc</Text></TextLabel></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkboxchecked={closeOnOutsideClick}disabled={!isClosable && !closeOnEsc}onChange={toggleCloseOnOutsideClick}/><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">closeOnOutsideClick</Text></TextLabel></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkboxchecked={isClosable}disabled={!closeOnEsc && !closeOnOutsideClick}onChange={toggleIsClosable}/><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">isClosable</Text></TextLabel></FormGroup><Divider my="4x" /><Box mb="4x"><Text fontSize="lg" lineHeight="lg">Modal composition</Text></Box><FormGroup><TextLabel display="flex" alignItems="center"><Checkbox checked={isOverlayVisible} onChange={toggleIsOverlayVisible} /><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">ModalOverlay</Text></TextLabel></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkbox checked={isHeaderVisible} onChange={toggleIsHeaderVisible} /><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">ModalHeader</Text></TextLabel></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkbox checked={isBodyVisible} onChange={toggleIsBodyVisible} /><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">ModalBody</Text></TextLabel></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkbox checked={isFooterVisible} onChange={toggleIsFooterVisible} /><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">ModalFooter</Text></TextLabel></FormGroup><FormGroup><TextLabel display="flex" alignItems="center"><Checkbox checked={isAlertVisible} onChange={toggleIsAlertVisible} /><Space width="2x" /><Text fontFamily="mono" whiteSpace="nowrap">Alert</Text></TextLabel></FormGroup><Divider my="4x" /><Box mb="4x"><Text fontSize="lg" lineHeight="lg">Extra modal setup</Text></Box><FormGroup><TextLabel display="flex" alignItems="center" mb="3x"><Checkboxchecked={enableModalOverflowBehavior}onChange={() => toggleModalOverflowBehavior()}/><Space width="2x" /><Text>Enable modal overflow behavior to make scrollbar visible</Text></TextLabel><Box ml="6x"><Text mb="2x">Enable this option will increase the content height to overflow the viewport and make the content scrollable.</Text><Text mb="2x">You can use the <Code>scrollBehavior</Code> prop to control how scrolling should behave:</Text><Box as="ul" my="2x"><li>If set to <Code>inside</Code>, only the <Code>ModalBody</Code> will scroll.</li><li>If set to <Code>outside</Code>, the entire <Code>ModalContent</Code> will scroll within the viewport.</li></Box></Box></FormGroup><FormGroup><TextLabel display="flex" alignItems="center" mb="3x"><Checkboxchecked={enableBodyScrollLock}onChange={() => toggleBodyScrollLock()}/><Space width="2x" /><Text>Enable body scroll locking</Text></TextLabel><Box ml="6x"><Box mb="4x"><Text mb="2x">When setting <Code>scrollBehavior="outside"</Code> to enable outside scrolling, you should also use <strong>Body Scroll Locking</strong> to prevent the user from scrolling the page while the modal is open.</Text><Text mb="2x"><strong>Body Scroll Locking</strong> is currently not available with default setup, you can follow the instructions below to append global styles to the body to prevent scrolling.</Text></Box><BoxbackgroundColor={colorStyle.background.secondary}fontFamily="mono"px="3x"py="2x"mb="4x"whiteSpace="pre">{`import { Global } from '@emotion/react'`}</Box><BoxbackgroundColor={colorStyle.background.secondary}fontFamily="mono"px="3x"py="2x"whiteSpace="pre">{modalWithBodyScrollLockCode}</Box></Box></FormGroup><Scalein={isOpen}duration={150}>{styles => (<ModalensureFocus={ensureFocus}autoFocus={autoFocus}closeOnEsc={closeOnEsc}closeOnOutsideClick={closeOnOutsideClick}initialFocusRef={initialFocusRef}isClosable={isClosable}isOpen={true} // Set to `true` if a transition is activeonClose={onClose}size={size}scrollBehavior={scrollBehavior}>{enableBodyScrollLock && (<Globalstyles={css`body {overflow: hidden;}`}/>)}{isOverlayVisible && (<ModalOverlay opacity={styles.opacity} />)}<ModalContent{...styles}height={height !== 'default' ? height : undefined}>{isHeaderVisible && (<ModalHeader>Modal Title</ModalHeader>)}{isBodyVisible && (<ModalBody>{isAlertVisible && (<Alert variant="outline" severity="info" mb="4x" isClosable onClose={() => toggleIsAlertVisible()}><Text>This is an info alert</Text></Alert>)}<Text mb="4x">You can put any elements you want here.</Text><GridtemplateColumns="auto 1fr"rowGap="2x"columnGap="3x"alignItems="center"mb="4x"><Icon icon="user" color={iconColor} /><Input ref={initialFocusRef} placeholder="User name" /><Icon icon="email" color={iconColor} /><Input placeholder="Email address" /></Grid>{enableModalOverflowBehavior && (<BoxbackgroundColor={colorStyle.background.tertiary}mb="4x"minHeight={1000}px="3x"py="2x"><Text>This is a very long content that will overflow the modal</Text></Box>)}<RadioGroup defaultValue="1"><Stack direction="row" spacing="3x"><Radio value="1">Radio 1</Radio><Radio value="2">Radio 2</Radio></Stack></RadioGroup></ModalBody>)}{isFooterVisible && (<ModalFooter><Button variant="primary">OK</Button><Space width="2x" /><Button onClick={onClose}>Cancel</Button></ModalFooter>)}</ModalContent></Modal>)}</Scale></>);}render(<Example />);
Sizes
Use the size
prop to change the size of the Modal
. You can set the value to auto
, xs
, sm
, md
, lg
, xl
, or full
.
EDITABLE EXAMPLEfunction Example() {const { isOpen, onOpen, onClose } = useDisclosure();const [size, setSize] = React.useState('auto');const handleClickBy = nextSize => (e) => {setSize(nextSize);onOpen();};return (<><Stack direction="row" spacing="3x" flexWrap="wrap" mt={-12}><Button onClick={handleClickBy('auto')} mt="3x">Auto width</Button><Button onClick={handleClickBy('xs')} mt="3x">Extra small width</Button><Button onClick={handleClickBy('sm')} mt="3x">Small width</Button><Button onClick={handleClickBy('md')} mt="3x">Medium width</Button><Button onClick={handleClickBy('lg')} mt="3x">Large width</Button><Button onClick={handleClickBy('xl')} mt="3x">Extra large width</Button><Button onClick={handleClickBy('full')} mt="3x">Full width</Button></Stack><ModalensureFocusautoFocuscloseOnEsccloseOnOutsideClickisClosableisOpen={isOpen}onClose={onClose}size={size}><ModalOverlay /><ModalContent><ModalHeader>Modal Title</ModalHeader><ModalBody><Lorem count={2} /></ModalBody><ModalFooter><Button onClick={onClose}>Close</Button></ModalFooter></ModalContent></Modal></>);}
Nested modals
EDITABLE EXAMPLEfunction Example() {const { isOpen, onOpen, onClose } = useDisclosure();const { isOpen: isNestedOpen, onOpen: onNestedOpen, onClose: onNestedClose } = useDisclosure();return (<><Button onClick={onOpen}>Launch modal</Button><ModalcloseOnEsccloseOnOutsideClickisClosableisOpen={isOpen}onClose={onClose}size="auto"><ModalOverlay /><ModalContent><ModalHeader>Modal Title</ModalHeader><ModalBody><Lorem count={2} /></ModalBody><ModalFooter justifyContent="space-between"><Buttondisabled={isNestedOpen}variant="primary"onClick={onNestedOpen}>Launch nested modal</Button><Box><Button minWidth="20x" onClick={onClose}>Close</Button></Box></ModalFooter></ModalContent></Modal><ModalcloseOnEsccloseOnOutsideClickisClosableisOpen={isNestedOpen}onClose={onNestedClose}><ModalOverlay /><ModalContent><ModalHeader>Nested modal title</ModalHeader><ModalBody><Lorem count={1} /></ModalBody><ModalFooter><Button variant="primary" onClick={onNestedClose} minWidth="20x">Yes</Button><Space width="2x" /><Button onClick={onNestedClose} minWidth="20x">No</Button></ModalFooter></ModalContent></Modal></>);}
Transitions
The modal doesn't come with transitions by default. You can use the Transition
components to provide simple transitions.
When using transitions, you have to set isOpen
to true
, or the transition will not take effect when closing the modal.
Adding scale transition
import { Scale } from '@trendmicro/react-styled-ui';
EDITABLE EXAMPLEfunction Example() {const { isOpen, onOpen, onClose } = useDisclosure();return (<><Button onClick={onOpen}>Launch modal</Button><Scale in={isOpen}>{styles => (<ModalensureFocusautoFocuscloseOnEsccloseOnOutsideClickisClosableisOpen={true} // Set to `true` if a transition is activeonClose={onClose}><ModalOverlay opacity={styles.opacity} /><ModalContent {...styles}><ModalHeader>Modal Title</ModalHeader><ModalBody><Lorem count={2} /></ModalBody><ModalFooter><Button onClick={onClose}>Close</Button></ModalFooter></ModalContent></Modal>)}</Scale></>);}
Adding slide-in transition
import { SlideIn } from '@trendmicro/react-styled-ui';
EDITABLE EXAMPLEfunction Example() {const { isOpen, onOpen, onClose } = useDisclosure();return (<><Button onClick={onOpen}>Launch modal</Button><SlideIn in={isOpen}>{styles => (<ModalensureFocusautoFocuscloseOnEsccloseOnOutsideClickisClosableisOpen={true} // Set to `true` if a transition is activeonClose={onClose}><ModalOverlay opacity={styles.opacity} /><ModalContent {...styles}><ModalHeader>Modal Title</ModalHeader><ModalBody><Lorem count={2} /></ModalBody><ModalFooter><Button onClick={onClose}>Close</Button></ModalFooter></ModalContent></Modal>)}</SlideIn></>);}
Props
Modal
Name | Type | Default | Description |
---|---|---|---|
ensureFocus | boolean | false | If true , it will always bring the focus back to the Modal descendants, which does not allow the focus to escape while open. |
autoFocus | boolean | false | If true and ensureFocus is true and initialFocusRef is not set, it will automatically set focus on the first focusable element. |
finalFocusRef | React.ref | The ref of element to receive focus when the modal closes. | |
initialFocusRef | React.ref | The ref of the element to receive focus when the modal opens. | |
isClosable | boolean | false | If true , a close button will appear on the right side. |
isOpen | boolean | false | If true , the modal is shown. |
closeOnEsc | boolean | false | If true , close the modal when the esc key is pressed. |
closeOnOutsideClick | boolean | false | If true , close the modal when click outside of the modal. |
onClose | function | Callback fired when the modal closes. | |
size | string | 'auto' | The size of the modal. One of: 'auto' , 'xs' , 'sm' , 'md' , 'lg' , 'xl' , 'full' |
scrollBehavior | string | 'inside' | Control the scroll behavior of the modal if the content overflows. One of: 'inside', 'outside' |
ModalOverlay
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode |
ModalContent
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode |
ModalHeader
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode |
ModalBody
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode |
ModalFooter
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode |