# Modal **📖 Live documentation:** https://cds.coinbase.com/components/overlay/Modal/ A component that displays content in a window that requires user interaction. ## Import ```tsx import { Modal } from '@coinbase/cds-web/overlays/modal/Modal' ``` ## Examples ### Basic example ```jsx live function Example() { const [visible, setVisible] = useState(false); return ( <> setVisible(false)} visible={visible}> setVisible(false)} testID="Basic Modal Test ID" title="Basic Modal" /> Body contents go here setVisible(false)}>Save} secondaryAction={ } /> ); } ``` ### Portal Modal :::warning This approach is deprecated Use the `visible` and `onRequestClose` props as outlined above ::: ```jsx live function Example() { const { openModal, closeModal } = useModal(); const handlePress = useCallback( () => openModal( Body contents go here Save} secondaryAction={ } /> , ), [openModal, closeModal], ); return ; } ``` ### Chained Modals :::tip Accessibility tip For chained modals, set `restoreFocusOnUnmount={false}` on each one and return focus to the opener when exiting the chain (e.g., `triggerRef.current?.focus()`) to keep tab order predictable. ::: ```jsx live function ChainedModalsExample() { const triggerRef = useRef(null); const [isFirstModalOpen, setIsFirstModalOpen] = useState(false); const [isSecondModalOpen, setIsSecondModalOpen] = useState(false); const closeFirstModal = () => { setIsFirstModalOpen(false); triggerRef.current?.focus(); }; const openSecondModal = () => { setIsFirstModalOpen(false); setIsSecondModalOpen(true); }; const closeSecondModal = () => { setIsSecondModalOpen(false); triggerRef.current?.focus(); }; const goBackToFirstModal = () => { setIsSecondModalOpen(false); setIsFirstModalOpen(true); }; return ( <> First modal content Next} secondaryAction={ } /> Second modal content Close} secondaryAction={ } /> ); } ``` ### Scrollable Modal Content If the Modal has content which is expected to overflow and doesn't have focusable elements, set the following props to ensure the scrollable content can be navigated using keyboard arrows: - `focusTabIndexElements`: `true` - `disableArrowKeyNavigation`: `true` As well, assign a `tabIndex` greater than or equal to `0` to the ModalBody so that the overflow can be reached via keyboard. ```jsx live function Example() { const [visible, setVisible] = useState(false); return ( <> setVisible(false)} visible={visible} > setVisible(false)} testID="Basic Modal Test ID" title="Basic Modal" /> This tray has content which will overflow. To enable keyboard scrolling, certain props have to be set. Otherwise, the content won't be viewable to users who navigate using a keyboard. It's important to account for this to ensure an accessible experience. Here's some text that is in the overflow and needs to be scrolled to. Here's some more text to help more easily showcase scrolling. setVisible(false)}>Save} secondaryAction={ } /> ); } ``` ## Props | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `onRequestClose` | `() => void` | Yes | `-` | Callback function fired when modal is closed. | | `visible` | `boolean` | Yes | `false false` | Controls visibility of the Modal | | `accessibilityLabelledBy` | `string` | No | `-` | On web, maps to aria-labelledby and lists the id(s) of the element(s) that label the element on which the attribute is set. On mobile (Android only), a reference to another element nativeID used to build complex forms. | | `alignContent` | `ResponsiveProp
` | No | `-` | - | | `alignItems` | `ResponsiveProp
` | No | `-` | - | | `alignSelf` | `ResponsiveProp
` | No | `-` | - | | `as` | `div` | No | `-` | The underlying element or component the polymorphic component will render. Changing as also changes the inherited native props (e.g. href for as=a) and the expected ref type. | | `aspectRatio` | `ResponsiveProp` | No | `-` | - | | `background` | `ResponsiveProp` | No | `-` | - | | `borderBottomLeftRadius` | `ResponsiveProp` | No | `-` | - | | `borderBottomRightRadius` | `ResponsiveProp` | No | `-` | - | | `borderBottomWidth` | `ResponsiveProp` | No | `-` | - | | `borderColor` | `ResponsiveProp` | No | `-` | - | | `borderEndWidth` | `ResponsiveProp` | No | `-` | - | | `borderRadius` | `ResponsiveProp` | No | `-` | - | | `borderStartWidth` | `ResponsiveProp` | No | `-` | - | | `borderTopLeftRadius` | `ResponsiveProp` | No | `-` | - | | `borderTopRightRadius` | `ResponsiveProp` | No | `-` | - | | `borderTopWidth` | `ResponsiveProp` | No | `-` | - | | `borderWidth` | `ResponsiveProp` | No | `-` | - | | `bordered` | `boolean` | No | `-` | Add a border around all sides of the box. | | `borderedBottom` | `boolean` | No | `-` | Add a border to the bottom side of the box. | | `borderedEnd` | `boolean` | No | `-` | Add a border to the trailing side of the box. | | `borderedHorizontal` | `boolean` | No | `-` | Add a border to the leading and trailing sides of the box. | | `borderedStart` | `boolean` | No | `-` | Add a border to the leading side of the box. | | `borderedTop` | `boolean` | No | `-` | Add a border to the top side of the box. | | `borderedVertical` | `boolean` | No | `-` | Add a border to the top and bottom sides of the box. | | `bottom` | `ResponsiveProp>` | No | `-` | - | | `color` | `ResponsiveProp` | No | `-` | - | | `columnGap` | `ResponsiveProp` | No | `-` | - | | `dangerouslyDisableResponsiveness` | `boolean` | No | `false` | Disable responsiveness so it maintains the same UI across different viewports. | | `dangerouslySetBackground` | `string` | No | `-` | - | | `dangerouslySetPosition` | `Position` | No | `-` | Set the position for the modal dialogue | | `disableArrowKeyNavigation` | `boolean` | No | `-` | If true, the focus trap will not allow arrow key navigation. | | `disableFocusTrap` | `boolean` | No | `-` | Disables the focus trap to allow normal keyboard navigation. | | `disableOverlayPress` | `boolean` | No | `false` | Disable overlay click that closes the Modal | | `disablePortal` | `boolean` | No | `-` | Disable React portal integration | | `display` | `ResponsiveProp` | No | `-` | - | | `elevation` | `ResponsiveProp` | No | `-` | - | | `flexBasis` | `ResponsiveProp>` | No | `-` | - | | `flexDirection` | `ResponsiveProp` | No | `-` | - | | `flexGrow` | `ResponsiveProp` | No | `-` | - | | `flexShrink` | `ResponsiveProp` | No | `-` | - | | `flexWrap` | `ResponsiveProp` | No | `-` | - | | `focusTabIndexElements` | `boolean` | No | `-` | If true, the focus trap will include all elements with tabIndex values in the list of focusable elements. | | `font` | `ResponsiveProp` | No | `-` | - | | `fontFamily` | `ResponsiveProp` | No | `-` | - | | `fontSize` | `ResponsiveProp` | No | `-` | - | | `fontWeight` | `ResponsiveProp` | No | `-` | - | | `gap` | `ResponsiveProp` | No | `-` | - | | `grid` | `ResponsiveProp` | No | `-` | - | | `gridArea` | `ResponsiveProp` | No | `-` | - | | `gridAutoColumns` | `ResponsiveProp>` | No | `-` | - | | `gridAutoFlow` | `ResponsiveProp` | No | `-` | - | | `gridAutoRows` | `ResponsiveProp>` | No | `-` | - | | `gridColumn` | `ResponsiveProp` | No | `-` | - | | `gridColumnEnd` | `ResponsiveProp` | No | `-` | - | | `gridColumnStart` | `ResponsiveProp` | No | `-` | - | | `gridRow` | `ResponsiveProp` | No | `-` | - | | `gridRowEnd` | `ResponsiveProp` | No | `-` | - | | `gridRowStart` | `ResponsiveProp` | No | `-` | - | | `gridTemplate` | `ResponsiveProp` | No | `-` | - | | `gridTemplateAreas` | `ResponsiveProp` | No | `-` | - | | `gridTemplateColumns` | `ResponsiveProp>` | No | `-` | - | | `gridTemplateRows` | `ResponsiveProp>` | No | `-` | - | | `height` | `ResponsiveProp>` | No | `-` | - | | `hideCloseButton` | `boolean` | No | `false` | Hide the close icon on the top right | | `hideDividers` | `boolean` | No | `false` | Hide top and bottom dividers inside Modal body | | `hideOverlay` | `boolean` | No | `false` | Configure if the overlay should be visible/hidden | | `justifyContent` | `ResponsiveProp` | No | `-` | - | | `key` | `Key \| null` | No | `-` | - | | `left` | `ResponsiveProp>` | No | `-` | - | | `lineHeight` | `ResponsiveProp` | No | `-` | - | | `margin` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginBottom` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginEnd` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginStart` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginTop` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginX` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginY` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `maxHeight` | `ResponsiveProp>` | No | `-` | - | | `maxWidth` | `ResponsiveProp>` | No | `-` | - | | `minHeight` | `ResponsiveProp>` | No | `-` | - | | `minWidth` | `ResponsiveProp>` | No | `-` | - | | `onChange` | `FormEventHandler` | No | `-` | - | | `onDidClose` | `((() => void) & (() => void))` | No | `-` | Callback fired after the component is closed. | | `opacity` | `ResponsiveProp` | No | `-` | - | | `overflow` | `ResponsiveProp` | No | `-` | - | | `padding` | `ResponsiveProp` | No | `-` | - | | `paddingBottom` | `ResponsiveProp` | No | `-` | - | | `paddingEnd` | `ResponsiveProp` | No | `-` | - | | `paddingStart` | `ResponsiveProp` | No | `-` | - | | `paddingTop` | `ResponsiveProp` | No | `-` | - | | `paddingX` | `ResponsiveProp` | No | `-` | - | | `paddingY` | `ResponsiveProp` | No | `-` | - | | `pin` | `PinningDirection` | No | `-` | Direction in which to absolutely pin the box. | | `position` | `ResponsiveProp` | No | `-` | - | | `ref` | `((instance: ModalRefBaseProps \| null) => void) \| RefObject \| null` | No | `-` | - | | `restoreFocusOnUnmount` | `boolean` | No | `true` | If true, the focus trap will restore focus to the previously focused element when it unmounts. WARNING: If you disable this, you need to ensure that focus is restored properly so it doesnt end up on the body | | `right` | `ResponsiveProp>` | No | `-` | - | | `role` | `dialog \| alertdialog` | No | `-` | WAI-ARIA Roles | | `rowGap` | `ResponsiveProp` | No | `-` | - | | `shouldCloseOnEscPress` | `boolean` | No | `true` | If pressing the esc key should close the modal | | `style` | `CSSProperties` | No | `-` | - | | `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID | | `textAlign` | `ResponsiveProp
` | No | `-` | - | | `textDecoration` | `ResponsiveProp` | No | `-` | - | | `textTransform` | `ResponsiveProp` | No | `-` | - | | `top` | `ResponsiveProp>` | No | `-` | - | | `transform` | `ResponsiveProp` | No | `-` | - | | `userSelect` | `ResponsiveProp` | No | `-` | - | | `visibility` | `ResponsiveProp` | No | `-` | - | | `width` | `ResponsiveProp>` | No | `-` | - |