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 "").
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. 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:
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"").