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.
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 "").
blank_optiondoes not render an empty-stringvalueattribute for theoption, so when you pass text for the label, HTML will actually send that text as the value of the option.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:
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:
themselves are valid params).
Use cases where it's broken:
The one-line upstream fix would be:
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 coercingnilto"", instead of using superform'sblank_option.Here's my indication this is in fact what happens:
⏺ Confirmed empirically in headless Chromium:
<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(&)invokesoption(selected: ..., &)without avalue: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"").