Svelte-spotlight
Headless spotlight component for Svelte
Build your site global search box in minutes
- Bring your own style, completely headless. Svelte-spotlight only handle the layout.
- Search method agnostic, local or asynchronous.
- Data agnostic, render flat lists or grouped results. Integrate perfectly with Algolia multi-index search.
- Great DX with full TS support
- 9 slots to customize every part you need
- Animate it as you wish
- Keyboard shortcut and navigation
- Accessible (but can be better)
- No dependencies
Anatomy
Overall structure of svelte-spotlight
Props
Name | description | type | defaultValue |
---|---|---|---|
isOpen |
Whether to spotlight is open | boolean | false |
results |
A flat array of result or a one level deep array of grouped results | R[] | [] |
groupResultsKey |
If the results are grouped, you want to define the key of the nested results | GK extends ConditionalKeys<R, any[]> | |
groupIdKey |
The key to find the ID of a group in order to keyed the each loop | ConditionalKeys<R, string | number> | |
resultIdKey |
The key to find the ID of a result in order to keyed the each loop | GroupedResult extends string ? ConditionalKeys<R, string | number> : ConditionalKeys<GroupedResult, string | number> | |
cleanQueryOnClose |
Whether to clean query on close. This option also clean the preselected result and the selected result | boolean | false |
distanceFromTop |
The top distance where the spotlight will be fixed | number | 100 |
searchPlaceholder |
The input placeholder | string | Search |
query |
The current value of the search input | string | "" |
preSelectedResult |
The current result that is preselected by the keyboard navigation | Result | |
selectedResult |
The current selected result either after hitting "Enter" or by clicking on it | Result | |
isFocused |
Whether the search input is focused | boolean | false |
listElement |
HTML element type for the the result list | string | ul |
resultElement |
HTML element type for the result element | string | li |
Animations
Name | description | defaultValue |
---|---|---|
overlayTransition |
The svelte-transition to animate the overlay | fade |
overlayTransitionConfig |
Config for the overlay aimation | { duration : 200 } |
modalTransitionIn |
The svelte-transition to animate the modal on enter | scale |
modalTransitionInConfig |
Config for the modal enter aimation | { start: 0.95 } |
modalTransitionOut |
The svelte-transition to animate the modal on exit | scale |
modalTransitionOutConfig |
Config for the modal exit aimation | { start: 0.95 } |
Classes
Name | description | type |
---|---|---|
overlayClass |
The overlay class | string |
modalClass |
The modal class | string |
headerClass |
The modal header class | string |
inputClass |
The input class | string |
contentClass |
The modal content container class | string |
resultsClass |
The modal results container class | string |
footerClass |
The modal footer class | string |
Dynamic slots
Name | description | type |
---|---|---|
headerCenterComponent |
Optional header center component to replace the default input. We don't use a slot here because it's impossible to define a conditional slot, and you don't want to display this component all the time. It's useful if you want to simulate some sort of navigation inside the spotlight component | Svelte component |
contentComponent |
Optional content component to replace the default result list. We don't use a slot here because it's impossible to define a conditional slot, and you don't want to display this component all the time. It's useful if you want to simulate some sort of navigation inside the spotlight component | Svelte component |
Slots
Default available slot props
Name | description | type | defaultValue |
---|---|---|---|
selectedResult |
The current selected result either after hitting "Enter" or by clicking on it | Result | undefined |
preSelectedResult |
The current result that is preselected by the keyboard navigation | Result | undefined |
noResults |
If search results list is empty | boolean | false |
query |
The current value of the search input | string | '' |
headerLeft
Use it to render a search icon or spinner to the left of the input.
Name | type |
---|---|
group |
R |
groupIndex |
number |
headerRight
Use it to render a close icon or a clear query button.
contentTop
Render inside the result container but before the results list, you can use it to display a filtering menu.
contentBottom
Render inside the result container but after the results list, you can use it to display a load more button.
emptySearch
Render when query is empty, useful to display some hints. If not provided the results list is shown (or if no result, the noResults slot)
noResults
Render when results list is empty.
groupHeader
If results are grouped you can use this slot to provide segmentation infos.
result
Slot to display the result.
Name | type |
---|---|
result |
Result |
selected |
boolean |
index |
number |
sidePanel
Side panel to render to the right of the results container. You can use it to render information about the current preselected result like Raycast does.
Name | type |
---|---|
maxHeight |
number |
footer
Render to the bottom of the modal. You can display some hints here too or the famous 'Search by Algolia' button
trigger
Slot to render a button that trigger the modal to open.
Name | type | descrition |
---|---|---|
toggle |
() => void | undefined |
Basic example
<script lang="ts">
import SvelteSpotlight from 'svelte-spotlight/SvelteSpotlight.svelte';
import { matchSorter } from 'match-sorter';
let query = "";
let items = [
{
title: 'Hit 1',
description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. ',
},
{
title: 'Hit 2',
description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. ',
},
{
title: 'Hit 3',
description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. ',
},
{
title: 'Hit 4',
description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. ',
}
];
$: results = matchSorter(items, query, { keys: ['title'] });
</script>
<SvelteSpotlight
{results}
bind:query
modalClass={'w-[600px] max-w-[95%] bg-white shadow-lg rounded-sm'}
headerClass={'py-3 px-10 border-b-2 border-slate-100 border-b-solid'}
inputClass="focus:outline-none bg-transparent"
resultIdKey="title"
on:select={(event) => {
// DO stuff
}}
>
<div slot="result" let:selected let:result class={"hover:bg-slate-100 cursor-pointer text-sm px-10 py-3 w-full" + selected ? "bg-slate-100" : ''}>
{result.title}
<p class="text-slate-500 text-sm">{result.description}</p>
</div>
<div slot="noResults" class="px-10 py-3 text-slate-500 text-sm">
No results...
</div>
</SvelteSpotlight>