Skip to content

select blank_option bug #72

@nimmolo

Description

@nimmolo

blank_option does not render an empty-string value attribute for the option, so when you pass text for the label, HTML will actually send that text as the value of the option. This seems like a bug, because the blank value label is not what you want to send for the value.

Claude flagged this in one of my forms, and I confirmed this is the case (see below), so it seems worth addressing. Here's what it said:

⏺ Yes — it's a real bug in upstream Superform, but only when you pass a label to blank_option. Here's the matrix:

  ┌──────────────────────────────┬─────────────────────────────────────┬─────────────────┐                                                   
  │             Call             │               Renders               │ Browser submits │    
  ├──────────────────────────────┼─────────────────────────────────────┼─────────────────┤                                                   
  │ blank_option (no block)      │ <option selected></option>          │ "" ✓            │                                                  
  ├──────────────────────────────┼─────────────────────────────────────┼─────────────────┤                                                   
  │ blank_option { "Pick one…" } │ <option selected>Pick one…</option> │ "Pick one…" ✗   │                                                   
  └──────────────────────────────┴─────────────────────────────────────┴─────────────────┘

The unlabeled case works by accident via HTML's "if option has no value, fall back to text content" rule — empty text content means submitted value is "". So upstream maintainers (and most callers) probably never noticed: they use blank_option without a block as a visual separator, and submissions look right. The moment someone adds a hint label, it silently breaks.

Use cases where upstream's behavior is actually fine:

  • Purely visual blank option, no label (e.g., a single empty row at the top of a dropdown).
  • Where the developer wants the form to submit the option's label as the value — niche but legal (e.g., a dropdown where the labels
    themselves are valid params).

Use cases where it's broken:

  • blank_option { "Pick one…" } as a hint — submits "Pick one…" instead of "".
  • blank_option { "(No Project)" } (our pattern) — submits "(No Project)" instead of "".

The one-line upstream fix would be:

  def blank_option(&)                                                                                                                        
    option(selected: field.value.nil?, value: "", &)                                          
  end                                                                                                                                        

I'm happy to submit a PR for this if it sounds right to you. Until then, I have an override in my code to submit a manual blank option with ["", "label"] by coercing nil to "", instead of using superform's blank_option.


Here's my indication this is in fact what happens:

⏺ Confirmed empirically in headless Chromium:

  {"field_slip[project_id]":"(No Project)",
    "other_field":""}                                                                                 
  • <option>(No Project)</option> (no value attr) selected → FormData entry is the literal string "(No Project)".
  • <option value="">(Empty value)</option> (explicit empty value) selected → FormData entry is "".

So Rails would receive params[:field_slip][:project_id] == "(No Project)" for the first case — which is almost certainly never what the developer wanted. The browser does the text-content fallback per the HTML Living Standard (constructing form data set, step where an option's value is value attribute if present, otherwise text content).

The concise repro:

Superform::Rails::Components::Select#blank_option(&) invokes option(selected: ..., &) without a value: argument. Phlex's HTML DSL then omits the attribute entirely, producing <option>label</option>. When a label block is passed (blank_option { "Pick one…" }), the browser submits the label text as the form value rather than the empty string the developer presumably intended. Confirmed in headless Chromium via FormData: selected <option>(No Project)</option> produces {"field": "(No Project)"}.

▎ Suggested fix: option(selected: field.value.nil?, value: "", &). Doesn't regress the unlabeled case (<option selected value=""> still submits "").

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions