Popover
A Popover is a page overlay triggered by a button that displays additional interactive content.
const PopoverExample = () => {return (<PopoverContainer baseId="popover-example"><PopoverButton variant="primary">✊ Action</PopoverButton><Popover aria-label="Popover"><Heading as="h3" variant="heading30">Heads up!</Heading><Separator orientation="horizontal" verticalSpacing="space50" /><Text as="span">Black Lives Matter. We stand with the Black community</Text><Separator orientation="horizontal" verticalSpacing="space50" /><Button onClick={() => {}} variant="primary">Agree</Button></Popover></PopoverContainer>);};render(<PopoverExample />)
The Popover component can be used for displaying supplemental or contextual interactive content over your UI. It is built on top of our Non-modal Dialog primitive. A Popover can contain a wide variety of information and interactive content and does not block the user from interacting with the rest of the page.
Popovers remain actively open until a user dismisses it in one of the following ways:
- Presses the Esc key
- Presses the close "x" button in the Popover
- Presses a "Cancel" button in in the Popover
- Clicks outside of the Popover
- Performs another action that closes the Popover
Popovers and non-modal dialogs follow these accessibility guidelines:
- A Popover must be triggered by an explicit user action, e.g., clicking a button.
- It must contain at least one focusable element, usually the close button.
- There should be a close button so screen readers have a specific close action to target.
- When a Popover is shown, focus is placed inside it, as it is a focus trap. A user cannot tab outside of the Popover until it is closed. After the Popover is closed, focus is placed back on the element that triggered it. Focus doesn’t return to the page until the user closes the Popover.
- The Popover should be labeled:
- Setting a value for the aria-labelledby property that refers to a visible Heading within the Popover .
- Providing a label directly specifying by aria-label attribute on the Popover.
- Popovers allow for much more dynamic content than Tooltips. They can display additional content that can contain interactive elements, like Buttons or Anchors. It is not possible to add interactivity inside a Tooltip. This is because a user cannot focus within a Tooltip; the content of the Tooltip is only visible when the trigger is hovered or focused.
- A Popover is opened on Click or Enter. A Tooltip is opened on Hover or Focus.
- If you want to provide a concise description of how or what an interactive element in your UI does, use a Tooltip.
- For displaying rich content or interactive elements, such as headings, anchors, or buttons, use a Popover.
For additional information on Tooltips, check out Heydon Pickering’s Inclusive Components guidance.
A Modal is a dialog that overlays the entire page. They can display interactive content, and place the users into a “mode” for inputting or reviewing information. It temporarily changes the user’s context for a more focused experience. We recommend using Modals to present critical information or request required input needed to complete a workflow instead of a Popover.
- For displaying contextual content, use a Popover. Popover content should be contextual to the page and be optional or supplementary. It should also be concise. For longer pieces of content, consider using a Modal instead.
- For a temporary change of context, use a Modal. Modals serve to place users into a “mode” or focused state for more complex interactions, inputting information, or displaying critical information.
- For displaying a series of steps that ask for user input or forms, use a modal over a Popover.
Use a Popover to display supplemental information and interactive controls.
const PopoverExample = () => {return (<PopoverContainer baseId="popover-example"><PopoverButton variant="primary">✊ Action</PopoverButton><Popover aria-label="Popover"><Heading as="h3" variant="heading30">Heads up!</Heading><Separator orientation="horizontal" verticalSpacing="space50" /><Text as="span">Black Lives Matter. We stand with the Black community</Text><Separator orientation="horizontal" verticalSpacing="space50" /><Button onClick={() => {}} variant="primary">Agree</Button></Popover></PopoverContainer>);};render(<PopoverExample />)
The benefit of using a Popover is its ability to display interactive content to help provide additional information or functionality to your UI. It can essentially contain anything, like links, buttons, images, or headings.
const PopoverExample = () => {const saveButtonRef = React.createRef();return (<PopoverContainer baseId="popover-interactive-example"><PopoverButton variant="primary">Open popover</PopoverButton><Popover aria-label="Update API key permissions" initialFocusRef={saveButtonRef}><Heading as="h3" variant="heading30">Update API key permissions</Heading><Paragraph>It is really important to update your permissions.</Paragraph><CheckboxGroup name="permissions" legend="API key permissions"><Checkbox id="full_access" value="full_access">Full access</Checkbox><Checkbox id="restricted_access" value="restricted_access">Restricted access</Checkbox></CheckboxGroup><Box marginTop='space70'><ButtonGroup><Button onClick={() => {}} variant="primary" ref={saveButtonRef}>Save</Button><Button onClick={() => {}} variant="secondary">Cancel</Button></ButtonGroup></Box></Popover></PopoverContainer>);};render(<PopoverExample />)
When a user opens a Popover, the focus is set inside the Popover. By default, user focus is set on the first focusable element, which is the close button. You can choose to explicitly set focus to any focusable UI control inside the Popover.
We recommend the following when adjusting the focus:
- We caution against setting the focus on an element that does a non-reversible action, in case a user accidentally selects it. (e.g. an “Agree” button that a user hits by mistake, thinking they were exiting the Popover.)
- Changing the focus placement away from the close button could aid in more efficient in-context editing (e.g. the first text input).
To set a different initial focus target, set the initialFocusRef
prop on the Popover component to a ref of a focusable element inside the Popover.
const countryList = [{code: 'AD', label: 'Andorra', phone: '376'},{code: 'AE', label: 'United Arab Emirates', phone: '971'},{code: 'AF', label: 'Afghanistan', phone: '93'},{code: 'AG', label: 'Antigua and Barbuda', phone: '1-268'},{code: 'AI', label: 'Anguilla', phone: '1-264'},{code: 'AL', label: 'Albania', phone: '355'},{code: 'AM', label: 'Armenia', phone: '374'},{code: 'AO', label: 'Angola', phone: '244'},{code: 'AQ', label: 'Antarctica', phone: '672'},{code: 'AR', label: 'Argentina', phone: '54'},{code: 'AS', label: 'American Samoa', phone: '1-684'},{code: 'AT', label: 'Austria', phone: '44'},{code: 'BS', label: 'Bahamas', phone: '43'},{code: 'BH', label: 'Bahrain', phone: '48'},{code: 'BD', label: 'Bangladesh', phone: '50'},{code: 'BB', label: 'Barbados', phone: '52'},{code: 'BY', label: 'Belarus', phone: '112'},{code: 'BE', label: 'Belgium', phone: '56'},{code: 'BZ', label: 'Belize', phone: '84'},{code: 'BJ', label: 'Benin', phone: '204'},{code: 'BM', label: 'Bermuda', phone: '60'},{code: 'BT', label: 'Bhutan', phone: '64'},{code: 'BO', label: 'Bolivia', phone: '68'},{code: 'BW', label: 'Botswana', phone: '72'},{code: 'BR', label: 'Brazil', phone: '76'},{code: 'KH', label: 'Cambodia', phone: '116'},{code: 'CA', label: 'Canada', phone: '124'},];const PopoverExample = () => {const phoneInputRef = React.createRef();const seed = useUIDSeed();return (<PopoverContainer baseId="popover-interactive-example"><PopoverButton variant="primary">Open popover</PopoverButton><Popoveraria-label="Edit phone number"initialFocusRef={phoneInputRef}width='size40'><Form aria-labelledby={seed('phone-heading')}><Headingas="h3"variant="heading40"id={seed('phone-heading')}marginBottom='space0'>Edit phone number</Heading><FormControl><Label htmlFor={seed('phone-input')}>Phone number</Label><Input type='text' id={seed('phone-input')} ref={phoneInputRef} /></FormControl><FormControl><Label htmlFor={seed('country-input')}>Country</Label><Select id={seed('country-input')}>{countryList.map(({code, label}) => (<Option value={code}>{label}</Option>))}</Select></FormControl><FormActions><Button onClick={() => {}} variant="primary">Save</Button><Button onClick={() => {}} variant="secondary">Cancel</Button></FormActions></Form></Popover></PopoverContainer>);};render(<PopoverExample />)
Use the placement prop to configure where to position the Popover in relation to its trigger. The available placement options are available in the props description here.
const PopoverPositionExample = () => {return (<Box display='flex' columnGap='space40'><PopoverContainer baseId="popover-top-example" placement="top-start"><PopoverButton variant="primary">Open top</PopoverButton><Popover aria-label="Popover" width={['size20', 'size40']}><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><PopoverContainer baseId="popover-right-example" placement="right-start"><PopoverButton variant="primary">Open right</PopoverButton><Popover aria-label="Popover" width={['size20', 'size40']}><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><PopoverContainer baseId="popover-bottom-example" placement="bottom-start"><PopoverButton variant="primary">Open bottom</PopoverButton><Popover aria-label="Popover" width={['size20', 'size40']}><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><PopoverContainer baseId="popover-left-example" placement="left-start"><PopoverButton variant="primary">Open left</PopoverButton><Popover aria-label="Popover" width={['size20', 'size40']}><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer></Box>);};render(<PopoverPositionExample />)
The Popover's width will grow to fit its content up to a maximum width of our size50 token. To set the size of the Popover, set a width
prop. It will accept any valid size token up to size50.
const countryList = [{code: 'AD', label: 'Andorra', phone: '376'},{code: 'AE', label: 'United Arab Emirates', phone: '971'},{code: 'AF', label: 'Afghanistan', phone: '93'},{code: 'AG', label: 'Antigua and Barbuda', phone: '1-268'},{code: 'AI', label: 'Anguilla', phone: '1-264'},{code: 'AL', label: 'Albania', phone: '355'},{code: 'AM', label: 'Armenia', phone: '374'},{code: 'AO', label: 'Angola', phone: '244'},{code: 'AQ', label: 'Antarctica', phone: '672'},{code: 'AR', label: 'Argentina', phone: '54'},{code: 'AS', label: 'American Samoa', phone: '1-684'},{code: 'AT', label: 'Austria', phone: '44'},{code: 'BS', label: 'Bahamas', phone: '43'},{code: 'BH', label: 'Bahrain', phone: '48'},{code: 'BD', label: 'Bangladesh', phone: '50'},{code: 'BB', label: 'Barbados', phone: '52'},{code: 'BY', label: 'Belarus', phone: '112'},{code: 'BE', label: 'Belgium', phone: '56'},{code: 'BZ', label: 'Belize', phone: '84'},{code: 'BJ', label: 'Benin', phone: '204'},{code: 'BM', label: 'Bermuda', phone: '60'},{code: 'BT', label: 'Bhutan', phone: '64'},{code: 'BO', label: 'Bolivia', phone: '68'},{code: 'BW', label: 'Botswana', phone: '72'},{code: 'BR', label: 'Brazil', phone: '76'},{code: 'KH', label: 'Cambodia', phone: '116'},{code: 'CA', label: 'Canada', phone: '124'},];const PopoverExample = () => {const seed = useUIDSeed();return (<Box display='flex' columnGap='space40'><PopoverContainer baseId="popover-example"><PopoverButton variant="primary">Auto width popover</PopoverButton><Popover aria-label="Edit phone number"><Form aria-labelledby={seed('phone-heading-0')}><Headingas="h3"variant="heading40"id={seed('phone-heading-0')}marginBottom='space0'>Edit phone number</Heading><FormControl><Label htmlFor={seed('phone-input-0')}>Phone number</Label><Input type='text' id={seed('phone-input-0')} /></FormControl><FormControl><Label htmlFor={seed('country-input-0')}>Country</Label><Select id={seed('country-input-0')}>{countryList.map(({code, label}) => (<Option value={code}>{label}</Option>))}</Select></FormControl><FormActions><Button onClick={() => {}} variant="primary">Save</Button><Button onClick={() => {}} variant="secondary">Cancel</Button></FormActions></Form></Popover></PopoverContainer><PopoverContainer baseId="popover-example"><PopoverButton variant="primary">size40 popover</PopoverButton><Popover aria-label="Edit phone number" width="size40"><Form aria-labelledby={seed('phone-heading-1')}><Headingas="h3"variant="heading40"id={seed('phone-heading-1')}marginBottom='space0'>Edit phone number</Heading><FormControl><Label htmlFor={seed('phone-input-1')}>Phone number</Label><Input type='text' id={seed('phone-input-1')} /></FormControl><FormControl><Label htmlFor={seed('country-input-1')}>Country</Label><Select id={seed('country-input-1')}>{countryList.map(({code, label}) => (<Option value={code}>{label}</Option>))}</Select></FormControl><FormActions><Button onClick={() => {}} variant="primary">Save</Button><Button onClick={() => {}} variant="secondary">Cancel</Button></FormActions></Form></Popover></PopoverContainer></Box>);};render(<PopoverExample />)
The PopoverButton renders a Button component and accepts all of its props, which are listed on the Button page.
const PopoverBadgeExample = () => {return (<Box display="flex" columnGap="space40"><PopoverContainer baseId="popover-example"><PopoverButton variant="primary" size="small">Open popover</PopoverButton><Popover aria-label="Popover"><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><PopoverContainer baseId="popover-example"><PopoverButton variant="secondary_icon" size="icon_small"><PlusIcon decorative={false} title="Open popover" /></PopoverButton><Popover aria-label="Popover"><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><PopoverContainer baseId="popover-example"><PopoverButton variant="reset" size="reset">Open popover</PopoverButton><Popover aria-label="Popover"><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer></Box>);};render(<PopoverBadgeExample />)
To launch a Popover from a Badge component, use the PopoverBadgeButton component. It renders a Badge and accepts all of its props except for as
, which are listed on the Badge page.
const PopoverBadgeExample = () => {return (<Box display="flex" columnGap="space40"><PopoverContainer baseId="popover-example"><PopoverBadgeButton variant="decorative10">Open popover</PopoverBadgeButton><Popover aria-label="Popover"><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><PopoverContainer baseId="popover-example"><PopoverBadgeButton variant="neutral"><><InformationIcon decorative={false} title="Information" />Open popover</></PopoverBadgeButton><Popover aria-label="Popover"><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><PopoverContainer baseId="popover-example"><PopoverBadgeButton variant="warning"><><WarningIcon decorative={false} title="Warning" />Open popover</></PopoverBadgeButton><Popover aria-label="Popover"><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><PopoverContainer baseId="popover-example"><PopoverBadgeButton variant="new"><><NewIcon decorative={false} title="New" />Open popover</></PopoverBadgeButton><Popover aria-label="Popover"><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer></Box>);};render(<PopoverBadgeExample />)
Popover comes with the option of "hooking" into the internal state by using the state hook originally provided by Reakit.
Rather than the state be internal to the component, you can use the usePopoverState
hook and pass the returned state
to PopoverContainer
as the state
prop.
This allows you to use certain returned props from the state hook, including functions like hide
and show
.
An example usecase of this might be programmatically providing the user a popover when a certain user action is taken.
In the example below we are showing a popover when another button is pressed. When pressed the button uses the show
function from the hook. Another button using the hide
function from the hook is also provided to hide the popover
when pressed.
It should be noted that when doing so, the state prop takes precedent over the other properties that affect the state or initial state of the Popover. They will be ignored in favour of them being provided as arguments to the usePopoverState hook.
For full details on how to use the state hook, and what props to provide it, follow the Non-Modal Dialog Primitive documentation. It's the same hook, just renamed.
const StateHookExample = () => {const popover = usePopoverState({baseId: 'test-id'});return (<Box display='flex' columnGap='space40'><PopoverContainer state={popover}><PopoverButton variant="primary">Open popover</PopoverButton><Popover aria-label="Popover"><Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text></Popover></PopoverContainer><Button variant="primary" onClick={() => popover.show()}>Open popover on click</Button><Button variant="primary" onClick={() => popover.hide()}>Close popover on click</Button></Box>);};render(<StateHookExample />)
To internationalize the Popover, pass different text as children to the Popover's contents and the aria-label
prop. The only exception is the dismiss button. To change the dismiss button’s text, use the i18nDismissLabel
prop.
const I18nExample = () => {return (<PopoverContainer baseId="popover-example"><PopoverButton variant="primary">Abrir popover</PopoverButton><Popover aria-label="Popover" i18nDismissLabel="Cerrar popover"><Text as="span">"Vivir en las fronteras y en los márgenes, mantener intacta la identidad múltiple y la integridad, escomo tratar de nadar en un nuevo elemento, un elemento 'extranjero'" — Gloria E. Anzaldúa</Text></Popover></PopoverContainer>);};render(<I18nExample />)
Use a Popover to present supplemental information with interactive elements, like an Anchor. Information in a Popover should never be essential to task completion.
Popover content should use full sentences and punctuation. Titles are optional.
Do
Use Popover when you need to display supplemental interactive content over the top of your UI. It should have at least one focusable element.
Don't
Don’t present critical information or required inputs in Popovers, use the Modal instead.
Do
Use Popover to present additional contextual information that is not essential to completing a task.
Don't
After the Popover closes, don’t change the focus placement from the trigger. If focus is moved from the original trigger, it can cause confusion and frustration for where a keyboard user’s location is on the page.
Do
When the Popover is triggered, the focus should be placed on the first focusable element, which is usually the close button.
Don't
Don’t place Popovers on non-focusable elements, like an icon. Wrap them in a focusable element and place the Popover on that.
Do
Popovers should be triggered by a button.
Don't
Don’t place more than one primary action in a Popover.
Do
Consider the best size (up to size50) for the Popover container, based on its content.
Don't
Don’t use a Popover to guide users through a complex workflow with a series of steps or for presenting critical information. Use a Modal for workflows, and an Alert Dialog for critical information.