Filter List Block
FilterList is a controlled filter panel block built on MUI with accordion groups, selected filter chips, and apply/reset actions.
Installation
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-filter-list-block
yarn add @nodeblocks/frontend-filter-list-block
pnpm add @nodeblocks/frontend-filter-list-block
bun add @nodeblocks/frontend-filter-list-block
What You Need
| Item | Why it matters |
|---|---|
data | Single source of truth for selected filters (data.filters) |
onDataChange | Receives updated state + metadata when filters change |
options | Defines accordion groups, checkbox options, and optional maxFilters caps |
labels (optional) | Overrides default Apply/Reset button text |
FilterList does not own filter state. Keep state in your app and pass it through data.
Code Examples
- Quick Start
- Labels
- Compound Components
- Block Override
function Example() { const filterOptions = [ { maxFilters: 2, groupName: 'Category', subtitle: 'Filter by product category', options: [ {key: 'category', value: 'electronics', label: 'Electronics'}, {key: 'category', value: 'clothing', label: 'Clothing'}, {key: 'category', value: 'books', label: 'Books'}, {key: 'category', value: 'home-garden', label: 'Home & Garden'}, ], }, { maxFilters: 3, groupName: 'Price Range', subtitle: 'Select your budget', options: [ {key: 'price', value: 'under-25', label: 'Under $25', layout: 'full-width'}, {key: 'price', value: '25-50', label: '$25 - $50'}, {key: 'price', value: '50-100', label: '$50 - $100'}, {key: 'price', value: 'over-100', label: 'Over $100'}, ], }, ]; const defaultData = {filters: []}; const [filters, setFilters] = React.useState(defaultData); const handleDataChange = nextData => { setFilters(nextData); }; return <FilterList data={filters} options={filterOptions} onDataChange={handleDataChange} />; }
function Example() { const filterOptions = [ { groupName: 'Brand', subtitle: 'Filter by brand', options: [ {key: 'brand', value: 'apple', label: 'Apple'}, {key: 'brand', value: 'samsung', label: 'Samsung'}, {key: 'brand', value: 'sony', label: 'Sony'}, ], }, ]; const defaultData = {filters: []}; const [data, setData] = React.useState(defaultData); const labels = { submitButton: 'Search now', resetButton: 'Start over', }; return <FilterList data={data} options={filterOptions} labels={labels} onDataChange={setData} />; }
Use child blocks to customize layout while keeping controlled state behavior.
function Example() { const filterOptions = [ { groupName: 'Category', subtitle: 'Filter by product category', options: [ {key: 'category', value: 'electronics', label: 'Electronics'}, {key: 'category', value: 'clothing', label: 'Clothing'}, ], }, { groupName: 'Price Range', subtitle: 'Select your budget', options: [ {key: 'price', value: 'under-25', label: 'Under $25'}, {key: 'price', value: '25-50', label: '$25 - $50'}, ], }, ]; const defaultData = {filters: []}; const [data, setData] = React.useState(defaultData); return ( <FilterList data={data} options={filterOptions} onDataChange={setData}> <FilterList.SelectedFilterList /> <FilterList.AccordionList sx={{ p: 2, mt: 1, bgcolor: 'background.paper', border: 2, borderColor: 'secondary.main', borderRadius: 3, boxShadow: 1, '& .nbb-filter-list-accordion': { mb: 1.5, border: 1, borderColor: 'secondary.light', borderRadius: 2, overflow: 'hidden', '&:before': {display: 'none'}, '&.Mui-expanded': { borderColor: 'secondary.main', boxShadow: 2, }, }, '& .MuiAccordionSummary-root': { bgcolor: 'secondary.light', minHeight: 52, fontWeight: 600, '&.Mui-expanded': { bgcolor: 'secondary.main', color: 'secondary.contrastText', }, }, '& .nbb-filter-list-checkbox-field': { borderRadius: 1, '&:hover': { bgcolor: 'action.selected', }, '& .MuiCheckbox-root.Mui-checked': { color: 'secondary.main', }, }, }} /> <FilterList.Actions sx={{ p: 2.5, mt: 2, bgcolor: 'secondary.light', borderRadius: 3, '& .nbb-filter-list-reset-button': { flex: 1, minHeight: 44, borderRadius: 2, borderWidth: 2, borderColor: 'secondary.main', color: 'secondary.main', bgcolor: 'background.paper', fontWeight: 600, textTransform: 'none', '&:hover': { borderColor: 'secondary.dark', bgcolor: 'grey.50', }, }, '& .nbb-filter-list-submit-button': { flex: 1, minHeight: 44, borderRadius: 2, bgcolor: 'secondary.main', color: 'secondary.contrastText', fontWeight: 700, textTransform: 'none', boxShadow: 2, '&:hover': { bgcolor: 'secondary.dark', boxShadow: 4, }, }, }} /> </FilterList> ); }
Use function children to override default blocks and order or add custom blocks.
function Example() { const filterOptions = [ { groupName: 'Category', subtitle: 'Filter by product category', options: [ {key: 'category', value: 'electronics', label: 'Electronics'}, {key: 'category', value: 'clothing', label: 'Clothing'}, ], }, { groupName: 'Price', subtitle: 'Select your budget', options: [ {key: 'price', value: 'under-25', label: 'Under $25'}, {key: 'price', value: '25-50', label: '$25 - $50'}, ], }, ]; const defaultData = {filters: []}; const [data, setData] = React.useState(defaultData); return ( <FilterList data={data} options={filterOptions} labels={{submitButton: 'Apply filters', resetButton: 'Clear all'}} onDataChange={setData} sx={{p: 2, bgcolor: 'background.default', borderRadius: 2}} > {({defaultBlocks}) => ({ blocks: { ...defaultBlocks, quickPicks: ( <Box sx={{ p: 2, bgcolor: 'grey.50', borderBottom: 1, borderColor: 'divider', }} > <Typography variant="caption" color="text.secondary" sx={{display: 'block', mb: 1}}> Quick picks </Typography> <Box sx={{display: 'flex', flexWrap: 'wrap', gap: 1}}> <FilterList.CheckboxField name="category" value="electronics" label="Electronics" sx={{ m: 0, px: 2, py: 0.5, borderRadius: 2, bgcolor: 'background.paper', border: 1, borderColor: 'divider', }} /> <FilterList.CheckboxField name="price" value="under-25" label="Under $25" sx={{ m: 0, px: 2, py: 0.5, borderRadius: 2, bgcolor: 'background.paper', border: 1, borderColor: 'divider', }} /> </Box> </Box> ), }, blockOrder: ['quickPicks', 'selectedFilterList', 'accordionList', 'actions'], })} </FilterList> ); }
When to use block overrides
Use overrides when you need to change order, replace default UI blocks, or inject custom blocks (like quickPicks above) while preserving shared state handling.
Important Props
Core Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data | FilterListFormData ({ filters: { key: string; value: string }[] }) | Yes | - | Current filter state; each selected filter is a { key, value } row under data.filters |
onDataChange | (data, meta) => void | Yes | - | Called on updates; meta includes name, value, cause (input, change, blur, clear, reset, programmatic), optional event |
errors | { [fieldName: string]: string | string[] } | No | undefined | Field errors keyed by data field name (e.g. filters); passed through context but not rendered by default blocks |
Content Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
options | FilterListOption[] | No | undefined | Nested accordion structure: each node may define options (checkboxes), groups (nested accordions), optional maxFilters, groupName, and subtitle |
labels | { submitButton?: ReactNode; resetButton?: ReactNode } | No | submitButton: 'Apply', resetButton: 'Reset' | Override default button content |
onResetFilters | () => void | No | undefined | Called after the reset button clears data.filters |
Each FilterListOption checkbox entry uses { key, value, label } with optional layout (flexible or full-width). Set maxFilters on a group to cap selections in that subtree (including nested groups).
Layout and Composition Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | No | undefined | Override default blocks or provide custom JSX children |
className | string | No | undefined | Class name for the root container |
sx | SxProps | No | undefined | MUI system styles for the root container |
FilterList also inherits StackProps (except children) and renders as a form, so standard form/stack props like onSubmit, id, and noValidate are supported. Default block order without overrides: selectedFilterList, accordionList, actions (defaultBlockOrder).
Default UI Blocks
| Block | Built on | Notes |
|---|---|---|
FilterList (root) | Stack rendered as form | Provides controlled context and renders default blocks in selectedFilterList, accordionList, actions order |
FilterList.SelectedFilterList | Box + Chip | Row of removable selected-filter chips (hidden when empty) |
FilterList.AccordionList | Box + Accordion | Scrollable list of filter groups |
FilterList.Actions | Stack + Button | Reset and submit buttons |
Extra field primitives
| Primitive | Built on | Notes |
|---|---|---|
FilterList.CheckboxField | Checkbox + FormControlLabel | Single filter checkbox; respects maxFilters from options |
FilterList.FilterChip | Chip | One selected filter chip with delete button |
FilterList.Accordion | Accordion + Typography | One filter group (summary, subtitle, options grid, nested groups) |
FilterList.ResetButton | Button | Clears all filters (variant="outlined") |
FilterList.SubmitButton | Button | Form submit button (variant="contained", type="submit") |
TypeScript
import * as React from 'react';
import {FilterList, FilterListFormData, FilterListOption} from '@nodeblocks/frontend-filter-list-block';
type ProductFilterData = FilterListFormData & {
searchText?: string;
};
const options: FilterListOption[] = [
{
groupName: 'Category',
subtitle: 'Filter by product category',
options: [
{key: 'category', value: 'electronics', label: 'Electronics'},
{key: 'category', value: 'clothing', label: 'Clothing'},
],
},
];
export function ProductFilters() {
const [data, setData] = React.useState<ProductFilterData>({filters: []});
return (
<FilterList<ProductFilterData>
data={data}
options={options}
labels={{submitButton: 'Apply filters', resetButton: 'Clear all'}}
onDataChange={setData}
onSubmit={event => {
event.preventDefault();
console.log('apply', data.filters);
}}
/>
);
}