Spotlight

A spotlight introduces users to points of interest, from focused messages to multi-step tours.

Motion in Early Access

The motion added into Spotlight is in Early Access. The motion updates are currently behind the feature flag: platform-dst-motion-uplift-spotlight.

Single step

Always aim for a single step experience.

By design, @atlaskit/spotlight does not have a blanket, scroll-lock, or focus-trap functionality. This is to ensure the user is not hijacked into the spotlight experience, and can opt-in if they are interested.

To show/hide the SpotlightCard, simply use a useState to control the isVisible prop on PopoverContent. To position the SpotlightCard, use PopoverProvider, PopoverTarget and PopoverContent and set the placement prop.

import { useState } from 'react'; import Button from '@atlaskit/button/default/button'; import { jsx } from '@atlaskit/css'; import { Flex, Text } from '@atlaskit/primitives/compiled'; import { SpotlightActions } from '@atlaskit/spotlight/actions'; import { SpotlightBody } from '@atlaskit/spotlight/body'; import { SpotlightCard } from '@atlaskit/spotlight/card'; import { SpotlightControls } from '@atlaskit/spotlight/controls'; import { SpotlightDismissControl } from '@atlaskit/spotlight/dismiss-control'; import { SpotlightFooter } from '@atlaskit/spotlight/footer'; import { SpotlightHeader } from '@atlaskit/spotlight/header'; import { SpotlightHeadline } from '@atlaskit/spotlight/headline'; import { PopoverContent } from '@atlaskit/spotlight/popover-content'; import { PopoverProvider } from '@atlaskit/spotlight/popover-provider'; import { PopoverTarget } from '@atlaskit/spotlight/popover-target'; import { SpotlightPrimaryAction } from '@atlaskit/spotlight/primary-action'; export default (): JSX.Element => { const [isVisible, setIsVisible] = useState<boolean>(false); const dismiss = () => setIsVisible(false); const done = () => setIsVisible(false); return ( <Flex> <PopoverProvider> <PopoverTarget> <Button onClick={() => setIsVisible(true)}>Show Spotlight</Button> </PopoverTarget> <PopoverContent dismiss={dismiss} placement="right-end" isVisible={isVisible}> <SpotlightCard testId="spotlight"> <SpotlightHeader> <SpotlightHeadline>Headline</SpotlightHeadline> <SpotlightControls> <SpotlightDismissControl /> </SpotlightControls> </SpotlightHeader> <SpotlightBody> <Text>Brief and direct textual content to elaborate on the intent.</Text> </SpotlightBody> <SpotlightFooter> <SpotlightActions> <SpotlightPrimaryAction onClick={done}>Done</SpotlightPrimaryAction> </SpotlightActions> </SpotlightFooter> </SpotlightCard> </PopoverContent> </PopoverProvider> </Flex> ); };

Multi-step tour

Multiple steps should be avoided if possible, but if they are required, manage the tour with a useState. If useState is not feasible, then a React context may be used. However, these contexts will often need to be wrapping the entire App and therefore will cause the entire App to re-render every time a new spotlight step is shown.

