Description:
Headless Option Selection is a JavaScript library for React and Next.js. It separates the logic for complex selection interfaces from the UI. This approach gives you complete control over the appearance and structure of your components.
The library manages the state and interactions for hierarchical data, multi-select dropdowns, and tree-style selectors. You can build custom selection components without writing the underlying state management logic from scratch.
Features
- 🎨 Headless Architecture. You control the entire user interface, which allows you to integrate your own components and styling.
- 🌳 Hierarchical Selection. The library handles nested data structures with parent-child relationships.
- ✅ Bulk Operations. You can use built-in functions to select or deselect all items at once.
- 🔍 Flexible Data Retrieval. You can fetch the list of selected items on demand or use a callback function that runs whenever the selection changes.
- ⚙️ Initial State Configuration. The library accepts a default selection state and correctly calculates a parent’s state based on its children.
Use Cases
- File Explorer UI. You can build an interface for selecting files and folders within a nested directory structure.
- E-commerce Category Filters. It can be used to create product filters with main categories and multiple levels of subcategories.
- Permissions Management System. You can develop a user roles editor where administrators assign permissions and sub-permissions from a tree.
- Hierarchical Tagging Input. It is suitable for creating a tag selection field that organizes tags into groups and subgroups.
How to Use It
1. Add the package to your project using either npm or yarn.
npm install option-selector
yarn add option-select2. Use the useOptionSelect hook in your React component. This hook requires your data, a function to get a unique ID for each item, and an optional callback for when the selection changes.
The following example demonstrates how to build a simple permission selector.
import React, { useCallback } from "react";
import { useOptionSelect, OptionItem } from "option-select";
// Define the structure for a permission item
interface Permission {
id: string;
name: string;
isSelected?: boolean;
subItems?: Permission[];
}
// Sample hierarchical data for permissions
const permissionsData: Permission[] = [
{
id: "admin",
name: "Administrator Access",
subItems: [
{ id: "admin-read", name: "Read Access", isSelected: true },
{ id: "admin-write", name: "Write Access" },
],
},
{
id: "editor",
name: "Editor Access",
subItems: [
{ id: "editor-read", name: "Read Access", isSelected: true },
{ id: "editor-publish", name: "Publish Access", isSelected: true },
],
},
];
// A recursive component to render each permission and its sub-permissions
function PermissionRenderer({ item, isSelected, toggleSelection, subItems }: OptionItem<Permission>) {
return (
<div key={item.id}>
<label>
<input type="checkbox" checked={isSelected} onChange={toggleSelection} />
{item.name}
</label>
{subItems && (
<div style={{ marginLeft: '25px' }}>
{subItems.map((subItem) => PermissionRenderer(subItem))}
</div>
)}
</div>
);
}
export function PermissionSelector() {
const onSelectionChange = useCallback((selectedItems: Permission[]) => {
console.log("Current selected permissions:", selectedItems);
}, []);
const { getAllItems, selectAll, deselectAll, getSelectedItems } = useOptionSelect({
items: permissionsData,
getId: (item) => item.id,
onSelectionChange: onSelectionChange,
});
function logSelectedItems() {
console.log("On-demand selected items:", getSelectedItems());
}
return (
<div>
<button onClick={selectAll}>Select All</button>
<button onClick={deselectAll}>Deselect All</button>
<button onClick={logSelectedItems}>Get Selected Items</button>
{getAllItems().map((permissionItem) => PermissionRenderer(permissionItem))}
</div>
);
}3. Available props for the useOptionSelect hook:
items: This is an array of your data. Each item in the array can have anisSelectedproperty, which defaults tofalse, and an optionalsubItemsarray for creating nested structures.getId: This is a function that takes an item from your data and returns a unique string ID for it. The library uses this ID for internal tracking.onSelectionChange: This is an optional callback function. It runs every time a selection is made or changed and receives the array of currently selected items.
4. The hook returns an object containing several methods to control and retrieve selection data.
getSelectedItems(): This function returns an array containing only the selected items. The array maintains the same hierarchical structure as the original input data.getAllItems(): This function returns all of your items wrapped with selection controls. Each item object includes the original item data, itsisSelectedstatus, atoggleSelectionfunction, and anysubItems.selectAll(): Call this function to select every item in the list, including all nested sub-items.deselectAll(): Call this function to clear the entire selection.
FAQs
Q: What does “headless” mean for this library?
A: Headless means the library provides the selection logic and state management but does not include any UI components. You are responsible for building the visual parts using your own HTML and CSS.
Q: Can I use this with a UI library like Material-UI or Chakra UI?
A: Yes. Since you control all rendering, you can use components from any UI library to construct your selectors.
Q: How does the library determine the selection state of parent items?
A: A parent item’s selection state is calculated from the selection states of its children. If all child items are selected, the parent item will also be selected.
Q: Is it possible to set an initial selection when the component first loads?
A: Yes, you can set isSelected: true on any items in the initial data array that you pass to the useOptionSelect hook.





