Modal
Modals are versatile dialogs that can display critical information, require user interaction, or show supplementary content without navigating away from the current screen.
Installation
$ cbui-cli install @crossbuildui/modal
cbui-cli
globally and authenticated your account. This will ensure you can access all the necessary features.Import
1import {
2 Modal,
3 ModalContent,
4 ModalHeader,
5 ModalBody,
6 ModalFooter
7} from '@/crossbuildui/modal';
8import { Button } from '@/crossbuildui/button';
9import { Text, View } from 'react-native';
10import React, { useState } from 'react';
Modal Component
Basic Usage
The Modal component is controlled via the isOpen
and onOpenChange
props. It typically wraps ModalContent
, which in turn contains ModalHeader
, ModalBody
, and ModalFooter
.
1function App() {
2 const [isOpen, setIsOpen] = useState(false);
3
4 return (
5 <View>
6 <Button onPress={() => setIsOpen(true)}>Open Modal</Button>
7 <Modal
8 isOpen={isOpen}
9 onOpenChange={setIsOpen}
10 >
11 <ModalContent>
12 <ModalHeader>Modal Title</ModalHeader>
13 <ModalBody>
14 <Text>This is the body of the modal.</Text>
15 <Text>You can put any content here.</Text>
16 </ModalBody>
17 <ModalFooter>
18 <Button variant="ghost" onPress={() => setIsOpen(false)}>Cancel</Button>
19 <Button onPress={() => setIsOpen(false)}>Confirm</Button>
20 </ModalFooter>
21 </ModalContent>
22 </Modal>
23 </View>
24 );
25}
Composition
The Modal system is designed to be composable, allowing you to structure your modal content flexibly:
Modal
: The main wrapper that controls visibility, backdrop, and placement.ModalContent
: The container for the modal's dialog. Styles like size, radius, and shadow are applied here.ModalHeader
: Typically contains the title and an optional close button.ModalBody
: The main content area of the modal. Can be scrollable.ModalFooter
: Usually contains action buttons like "Confirm" or "Cancel".
size
, radius
, shadow
, scrollBehavior
, and close handlers are passed down from Modal
to its children via React Context, simplifying their usage.Sizes
Control the modal's width (and height for full
) using the size
prop. Available sizes: xs
, sm
, md
(default), lg
, xl
, 2xl
, 3xl
, 4xl
, 5xl
, full
.
1import type { ModalSize } from '@crossbuildui/modal';
2function App() {
3 const [size, setSize] = useState<ModalSize>('md');
4 const [isOpen, setIsOpen] = useState(false);
5
6 const openModal = (s) => { setSize(s); setIsOpen(true); };
7
8 return (
9 <View style={{ flexDirection: 'row', gap: 8 }}>
10 <Button onPress={() => openModal('sm')}>Open SM</Button>
11 <Button onPress={() => openModal('lg')}>Open LG</Button>
12 <Button onPress={() => openModal('full')}>Open Full</Button>
13
14 <Modal isOpen={isOpen} onOpenChange={setIsOpen} size={size}>
15 <ModalContent>
16 <ModalHeader>Modal Size: {size.toUpperCase()}</ModalHeader>
17 <ModalBody><Text>Content for {size} modal.</Text></ModalBody>
18 <ModalFooter><Button onPress={() => setIsOpen(false)}>Close</Button></ModalFooter>
19 </ModalContent>
20 </Modal>
21 </View>
22 );
23}
Backdrop
Customize the modal backdrop with the backdrop
prop:
opaque
(default): A semi-transparent dark overlay.blur
: Applies a blur effect to the content behind the modal (requiresexpo-blur
).transparent
: No visible backdrop.
1import type { ModalBackdrop } from '@crossbuildui/modal'; // Import the necessary types
2function App() {
3 const [backdrop, setBackdrop] = useState<ModalBackdrop>('opaque');
4 const [isOpen, setIsOpen] = useState(false);
5
6 const openModal = (b) => { setBackdrop(b); setIsOpen(true); };
7
8 return (
9 <View style={{ flexDirection: 'row', gap: 8 }}>
10 <Button onPress={() => openModal('opaque')}>Opaque</Button>
11 <Button onPress={() => openModal('blur')}>Blur</Button>
12 <Button onPress={() => openModal('transparent')}>Transparent</Button>
13
14 <Modal isOpen={isOpen} onOpenChange={setIsOpen} backdrop={backdrop}>
15 <ModalContent>
16 <ModalHeader>Backdrop: {backdrop}</ModalHeader>
17 <ModalBody><Text>Modal with {backdrop} backdrop.</Text></ModalBody>
18 <ModalFooter><Button onPress={() => setIsOpen(false)}>Close</Button></ModalFooter>
19 </ModalContent>
20 </Modal>
21 </View>
22 );
23}
Scroll Behavior
The scrollBehavior
prop determines how overflow content is handled:
outside
(default): TheModalContent
grows with its content. If it exceeds screen height, the entire backdrop area becomes scrollable.inside
: TheModalBody
becomes scrollable, while the header and footer remain fixed.ModalContent
itself will not exceed screen height.
1function App() {
2 const [isOpenInside, setIsOpenInside] = useState(false);
3 const [isOpenOutside, setIsOpenOutside] = useState(false);
4
5 const longContent = Array.from({ length: 30 }, (_, i) => `Item ${i + 1}`).join('\n');
6
7 return (
8 <View style={{ flexDirection: 'row', gap: 8 }}>
9 <Button onPress={() => setIsOpenInside(true)}>Scroll Inside</Button>
10 <Button onPress={() => setIsOpenOutside(true)}>Scroll Outside</Button>
11
12 <Modal isOpen={isOpenInside} onOpenChange={setIsOpenInside} scrollBehavior="inside">
13 <ModalContent style={{ height: 300 }}>
14 <ModalHeader>Scroll Inside</ModalHeader>
15 <ModalBody><Text>{longContent}</Text></ModalBody>
16 <ModalFooter><Button onPress={() => setIsOpenInside(false)}>Close</Button></ModalFooter>
17 </ModalContent>
18 </Modal>
19
20 <Modal isOpen={isOpenOutside} onOpenChange={setIsOpenOutside} scrollBehavior="outside">
21 <ModalContent>
22 <ModalHeader>Scroll Outside</ModalHeader>
23 <ModalBody><Text>{longContent}</Text></ModalBody>
24 <ModalFooter><Button onPress={() => setIsOpenOutside(false)}>Close</Button></ModalFooter>
25 </ModalContent>
26 </Modal>
27 </View>
28 );
29}
Placement
Position the modal on the screen using the placement
prop. Options include auto
(default, center for non-full, top for full), top
, center
, bottom
, top-center
, bottom-center
.
1import type { ModalPlacement } from '@crossbuildui/modal'; // Import the necessary types
2function App() {
3 const [placement, setPlacement] = useState<ModalPlacement>('center');
4 const [isOpen, setIsOpen] = useState(false);
5
6 const openModal = (p) => { setPlacement(p); setIsOpen(true); };
7
8 return (
9 <View style={{ flexDirection: 'row', gap: 8 }}>
10 <Button onPress={() => openModal('top')}>Top</Button>
11 <Button onPress={() => openModal('center')}>Center</Button>
12 <Button onPress={() => openModal('bottom')}>Bottom</Button>
13
14 <Modal isOpen={isOpen} onOpenChange={setIsOpen} placement={placement}>
15 <ModalContent>
16 <ModalHeader>Placement: {placement}</ModalHeader>
17 <ModalBody><Text>Modal placed at {placement}.</Text></ModalBody>
18 <ModalFooter><Button onPress={() => setIsOpen(false)}>Close</Button></ModalFooter>
19 </ModalContent>
20 </Modal>
21 </View>
22 );
23}
Dismissable Behavior
By default (isDismissable=
), modals can be closed by clicking the backdrop or pressing the hardware back button (Android) / Escape key (Web). Set isDismissable=
to prevent this.
1function App() {
2 const [isOpen, setIsOpen] = useState(false);
3
4 return (
5 <View>
6 <Button onPress={() => setIsOpen(true)}>Open Non-Dismissable Modal</Button>
7 <Modal
8 isOpen={isOpen}
9 onOpenChange={setIsOpen}
10 isDismissable={false}
11 >
12 <ModalContent>
13 <ModalHeader>Non-Dismissable Modal</ModalHeader>
14 <ModalBody>
15 <Text>This modal cannot be dismissed by clicking the backdrop or pressing Escape/Back.</Text>
16 </ModalBody>
17 <ModalFooter>
18 <Button onPress={() => setIsOpen(false)}>Close Manually</Button>
19 </ModalFooter>
20 </ModalContent>
21 </Modal>
22 </View>
23 );
24}
Animations
Apply animations to the ModalContent
using the animationProps
prop on the Modal
component. This prop accepts an object that will be spread onto an Animated.View
from react-native-reanimated
.
animationProps
requires react-native-reanimated
to be installed and configured in your project. Example: { entering: FadeIn, exiting: FadeOut }
1// Ensure you have 'react-native-reanimated' installed
2// import { FadeIn, FadeOut } from 'react-native-reanimated';
3
4function App() {
5 const [isOpen, setIsOpen] = useState(false);
6
7 return (
8 <View>
9 <Button onPress={() => setIsOpen(true)}>Open Animated Modal</Button>
10 <Modal
11 isOpen={isOpen}
12 onOpenChange={setIsOpen}
13 animationProps={{ entering: FadeIn.duration(500), exiting: FadeOut.duration(300) }}
14 >
15 <ModalContent>
16 <ModalHeader>Animated Modal</ModalHeader>
17 <ModalBody><Text>This modal uses animations.</Text></ModalBody>
18 </ModalContent>
19 </Modal>
20 </View>
21 );
22}
Props Overview
Modal Props
PROP | TYPE | DEFAULT | DESCRIPTION |
---|---|---|---|
children | ReactNode | - | Content, usually ModalContent . |
size | ModalSize | 'md' | Modal width/height. See types for options. |
radius | ModalRadius | 'lg' | Border radius of ModalContent . |
shadow | ModalShadow | 'lg' | Shadow of ModalContent . |
backdrop | 'transparent' | 'opaque' | 'blur' | 'opaque' | Backdrop type. |
scrollBehavior | 'inside' | 'outside' | 'outside' | Scroll behavior for overflow content. |
placement | ModalPlacement | 'auto' | Modal position on screen. |
isOpen | boolean | - | Controlled open state. |
defaultOpen | boolean | false | Initial open state (uncontrolled). |
isDismissable | boolean | true | If modal can be closed by backdrop click or escape/back. |
hideCloseButton | boolean | false | Hide default close button in header. |
closeButton | ReactNode | - | Custom close button element. |
animationProps | object | - | Props for Animated.View (reanimated). |
onOpenChange | (isOpen: boolean) => void | - | Callback for open state changes. |
onClose | () => void | - | Callback when modal closes. |
style | StyleProp<ViewStyle> | - | Style for ModalContent wrapper. |
backdropStyle | StyleProp<ViewStyle> | - | Style for the backdrop view. |
ModalContent Props
PROP | TYPE | DESCRIPTION |
---|---|---|
children | ReactNode | Content of the modal dialog. |
style | StyleProp<ViewStyle> | Custom styles for the content container. |
ModalHeader Props
PROP | TYPE | DESCRIPTION |
---|---|---|
children | ReactNode | Content of the header, e.g., title text. |
style | StyleProp<ViewStyle> | Custom styles for the header container. |
ModalBody Props
PROP | TYPE | DESCRIPTION |
---|---|---|
children | ReactNode | Main content of the modal. |
style | StyleProp<ViewStyle> | Custom styles for the body container. |
ModalFooter Props
PROP | TYPE | DESCRIPTION |
---|---|---|
children | ReactNode | Content for the footer, e.g., action buttons. |
style | StyleProp<ViewStyle> | Custom styles for the footer container. |
Styling
The Modal components can be styled using the style
prop available on Modal
(which applies to ModalContent
), ModalContent
, ModalHeader
, ModalBody
, and ModalFooter
.
The Modal
component also accepts a backdropStyle
prop to customize the backdrop view.
The radius
and shadow
props on the main Modal
component control the appearance of the ModalContent
.
1
2function App() {
3 const [isOpen, setIsOpen] = useState(false);
4 return (
5 <View>
6 <Button onPress={() => setIsOpen(true)}>Open Styled Modal</Button>
7 <Modal
8 isOpen={isOpen}
9 onOpenChange={setIsOpen}
10 backdropStyle={{ backgroundColor: 'rgba(0, 100, 0, 0.3)' }}
11 style={{ borderColor: 'green', borderWidth: 2 }} // Applies to ModalContent
12 >
13 <ModalContent>
14 <ModalHeader style={{ backgroundColor: '#e0ffe0' }}>
15 Styled Header
16 </ModalHeader>
17 <ModalBody style={{ paddingVertical: 20 }}>
18 <Text>This modal has custom styles.</Text>
19 </ModalBody>
20 <ModalFooter style={{ justifyContent: 'center' }}>
21 <Button onPress={() => setIsOpen(false)}>OK</Button>
22 </ModalFooter>
23 </ModalContent>
24 </Modal>
25 </View>
26 );
27}
Accessibility
The Modal component aims to follow accessibility best practices:
- The underlying React Native
Modal
handles focus trapping and screen reader announcements to some extent. - The
onRequestClose
prop on the native modal is wired to thehandleClose
function, which aids in handling the Android back button for dismissal. - The default close button in
ModalHeader
includes anaccessibilityLabel
. Custom close buttons should also provide appropriate accessibility labels. - Ensure that interactive elements within the modal are accessible and that the modal's purpose is clear to screen reader users, often through a descriptive
ModalHeader
.