Combobox

A searchable select component that combines an input field with a dropdown list.

Anatomy

Combobox do |c|
c.trigger
c.content do
c.group do
c.label
c.item
c.item
end
c.item
c.item
end
end

API Reference

Combobox

NameTypeDefault
id:stringnil
name:stringnil
value:stringnil
placeholder:stringnil
include_blank:booleanfalse
disabled:booleanfalse
search_path:stringnil
URL for remote search functionality. Backend must return JSON array of { html: string, group: string } objects. Enables dynamic search results from server.
search_error_text:string"Something went wrong, please try again."
Error message displayed when remote search requests fail. Only relevant when using search_path.
search_empty_text:string"No results found"
Message shown when search returns no results, both for local filtering and remote search.
search_placeholder_text:string"Search..."
Placeholder text displayed in the search input field within the combobox dropdown.
attributeskeyword arguments

Combobox#trigger

NameType
attributeskeyword arguments

Combobox#content

NameType
attributeskeyword arguments

Combobox#group

NameType
attributeskeyword arguments

Combobox#label

NameType
attributeskeyword arguments

Combobox#item

NameTypeDefault
value:stringnil
The value associated with this option. This is what gets submitted when the form is sent to the server.
disabled:booleanfalse
Whether this specific option is disabled and cannot be selected. Useful for unavailable or restricted choices.
attributeskeyword arguments

Combobox#items (Builder)

Builder method that generates multiple options from a collection automatically, including trigger and content.

NameTypeDefault
required collectionarray
Array of objects or hashes to convert into selectable options. Each item will generate one combobox option.
required value_method:symbol
Method called on each collection item to get the option value (what's submitted when selected).
required text_method:symbol
Method called on each collection item to get the display text shown to users in the dropdown.
disabled_items:string or arraynil
Value(s) of options that should be disabled and non-selectable within the dropdown.

Keyboard Interactions

KeyDescription
SpaceWhen focus is on trigger, opens the combobox and focuses the selected item. When focus is on an item, selects the focused item.
EnterWhen focus is on trigger, opens the combobox and focuses the selected item. When focus is on an item, selects the focused item.
ArrowDownWhen focus is on trigger, opens the combobox. When focus is on an item, moves focus to the next item.
ArrowUpWhen focus is on trigger, opens the combobox. When focus is on an item, moves focus to the previous item.
EscCloses the select and moves focus to trigger.

Examples

Search Path

# some_controller.rb (without jbuilder)
def index
search_term = params[:q]
data = SomeModel.query(search_term)
results = []
data.each do |d|
results.push({
html: render_to_string(ComboboxItem.new(value: d.name) { d.name.titleize }, layout: false),
group: d.group # or nil
})
end
render json: results
end
# some_controller.rb (with jbuilder)
def index
search_term = params[:q]
@data = SomeModel.query(search_term)
end
# some_controller/index.json.jbuilder
json.array! @data do |d|
json.html render ComboboxItem.new(value: d.name) { d.name.titleize }
json.group d.group # or nil
end
# views/some_controller/index.html.erb
<%= render Combobox.new(search_path: combobox_search_examples_path, class: "max-w-sm") do |c| %>
<%= c.trigger %>
<%= c.content do %>
<%= c.group do %>
<%= c.label { "Fruits" } %>
<%= c.item(value: "apple", disabled: true) { "Apple" }%>
<%= c.item(value: "banana") { "Banana" }%>
<%= c.item(value: "blueberry") { "Blueberry" }%>
<%= c.item(value: "grapes") { "Grapes" }%>
<%= c.item(value: "pineapple", disabled: true) { "Pineapple" }%>
<% end %>
<% end %>
<% end %>
<div data-aria-id="combobox-418d14bde4" data-controller="combobox" data-search-path="/examples/combobox_search" data-shadcn-phlexcomponents="combobox" class="w-full max-w-sm"><input type="hidden" data-combobox-target="hiddenInput"> <button type="button" role="combobox" aria-autocomplete="none" aria-controls="combobox-418d14bde4-content" data-has-value="false" data-action="click->combobox#toggle
keydown.down->combobox#open:prevent
" data-combobox-target="trigger" data-shadcn-phlexcomponents="combobox-trigger" class="border-input [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 h-9 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[placeholder]:data-[has-value=false]:text-muted-foreground w-full disabled:dark:hover:bg-input/30"><span data-combobox-target="triggerText" data-shadcn-phlexcomponents="combobox-trigger-text" class="pointer-events-none line-clamp-1 flex items-center gap-2"></span><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 opacity-50 text-foreground"><path d="m6 9 6 6 6-6"></path></svg></button>
<div style="display: none;" data-combobox-target="contentContainer" data-shadcn-phlexcomponents="combobox-content-container" class="fixed top-0 left-0 w-max z-50">
<div id="combobox-418d14bde4-content" tabindex="-1" role="listbox" aria-labelledby="combobox-418d14bde4-trigger" aria-orientation="vertical" data-side="bottom" data-align="center" data-state="closed" data-combobox-target="content" data-action="combobox:click:outside->combobox#clickOutside
keydown.up->combobox#highlightItem:prevent
keydown.down->combobox#highlightItem:prevent
keydown.enter->combobox#select:prevent
" data-shadcn-phlexcomponents="combobox-content" class="bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 min-w-[8rem] origin-(--radix-popper-transform-origin) rounded-md border shadow-md pointer-events-auto outline-none"><template>
<div role="group" aria-labelledby="-group-32f9bff3ad" data-combobox-target="group" data-shadcn-phlexcomponents="combobox-group">
<div data-shadcn-phlexcomponents="combobox-label" class="text-muted-foreground px-2 py-1.5 text-xs"></div>
</div>
</template><label class="sr-only" id="combobox-418d14bde4-search-label" for="combobox-418d14bde4-search">Search...</label>
<div data-shadcn-phlexcomponents="combobox-search-input-container" class="flex h-9 items-center gap-2 border-b px-3"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 shrink-0 opacity-50"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg><input id="combobox-418d14bde4-search" placeholder="Search..." type="text" autocomplete="off" autocorrect="off" role="combobox" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-418d14bde4-list" aria-labelledby="combobox-418d14bde4-search-label" data-combobox-target="searchInput" data-action="keydown->combobox#inputKeydown input->combobox#search" data-shadcn-phlexcomponents="combobox-search-input" class="placeholder:text-muted-foreground flex w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50 h-9"></div>
<div data-combobox-target="listContainer" data-shadcn-phlexcomponents="combobox-list-container" class="p-1 max-h-80 overflow-y-auto">
<div role="presentation" data-combobox-target="empty" data-shadcn-phlexcomponents="combobox-text" class="py-6 text-center text-sm hidden">No results found</div>
<div role="presentation" data-combobox-target="error" data-shadcn-phlexcomponents="combobox-text" class="py-6 text-center text-sm hidden">Something went wrong, please try again.</div>
<div role="presentation" data-combobox-target="loading" data-shadcn-phlexcomponents="combobox-text" class="py-6 text-center text-sm hidden">
<div class="flex justify-center" aria-label="Loading"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="animate-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg></div>
</div>
<div id="combobox-418d14bde4-list" data-combobox-target="list">
<div role="group" aria-labelledby="combobox-418d14bde4-group-010f93785e" data-combobox-target="group" data-shadcn-phlexcomponents="combobox-group">
<div data-shadcn-phlexcomponents="combobox-label" class="text-muted-foreground px-2 py-1.5 text-xs">Fruits</div>
<div role="option" tabindex="-1" aria-labelledby="combobox-418d14bde4-item-93f57c285b" data-highlighted="false" data-disabled data-value="apple" data-action="click->combobox#select
mouseover->combobox#highlightItem
" data-combobox-target="item" data-shadcn-phlexcomponents="combobox-item" class="data-[highlighted=true]:bg-accent data-[highlighted=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:items-center *:[span]:last:gap-2 group/item"><span id="combobox-418d14bde4-item-93f57c285b">Apple</span><span class="absolute right-2 h-3.5 w-3.5 items-center hidden justify-center
group-aria-[selected=true]/item:flex group-data-[value='']/item:hidden"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d="M20 6 9 17l-5-5"></path></svg></span></div>
<div role="option" tabindex="-1" aria-labelledby="combobox-418d14bde4-item-c6d030e507" data-highlighted="false" data-value="banana" data-action="click->combobox#select
mouseover->combobox#highlightItem
" data-combobox-target="item" data-shadcn-phlexcomponents="combobox-item" class="data-[highlighted=true]:bg-accent data-[highlighted=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:items-center *:[span]:last:gap-2 group/item"><span id="combobox-418d14bde4-item-c6d030e507">Banana</span><span class="absolute right-2 h-3.5 w-3.5 items-center hidden justify-center
group-aria-[selected=true]/item:flex group-data-[value='']/item:hidden"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d="M20 6 9 17l-5-5"></path></svg></span></div>
<div role="option" tabindex="-1" aria-labelledby="combobox-418d14bde4-item-8ec2082054" data-highlighted="false" data-value="blueberry" data-action="click->combobox#select
mouseover->combobox#highlightItem
" data-combobox-target="item" data-shadcn-phlexcomponents="combobox-item" class="data-[highlighted=true]:bg-accent data-[highlighted=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:items-center *:[span]:last:gap-2 group/item"><span id="combobox-418d14bde4-item-8ec2082054">Blueberry</span><span class="absolute right-2 h-3.5 w-3.5 items-center hidden justify-center
group-aria-[selected=true]/item:flex group-data-[value='']/item:hidden"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d="M20 6 9 17l-5-5"></path></svg></span></div>
<div role="option" tabindex="-1" aria-labelledby="combobox-418d14bde4-item-3a2f9bf84e" data-highlighted="false" data-value="grapes" data-action="click->combobox#select
mouseover->combobox#highlightItem
" data-combobox-target="item" data-shadcn-phlexcomponents="combobox-item" class="data-[highlighted=true]:bg-accent data-[highlighted=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:items-center *:[span]:last:gap-2 group/item"><span id="combobox-418d14bde4-item-3a2f9bf84e">Grapes</span><span class="absolute right-2 h-3.5 w-3.5 items-center hidden justify-center
group-aria-[selected=true]/item:flex group-data-[value='']/item:hidden"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d="M20 6 9 17l-5-5"></path></svg></span></div>
<div role="option" tabindex="-1" aria-labelledby="combobox-418d14bde4-item-5bd39db92b" data-highlighted="false" data-disabled data-value="pineapple" data-action="click->combobox#select
mouseover->combobox#highlightItem
" data-combobox-target="item" data-shadcn-phlexcomponents="combobox-item" class="data-[highlighted=true]:bg-accent data-[highlighted=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:items-center *:[span]:last:gap-2 group/item"><span id="combobox-418d14bde4-item-5bd39db92b">Pineapple</span><span class="absolute right-2 h-3.5 w-3.5 items-center hidden justify-center
group-aria-[selected=true]/item:flex group-data-[value='']/item:hidden"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d="M20 6 9 17l-5-5"></path></svg></span></div>
</div>
</div>
</div>
</div>
</div>
</div>