Solid List Utility

0.3.0 · active · verified Sun Apr 19

solid-list is a SolidJS utility designed to simplify the creation of accessible and keyboard-navigable lists, suitable for components like search results, custom selects, or autocompletes. It supports both vertical and horizontal orientations, single and multi-selection patterns, and integrates with `rtl` and `ltr` text directions. The library is unopinionated about styling and works with various list implementations, including virtualized lists, by providing utilities rather than prescriptive components. Key features include optional looping behavior, a 'Vim mode' for navigation, and configurable tab key handling. It is currently at version 0.3.0 and is part of the `corvu` collection of unstyled UI primitives for SolidJS. This suggests active development and a release cadence aligned with other `corvu` packages, which generally see updates every few weeks or months. Its primary differentiator is its strong focus on accessibility and keyboard navigation capabilities without imposing any specific UI or styling opinions on the developer.

Common errors

Warnings

Install

Imports

Quickstart

This example demonstrates how to create a simple, keyboard-navigable search results list using `createList`. It includes basic reactivity for filtering results and correct event handling for keyboard and mouse interaction, along with ARIA attributes for accessibility.

import { createSignal, For } from 'solid-js';
import { createList } from 'solid-list';

const Search = () => {
  const [results, setResults] = createSignal([
    { id: '1', name: 'Apple', href: '/products/apple' },
    { id: '2', name: 'Banana', href: '/products/banana' },
    { id: '3', name: 'Cherry', href: '/products/cherry' },
    { id: '4', name: 'Date', href: '/products/date' }
  ]);

  const { active, setActive, onKeyDown } = createList({
    items: () => results().map(result => result.id), // required, should be a reactive accessor
    initialActive: null, // default, T | null
    orientation: 'vertical', // default, 'vertical' | 'horizontal'
    loop: true, // default
    textDirection: 'ltr', // default, 'ltr' | 'rtl'
    handleTab: true, // default
    vimMode: false, // default
    onActiveChange: (newActiveId) => {
      console.log('Active item changed to:', newActiveId);
      // Optionally, scroll into view or navigate
    }
  });

  const handleInput = (e) => {
    const query = e.target.value.toLowerCase();
    setResults([
      { id: '1', name: 'Apple', href: '/products/apple' },
      { id: '2', name: 'Banana', href: '/products/banana' },
      { id: '3', name: 'Cherry', href: '/products/cherry' },
      { id: '4', name: 'Date', href: '/products/date' }
    ].filter(item => item.name.toLowerCase().includes(query)));
  };

  return (
    <div class="search-container">
      <input
        type="text"
        placeholder="Search fruits..."
        onInput={handleInput}
        onKeyDown={onKeyDown}
        aria-controls="search-results-list"
        aria-expanded={results().length > 0}
        role="combobox"
      />
      {results().length > 0 && (
        <ul id="search-results-list" role="listbox">
          <For each={results()}>
            {(item) => (
              <li
                id={`item-${item.id}`}
                role="option"
                aria-selected={active() === item.id}
                tabIndex={active() === item.id ? 0 : -1}
                onClick={() => setActive(item.id)}
                onMouseEnter={() => setActive(item.id)} // for mouse navigation
              >
                <a href={item.href} tabIndex={-1}>{item.name}</a>
              </li>
            )}
          </For>
        </ul>
      )}
    </div>
  );
};

view raw JSON →