Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/huntabyte/cmdk-sv

cmdk, but for Svelte ✨
https://github.com/huntabyte/cmdk-sv

Last synced: about 7 hours ago
JSON representation

cmdk, but for Svelte ✨

Awesome Lists containing this project

README

        



# ⌘K-sv [![cmdk package version](https://img.shields.io/npm/v/cmdk-sv.svg?colorB=green)](https://www.npmjs.com/package/cmdk-sv)

[![npm version](https://flat.badgen.net/npm/v/cmdk-sv?color=green)](https://npmjs.com/package/cmdk-sv)
[![npm downloads](https://flat.badgen.net/npm/dm/cmdk-sv?color=green)](https://npmjs.com/package/cmdk-sv)
[![license](https://flat.badgen.net/github/license/huntabyte/cmdk-sv?color=green)](https://github.com/huntabyte/cmdk-sv/blob/main/LICENSE)

A port of [cmdk](https://cmdk.paco.me), to Svelte.

⌘K-sv is a command menu Svelte component that can also be used as an accessible combobox. You render items, it filters and sorts them automatically.

Demo and examples: [cmdk-sv.com](https://cmdk-sv.com)

## Install

```bash
npm install cmdk-sv
```

## Use

```svelte

import { Command } from 'cmdk-sv';



No results found.


a
b

c

Apple

```

Or in a dialog:

```svelte

import { Command } from 'cmdk-sv';



No results found.


a
b

c

Apple

```

## Styling

Each part has a specific data-attribute (starting with `data-cmdk-`) that can be used for styling.

### Command `[cmdk-root]`

Render this to show the command menu inline, or use [Dialog](#dialog-cmdk-dialog-cmdk-overlay) to render in a elevated context. Can be controlled by binding to the `value` prop.

```svelte

import { Command } from 'cmdk-sv';

let value = 'apple';



Orange
Apple

```

By default, this uses a scoring algorithm to filter and rank items based on the user's search input.
The algorithm takes into account various factors like continuous matches, word and character jumps among other things.

You can provide a custom `filter` function that is called to rank each item. Both strings are normalized as lowercase and trimmed.

The following example implements a strict substring match:

```svelte
{
if (value.includes(search)) return 1;
return 0;
}}
/>
```

In this strict substring match example, the filter function returns a score of 1 if the item's value contains the search string as a substring, and 0 otherwise, removing it from the result list.

Or disable filtering and sorting entirely:

```svelte


{#each filteredItems as item}

{item}

{/each}

```

You can make the arrow keys wrap around the list (when you reach the end, it goes back to the first item) by setting the `loop` prop:

```svelte

```

This component also exposes two additional slot props for `state` (the current reactive value of the command state) and `stateStore` (the underlying writable state store). These can be used to implement more advanced use cases, such as debouncing the search updates with the `stateStore.updateState` method:

```svelte

{@const handleUpdateState = debounce(stateStore.updateState, 200)}

```

### Dialog `[cmdk-dialog]` `[cmdk-overlay]`

Props are forwarded to [Command](#command-cmdk-root). Composes Bits UI's Dialog component. The overlay is always rendered. See the [Bits Documentation](https://bits-ui.com/docs/) for more information. Can be controlled by binding to the `open` prop.

```svelte

let open = false;
let value: string;

```

You can provide a `portal` prop that accepts an HTML element that is forwarded to Bits UI's Dialog Portal component to specify which element the Dialog should portal into (defaults to `body`). To disable portalling, pass `null` as the `portal` prop.

```svelte

```

### Input `[cmdk-input]`

All props are forwarded to the underlying `input` element. Can be controlled as a normal input by binding to its `value` prop.

```svelte

import { Command } from 'cmdk-sv';

let search = '';

```

### List `[cmdk-list]`

Contains items and groups. Animate height using the `--cmdk-list-height` CSS variable.

```css
[data-cmdk-list] {
min-height: 300px;
height: var(--cmdk-list-height);
max-height: 500px;
transition: height 100ms ease;
}
```

To scroll item into view earlier near the edges of the viewport, use scroll-padding:

```css
[data-cmdk-list] {
scroll-padding-block-start: 8px;
scroll-padding-block-end: 8px;
}
```

### Item `[cmdk-item]` `[data-disabled?]` `[data-selected?]`

Item that becomes active on pointer enter. You should provide a unique `value` for each item, but it will be automatically inferred from the `.textContent` if you don't. Text content is normalized as lowercase and trimmed.

```svelte
{
console.log('Selected', value);
// Value is implicity "apple" because of the provided text content
}}
>
Apple

```

You can force an item to always render, regardless of filtering, by passing the `alwaysRender` prop.

### Group `[cmdk-group]` `[hidden?]`

Groups items together with the given `heading` (`[cmdk-group-heading]`).

```svelte

Apple

```

Groups will not be removed from the DOM, rather the `hidden` attribute is applied to hide it from view. This may be relevant in your styling.

You can force a group to always be visible, regardless of filtering, by passing the `alwaysRender` prop.

### Separator `[cmdk-separator]`

Visible when the search query is empty or `alwaysRender` is true, hidden otherwise.

### Empty `[cmdk-empty]`

Automatically renders when there are no results for the search query.

### Loading `[cmdk-loading]`

You should conditionally render this with `progress` while loading asynchronous items.

```svelte

import { Command } from 'cmdk-sv';

let loading = false;

{#if loading}
Loading…
{/if}
;
```

### `createState(initialState?: State)`

Create a state store which can be passed and used by the component. This is provided for more advanced use cases and should not be commonly used.

A good use case would be to render a more detailed empty state, like so:

```svelte

import { Command, createState } from 'cmdk-sv';

const state = createState();


{#if $state.search}
No results found for "{state.search}".
{:else}
No results found.
{/if}

```

## Examples

Code snippets for common use cases.

### Nested items

Often selecting one item should navigate deeper, with a more refined set of items. For example selecting "Change theme…" should show new items "Dark theme" and "Light theme". We call these sets of items "pages", and they can be implemented with simple state:

```svelte

let open = false;
let search = '';
let pages: string[] = [];
let page: string | undefined = undefined;

$: page = pages[pages.length - 1];

function changePage(newPage: string) {
pages = [...pages, newPage];
}

{
// Escape goes to previous page
// Backspace goes to previous page when search is empty
if (e.key === 'Escape' || (e.key === 'Backspace' && !search)) {
e.preventDefault();
const newPages = pages.slice(0, -1);
pages = newPages;
}
}}
>


{#if !page}
changePage('projects')}>Search projects…
changePage('teams')}>Join a team…
{:else if page === 'projects'}
Project A
Project B
{:else if page === 'teams'}
Team 1
Team 2
{/if}

```

### Show sub-items when searching

If your items have nested sub-items that you only want to reveal when searching, render based on the search state:

```svelte

import { Command } from 'cmdk-sv';

type $$Props = Command.ItemProps & {
search?: string;
};

{#if search}



{/if}
```

Using the state store:

```svelte

import { Command, createState } from 'cmdk-sv';
import SubItem from './SubItem.svelte';
const state = createState();



Change theme…
Change theme to dark
Change theme to light

```

or

Using the input value:

```svelte

import { Command } from 'cmdk-sv';
import SubItem from './SubItem.svelte';
let search: string;



Change theme…
Change theme to dark
Change theme to light

```

### Asynchronous results

Render the items as they become available. Filtering and sorting will happen automatically.

```svelte

import { Command } from 'cmdk-sv';

let loading = false;
let items: string[] = [];

onMount(async () => {
loading = true;
const res = await api.get('/dictionary');
items = res;
loading = false;
});



{#if loading}
Fetching words…
{:else}
{#each items as item}

{item}

{/each}
{/if}

```

### Use inside Popover

We recommend using the [Bits UI popover](https://www.bits-ui.com/docs/components/popover) component. ⌘K-sv relies on the Bits UI Dialog component, so this will reduce the number of dependencies you'll need.

```bash
npm install bits-ui
```

Render `Command` inside of the popover content:

```svelte

import { Command } from 'cmdk-sv';
import { Popover } from 'bits-ui';

Toggle popover





Apple


```

### Drop in stylesheets

You can find global stylesheets to drop in as a starting point for styling. See [src/styles/cmdk](src/styles/cmdk) for examples.

### Render Delegation

Each of the components (except the dialog) accept an `asChild` prop that can be used to render a custom element in place of the default. When using this prop, you'll need to check the components slot props to see what attributes & actions you'll need to pass to your custom element.

Components that contain only a single element will just have `attrs` & `action` slot props, or just `attrs`. Components that contain multiple elements will have an `attrs` and possibly an `actions` object whose properties are the attributes and actions for each element.

## FAQ

**Accessible?** Yes. Labeling, aria attributes, and DOM ordering tested with Voice Over and Chrome DevTools. [Dialog](#dialog-cmdk-dialog-cmdk-overlay) composes an accessible Dialog implementation.

**Filter/sort items manually?** Yes. Pass `shouldFilter={false}` to [Command](#command-cmdk-root). Better memory usage and performance. Bring your own virtualization this way.

**Unstyled?** Yes, use the listed CSS selectors.

**Weird/wrong behavior?** Make sure your `Command.Item` has a unique `value`.

**Listen for ⌘K automatically?** No, do it yourself to have full control over keybind context.

## History

Written in 2019 by Paco ([@pacocoursey](https://twitter.com/pacocoursey)) to see if a composable combobox API was possible. Used for the Vercel command menu and autocomplete by Rauno ([@raunofreiberg](https://twitter.com/raunofreiberg)) in 2020. Re-written independently in 2022 with a simpler and more performant approach. Ideas and help from Shu ([@shuding\_](https://twitter.com/shuding_)).

Ported to Svelte in 2023 by Huntabyte ([@huntabyte](https://twitter.com/huntabyte))

## Sponsors

This project is supported by the following beautiful people/organizations:



Logos from Sponsors

## License

Published under the [MIT](https://github.com/huntabyte/cmdk-sv/blob/main/LICENSE) license.
Made by [@huntabyte](https://github.com/huntabyte) and [community](https://github.com/huntabyte/cmdk-sv/graphs/contributors) 💛