Combobox
Searchable typed choices with distinct display labels and submitted values.
- Source
app/components/nitro_kit/combobox.rb- API
NitroKit::Combobox.new(id:, name:, label:, options:, value:, include_blank:)
Selection states
A named native select submits without JavaScript; enhancement adds filtering without a duplicate form value.
Selection matrix
- Denmark
- Sweden
- Norway
- Finland
- Iceland
- Denmark
- Sweden
- Norway
- Finland
- Iceland
- Denmark
- Sweden
- Norway
- Finland
- Iceland
- Denmark
- Sweden
- Norway
- Finland
- Iceland
- 1
- 2
- 3
test/dummy/app/components/gallery/components/combobox_page.rbsample("Empty optional", slug: "empty") do
render_combobox("gallery-combobox-empty", placeholder: "Choose a country")
end
sample("Selected", slug: "selected") do
render_combobox("gallery-combobox-selected", value: "dk")
end
sample("Required", slug: "required") do
render_combobox("gallery-combobox-required", required: true, placeholder: "Country required")
end
sample("Disabled", slug: "disabled") do
render_combobox("gallery-combobox-disabled", value: "se", disabled: true)
end
sample("Numeric labels", slug: "numeric-labels") do
render NitroKit::Combobox.new(
id: "gallery-combobox-numeric",
name: "deployment[retry_count]",
label: "Retry count",
options: [ [ 1, "one" ], [ 2, "two" ], [ 3, "three" ] ],
value: "two"
)
endPlacements and choice pressure
All placements and long labels retain deterministic IDs and native listbox semantics.
Placement matrix
- DevelopmentLocal data only
- StagingShared verification
- ProductionCustomer traffic
- DevelopmentLocal data only
- StagingShared verification
- ProductionCustomer traffic
- DevelopmentLocal data only
- StagingShared verification
- ProductionCustomer traffic
- DevelopmentLocal data only
- StagingShared verification
- ProductionCustomer traffic
test/dummy/app/components/gallery/components/combobox_page.rbNitroKit::Combobox::PLACEMENTS.each do |placement|
sample(placement.to_s.humanize, slug: placement.to_s) do
render NitroKit::Combobox.new(
id: "gallery-combobox-#{placement}",
name: "deployment[#{placement}]",
label: "Deployment environment",
options: ENVIRONMENTS,
value: "staging",
placement:
)
end
endForm composition
The combobox keeps one native select as its submission source alongside ordinary Nitro fields.
Deployment target
test/dummy/app/components/gallery/components/combobox_page.rbrender NitroKit::Card.new(id: "gallery-combobox-deployment-card") do |card|
card.title("Promote release", level: 3)
card.body do
form(id: "gallery-combobox-deployment-form", action: "/gallery/deployments", method: "post") do
render NitroKit::FieldGroup.new do
render NitroKit::Field.new(
nil,
:release,
id: "gallery-combobox-release",
name: "deployment[release]",
label: "Release",
value: "2026.07.13",
readonly: true
)
render NitroKit::Combobox.new(
id: "gallery-combobox-environment",
name: "deployment[environment]",
label: "Target environment",
options: ENVIRONMENTS,
value: "production",
required: true
)
render NitroKit::Button.new(
"Promote release",
id: "gallery-combobox-submit",
type: :submit,
variant: :primary
)
end
end
end
endRails form builder
`form.field(:country, as: :combobox)` puts the searchable control inside an ordinary Field with its own label, description, and errors.
Billing country
test/dummy/app/components/gallery/components/combobox_page.rbcontact = Gallery::FormExamples.billing_contact
form_with(
model: contact,
scope: :billing_contact,
url: "#billing-country",
builder: NitroKit::FormBuilder,
id: "gallery-combobox-billing-form"
) do |form|
form.group do
form.field(
:country,
as: :combobox,
id: "gallery-combobox-billing-country",
label: "Billing country",
description: "Invoices apply this country's tax rules.",
options: BILLING_COUNTRIES,
placeholder: "Search countries",
required: true
)
form.submit("Save billing country", id: "gallery-combobox-billing-save")
end
endUnder pressure
Thirty Gallery::Hostile badge labels as searchable options with sentence-long descriptions, an unbroken selected label, a hostile placeholder, and a required disabled combobox inside an invalid field.
Hostile comboboxes
- Active
- Regional data residency, retention, and lawful-basis preferences for every workspaceThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Invited
- WolfeschlegelsteinhausenbergerdorffwelchevoralternwarengewissenhaftschaferswessenschafewarenwohlgepflegeThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Suspended
- محمد عبد الرحمن الهاشمي القحطانيThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Pending review
- 山田太郎(プラットフォームエンジニアリング)This description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Owner
- 🦄 Unicorn Ops 🚀🔥💯 On-callThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Read-only
- 2,147,483,647,000This description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Beta
- สวัสดีครับยินดีต้อนรับสู่ระบบจัดการทีมงานของเราThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Deprecated
- 1This description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- 999+
- Awaiting payment confirmation from the issuing bankThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Trial
- EnterpriseThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Past due
- ArchivedThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Locked
- VerifiedThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Unverified
- DisabledThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Draft
- ScheduledThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Failed
- QueuedThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Active
- Regional data residency, retention, and lawful-basis preferences for every workspaceThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Invited
- WolfeschlegelsteinhausenbergerdorffwelchevoralternwarengewissenhaftschaferswessenschafewarenwohlgepflegeThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Suspended
- محمد عبد الرحمن الهاشمي القحطانيThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Pending review
- 山田太郎(プラットフォームエンジニアリング)This description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Owner
- 🦄 Unicorn Ops 🚀🔥💯 On-callThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Read-only
- 2,147,483,647,000This description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Beta
- สวัสดีครับยินดีต้อนรับสู่ระบบจัดการทีมงานของเราThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Deprecated
- 1This description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- 999+
- Awaiting payment confirmation from the issuing bankThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Trial
- EnterpriseThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Past due
- ArchivedThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Locked
- VerifiedThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Unverified
- DisabledThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Draft
- ScheduledThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
- Failed
- QueuedThis description keeps going well past the point where a sensible product would have stopped, because real customers paste release notes, legal disclaimers, and entire support tickets into fields that were designed for a single line of text.
test/dummy/app/components/gallery/components/combobox_page.rboptions = Gallery::Hostile::BADGE_LABELS.each_with_index.map do |label, index|
{ label:, value: "hostile_#{index}", description: index.odd? ? Gallery::Hostile::LONG_SENTENCE : nil }
end
render NitroKit::Flex.new(dir: :col, gap: 4, align: :stretch) do
render NitroKit::Combobox.new(
id: "gallery-combobox-hostile-word",
name: "hostile[word]",
label: Gallery::Hostile::LONG_LABEL,
options:,
value: "hostile_3",
placeholder: Gallery::Hostile::LONG_SENTENCE
)
render NitroKit::Combobox.new(
id: "gallery-combobox-hostile-locked",
name: "hostile[locked]",
label: Gallery::Hostile::LONG_WORD,
options:,
value: "hostile_1",
placement: :top_end,
required: true,
disabled: true
)
endHostile combobox field
- Activehttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Regional data residency, retention, and lawful-basis preferences for every workspace
- Invitedhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Wolfeschlegelsteinhausenbergerdorffwelchevoralternwarengewissenhaftschaferswessenschafewarenwohlgepflege
- Suspendedhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- محمد عبد الرحمن الهاشمي القحطاني
- Pending reviewhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- 山田太郎(プラットフォームエンジニアリング)
- Ownerhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- 🦄 Unicorn Ops 🚀🔥💯 On-call
- Read-onlyhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- 2,147,483,647,000
- Betahttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- สวัสดีครับยินดีต้อนรับสู่ระบบจัดการทีมงานของเรา
- Deprecatedhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- 1
- 999+https://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Awaiting payment confirmation from the issuing bank
- Trialhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Enterprise
- Past duehttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Archived
- Lockedhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Verified
- Unverifiedhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Disabled
- Drafthttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Scheduled
- Failedhttps://enterprise-holdings-international.example/workspaces/analytical-engines-research-and-production/settings/integrations/webhooks/deliveries/whd_01J8ZK3Q9W7RX2M5N6P8T4V1Y0/attempts/3?include=headers,body,response&expand=endpoint.owner
- Queued
- must be between 3 and 64 characters, may only contain letters, numbers, hyphens, and underscores, must not start or end with a hyphen, and must not collide with a reserved workspace slug such as admin, api, or www
test/dummy/app/components/gallery/components/combobox_page.rboptions = Gallery::Hostile::BADGE_LABELS.each_with_index.map do |label, index|
{ label:, value: "hostile_#{index}", description: index.even? ? Gallery::Hostile::LONG_URL : nil }
end
render NitroKit::Field.new(
nil,
:status,
as: :combobox,
id: "gallery-combobox-hostile-field",
name: "hostile[status]",
value: "hostile_3",
label: Gallery::Hostile::LONG_LABEL,
description: Gallery::Hostile::LONG_PARAGRAPH,
errors: [ Gallery::Hostile::LONG_ERROR ],
options:,
placeholder: Gallery::Hostile::LONG_WORD,
required: true,
html: { id: "gallery-combobox-hostile-field-wrapper" }
)Component contract
Constructor options, rendered root, closed vocabularies, and compound boundary for this component, exactly as shipped.
docs/component_contracts.md · NitroKit::Combobox
- Constructor-specific options
- required
id:,name:,label:,options: value: nil,placeholder: nil,include_blank: true,placement: :bottom_start,required: false,disabled: false,autocomplete: "off",control_aria: {}
- required
- Root and closed vocabulary
div[data-nk=combobox]; placementsbottom_start bottom_end top_start top_end- Compound contract
- Options are a non-empty set of unique typed choices;
Choice#descriptionrenders as a described secondary line in the listbox. A non-nil value must match a declared option.label:renders a realLabelbound to#{id}-input;label: falserequirescontrol_aria: { label: }or{ labelledby: }and names the input, listbox, and native select from it.placeholder:is the input hint andinclude_blank:is the native blank option. The named native Select is the truthful no-JavaScript control and submission source; Stimulus reveals and synchronizes the searchable combobox enhancement.
Relevant patterns
Application conventions this component belongs to. Each summary is the leading section of its pattern document.
docs/patterns/resource_form.md
Resource form
- One model-backed Phlex component renders initial and invalid states.
- Rails owns names, values, and errors; Nitro owns presentation.
- Invalid mutations render the same model with
422; success redirects with303. - Use one primary submit: the shell toolbar owns it, or a standalone form keeps it inside
form.group. - Wrap the form in a Turbo Frame only when it needs an independent lifecycle.
System rules for coding agents
Instructions for coding agents, repeated on every component page so one fetched page has enough context. Humans can usually skip this section.
test/dummy/app/components/gallery/agent_rules.rb
View agent instructions
- Nitro Kit 2.0 is a gem-owned, Phlex-only UI system for Rails. Render components directly:
render NitroKit::Button.new("Save", variant: :primary). - Compose Nitro components inside application-owned Phlex classes. Public initializers and declared slots are the component API; private methods are not.
- Public options are explicit keywords, and enumerated options are closed vocabularies. Invalid names or values raise
ArgumentError; this component's accepted options are in the contract above. - Pass native attributes through their typed boundary:
html:for HTML,aria:for ARIA, anddata:for application data.classandstyleare forbidden, including insidehtml:. - Nitro owns
NitroKit::Component::RESERVED_DATA_ATTRIBUTES:data-nk,data-slot,data-variant,data-size,data-nk-escape,data-enhanced,data-state,data-disabled,data-required,data-orientation,data-presentation,data-placement,data-layout,data-side,data-field-type,data-dir,data-gap,data-align,data-justify,data-wrap,data-cols,data-mode,data-key. Do not pass them throughdata:.data-action,data-controllerare additive and compose with Nitro behavior. - If an integration truly requires a class, use
desperately_need_a_class:. It requires a non-blank String and marks the exception withdata-nk-escape="class". - Every root emits
data-nk; owned parts emit component-qualifieddata-slotvalues such asfield-controlorcard-title. Select on those attributes, never on classes. - Customize components with documented
--nk-*custom properties in an application stylesheet. Variables beginning with--_nk-*are private component mechanics. - Use
NitroKit::FlexandNitroKit::Gridfor layout. Parents own external placement and available width; components own their intrinsic geometry. - Preserve native elements and accessibility semantics. State is exposed through native semantics and ARIA first, and through
data-statewhen styling or behavior also needs it.