Skip to content

Add List, Listbox, and Chip components#111

Draft
developit wants to merge 20 commits intomainfrom
claude/kinu-missing-components-bmdgf
Draft

Add List, Listbox, and Chip components#111
developit wants to merge 20 commits intomainfrom
claude/kinu-missing-components-bmdgf

Conversation

@developit
Copy link
Copy Markdown
Owner

Summary

This PR introduces three new UI components to the component library: List, Listbox, and Chip. These components extend the existing menu/command infrastructure with additional interactive list patterns and a compact chip/tag component.

Key Changes

  • List Component: A flexible interactive list with List and List.Item subcomponents

    • Supports optional nav variant for sidebar-style navigation with accent colors
    • Integrates with existing menu shortcuts system for keyboard navigation
    • Items can be marked as selected or destructive
  • Listbox Component: A non-modal, filterable list component with Listbox, Listbox.Input, Listbox.List, and Listbox.Option subcomponents

    • Input field with real-time filtering of options
    • Automatically selects first matching option during filtering
    • Designed as an inline command palette alternative
    • Integrates with menu shortcuts for keyboard navigation
  • Chip Component: A compact Chip component with optional Chip.Button subcomponent

    • Supports three variants: secondary, destructive, and outline (primary is default)
    • Includes styled close/action button variant
    • Uses design system color tokens for consistency
  • Shared Utilities:

    • Extracted filterItems() utility function in src/lib/filter.ts for reusable filtering logic
    • Updated installMenuShortcuts() to support List and Listbox containers in addition to dialogs
    • Extended dropdown menu styles to apply to List and Listbox items for consistent styling
  • Exports: Added all new components and their type definitions to main src/index.ts

Implementation Details

  • All components use the existing createSimpleComponent factory pattern
  • List and Listbox integrate with the menu shortcuts system for arrow key navigation
  • Listbox input filtering reuses the same logic as Combobox for consistency
  • Styling leverages existing design system variables and follows established patterns
  • Type definitions follow the same structure as existing components with proper JSX integration

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg

Add three high-value missing components:

- **List + ListItem**: Interactive selectable list primitive. ListItem shares
  the same CSS as DropdownMenuItem/ContextMenuItem/ComboboxOption (same
  implementation, different `k` attribute). Supports `variant="nav"` for
  sidebar-style accent-colored hover. Polymorphic (button or anchor).

- **Listbox**: Non-modal filterable list (inline command palette pattern).
  Structurally identical to Combobox but always visible — uses `<div>` instead
  of `<dialog>`, no focus trap or blur-to-close. Shares filtering logic with
  Combobox via extracted `filterItems()` utility in `src/lib/filter.ts`.

- **Chip**: Badge with an optional icon button (`Chip.Button`). Pure CSS
  component with zero JS logic. Button fires standard click events.

Also:
- Extract `filterItems()` to `src/lib/filter.ts`, refactor Combobox to use it
- Extend keyboard navigation in `commands.ts` to support non-dialog containers
  (`[k="list"]` with focus-based nav, `[k="listbox"]` with selected-attribute nav)
- Add `[k="list-item"]` and `[k="listbox-option"]` to all shared item CSS
  selectors in `dropdown-menu/style.css`

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 9, 2026

Deploy Preview for kinu-sh ready!

Name Link
🔨 Latest commit 8ed170b
🔍 Latest deploy log https://app.netlify.com/projects/kinu-sh/deploys/69dd3798f69b7d0008e9add5
😎 Deploy Preview https://deploy-preview-111--kinu-sh.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Size Change: +1.3 kB (+4.11%)

Total Size: 32.9 kB

📦 View Changed
Filename Size Change
benchmarks/size/.tmp/nearly-all-components/bundle.css 9.11 kB +314 B (+3.57%)
benchmarks/size/.tmp/nearly-all-components/bundle.js 3.62 kB +395 B (+12.26%) ⚠️
dist/index.css 9.16 kB +263 B (+2.96%)
dist/index.js 5.97 kB +327 B (+5.8%) 🔍
ℹ️ View Unchanged
Filename Size
benchmarks/size/.tmp/few-components/bundle.css 2.85 kB
benchmarks/size/.tmp/few-components/bundle.js 405 B
benchmarks/size/.tmp/one-component/bundle.css 1.43 kB
benchmarks/size/.tmp/one-component/bundle.js 333 B

compressed-size-action

claude and others added 19 commits April 9, 2026 11:25
- List: Navigation category (alongside Tabs) — interactive selectable list
  with default and nav variants, shows separator support
- Listbox: Data Input category (alongside Combobox) — non-modal filterable
  list with search input and option items
- Chip: Data Display category (alongside Badge) — badge with inline action
  button, shows removable tags and all variants