Target 1
Target 2
Target 3
import { useState } from 'react'; import Button from '@atlaskit/button/default/button'; import { cssMap, jsx } from '@atlaskit/css'; import { Box, Text } from '@atlaskit/primitives/compiled'; import { SpotlightActions } from '@atlaskit/spotlight/actions'; import { SpotlightBody } from '@atlaskit/spotlight/body'; import { SpotlightCard } from '@atlaskit/spotlight/card'; import { SpotlightControls } from '@atlaskit/spotlight/controls'; import { SpotlightDismissControl } from '@atlaskit/spotlight/dismiss-control'; import { SpotlightFooter } from '@atlaskit/spotlight/footer'; import { SpotlightHeader } from '@atlaskit/spotlight/header'; import { SpotlightHeadline } from '@atlaskit/spotlight/headline'; import { PopoverContent } from '@atlaskit/spotlight/popover-content'; import { PopoverProvider } from '@atlaskit/spotlight/popover-provider'; import { PopoverTarget } from '@atlaskit/spotlight/popover-target'; import { SpotlightPrimaryAction } from '@atlaskit/spotlight/primary-action'; import { SpotlightSecondaryAction } from '@atlaskit/spotlight/secondary-action'; import { SpotlightStepCount } from '@atlaskit/spotlight/step-count'; import { token } from '@atlaskit/tokens'; const styles = cssMap({ root: { display: 'flex', alignItems: 'center', flexDirection: 'column', gap: token('space.300'), }, target: { paddingBlockStart: token('space.100'), paddingInlineEnd: token('space.100'), paddingBlockEnd: token('space.100'), paddingInlineStart: token('space.100'), borderStyle: 'solid', borderWidth: token('border.width'), borderColor: token('color.border.bold'), }, }); const Example = (): JSX.Element => { const [currentStep, setCurrentStep] = useState<number>(0); const dismiss = () => setCurrentStep(0); const back = () => setCurrentStep(Math.max(currentStep - 1, 1)); const next = () => setCurrentStep(Math.min(currentStep + 1, 3)); const done = () => setCurrentStep(0); return ( <div css={styles.root}> <PopoverProvider> <PopoverTarget> <Box xcss={styles.target}> <Text>Target 1</Text> </Box> </PopoverTarget> <PopoverContent dismiss={dismiss} placement="right-end" isVisible={currentStep === 1}> <SpotlightCard testId="spotlight"> <SpotlightHeader> <SpotlightHeadline>Headline</SpotlightHeadline> <SpotlightControls> <SpotlightDismissControl onClick={dismiss} /> </SpotlightControls> </SpotlightHeader> <SpotlightBody> <Text>Brief and direct textual content to elaborate on the intent.</Text> </SpotlightBody> <SpotlightFooter> <SpotlightStepCount>1 of 3</SpotlightStepCount> <SpotlightActions> <SpotlightPrimaryAction onClick={next}>Next</SpotlightPrimaryAction> </SpotlightActions> </SpotlightFooter> </SpotlightCard> </PopoverContent> </PopoverProvider> <PopoverProvider> <PopoverTarget> <Box xcss={styles.target}> <Text>Target 2</Text> </Box> </PopoverTarget> <PopoverContent dismiss={dismiss} placement="left-end" isVisible={currentStep === 2}> <SpotlightCard testId="spotlight"> <SpotlightHeader> <SpotlightHeadline>Headline</SpotlightHeadline> <SpotlightControls> <SpotlightDismissControl onClick={dismiss} /> </SpotlightControls> </SpotlightHeader> <SpotlightBody> <Text>Brief and direct textual content to elaborate on the intent.</Text> </SpotlightBody> <SpotlightFooter> <SpotlightStepCount>2 of 3</SpotlightStepCount> <SpotlightActions> <SpotlightSecondaryAction onClick={back}>Back</SpotlightSecondaryAction> <SpotlightPrimaryAction onClick={next}>Next</SpotlightPrimaryAction> </SpotlightActions> </SpotlightFooter> </SpotlightCard> </PopoverContent> </PopoverProvider> <PopoverProvider> <PopoverTarget> <Box xcss={styles.target}> <Text>Target 3</Text> </Box> </PopoverTarget> <PopoverContent dismiss={dismiss} placement="right-end" isVisible={currentStep === 3}> <SpotlightCard testId="spotlight"> <SpotlightHeader> <SpotlightHeadline>Headline</SpotlightHeadline> <SpotlightControls> <SpotlightDismissControl onClick={dismiss} /> </SpotlightControls> </SpotlightHeader> <SpotlightBody> <Text>Brief and direct textual content to elaborate on the intent.</Text> </SpotlightBody> <SpotlightFooter> <SpotlightStepCount>3 of 3</SpotlightStepCount> <SpotlightActions> <SpotlightSecondaryAction onClick={back}>Back</SpotlightSecondaryAction> <SpotlightPrimaryAction onClick={done}>Done</SpotlightPrimaryAction> </SpotlightActions> </SpotlightFooter> </SpotlightCard> </PopoverContent> </PopoverProvider> <Button onClick={() => setCurrentStep(1)}>Restart Tour</Button> </div> ); }; export default Example;

Placements

