Spotlight

Command center for your application
Import
License

Installation

Package depends on @mantine/core and @mantine/hooks.

Install with npm:

npm install @mantine/spotlight @mantine/core @mantine/hooks

Install with yarn:

yarn add @mantine/spotlight @mantine/core @mantine/hooks

Usage

Spotlight component let you build a popup search interface which can be triggered with keyboard shortcut or programmatically from anywhere inside your application. To get started, wrap your application with SpotlightProvider component:

import { Button, Group } from '@mantine/core';
import { SpotlightProvider, openSpotlight } from '@mantine/spotlight';
import type { SpotlightAction } from '@mantine/spotlight';
import { Home, Dashboard, FileText, Search } from 'tabler-icons-react';
function SpotlightControl() {
return (
<Group position="center">
<Button onClick={openSpotlight}>Open spotlight</Button>
</Group>
);
}
const actions: SpotlightAction[] = [
{
title: 'Home',
description: 'Get to home page',
onTrigger: () => console.log('Home'),
icon: <Home size={18} />,
},
{
title: 'Dashboard',
description: 'Get full information about current system status',
onTrigger: () => console.log('Dashboard'),
icon: <Dashboard size={18} />,
},
{
title: 'Documentation',
description: 'Visit documentation to lean more about all features',
onTrigger: () => console.log('Documentation'),
icon: <FileText size={18} />,
},
];
function Demo() {
return (
<SpotlightProvider
actions={actions}
searchIcon={<Search size={18} />}
searchPlaceholder="Search..."
shortcut="mod + shift + 1"
nothingFoundMessage="Nothing found..."
>
<SpotlightControl />
</SpotlightProvider>
);
}

Note that if you are using MantineProvider, SpotlightProvider must be used as its child:

import { MantineProvider } from '@mantine/core';
import { SpotlightProvider } from '@mantine/spotlight';
function App() {
return (
<MantineProvider>
<SpotlightProvider>
<YourApp />
</SpotlightProvider>
</MantineProvider>
);
}

Keyboard shortcuts

SpotlightProvider uses use-hotkeys hook to add keyboard shortcuts, the default shortcut to trigger popup is mod + K, it means that it will be shown when users press ⌘ + K on MacOS and Ctrl + K on any other os.

You can setup multiple shortcuts, for example, Mantine documentation uses the following setup:

import { SpotlightProvider } from '@mantine/spotlight';
function App() {
return (
<SpotlightProvider shortcut={['mod + P', 'mod + K', '/']}>
<YourApp />
</SpotlightProvider>
);
}

It means that user will be able to open documentation search with the following shortcuts:

  • ⌘ + K / Ctrl + K
  • ⌘ + P / Ctrl + P
  • / – single keys are also supported

Note that provided shortcuts will prevent the default behavior, for example, mod + P will disable "Print page" native browser function, choose those shortcuts that will not interfere with desired default browser behavior.

Keyboard shortcuts will not work if:

  • focus is not on current page
  • input, textarea or select elements are focused

To disabled keyboard shortcuts set shortcut={null}:

import { SpotlightProvider } from '@mantine/spotlight';
function App() {
return (
<SpotlightProvider shortcut={null}>
<YourApp />
</SpotlightProvider>
);
}

use-spotlight hook

use-spotlight hook lets you control spotlight from anywhere in your application. For example, it can be used to open spotlight with button click:

import { useSpotlight } from '@mantine/spotlight';
import { Button, Group } from '@mantine/core';
function SpotlightControl() {
const spotlight = useSpotlight();
return (
<Group position="center">
<Button onClick={spotlight.openSpotlight}>Open spotlight</Button>
</Group>
);
}

use-spotlight returns an object with the following properties:

interface UseSpotlight {
/** Opens spotlight */
openSpotlight(): void;
/** Closes spotlight */
closeSpotlight(): void;
/** Toggles spotlight opened state */
toggleSpotlight(): void;
/** Triggers action with given id */
triggerAction(actionId: string): void;
/** Registers additional actions */
registerActions(action: SpotlightAction[]): void;
/** Removes actions with given ids */
removeActions(actionIds: string[]): void;
/** Current opened state */
opened: boolean;
/** List of registered actions */
actions: SpotlightAction[];
/** Search query */
query: string;
}

Event based functions

@mantine/spotlight exports several functions which can be used to perform certain actions from any part of your application:

// All functions can be called from anywhere (not only from components)
import {
openSpotlight,
closeSpotlight,
toggleSpotlight,
triggerSpotlightAction,
registerSpotlightActions,
removeSpotlightActions,
} from '@mantine/spotlight';
// Opens spotlight
openSpotlight();
// Closes spotlight
closeSpotlight();
// Toggles spotlight opened state
toggleSpotlight();
// triggers action with given id
triggerSpotlightAction('action-id');
// registers additional actions
registerSpotlightActions([
{
id: 'secret-action-1',
title: 'Secret action',
onTrigger: () => console.log('Secret'),
},
{
id: 'secret-action-2',
title: 'Another secret action',
onTrigger: () => console.log('Secret'),
},
]);
// removes actions
removeSpotlightActions(['secret-action-1', 'secret-action-2']);

