Skip to Content
Spotlight ALPHA

Spotlight

Command menu for search and application navigation.

#

Documentation

#

Usage

Use Spotlight for a global search or command palette. It opens with Cmd/Ctrl+K, and SpotlightTrigger renders a search button that opens it too.

Spotlight takes the same options as Menu: group them with group, navigate with href, or run an action with execute. Typing in the search input filters the options.

"use client"; import { type MenuOption, toaster } from "@optiaxiom/react"; import { Spotlight, SpotlightContent, SpotlightTrigger, } from "@optiaxiom/react/unstable"; import { useMemo } from "react"; const groups = { actions: { label: "Actions" }, pages: { label: "Pages" }, } satisfies Record<string, MenuOption["group"]>; export function App() { return ( <Spotlight options={useMemo<MenuOption[]>( () => [ { group: groups.pages, href: "../", label: "Components" }, { group: groups.pages, href: "../../", label: "Home" }, { execute: () => toaster.create("Created a project"), group: groups.actions, label: "Create project", }, { execute: () => toaster.create("Invited a member"), group: groups.actions, label: "Invite member", }, ], [], )} > <SpotlightTrigger>Search</SpotlightTrigger> <SpotlightContent /> </Spotlight> ); }

#

Anatomy

import { Spotlight, SpotlightContent, SpotlightTrigger, } from "@optiaxiom/react/unstable"; export default () => ( <Spotlight> <SpotlightTrigger /> <SpotlightContent /> </Spotlight> );

#

Recent items

Use the visible callback on options to show recent items while the search input is empty, and search results once the user types.

"use client"; import { type MenuOption, toaster } from "@optiaxiom/react"; import { Spotlight, SpotlightContent, SpotlightTrigger, } from "@optiaxiom/react/unstable"; import { useMemo } from "react"; const groups = { projects: { label: "Projects" }, recent: { label: "Recent" }, } satisfies Record<string, MenuOption["group"]>; const projects = ["Atlas", "Beacon", "Comet", "Delta", "Echo"]; const recent = ["Comet", "Atlas"]; export function App() { return ( <Spotlight options={useMemo( () => [ ...recent.map<MenuOption>((name) => ({ execute: () => toaster.create(`Opened ${name}`), group: groups.recent, key: `recent-${name}`, label: name, visible: ({ inputValue }) => !inputValue, })), ...projects.map<MenuOption>((name) => ({ execute: () => toaster.create(`Opened ${name}`), group: groups.projects, key: `project-${name}`, label: name, visible: ({ inputValue }) => inputValue ? name.toLowerCase().includes(inputValue.toLowerCase()) : false, })), ], [], )} > <SpotlightTrigger>Search projects</SpotlightTrigger> <SpotlightContent /> </Spotlight> ); }

#

Responsive trigger

A Spotlight can have more than one SpotlightTrigger. Leave out children (and give it an aria-label) for an icon-only trigger, then use responsive display to show the icon on small screens and the full search button on larger ones. Resize the window to see the triggers swap.

"use client"; import { Kbd, type MenuOption } from "@optiaxiom/react"; import { Spotlight, SpotlightContent, SpotlightTrigger, } from "@optiaxiom/react/unstable"; import { useMemo } from "react"; export function App() { return ( <Spotlight options={useMemo<MenuOption[]>( () => [ { href: "../", label: "Components" }, { href: "../../", label: "Home" }, ], [], )} > <SpotlightTrigger aria-label="Search" display={["flex", "none"]} /> <SpotlightTrigger addonAfter={ <Kbd color="fg.tertiary" ml="auto" modifiers="mod" variant="subtle"> K </Kbd> } display={["none", "flex"]} w="224" > Search </SpotlightTrigger> <SpotlightContent /> </Spotlight> ); }

Menu

Dropdown menu for displaying actions.

#

Props

#

Spotlight

import { Spotlight } from "@optiaxiom/react/unstable";

Doesn't render its own HTML element.

Prop

children*

ReactNode

defaultOpen

The initial open state in uncontrolled mode.

false | true

disableHotkey

Turn off the Cmd/Ctrl+K hotkey, such as when another Spotlight on the page owns it.

false | true

empty

Custom empty state content.

ReactNode

inputValue

The input value in controlled mode.

string

loading

Whether to show loading spinner or skeleton items inside the menu.

false | true | "both" | "spinner" | "skeleton"

onInputValueChange

Handler that is called when input value changes.

(inputValue: string) => void

onOpenChange

Handler that is called when the open state changes.

(open: boolean) => void

open

The open state in controlled mode.

A controlled menu doesn’t close when an item is picked: close it from the item’s execute when its dismiss argument is true. This lets an item keep the menu open, such as while an async create finishes.

false | true

options*

The items we want to render.

CommandOption[] | readonly CommandOption[]

placeholder

The placeholder for the search input. The default is translated for the AxiomProvider locale.

string

Default: Filter...

#

SpotlightTrigger

import { SpotlightTrigger } from "@optiaxiom/react/unstable";

Supports all Box props in addition to its own. Renders a <div> element.

Prop

addonAfter

Display content inside the button after children.

ReactNode

addonBefore

Display content inside the button before children.

ReactNode

appearance

Control the appearance by selecting between the different button types.

"default" | "danger" | "primary" | "subtle" | "danger-outline" | "default-opal" | "inverse" | "primary-opal"

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

children

ReactNode

className

string

disabled

Whether the button is disabled.

false | true

icon

Display an icon before or after the button content or omit children to only show the icon.

ReactNode

Default: <IconSearch />

iconPosition

Control whether to show the icon before or after the button content.

"end" | "start"

loading

Show a loading spinner and mark the button aria-busy. The button stays focusable and clickable: don’t also set disabled, which drops keyboard focus mid-action, and ignore repeat clicks in onClick if the action shouldn’t run twice. Nothing is announced when loading starts or ends, so tell screen reader users the outcome yourself, e.g. with a toast.

false | true

size

Control the size of the button.

"sm" | "md" | "lg"

square

Whether button should have square shape (an icon-only button with no label).

false | true

type

The default behavior of the button.

"button" | "reset" | "submit"

#

SpotlightContent

import { SpotlightContent } from "@optiaxiom/react/unstable";

Supports all Box props in addition to its own. Renders a <div> element.

Prop

align

"center" | "end" | "start"

alignOffset

number

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

children

ReactNode

className

string

onCloseAutoFocus

Event handler called when auto-focusing on close. Can be prevented.

(event: Event) => void

onEscapeKeyDown

Event handler called when the escape key is down. Can be prevented.

(event: KeyboardEvent) => void

onFocusOutside

Event handler called when the focus moves outside of the DismissableLayer. Can be prevented.

(event: FocusOutsideEvent) => void

onInteractOutside

Event handler called when an interaction happens outside the DismissableLayer. Specifically, when a pointerdown event happens outside or focus moves outside of it. Can be prevented.

(event: PointerDownOutsideEvent | FocusOutsideEvent) => void

onOpenAutoFocus

Event handler called when auto-focusing on open. Can be prevented.

(event: Event) => void

onPointerDownOutside

Event handler called when the a pointerdown event happens outside of the DismissableLayer. Can be prevented.

(event: PointerDownOutsideEvent) => void

side

"bottom" | "left" | "right" | "top"

sideOffset

number

#

Changelog

#

0.5.0

  • Added component
Last updated on