Spotlight
A spotlight introduces users to points of interest, from focused messages to multi-step tours.Motion in Early Access
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.
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
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.
Try the new experience
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>
);Links
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").
Try the new experience
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>
);