Popover
Popover is a non-modal dialog that floats around a trigger.
Popover
is built on top of the Popper.js library.
Import#
import { Popover } from '@chakra-ui/react'
Basic Usage#
It is used to display contextual information to the user, and should be paired with a clickable trigger element.
When Popover opens, focus is sent to Popover.Content
. When it closes, focus is
returned to the trigger.
Tip: When using this component, ensure the children passed to
Popover.Trigger
is focusable and has a forwardedref
.
<Popover.Root><Popover.Trigger asChild><Button>Trigger</Button></Popover.Trigger><Popover.Positioner><Popover.Content><Popover.Arrow /><Popover.CloseTrigger /><Popover.Header>Confirmation!</Popover.Header><Popover.Body>Are you sure you want to have that milkshake?</Popover.Body></Popover.Content></Popover.Positioner></Popover.Root>
Rendering the Popover in a Portal#
By default, the Popover doesn't render in a Portal. To make them display in a
portal, wrap the Popover.Content
in a Portal
You might need to Inspect Element to see this in action. Notice that
Popover.Content
is rendered as a child of<body>
<Popover.Root><Popover.Trigger asChild><Button>Trigger</Button></Popover.Trigger><Portal><Popover.Positioner><Popover.Content><Popover.Arrow /><Popover.Header>Header</Popover.Header><Popover.CloseTrigger /><Popover.Body><Button colorPalette='blue'>Button</Button></Popover.Body><Popover.Footer>This is the footer</Popover.Footer></Popover.Content></Popover.Positioner></Portal></Popover.Root>
Setting the initial focused element#
By default, focus is to set to Popover.Content
when it opens. Pass the
initialFocusRef
prop to send focus to a specific element instead.
function Demo() {const initialFocusRef = React.useRef()return (<PopoverinitialFocusRef={initialFocusRef}placement='bottom'closeOnBlur={false}><Popover.Trigger asChild><Button>Trigger</Button></Popover.Trigger><Popover.Positioner><Popover.Content color='white' bg='blue.800' borderColor='blue.800'><Popover.Header pt={4} fontWeight='bold' border='0'>Manage Your Channels</Popover.Header><Popover.Arrow bg='blue.800' /><Popover.CloseTrigger /><Popover.Body>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed doeiusmod tempor incididunt ut labore et dolore.</Popover.Body><Popover.Footerborder='0'display='flex'alignItems='center'justifyContent='space-between'pb={4}><Box fontSize='sm'>Step 2 of 4</Box><ButtonGroup size='sm'><Button colorPalette='green'>Setup Email</Button><Button colorPalette='blue' ref={initialFocusRef}>Next</Button></ButtonGroup></Popover.Footer></Popover.Content></Popover.Positioner></Popover>)}
Trapping Focus within Popover#
If the popover contains a form, you might need to trap focus within the popover and close it when the user fills the form and hits "save".
You can leverage
react-focus-lock to trap focus
within the Popover.Content
.
// import FocusLock from "react-focus-lock"// 1. Create a text input componentconst TextInput = React.forwardRef((props, ref) => {return (<Field.Root><Field.Label htmlFor={props.id}>{props.label}</Field.Label><Input ref={ref} id={props.id} {...props} /></Field.Root>)})// 2. Create the formconst Form = ({ firstFieldRef, onCancel }) => {return (<Stack gap={4}><TextInputlabel='First name'id='first-name'ref={firstFieldRef}defaultValue='John'/><TextInput label='Last name' id='last-name' defaultValue='Smith' /><Group justifyContent='flex-end'><Button variant='outline' onClick={onCancel}>Cancel</Button><Button disabled colorPalette='teal'>Save</Button></Group></Stack>)}// 3. Create the Popover// Ensure you set `closeOnBlur` prop to false so it doesn't close on outside clickconst PopoverForm = () => {const { onOpen, onClose, open } = useDisclosure()const firstFieldRef = React.useRef(null)return (<><Box display='inline-block' mr={3}>John Smith</Box><Popover.Rootopen={open}initialFocusRef={firstFieldRef}onOpen={onOpen}onClose={onClose}placement='right'closeOnBlur={false}><Popover.Trigger asChild><IconButton size='sm'><EditIcon /></IconButton></Popover.Trigger><Popover.Positioner><Popover.Content p={5}><FocusLock returnFocus persistentFocus={false}><Popover.Arrow /><Popover.CloseTrigger /><Form firstFieldRef={firstFieldRef} onCancel={onClose} /></FocusLock></Popover.Content></Popover.Positioner></Popover.Root></>)}render(<PopoverForm />)
Controlling the Popover#
You can control the opening and closing of the popover by passing the open
,
and onClose
props.
Sometimes you might need to set the returnFocusOnClose
prop to false
to
prevent popover from returning focus to Popover.Trigger
's children.
function ControlledUsage() {const { open, onToggle, onClose } = useDisclosure()return (<><Button mr={5} onClick={onToggle}>Trigger</Button><Popover.RootreturnFocusOnClose={false}open={open}onClose={onClose}placement='right'closeOnBlur={false}><Popover.Trigger asChild><Button colorPalette='pink'>Popover Target</Button></Popover.Trigger><Popover.Positioner><Popover.Content><Popover.Header fontWeight='semibold'>Confirmation</Popover.Header><Popover.Arrow /><Popover.CloseTrigger /><Popover.Body>Are you sure you want to continue with your action?</Popover.Body><Popover.Footer display='flex' justifyContent='flex-end'><ButtonGroup size='sm'><Button variant='outline'>Cancel</Button><Button colorPalette='red'>Apply</Button></ButtonGroup></Popover.Footer></Popover.Content></Popover.Positioner></Popover.Root></>)}
Setting the popover anchor#
You can wrap your component with Popover.Anchor
to prevent trigger any action.
The wrapped component will become a position reference. Actions will only be
triggered by components inside Popover.Trigger
.
function Demo() {const [isEditing, setIsEditing] = useBoolean()const [color, setColor] = React.useState('red')return (<Popover.Rootopen={isEditing}onOpen={setIsEditing.on}onClose={setIsEditing.off}closeOnBlur={false}lazyMountlazyBehavior='keepMounted'><HStack><Popover.Anchor><Inputcolor={color}w='auto'display='inline-flex'disabled={!isEditing}defaultValue='Popover Anchor'/></Popover.Anchor><Popover.Trigger asChild><Button h='40px' colorPalette='pink'>{isEditing ? 'Save' : 'Edit'}</Button></Popover.Trigger></HStack><Popover.Content><Popover.Body>Colors:<RadioGroup.Rootvalue={color}onChange={(newColor) => setColor(newColor)}><RadioGroup.Item value='red'>red</RadioGroup.Item><RadioGroup.Item value='blue'>blue</RadioGroup.Item><RadioGroup.Item value='green'>green</RadioGroup.Item><RadioGroup.Item value='purple'>purple</RadioGroup.Item></RadioGroup.Root></Popover.Body></Popover.Content></Popover.Root>)}
Accessing Internal state#
Chakra provides access to two internal details: open
and onClose
. Use the
render prop pattern to gain access to them.
function Demo() {const ref = React.useRef()return (<Popover closeOnBlur={false} placement='left' initialFocusRef={ref}>{(state) => (<><Popover.Trigger asChild><Button>Click to {state.open ? 'close' : 'open'}</Button></Popover.Trigger><Portal><Popover.Content><Popover.Header>This is the header</Popover.Header><Popover.CloseTrigger /><Popover.Body><Input ref={ref} placeholder='Enter value' /><Box>Hello. Nice to meet you! This is the body of the popover</Box></Popover.Body><Popover.Footer>This is the footer</Popover.Footer></Popover.Content></Portal></>)}</Popover>)}
Customizing the Popover#
Chakra exports all the components you need to customize the look and feel of the popover. You can change the background, arrow size, box shadow and so on.
<Popover.Root><Popover.Trigger asChild><BoxtabIndex='0'role='button'aria-label='Some box'p={5}w='120px'bg='gray.300'children='Click'/></Popover.Trigger><Popover.Content bg='tomato' color='white'><Popover.Header fontWeight='semibold'>Customization</Popover.Header><Popover.Arrow bg='pink.500' /><Popover.CloseTrigger /><Popover.Body>Tadaa!! The arrow color and background color is customized. Check theprops for each component.</Popover.Body></Popover.Content></Popover.Root>
Changing the placement of the Popover#
<Popover placement='top-start'><Popover.Trigger asChild><Button>Click me</Button></Popover.Trigger><Popover.Content><Popover.Header fontWeight='semibold'>Popover placement</Popover.Header><Popover.Arrow /><Popover.CloseTrigger /><Popover.Body>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmodtempor incididunt ut labore et dolore.</Popover.Body></Popover.Content></Popover>
Lazily mounting Popover#
By default, the Popover
component renders children of Popover.Content
to the
DOM, meaning that invisible popover contents are still rendered but are hidden
by styles.
If you want to defer rendering of popover content until that Popover
is
opened, you can use the lazyMount
prop. This is useful if your
Popover.Content
needs to be extra performant, or make network calls on mount
that should only happen when the component is displayed.
<Popover lazyMount><Popover.Trigger asChild><Button>Click me</Button></Popover.Trigger><Popover.Content><Popover.Header fontWeight='semibold'>Popover placement</Popover.Header><Popover.Arrow /><Popover.CloseTrigger /><Popover.Body>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmodtempor incididunt ut labore et dolore.</Popover.Body></Popover.Content></Popover>
Accessibility#
When you see the word "trigger", it is referring to the
children
ofPopover.Trigger
Keyboard and Focus#
- When the popover is opened, focus is moved to the
Popover.Content
. If theinitialFocusRef
is set, then focus moves to the element with thatref
. - When the popover is closed, focus returns to the trigger. If you set
returnFocusOnClose
tofalse
, focus will not return. - If trigger is set to
hover
:- Focusing on or mousing over the trigger will open the popover.
- Blurring or mousing out of the trigger will close the popover. If you move
your mouse into the
Popover.Content
, it'll remain visible.
- If trigger is set to
click
:- Clicking the trigger or using the
Space
orEnter
when focus is on the trigger will open the popover. - Clicking the trigger again will close the popover.
- Clicking the trigger or using the
- Hitting the
Esc
key while the popover is open and focus is within thePopover.Content
, will close the popover. If you setcloseOnEsc
tofalse
, it will not close. - Clicking outside or blurring out of the
Popover.Content
closes the popover. If you setcloseOnBlur
tofalse
, it will not close.
ARIA Attributes#
- If the trigger is set to
click
, thePopover.Content
element has role set todialog
. If the trigger is set tohover
, thePopover.Content
hasrole
set totooltip
. - The
Popover.Content
hasaria-labelledby
set to theid
of thePopover.Header
. - The
Popover.Content
hasaria-describedby
set to theid
of thePopover.Body
. - The
Popover.Content
hasaria-hidden
set totrue
orfalse
depending on the open/closed state of the popover. - The trigger has
aria-haspopup
set totrue
to denote that it triggers a popover. - The trigger has
aria-controls
set to theid
of thePopover.Content
to associate the popover and the trigger. - The trigger has
aria-expanded
set totrue
orfalse
depending on the open/closed state of the popover.
Props#
Popover Props#
Other Props#
PopoverContent
composesBox
and has the ability to smartly position itself. Thanks to popper.js.PopoverArrow
,PopoverHeader
,PopoverFooter
andPopoverBody
composesBox
.PopoverCloseButton
composesBox
component.
The Popover
component is a multipart component. The styling needs to be
applied to each part specifically.
To learn more about styling multipart components, visit the Component Style page.
Anatomy#
- A:
content
- B:
header
- C:
body
- D:
footer
- E:
popper
- F:
arrow
- G:
closeButton
Theming properties#
The Popover
doesn't have any default properties that affect the theming,
however it is possible to create the following properties:
size
variant
colorScheme
Theming utilities#
createMultiStyleConfigHelpers
: a function that returns a set of utilities for creating style configs for a multipart component (definePartsStyle
anddefineMultiStyleConfig
).definePartsStyle
: a function used to create multipart style objects.defineMultiStyleConfig
: a function used to define the style configuration for a multipart component.
import { popoverAnatomy as parts } from '@chakra-ui/anatomy'import { createMultiStyleConfigHelpers } from '@chakra-ui/react'const { definePartsStyle, defineMultiStyleConfig } =createMultiStyleConfigHelpers(parts.keys)
Customizing the default theme#
import { popoverAnatomy as parts } from '@chakra-ui/anatomy'import { createMultiStyleConfigHelpers } from '@chakra-ui/react'const { definePartsStyle, defineMultiStyleConfig } =createMultiStyleConfigHelpers(parts.keys)const baseStyle = definePartsStyle({// define the part you're going to stylebody: {bg: 'gray.800', // change the background of the body to gray.800},content: {padding: 3, // change the padding of the content},})export const popoverTheme = defineMultiStyleConfig({ baseStyle })
After customizing the popover theme, we can import it in our theme file and add
it in the components
property:
import { extendTheme } from '@chakra-ui/react'import { popoverTheme } from './theme/components/popover.ts'export const theme = extendTheme({components: { Popover: popoverTheme },})
This is a crucial step to make sure that any changes that we make to the popover theme are applied.
Adding a custom size#
Let's assume we want to include an extra large popover size. Here's how we can do that:
import { popoverAnatomy as parts } from '@chakra-ui/anatomy'import { createMultiStyleConfigHelpers } from '@chakra-ui/react'const { definePartsStyle, defineMultiStyleConfig } =createMultiStyleConfigHelpers(parts.keys)const sizes = {xl: definePartsStyle({header: defineStyle({padding: 14}),content: defineStyle({fontSize: "2xl",marginLeft: 6})}),}export const popoverTheme = defineMultiStyleConfig({ sizes })// Now we can use the new `xl` size<Popover size="xl">...</Popover>
Every time you're adding anything new to the theme, you'd need to run the CLI command to get proper autocomplete in your IDE. You can learn more about the CLI tool here.
Adding a custom variant#
Let's assume we want to include a custom variant. Here's how we can do that:
import { popoverAnatomy as parts } from '@chakra-ui/anatomy'import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react'const { definePartsStyle, defineMultiStyleConfig } =createMultiStyleConfigHelpers(parts.keys)const custom = definePartsStyle({content: defineStyle({padding: 7bg: "gray.700"}),footer: defineStyle({fontSize: "xl"})})export const popoverTheme = defineMultiStyleConfig({variants: { custom },})// Now we can use the new `custom` variant<Popover variant="custom">...</Popover>
Creating the default properties#
Let's assume we want to create the default property for the Popover
. Here's
how we can do that:
import { popoverAnatomy as parts } from '@chakra-ui/anatomy'import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react'const { defineMultiStyleConfig } =createMultiStyleConfigHelpers(parts.keys)//Let's assume we need each popover to have red contentconst defaultVariant = definePartsStyle({content: {bg: "red.200"}})export const popoverTheme = defineMultiStyleConfig({variant: {default: defaultVariant}defaultProps: {variant: default},})//Now by default, each Popover will have a 'default' variant with red content.
Showcase#
import { Popover, PopoverTrigger, PopoverContent, PopoverHeader, PopoverBody, PopoverArrow, Button, Flex, Box, IconButton, useColorMode, Text, HStack, Avatar, AvatarBadge, VStack, StackDivider, } from "@chakra-ui/react"; import { FaMoon, FaSun } from "react-icons/fa"; const CustomButton = ({ label, notifications, colorMode }) => ( <Box h="40px" cursor="pointer" p={2} _hover={{ borderBottomWidth: "2px", borderBottomColor: colorMode === "light" ? "blue.600" : "blue.200", }} > <HStack> <Box> <Text fontSize={"sm"}>{label}</Text> </Box> {notifications && ( <Box bg={colorMode === "light" ? "blue.50" : "blue.300"} pl={1.5} pr={1.5} borderRadius="full" > <Text fontSize={"xs"}>{notifications}</Text> </Box> )} </HStack> </Box> ); const Notification = ({ name, src, action, time, hasBadge, colorMode, ...rest }) => ( <Box bg={ hasBadge ? colorMode === "light" ? "gray.100" : "gray.600" : "transparent" } {...rest} > <Flex gap={3} p={2}> <Box> <Avatar size="sm" name={name} src={src}> {hasBadge && ( <AvatarBadge placement="top-start" bg={colorMode === "light" ? "blue.600" : "blue.300"} borderWidth="2px" boxSize="12px" /> )} </Avatar> </Box> <VStack> <Box> <Text fontSize={"sm"}> <b>{name}</b> {action} </Text> </Box> <Box alignSelf={"flex-start"} mt="0 !important"> <Text fontSize={"xs"} fontWeight="lighter"> {time} </Text> </Box> </VStack> </Flex> </Box> ); const PopoverExample = ({ name, colorMode, ...rest }) => ( <Box> <Popover size="md" {...rest}> <PopoverTrigger> <Button variant="outline">{name}</Button> </PopoverTrigger> <PopoverContent> <PopoverArrow /> <PopoverHeader> <Flex justify="space-between" align="center" p={2}> <Box> <Text as="b">Notifications</Text> </Box> <Box> <Button size="xs" colorScheme={"blue"} variant="ghost"> Mark all as read </Button> </Box> </Flex> <Flex gap={4}> <CustomButton label={"All"} notifications={2} colorMode={colorMode} /> <CustomButton label={"Following"} colorMode={colorMode} /> <CustomButton label={"Archive"} colorMode={colorMode} /> </Flex> </PopoverHeader> <PopoverBody> <VStack justify="flex-start" divider={<StackDivider bg="gray.600" m={"0 !important"} />} align="stretch" > <Notification name="Segun Adebayo" action="created Chakra UI" time="10 min ago" src="https://bit.ly/sage-adebayo" hasBadge={true} colorMode={colorMode} /> <Notification name="Dan Abramov" action="created Redux" time="1 hour ago" src="https://bit.ly/dan-abramov" hasBadge={true} colorMode={colorMode} /> <Notification name="Gleb Koshcheev" action="added theme docs" time="12:10pm" colorMode={colorMode} /> <Notification name="Jacob Jones" action="deleted profile image" time="11:30am" colorMode={colorMode} /> </VStack> </PopoverBody> </PopoverContent> </Popover> </Box> ); export default function App() { const { toggleColorMode, colorMode } = useColorMode(); return ( <Box pos="relative"> <Flex direction="column" gap={3} align="center" p={8} alignItems={"center"} justify="center" > <PopoverExample name={"Themed Popover"} colorMode={colorMode} /> <PopoverExample name={"Themed XL Popover"} size="xl" colorMode={colorMode} /> <PopoverExample name={"Themed XL Rounded Popover"} size="xl" variant="rounded" colorMode={colorMode} /> </Flex> <Box> <IconButton aria-label="toggle theme" rounded="full" size="xs" position="fixed" bottom={4} left={4} onClick={toggleColorMode} icon={colorMode === "dark" ? <FaSun /> : <FaMoon />} /> </Box> </Box> ); }