Adds markdown docs, interactive examples, manifest.json entries, and
metadata.mjs entries for all three components.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
Chip:
- Fully rounded (pill shape) with secondary default, subtle hover
- Chip.Button now spans the full height and hugs the rounded edge with
  a semi-transparent background. Auto-detects start/end placement via
  :first-child/:last-child for correct border-radius
- Add `selected` attribute (consistent with menu items)
- Changed default variant from primary to secondary (more subtle)
- Added `primary` variant, removed `secondary` (was the old default)
- Demo uses × (multiplication sign) instead of "x"

List:
- Selected items now use foreground as background with contrast-aware
  text (hsl(--k-foreground) bg, hsl(--k-background) text). This
  automatically provides correct contrast in both light and dark mode
  since the token system guarantees these are contrasting pairs.

Listbox:
- Selection is now developer-controlled. filterItems() gained an
  autoSelect parameter (default true for Combobox backward compat).
  Listbox passes autoSelect=false so filtering only shows/hides
  options without touching the selected attribute.
- Demo updated to show onClick-driven selection.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
Introduce `Item` (`k="item"`) as the single component used across all
list-like containers: List, Listbox, DropdownMenu, ContextMenu, Combobox.

The same `<Item>` component works everywhere — the parent container
determines the styling context and behavior, just like `<li>` in HTML.

Key changes:
- New `src/components/item/` with unified component, types, and CSS
- All items now polymorphic (button/a via href), tabIndex:-1
- ComboboxOption's click-to-select moved to delegated handler on
  ComboboxList (reads `value` attr or textContent)
- CSS collapsed from 5-way selector to single `[k="item"]` (-1.6KB CSS)
- commands.ts keyboard nav updated for `[k="item"]`
- Context-specific TS interfaces (DropdownMenuItemProps, ComboboxItemProps,
  etc.) narrow the docs surface while sharing the runtime component
- Backward-compat: DropdownMenuItem, ContextMenuItem, ComboboxOption,
  ListboxOption, ListItem all re-exported as aliases of Item
- Parent components expose `.Item` (e.g. DropdownMenu.Item, Combobox.Item)
- All docs/examples updated to use `<Item>`
- Demo still builds (72 pages) — old names work via re-exports

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
- Remove negative left margin that caused button to overlap text
- Increase button padding for a proper hit target
- Bump font-size to 1em so × character renders at readable size

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
The button margins now use calc(-padding - 1px) to fully cancel both
the chip's padding and its 1px border, so the button sits flush against
the rounded edge of the chip instead of being inset.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
Use position:relative + left:0.625rem to push the button flush to the
chip's right edge. The chip's overflow:hidden + border-radius clips it
to the pill shape naturally. Much simpler than fighting with margins.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
Rewrite with the correct mental model: the button is a small circular
icon (like Material Chip) inline with the text, not a full-height bar
stretching to the chip's edge.

- Chip uses gap:0.375rem for natural separation between text and button
- ChipButton is 1.125rem × 1.125rem with a circular background
- :has() selector tightens chip padding on the edge where the button sits
- No positioning tricks or overflow hacks — just flexbox

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
…rgin

- Chip keeps its normal padding on all sides
- Button uses align-self:stretch + aspect-ratio:1 for a full-height circle
- Negative margins on the outward edge pull the button flush to the chip's
  rounded edge (margin-right for last-child, margin-left for first-child)
- margin-block cancels vertical padding so the button fills chip height
- Button's border-radius naturally aligns with the chip's pill shape

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
aspect-ratio:1 doesn't give flex items a width when only height is
derived via stretch. Set explicit width and height to match the chip's
full border-box height (1.5rem + 2px), keeping the negative margin
only on the outward edge.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
The button already has align-items:center on the chip, so it stays
vertically centered without needing to cancel padding. The explicit
height is taller than the chip's content box so it visually overlaps
the padding area naturally.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
The flush-to-edge approach is a dead end: text nodes don't count for
:first-child/:last-child, so a button next to text always matches both
selectors and gets both outward margins. Back to a simple inline
button that respects the chip's normal padding + gap.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
Chip renders as <button> so it gets native keyboard activation, focus
ring, and accessible button role for free. Chip.Button renders as a
<span role="button"> so it can be nested inside without breaking HTML
validity (nested <button> elements are reparented by the HTML parser).

Chip.Button installs a default onClickCapture that calls stopPropagation,
so clicks on the action button don't bubble up to trigger the chip's
main click handler. The span is marked aria-hidden so screen readers
announce only the chip's text, not the × glyph.

Developers who need a keyboard-accessible remove button can add
tabIndex and a keydown handler themselves — that's an explicit
opt-in for a less common pattern.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
onClickCapture with stopPropagation was preventing the user's onClick
prop on the same element from firing. Switch to attaching a plain
addEventListener('click') handler via the ref callback, which runs
after Preact applies the user's onClick prop listener. At click time,
handlers fire in registration order: user's onClick first, then our
stopPropagation.

https://claude.ai/code/session_01VUkypWpSmfEMA5zhE3DCEg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants