Detent web components
By Shane Logsdon

Content & disclosure

FAQ

Implemented

Searchable FAQ accordion with categories

detent-faq

Port status
Catalog status
implemented
Origin
upstream
Phase
2
Wave
phase-2-disclosure
Group
Content & disclosure

6.7 KB gzip for the class module

Variants

In the searchable FAQ above, type in the search field to filter, use the pills to narrow by category, and press Enter or Space on a header to expand it. With reduced motion the panel and the indicator jump straight to their final state.

Recorded states

  • focus-visible recorded state; no setter
  • open recorded state; no setter
  • expanded recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/faq/define.js';

// or register it yourself
import { DetentFAQ } from '/components/faq/faq.js';

Attributes

  • allow-multiple
  • icon-style
  • search-placeholder
  • searchable
  • show-categories
  • variant

Properties

  • items
  • openIds
  • defaultOpen
  • renderIcon
  • onOpenChange

Slots

No slots are recorded for this component.

Events

  • detent-open-change — { ids: string[] }

Example

Runnable example: site/examples/faq.ts. The snippet below is generated from the catalogued attributes.

<detent-faq
  allow-multiple
  icon-style="chevron"
  search-placeholder
  searchable
  show-categories
  variant="unified"
></detent-faq>

Property-only values

const element = document.querySelector('detent-faq');
if (element !== null) {
  // items: FAQItem[]
  element.items = [];
  // defaultOpen: string[] | string
  element.defaultOpen = undefined;
  // openIds: string[]
  element.openIds = [];
  // onOpenChange: (ids: string[]) => void
  element.onOpenChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/FAQ.tsx sha256 f2d731262db1…

Native files

The native module and its artifacts:

  • src/components/faq/faq.ts
  • src/components/faq/styles.css
  • src/components/faq/define.ts
  • docs/components/faq.md
  • site/examples/faq.ts
  • tests/browser/faq.spec.ts
  • evidence/components/faq.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
allow-multipleallowMultiplebooleanfalsePresence semantics: present means true, absent means false.
icon-styleiconStyle'chevron' | 'plus-minus' | 'custom''chevron'Kebab-case attribute; camelCase property.
search-placeholder———Recorded in the catalog as an attribute.
searchablesearchablebooleanfalsePresence semantics: present means true, absent means false.
show-categoriesshowCategoriesbooleanfalsePresence semantics: present means true, absent means false.
variantvariant'unified' | 'separated''unified'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
itemsFAQItem[][]Property-only: assign from script; not accepted as an attribute.
openIdsstring[]undefinedProperty-only: assign from script; not accepted as an attribute.
defaultOpenstring[] | stringundefinedProperty-only: assign from script; not accepted as an attribute.
renderIcon——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
onOpenChange(ids: string[]) => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-open-changeonOpenChange(ids: string[]) => voidComposed, bubbles; fires only for a user action.

Slots

No slots are recorded for this component.

CSS customisation

  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • `answer` is a `string` rendered as text, not a React node (no slots recorded, PC-11.1).
  • `item.icon` is not ported; the icon surface is `icon-style` plus `render-icon`.
  • `render-icon` returns `string | Element | null`; strings are inserted as text.
  • Categories render only when more than one category exists (upstream parity), and the selected pill stays valid when `items` changes.
  • The `openIds` getter returns the effective ids; assigning `undefined` opts out of controlled mode.

What it does

A searchable, category-filterable FAQ accordion with spring-style height expansion, single- or multi-open state, and a unified or separated card layout.

Upstream behaviour
  • Smooth height calculation and rotation via Framer Motion springGentle
  • Single-open accordion or multi-open simultaneous expansion modes
  • Controlled and uncontrolled state management (openIds / defaultOpen)
  • Integrated search filter bar and optional category filter pills
  • Two layout modes: Unified grouped card or separated individual cards
  • Full keyboard accessibility (Space, Enter, Tab) and ARIA attributes
Differences from upstream
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • `answer` is a `string` rendered as text, not a React node (no slots recorded, PC-11.1).
  • `item.icon` is not ported; the icon surface is `icon-style` plus `render-icon`.
  • `render-icon` returns `string | Element | null`; strings are inserted as text.
  • Categories render only when more than one category exists (upstream parity), and the selected pill stays valid when `items` changes.
  • The `openIds` getter returns the effective ids; assigning `undefined` opts out of controlled mode.
Previous: Expandable Search Next: Action Dock

Docs

Components

Project