Skip to content

Enable typing to open and search select dropdowns#164

Merged
jaruesink merged 2 commits intomainfrom
codegen-bot/type-to-open-select-f85f09
Jan 22, 2026
Merged

Enable typing to open and search select dropdowns#164
jaruesink merged 2 commits intomainfrom
codegen-bot/type-to-open-select-f85f09

Conversation

@codegen-sh
Copy link
Copy Markdown
Contributor

@codegen-sh codegen-sh bot commented Nov 11, 2025

Overview

This PR adds the ability to open select dropdowns and start searching immediately when typing while focused on the select trigger. This improves the user experience by making select components feel more responsive and keyboard-friendly.

Changes

  • Type-to-open functionality: When focused on a select trigger, typing any printable character will:
    • Open the dropdown if it's not already open
    • Focus the search input
    • Pre-populate the search with the typed character
  • Auto-focus search input: When the popover opens (via any method), the search input is automatically focused if searchable is enabled
  • Clear search on close: The search query is automatically cleared when the popover closes
  • Preserved keyboard navigation: All standard keyboard shortcuts remain functional (Enter, Space, Arrow keys, Escape, Tab)

Testing

The changes were tested and the component builds successfully:

  • yarn lint passes
  • yarn workspace @lambdacurry/forms build succeeds

Example Usage

<USStateSelect name="state" label="US State" />

Now when you focus the select and start typing "Cal", the dropdown will open and show California in the filtered results.

Implementation Details

  • Added searchInputRef to track the search input element
  • Added handleTriggerKeyDown to detect printable characters and trigger the open/search behavior
  • Modified the useEffect to auto-focus the search input when opening
  • Search query is cleared when the popover closes to reset state

Requested by: Jake Ruesink (@jake)


💻 View my workAbout Codegen
⛔ Remove Codegen from PR🚫 Ban action checks

Summary by CodeRabbit

  • New Features

    • Typing while focused opens the Select and begins a search immediately.
    • Search field now auto-focuses when the dropdown opens.
    • Selected item is auto-scrolled into view on open.
  • Bug Fixes / Behavior

    • Search query is cleared when the dropdown closes.
    • Keyboard handling preserves navigation keys and forwards other keys appropriately.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add keydown handler to select trigger that opens popover when typing printable characters
- Automatically focus search input when popover opens
- Set initial search query to the typed character
- Clear search query when popover closes
- Maintains normal keyboard navigation (Enter, Space, Arrow keys, etc.)
@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Nov 11, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds keyboard-driven opening and searchable focus/clear behavior to Select: captures printable keystrokes on trigger to open and prefill search, focuses the search input on open, clears search on close, and scrolls the selected item into view.

Changes

Cohort / File(s) Change Summary
Select Component Search & Keyboard Interaction
packages/components/src/ui/select.tsx
Added searchInputRef; implemented handleTriggerKeyDown to open popover and pre-fill searchQuery on printable-character keypress; attached onKeyDown to trigger; focus search input on open (when searchable) and clear searchQuery on close; scroll selected item into view; updated effect dependencies to include searchable.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Trigger
    participant SelectComponent as Component
    participant SearchInput
    participant Popover

    User->>Trigger: Type printable character
    Trigger->>Component: onKeyDown
    Component->>Component: handleTriggerKeyDown (detect printable, set searchQuery)
    Component->>Popover: open()
    Popover->>Component: onOpenChange(open=true)
    Component->>SearchInput: focus via searchInputRef
    Component->>Component: scroll selected item into view

    User->>Popover: close
    Popover->>Component: onOpenChange(open=false)
    Component->>Component: clear searchQuery
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 I heard a key, I gave a peek,
Popover opened, search found a streak,
Focused the field with a hop and a twirl,
Cleared when closed — happy rabbit swirl! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Enable typing to open and search select dropdowns' directly and clearly describes the main change: adding type-to-open behavior for select components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Nov 11, 2025

📝 Storybook Preview: View Storybook

This preview will be updated automatically when you push new changes to this PR.

Note: The preview will be available after the workflow completes and the PR is approved for deployment.

@jaruesink
Copy link
Copy Markdown
Contributor

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 21, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/components/src/ui/select.tsx`:
- Around line 128-154: The early return in handleTriggerKeyDown stops consumer
buttonProps.onKeyDown from being invoked for navigation keys; update
handleTriggerKeyDown to always call buttonProps.onKeyDown(e) for all events
(including Enter/Space/ArrowUp/ArrowDown/Escape/Tab) before returning, and keep
the existing searchable behavior (preventDefault, open popover via
popoverState.open(), and setSearchQuery) only for printable characters;
reference handleTriggerKeyDown, buttonProps.onKeyDown, popoverState.open,
setSearchQuery and searchable when making the change.
- Around line 199-201: DefaultSearchInput currently does not forward the ref so
SearchInput receives a ref that never becomes searchInputRef.current, breaking
auto-focus; update DefaultSearchInput to accept and forward refs (use
forwardRef) and pass the incoming ref to the underlying input element, ensuring
the SearchInput usage with ref={searchInputRef} receives the real DOM node;
locate DefaultSearchInput and replace its function/constant with
React.forwardRef((props, ref) => ...) and attach ref to the actual input
element, leaving SearchInput and the useEffect that calls
searchInputRef.current.focus() unchanged.

Comment on lines +128 to +154
// Handle keydown on trigger to open popover and start typing
const handleTriggerKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
// Allow normal keyboard navigation (Enter, Space, Arrow keys, etc.)
if (
e.key === 'Enter' ||
e.key === ' ' ||
e.key === 'ArrowDown' ||
e.key === 'ArrowUp' ||
e.key === 'Escape' ||
e.key === 'Tab'
) {
return;
}

// If it's a printable character and searchable is enabled, open the popover and start typing
if (searchable && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
e.preventDefault();
if (!popoverState.isOpen) {
popoverState.open();
}
// Set the initial search query
setSearchQuery(e.key);
}

// Call the original onKeyDown if provided
buttonProps.onKeyDown?.(e);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/components/src/ui/select.tsx | sed -n '120,160p'

Repository: lambda-curry/forms

Length of output: 1683


🏁 Script executed:

cat -n packages/components/src/ui/select.tsx | head -130

Repository: lambda-curry/forms

Length of output: 5790


🏁 Script executed:

cat -n packages/components/src/ui/select.tsx | sed -n '156,200p'

Repository: lambda-curry/forms

Length of output: 2569


🏁 Script executed:

fd select -e test -e spec | head -10

Repository: lambda-curry/forms

Length of output: 44


🏁 Script executed:

rg "handleTriggerKeyDown" packages/components/src/ui/select.tsx -B 5 -A 15

Repository: lambda-curry/forms

Length of output: 1811


Preserve consumer onKeyDown for navigation keys.

The early return at line 139 prevents buttonProps.onKeyDown from firing for Enter/Space/Arrow/Escape/Tab, breaking the prop delegation contract. Consumers passing an onKeyDown handler expect it to be called for all keyboard events.

🛠️ Proposed fix
 const handleTriggerKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
   // Allow normal keyboard navigation (Enter, Space, Arrow keys, etc.)
   if (
     e.key === 'Enter' ||
     e.key === ' ' ||
     e.key === 'ArrowDown' ||
     e.key === 'ArrowUp' ||
     e.key === 'Escape' ||
     e.key === 'Tab'
   ) {
+    buttonProps.onKeyDown?.(e);
     return;
   }

   // If it's a printable character and searchable is enabled, open the popover and start typing
   if (searchable && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
     e.preventDefault();
     if (!popoverState.isOpen) {
       popoverState.open();
     }
     // Set the initial search query
     setSearchQuery(e.key);
   }

   // Call the original onKeyDown if provided
   buttonProps.onKeyDown?.(e);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Handle keydown on trigger to open popover and start typing
const handleTriggerKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
// Allow normal keyboard navigation (Enter, Space, Arrow keys, etc.)
if (
e.key === 'Enter' ||
e.key === ' ' ||
e.key === 'ArrowDown' ||
e.key === 'ArrowUp' ||
e.key === 'Escape' ||
e.key === 'Tab'
) {
return;
}
// If it's a printable character and searchable is enabled, open the popover and start typing
if (searchable && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
e.preventDefault();
if (!popoverState.isOpen) {
popoverState.open();
}
// Set the initial search query
setSearchQuery(e.key);
}
// Call the original onKeyDown if provided
buttonProps.onKeyDown?.(e);
};
// Handle keydown on trigger to open popover and start typing
const handleTriggerKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
// Allow normal keyboard navigation (Enter, Space, Arrow keys, etc.)
if (
e.key === 'Enter' ||
e.key === ' ' ||
e.key === 'ArrowDown' ||
e.key === 'ArrowUp' ||
e.key === 'Escape' ||
e.key === 'Tab'
) {
buttonProps.onKeyDown?.(e);
return;
}
// If it's a printable character and searchable is enabled, open the popover and start typing
if (searchable && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
e.preventDefault();
if (!popoverState.isOpen) {
popoverState.open();
}
// Set the initial search query
setSearchQuery(e.key);
}
// Call the original onKeyDown if provided
buttonProps.onKeyDown?.(e);
};
🤖 Prompt for AI Agents
In `@packages/components/src/ui/select.tsx` around lines 128 - 154, The early
return in handleTriggerKeyDown stops consumer buttonProps.onKeyDown from being
invoked for navigation keys; update handleTriggerKeyDown to always call
buttonProps.onKeyDown(e) for all events (including
Enter/Space/ArrowUp/ArrowDown/Escape/Tab) before returning, and keep the
existing searchable behavior (preventDefault, open popover via
popoverState.open(), and setSearchQuery) only for printable characters;
reference handleTriggerKeyDown, buttonProps.onKeyDown, popoverState.open,
setSearchQuery and searchable when making the change.

Comment on lines 199 to 201
<SearchInput
ref={searchInputRef}
placeholder="Search..."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/components/src/ui/select.tsx | head -250 | tail -100

Repository: lambda-curry/forms

Length of output: 5451


🏁 Script executed:

wc -l packages/components/src/ui/select.tsx

Repository: lambda-curry/forms

Length of output: 103


🏁 Script executed:

grep -n "DefaultSearchInput\|searchInputRef" packages/components/src/ui/select.tsx | head -30

Repository: lambda-curry/forms

Length of output: 505


🏁 Script executed:

sed -n '59,65p' packages/components/src/ui/select.tsx

Repository: lambda-curry/forms

Length of output: 343


🏁 Script executed:

sed -n '85,105p' packages/components/src/ui/select.tsx

Repository: lambda-curry/forms

Length of output: 942


Forward the ref through DefaultSearchInput so auto-focus works.

DefaultSearchInput ignores the ref passed to it, leaving searchInputRef.current null. This breaks the auto-focus functionality in the useEffect that attempts to focus the search input when the popover opens.

🛠️ Proposed fix
-const DefaultSearchInput = forwardRef<HTMLInputElement, React.ComponentPropsWithoutRef<typeof CommandInput>>(
-  (props, _ref) => <CommandInput {...props} />,
-);
+const DefaultSearchInput = forwardRef<HTMLInputElement, React.ComponentPropsWithoutRef<typeof CommandInput>>(
+  (props, ref) => <CommandInput ref={ref} {...props} />,
+);
🤖 Prompt for AI Agents
In `@packages/components/src/ui/select.tsx` around lines 199 - 201,
DefaultSearchInput currently does not forward the ref so SearchInput receives a
ref that never becomes searchInputRef.current, breaking auto-focus; update
DefaultSearchInput to accept and forward refs (use forwardRef) and pass the
incoming ref to the underlying input element, ensuring the SearchInput usage
with ref={searchInputRef} receives the real DOM node; locate DefaultSearchInput
and replace its function/constant with React.forwardRef((props, ref) => ...) and
attach ref to the actual input element, leaving SearchInput and the useEffect
that calls searchInputRef.current.focus() unchanged.

@jaruesink
Copy link
Copy Markdown
Contributor

@coderabbitai working on the select component, I wanted to be able to type in that component when it was focused and have it start searching. This almost works but currently when we input our first character it focuses the input and selects the first character so naturally if we keep typing it overwrites the first character. Can you review our approach here and determine what's going on and how we should fix this.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 21, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the select.tsx file to analyze the implementation
cat packages/components/src/ui/select.tsx

Length of output: 13400


@jaruesink I found the issue! Here's what's happening:

Root Cause

  1. When you type a character on the focused trigger, handleTriggerKeyDown calls setSearchQuery(e.key) to set the search query to that character (e.g., "a")
  2. The popover opens and the useEffect runs
  3. Inside the effect, searchInputRef.current.focus() focuses the search input
  4. The problem: When the browser focuses an input that already has a value, it selects all the text by default
  5. When you continue typing, the selected text gets replaced, losing your first character

Solution

After focusing the input, explicitly place the cursor at the end of the text instead of letting the browser select it all:

useEffect(() => {
  if (!popoverState.isOpen) {
    // Clear search query when closing
    setSearchQuery('');
    return;
  }
  requestAnimationFrame(() => {
    if (searchable && searchInputRef.current) {
      searchInputRef.current.focus();
      // Place cursor at the end instead of selecting all text
      const len = searchInputRef.current.value.length;
      searchInputRef.current.setSelectionRange(len, len);
    }
    const selectedEl = selectedItemRef.current as HTMLElement | null;
    if (selectedEl) selectedEl.scrollIntoView({ block: 'center' });
  });
}, [popoverState.isOpen, searchable]);

The setSelectionRange(len, len) call positions the cursor at the end of the existing text without selecting anything, allowing the user to continue typing naturally.

Prevents text selection when opening select dropdown via typing, allowing users to continue typing without overwriting the first character.
@jaruesink jaruesink merged commit 51cf8b8 into main Jan 22, 2026
6 checks passed
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.

1 participant