Spotlight actions

actions is the only required prop of SpotlightProvider. Action shape:

interface SpotlightAction {
/** Action id, may be used to trigger action or find it in actions array,
* if not provided random string will be generated instead */
id?: string;
/** Action title, topmost large text, used for filtering */
title: string;
/** Action description, small text displayed after title, used for filtering */
description?: string;
/** Action group, used to render group label */
group?: string;
/** Keywords that are used for filtering, not displayed anywhere,
* can be a string: "react,router,javascript" or an array: ['react', 'router', 'javascript'] */
keywords?: string | string[];
/** Decorative icon */
icon?: ReactNode;
/** Function that is called when action is triggered */
onTrigger(action: SpotlightAction): void;
/** Any other properties that can be consumed by custom action component */
[key: string]: any;
}

You can import SpotlightAction type from @mantine/spotlight package:

import type { SpotlightAction } from '@mantine/spotlight';

Actions filtering

When user searches spotlight actions are filtered based on the following action properties:

  • titlestring
  • descriptionstring
  • keywordsstring | string[]

You can change filtering logic by setting filter prop on SpotlightProvider. The following example filters actions only by title:

import { SpotlightProvider } from '@mantine/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + alt + V"
nothingFoundMessage="Nothing found..."
filter={(query, actions) =>
actions.filter((action) => action.title.toLowerCase().includes(query.toLowerCase()))
}
{...otherProps}
>
<YourApp />
</SpotlightProvider>
);
}

Actions limit

If you have a large list of actions, most of them will not be get to the initial list (when user have not enter any text yet). You can control how many actions are displayed at a time with limit prop:

import { SpotlightProvider } from '@mantine/spotlight';
import type { SpotlightAction } from '@mantine/spotlight';
const actions: SpotlightAction[] = Array(100)
.fill(0)
.map((_, index) => ({
title: `Action ${index}`,
onTrigger: () => console.log(`Action ${index}`),
}));
function Demo() {
return (
<SpotlightProvider
actions={actions}
limit={7}
searchPlaceholder="Search..."
shortcut="mod + shift + H"
>
<YourApp />
</SpotlightProvider>
);
}

Register additional actions

You can register any amount of additional actions with registerActions function on use-spotlight hook. To remove actions from the list use removeActions function:

import { useState } from 'react';
import { Group, Button } from '@mantine/core';
import { SpotlightProvider, useSpotlight } from '@mantine/spotlight';
import { Alien, Search } from 'tabler-icons-react';
function SpotlightControls() {
const [registered, setRegistered] = useState(false);
const spotlight = useSpotlight();
return (
<Group position="center">
<Button onClick={spotlight.openSpotlight}>Open spotlight</Button>
{registered ? (
<Button
variant="outline"
color="red"
onClick={() => {
setRegistered(false);
spotlight.removeActions(['secret-action-1', 'secret-action-2']);
}}
>
Remove extra actions
</Button>
) : (
<Button
variant="outline"
onClick={() => {
setRegistered(true);
spotlight.registerActions([
{
id: 'secret-action-1',
title: 'Secret action',
description: 'It was registered with a button click',
icon: <Alien size={18} />,
onTrigger: () => console.log('Secret'),
},
{
id: 'secret-action-2',
title: 'Another secret action',
description: 'You can register multiple actions with just one command',
icon: <Alien size={18} />,
onTrigger: () => console.log('Secret'),
},
]);
}}
>
Register extra actions
</Button>
)}
</Group>
);
}
const actions = [/* ... see in previous demos */];
export function Demo() {
return (
<SpotlightProvider
actions={actions}
searchIcon={<Search size={18} />}
searchPlaceholder="Search..."
shortcut="mod + shift + C"
>
<SpotlightControls />
</SpotlightProvider>
);
}

Group actions

import { SpotlightProvider, SpotlightAction } from '@mantine/spotlight';
const onTrigger = () => {};
const actions: SpotlightAction[] = [
{ title: 'Home', group: 'main', onTrigger },
{ title: 'Docs', group: 'main', onTrigger },
{ title: 'Dashboard', group: 'main', onTrigger },
{ title: 'Component: Tabs', group: 'search', onTrigger },
{ title: 'Component: SegmentedControl', group: 'search', onTrigger },
{ title: 'Component: Button', group: 'search', onTrigger },
];
function Demo() {
return (
<SpotlightProvider
actions={actions}
searchPlaceholder="Search..."
shortcut="mod + shift + V"
>
<YourApp />
</SpotlightProvider>
);
}

Custom action component

You can provide custom component to render actions, this feature can be used to customize how actions are displayed:

import { createStyles, UnstyledButton, Group, Text, Image, Center, Badge } from '@mantine/core';
import { SpotlightProvider, SpotlightAction, SpotlightActionProps } from '@mantine/spotlight';
const actions: SpotlightAction[] = [
{
image: 'https://img.icons8.com/clouds/256/000000/futurama-bender.png',
title: 'Bender Bending Rodríguez',
description: 'Fascinated with cooking, though has no sense of taste',
new: true,
onTrigger: () => {},
},
{
image: 'https://img.icons8.com/clouds/256/000000/futurama-mom.png',
title: 'Carol Miller',
description: 'One of the richest people on Earth',
new: false,
onTrigger: () => {},
},
{
image: 'https://img.icons8.com/clouds/256/000000/homer-simpson.png',
title: 'Homer Simpson',
description: 'Overweight, lazy, and often ignorant',
new: false,
onTrigger: () => {},
},
{
image: 'https://img.icons8.com/clouds/256/000000/spongebob-squarepants.png',
title: 'Spongebob Squarepants',
description: 'Not just a sponge',
new: false,
onTrigger: () => {},
},
];
const useStyles = createStyles((theme) => ({
action: {
position: 'relative',
display: 'block',
width: '100%',
padding: '10px 12px',
borderRadius: theme.radius.sm,
},
actionHovered: {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[1],
},
actionBody: {
flex: 1,
},
}));
function CustomAction({
action,
styles,
classNames,
hovered,
onTrigger,
...others
}: SpotlightActionProps) {
const { classes, cx } = useStyles(null, { styles, classNames, name: 'Spotlight' });
return (
<UnstyledButton
className={cx(classes.action, { [classes.actionHovered]: hovered })}
tabIndex={-1}
onMouseDown={(event) => event.preventDefault()}
onClick={onTrigger}
{...others}
>
<Group noWrap>
{action.image && (
<Center>
<Image src={action.image} alt={action.title} width={50} height={50} />
</Center>
)}
<div className={classes.actionBody}>
<Text>{action.title}</Text>
{action.description && (
<Text color="dimmed" size="xs">
{action.description}
</Text>
)}
</div>
{action.new && <Badge>new</Badge>}
</Group>
</UnstyledButton>
);
}
function Demo() {
return (
<SpotlightProvider
actions={actions}
actionComponent={CustomAction}
searchPlaceholder="Search..."
shortcut="mod + shift + I"
>
<YourApp />
</SpotlightProvider>
);
}

Custom actions wrapper component

With custom actions wrapper component you can customize how actions list is rendered, for example, you can add a footer:

import { SpotlightProvider } from '@mantine/spotlight';
import { Group, Text, Anchor } from '@mantine/core';
function ActionsWrapper({ children }: { children: React.ReactNode }) {
return (
<div>
{children}
<Group
position="apart"
px={15}
py="xs"
sx={(theme) => ({
borderTop: `1px solid ${
theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[2]
}`,
})}
>
<Text size="xs" color="dimmed">
Search powered by Mantine
</Text>
<Anchor size="xs" href="#">
Learn more
</Anchor>
</Group>
</div>
);
}
function Demo() {
return (
<SpotlightProvider
shortcut="mod + alt + T"
nothingFoundMessage="Nothing found..."
actionsWrapperComponent={ActionsWrapper}
{...otherProps}
>
<YourApp />
</SpotlightProvider>
);
}

Close spotlight on action trigger

By default, spotlight will be closed with any action is triggered with mouse click or Enter key. To change this behavior set closeOnActionTrigger={false} prop on SpotlightProvider:

import { SpotlightProvider } from '@mantine/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + shift + G"
closeOnActionTrigger={false}
{...otherProps}
>
<YourApp />
</SpotlightProvider>
);
}

Highlight query

Default action component supports highlighting search query with Highlight component. To enable this option set highlightQuery on SpotlightProvider:

import { SpotlightProvider } from '@mantine/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + alt + L"
highlightQuery
{...otherProps}
>
<YourApp />
</SpotlightProvider>
);
}

Change transitions

Component presence is animated with Transition component, it supports all premade and custom transitions. To change transition set the following properties:

  • transition – premade transition name or custom transition object
  • transitionDuration – transition duration in ms
import { SpotlightProvider } from '@mantine/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + shift + 2"
transitionDuration={300}
transition="slide-down"
{...otherProps}
>
<YourApp />
</SpotlightProvider>
);
}

To disable transitions set transitionDuration={0}:

import { SpotlightProvider } from '@mantine/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + shift + 2"
transitionDuration={0}
{...otherProps}
>
<YourApp />
</SpotlightProvider>
);
}