List
A small utility to create accessible, keyboard navigable lists like search results, selects or autocompletes.
Features Section titled Features
- Vertical and horizontal lists
- Utilities for both single and multi select lists
- Supports both rtl and ltr text directions
- Is unopinonated and works with any kind of lists, even virtual lists
- Optionally loops, vim mode and handles tab key
Installation Section titled Installation
npm install solid-list
Usage Section titled Usage
import { createList, createMultiList } from 'solid-list'
const Search = () => {
const [results, setResults] = createSignal([])
const { active, setActive, onKeyDown } = createList({
items: () => results().map(result => result.id), // required
initialActive: null, // default, T | null
orientation: 'vertical', // default, 'vertical' | 'horizontal'
loop: true, // default
textDirection: 'ltr', // default, 'ltr' | 'rtl'
handleTab: false, // default = true
vimMode: false, // default
onActiveChange: (active) => {} // optional callback to handle changes
})
return (
<>
<input onKeyDown={onKeyDown} />
<For each={result}>
{(item, index) => (
<a href={result.href} aria-selected={active() === index()}>{result.name}<a>
)}
</For>
</>
)
}
Multi select list Section titled Multi select list
A tabbable multi select list with keyboard navigation and selection.
Rook
Chough
Raven
Jackdaw
Magpie
Jay
import { createEffect, createSignal, For, onCleanup } from 'solid-js'
import clsx from 'clsx'
import { createMultiList } from 'solid-list'
const ITEMS = ['Rook', 'Chough', 'Raven', 'Jackdaw', 'Magpie', 'Jay']
const MultiListExample = () => {
const [refs, setRefs] = createSignal<HTMLDivElement[]>([])
const {
cursor,
active,
setCursorActive,
selected,
toggleSelected,
onKeyDown,
} = createMultiList({
items: ITEMS,
vimMode: true,
onCursorChange: (item) => {
if (item === null) return
refs()[ITEMS.indexOf(item)]?.focus()
},
})
createEffect(() => {
document.addEventListener('mousedown', onMouseDown)
onCleanup(() => document.removeEventListener('mousedown', onMouseDown))
})
const onMouseDown = () => setCursorActive(null)
return (
<div class="flex w-full flex-col items-center space-y-2 p-5 md:px-10">
<For each={ITEMS}>
{(crow) => (
<div
ref={(ref) => setRefs((refs) => [...refs, ref])}
role="checkbox"
aria-checked={selected().includes(crow)}
tabindex="0"
onFocus={() => {
if (cursor() !== null) return
setCursorActive(crow)
}}
onKeyDown={(e) => {
if (e.key === 'x' || e.key === ' ' || e.key === 'Enter') {
toggleSelected(crow)
e.preventDefault()
return
}
onKeyDown(e)
}}
onClick={() => {
setCursorActive(crow)
toggleSelected(crow)
}}
class={clsx(
'flex w-full max-w-80 cursor-pointer items-center space-x-2 rounded-md px-4 py-2 transition-all hover:scale-[1.01] hover:bg-corvu-200 focus:outline-none',
{
'bg-corvu-bg': !active().includes(crow),
'bg-corvu-200 scale-[1.01]': active().includes(crow),
},
)}
>
<div class="flex size-6 items-center justify-center rounded border-2 border-corvu-300">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 22 22"
class={clsx('size-3 transition-all', {
'opacity-0': !selected().includes(crow),
'opacity-100': selected().includes(crow),
})}
>
<path
fill="currentColor"
d="M7.868 13.72 3.546 9.398a2.078 2.078 0 1 0-2.937 2.938l5.79 5.79a2.072 2.072 0 0 0 2.264.45c.252-.104.481-.258.674-.45L20.918 6.544a2.077 2.077 0 0 0-2.938-2.938L7.868 13.72Z"
/>
</svg>
</div>
<p>{crow}</p>
</div>
)}
</For>
</div>
)
}
export default MultiListExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
API reference Section titled API reference
Props
Property | Default | Type/Description |
---|---|---|
items | - | MaybeAccessor<T[]> The items in the list. Should be in the same order as they appear in the DOM. |
initialActive | null | null | T The initially active item. |
orientation | 'vertical' | MaybeAccessor<'vertical' | 'horizontal'> The orientation of the list. |
loop | true | MaybeAccessor<boolean> Whether the list should loop. |
textDirection | 'ltr' | MaybeAccessor<'ltr' | 'rtl'> The text direction of the list. |
handleTab | true | MaybeAccessor<boolean> Whether tab key presses should be handled. |
vimMode | false | MaybeAccessor<boolean> Whether vim movement key bindings should be used additionally to arrow key navigation. |
vimKeys | { up: 'k', down: 'j', right: 'l', left: 'h' } | MaybeAccessor<{ down: string, left: string, right: string, up: string, }> The vim movement key bindings to use. |
onActiveChange | - | (active: null | T) => void Callback fired when the active item changes. |
Returns
Property | Type/Description |
---|---|
active | Accessor<null | T> |
setActive | Setter<null | T> |
onKeyDown | (event: KeyboardEvent) => void |
Props
Property | Default | Type/Description |
---|---|---|
items | - | MaybeAccessor<T[]> The items in the list. Should be in the same order as they appear in the DOM. |
initialCursor | null | null | T The initially focused item. |
initialActive | [] | T[] The initially active items. |
initialSelected | [] | T[] The initially selected items. |
orientation | 'vertical' | MaybeAccessor<'vertical' | 'horizontal'> The orientation of the list. |
loop | true | MaybeAccessor<boolean> Whether the list should loop. |
textDirection | 'ltr' | MaybeAccessor<'ltr' | 'rtl'> The text direction of the list. |
handleTab | true | MaybeAccessor<boolean> Whether tab key presses should be handled. |
vimMode | false | MaybeAccessor<boolean> Whether vim movement key bindings should be used additionally to arrow key navigation. |
vimKeys | { up: 'k', down: 'j', right: 'l', left: 'h' } | MaybeAccessor<{ down: string, left: string, right: string, up: string, }> The vim movement key bindings to use. |
onCursorChange | - | (cursor: null | T) => void Callback fired when the cursor changes. |
onActiveChange | - | (active: T[]) => void Callback fired when the active items change. |
onSelectedChange | - | (selected: T[]) => void Callback fired when the selected items change. |
Returns
Property | Type/Description |
---|---|
cursor | Accessor<null | T> |
setCursor | Setter<null | T> |
active | Accessor<T[]> |
setActive | Setter<T[]> |
setCursorActive | (item: null | T) => void |
selected | Accessor<T[]> |
setSelected | Setter<T[]> |
toggleSelected | (item: T) => void |
onKeyDown | (event: KeyboardEvent) => void |
Developed and designed by Jasmin