Spotlight placements are static. They do not change as the user scrolls, or if the PopoverContent overflows out of the viewport. Make sure to choose a placement that ensures the SpotlightCard is displayed in full.

import { useState } from 'react'; import Button from '@atlaskit/button/default/button'; import { cssMap, jsx } from '@atlaskit/css'; import DropdownMenu from '@atlaskit/dropdown-menu/dropdown-menu'; import DropdownItem from '@atlaskit/dropdown-menu/dropdown-menu-item'; import DropdownItemGroup from '@atlaskit/dropdown-menu/dropdown-menu-item-group'; import { Text } from '@atlaskit/primitives/compiled'; import { SpotlightActions } from '@atlaskit/spotlight/actions'; import { SpotlightBody } from '@atlaskit/spotlight/body'; import { SpotlightCard } from '@atlaskit/spotlight/card'; import { SpotlightControls } from '@atlaskit/spotlight/controls'; import { SpotlightDismissControl } from '@atlaskit/spotlight/dismiss-control'; import { SpotlightFooter } from '@atlaskit/spotlight/footer'; import { SpotlightHeader } from '@atlaskit/spotlight/header'; import { SpotlightHeadline } from '@atlaskit/spotlight/headline'; import { PopoverContent } from '@atlaskit/spotlight/popover-content'; import { PopoverProvider } from '@atlaskit/spotlight/popover-provider'; import { PopoverTarget } from '@atlaskit/spotlight/popover-target'; import { SpotlightPrimaryAction } from '@atlaskit/spotlight/primary-action'; import type { Placement } from '@atlaskit/spotlight/types'; import { token } from '@atlaskit/tokens'; const styles = cssMap({ root: { width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexDirection: 'column', gap: token('space.200'), }, }); const cardPlacements: Placement[] = [ 'bottom-start', 'bottom-center', 'bottom-end', 'left-start', 'left-end', 'top-start', 'top-center', 'top-end', 'right-start', 'right-end', ] as const; const Example = (): JSX.Element => { const [placement, setPlacement] = useState<(typeof cardPlacements)[number]>('top-end'); const [isVisible, setIsVisible] = useState<boolean>(false); const dismiss = () => setIsVisible(false); const done = () => setIsVisible(false); return ( <div css={styles.root}> <PopoverProvider> <PopoverTarget> <Button onClick={() => setIsVisible(true)}>Show Spotlight</Button> </PopoverTarget> <PopoverContent dismiss={dismiss} isVisible={isVisible} placement={placement}> <SpotlightCard testId="spotlight"> <SpotlightHeader> <SpotlightHeadline>Headline</SpotlightHeadline> <SpotlightControls> <SpotlightDismissControl /> </SpotlightControls> </SpotlightHeader> <SpotlightBody> <Text>Brief and direct textual content to elaborate on the intent.</Text> </SpotlightBody> <SpotlightFooter> <SpotlightActions> <SpotlightPrimaryAction onClick={done}>Done</SpotlightPrimaryAction> </SpotlightActions> </SpotlightFooter> </SpotlightCard> </PopoverContent> </PopoverProvider> <DropdownMenu trigger={`Placement: ${placement}`} shouldRenderToParent> <DropdownItemGroup> {cardPlacements.map((placement) => ( <DropdownItem key={placement} onClick={() => setPlacement(placement)}> {placement} </DropdownItem> ))} </DropdownItemGroup> </DropdownMenu> </div> ); }; export default Example;

Media

Media is optional for a SpotlightCardand should only be used for more complex features. To ensure correct reflow on smaller viewports, media must be 295px width X 135px height.

Media can be an image, gif, or video that helps communicate spotlight intent.

import { useState } from 'react'; import Button from '@atlaskit/button/default/button'; import { cssMap, jsx } from '@atlaskit/css'; import Image from '@atlaskit/image'; import { Text } from '@atlaskit/primitives/compiled'; import { SpotlightActions } from '@atlaskit/spotlight/actions'; import { SpotlightBody } from '@atlaskit/spotlight/body'; import { SpotlightCard } from '@atlaskit/spotlight/card'; import { SpotlightControls } from '@atlaskit/spotlight/controls'; import { SpotlightDismissControl } from '@atlaskit/spotlight/dismiss-control'; import { SpotlightFooter } from '@atlaskit/spotlight/footer'; import { SpotlightHeader } from '@atlaskit/spotlight/header'; import { SpotlightHeadline } from '@atlaskit/spotlight/headline'; import { SpotlightMedia } from '@atlaskit/spotlight/media'; import { PopoverContent } from '@atlaskit/spotlight/popover-content'; import { PopoverProvider } from '@atlaskit/spotlight/popover-provider'; import { PopoverTarget } from '@atlaskit/spotlight/popover-target'; import { SpotlightPrimaryAction } from '@atlaskit/spotlight/primary-action'; import { token } from '@atlaskit/tokens'; import ExampleImage from '../assets/295x135.png'; const styles = cssMap({ root: { display: 'flex', paddingBlockStart: token('space.400'), paddingInlineEnd: token('space.400'), paddingBlockEnd: token('space.400'), paddingInlineStart: token('space.400'), height: '100%', }, }); const Example = (): JSX.Element => { const [isVisible, setIsVisible] = useState<boolean>(false); const dismiss = () => setIsVisible(false); const done = () => setIsVisible(false); return ( <div css={styles.root}> <PopoverProvider> <PopoverTarget> <Button onClick={() => setIsVisible(true)}>Show Spotlight</Button> </PopoverTarget> <PopoverContent dismiss={dismiss} isVisible={isVisible} placement="right-end"> <SpotlightCard testId="spotlight"> <SpotlightHeader> <SpotlightHeadline>Headline</SpotlightHeadline> <SpotlightControls> <SpotlightDismissControl /> </SpotlightControls> </SpotlightHeader> <SpotlightMedia> <Image src={ExampleImage} alt="placeholder" /> </SpotlightMedia> <SpotlightBody> <Text>Brief and direct textual content to elaborate on the intent.</Text> </SpotlightBody> <SpotlightFooter> <SpotlightActions> <SpotlightPrimaryAction onClick={done}>Done</SpotlightPrimaryAction> </SpotlightActions> </SpotlightFooter> </SpotlightCard> </PopoverContent> </PopoverProvider> </div> ); }; export default Example;

Controls

SpotlightDismissControl is required for all SpotlightCard components. It must be the first focusable element on the SpotlightCard card to provide an accessible experience.

Actions

Buttons

When the primary or secondary control should perform an in-app action (e.g. dismiss, advance to the next step, or complete a flow), use SpotlightPrimaryAction and SpotlightSecondaryAction. They accept an onClick handler and render as buttons.

SpotlightPrimaryAction and SpotlightPrimaryLink also accept an appearance prop, which allow for brand styling.

Target
import { cssMap, jsx } from '@atlaskit/css'; import { Flex, Text } from '@atlaskit/primitives/compiled'; import { SpotlightActions } from '@atlaskit/spotlight/actions'; import { SpotlightBody } from '@atlaskit/spotlight/body'; import { SpotlightCard } from '@atlaskit/spotlight/card'; import { SpotlightControls } from '@atlaskit/spotlight/controls'; import { SpotlightDismissControl } from '@atlaskit/spotlight/dismiss-control'; import { SpotlightFooter } from '@atlaskit/spotlight/footer'; import { SpotlightHeader } from '@atlaskit/spotlight/header'; import { SpotlightHeadline } from '@atlaskit/spotlight/headline'; import { PopoverContent } from '@atlaskit/spotlight/popover-content'; import { PopoverProvider } from '@atlaskit/spotlight/popover-provider'; import { PopoverTarget } from '@atlaskit/spotlight/popover-target'; import { SpotlightPrimaryAction } from '@atlaskit/spotlight/primary-action'; import { SpotlightSecondaryLink } from '@atlaskit/spotlight/secondary-link'; import { token } from '@atlaskit/tokens'; const styles = cssMap({ target: { paddingBlockStart: token('space.100'), paddingInlineEnd: token('space.100'), paddingBlockEnd: token('space.100'), paddingInlineStart: token('space.100'), borderStyle: 'solid', borderWidth: token('border.width'), borderColor: token('color.border.bold'), }, }); export default (): JSX.Element => ( <Flex> <PopoverProvider> <PopoverTarget> <div css={styles.target}> <Text>Target</Text> </div> </PopoverTarget> <PopoverContent isVisible={true} placement="right-end" dismiss={() => {}}> <SpotlightCard> <SpotlightHeader> <SpotlightHeadline>Try the new experience</SpotlightHeadline> <SpotlightControls> <SpotlightDismissControl /> </SpotlightControls> </SpotlightHeader> <SpotlightBody> <Text> When your primary or secondary control should navigate to a URL instead of performing an action, use SpotlightPrimaryLink and SpotlightSecondaryLink. </Text> </SpotlightBody> <SpotlightFooter> <SpotlightActions> <SpotlightSecondaryLink href="https://atlassian.design/components/spotlight" target="_blank" rel="noopener noreferrer" > Learn more </SpotlightSecondaryLink> <SpotlightPrimaryAction appearance="primary">Done</SpotlightPrimaryAction> </SpotlightActions> </SpotlightFooter> </SpotlightCard> </PopoverContent> </PopoverProvider> </Flex> );

When the primary or secondary control should navigate to a URL instead of performing an action, use SpotlightPrimaryLink and SpotlightSecondaryLink. They mirror the appearance of SpotlightPrimaryAction and SpotlightSecondaryAction but render as links (e.g. "Get started", "Learn more").

Target
import { cssMap, jsx } from '@atlaskit/css'; import { Flex } from '@atlaskit/primitives/compiled/flex'; import { Text } from '@atlaskit/primitives/compiled/text'; import { SpotlightActions } from '@atlaskit/spotlight/actions'; import { SpotlightBody } from '@atlaskit/spotlight/body'; import { SpotlightCard } from '@atlaskit/spotlight/card'; import { SpotlightControls } from '@atlaskit/spotlight/controls'; import { SpotlightDismissControl } from '@atlaskit/spotlight/dismiss-control'; import { SpotlightFooter } from '@atlaskit/spotlight/footer'; import { SpotlightHeader } from '@atlaskit/spotlight/header'; import { SpotlightHeadline } from '@atlaskit/spotlight/headline'; import { PopoverContent } from '@atlaskit/spotlight/popover-content'; import { PopoverProvider } from '@atlaskit/spotlight/popover-provider'; import { PopoverTarget } from '@atlaskit/spotlight/popover-target'; import { SpotlightPrimaryAction } from '@atlaskit/spotlight/primary-action'; import { SpotlightSecondaryLink } from '@atlaskit/spotlight/secondary-link'; import { token } from '@atlaskit/tokens'; const styles = cssMap({ target: { paddingBlockStart: token('space.100'), paddingInlineEnd: token('space.100'), paddingBlockEnd: token('space.100'), paddingInlineStart: token('space.100'), borderStyle: 'solid', borderWidth: token('border.width'), borderColor: token('color.border.bold'), }, }); export default (): JSX.Element => ( <Flex> <PopoverProvider> <PopoverTarget> <div css={styles.target}> <Text>Target</Text> </div> </PopoverTarget> <PopoverContent isVisible={true} placement="right-end" dismiss={() => {}}> <SpotlightCard> <SpotlightHeader> <SpotlightHeadline>Try the new experience</SpotlightHeadline> <SpotlightControls> <SpotlightDismissControl /> </SpotlightControls> </SpotlightHeader> <SpotlightBody> <Text> When your primary or secondary control should navigate to a URL instead of performing an action, use SpotlightPrimaryLink and SpotlightSecondaryLink. </Text> </SpotlightBody> <SpotlightFooter> <SpotlightActions> <SpotlightSecondaryLink href="https://atlassian.design/components/spotlight" target="_blank" rel="noopener noreferrer" > Learn more </SpotlightSecondaryLink> <SpotlightPrimaryAction>Done</SpotlightPrimaryAction> </SpotlightActions> </SpotlightFooter> </SpotlightCard> </PopoverContent> </PopoverProvider> </Flex> );
Was this page helpful?
We use this feedback to improve our documentation.
  • Design system
    • Get started
    • Foundations
    • Components
    • Rovo UI
    • Tools
    • Release phases
    • Contact us
© 2026 AtlassianTrademark, (opens new window)Privacy, (opens new window)License