SignalR/SignalR · error · Error

options binding applies only to SELECT elements

Error message

options binding applies only to SELECT elements

What it means

Knockout's `options` binding rebuilds the `<option>` children of a `<select>`. Its `update` handler rejects any element whose lowercased tag name is not `select`, because the logic depends on select-specific DOM properties (`element.childNodes`, option selection). The throw happens during binding evaluation.

Source

Thrown at samples/Microsoft.AspNet.SignalR.LoadTestHarness/Scripts/knockout-2.1.0.debug.js:2260

            // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
            // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
            // to apply the value as well.
            var alsoApplyAsynchronously = valueIsSelectOption;
            if (alsoApplyAsynchronously)
                setTimeout(applyValueAction, 0);
        }

        // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
        // because you're not allowed to have a model value that disagrees with a visible UI selection.
        if (valueIsSelectOption && (element.length > 0))
            ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false);
    }
};

ko.bindingHandlers['options'] = {
    'update': function (element, valueAccessor, allBindingsAccessor) {
        if (ko.utils.tagNameLower(element) !== "select")
            throw new Error("options binding applies only to SELECT elements");

        var selectWasPreviouslyEmpty = element.length == 0;
        var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
            return node.tagName && (ko.utils.tagNameLower(node) === "option") && node.selected;
        }), function (node) {
            return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
        });
        var previousScrollTop = element.scrollTop;

        var value = ko.utils.unwrapObservable(valueAccessor());
        var selectedValue = element.value;

        // Remove all existing <option>s.
        // Need to use .remove() rather than .removeChild() for <option>s otherwise IE behaves oddly (https://github.com/SteveSanderson/knockout/issues/134)
        while (element.length > 0) {
            ko.cleanNode(element.options[0]);
            element.remove(0);
        }

View on GitHub (pinned to 693053b89a)

Solutions

  1. Move the `options` binding onto a `<select>` element.
  2. For non-select lists use `foreach` (with `<li>`/`<option>` templates) instead of `options`.
  3. Re-check the markup where the binding is declared.

Example fix

// before
<input data-bind="options: items" />

// after
<select data-bind="options: items"></select>
Defensive patterns

Strategy: validation

Validate before calling

// Static markup check helper
function assertSelectForOptions(el) {
    if (el && el.tagName && el.tagName.toLowerCase() !== 'select') {
        console.error('options binding must be on a <select>, got <' + el.tagName.toLowerCase() + '>');
        return false;
    }
    return true;
}

Type guard

function isSelectElement(el) { return !!el && el.nodeType === 1 && el.tagName.toLowerCase() === 'select'; }

Prevention

When it happens

Trigger: Writing `data-bind="options: items"` on an element that is not a `<select>` (e.g. `<input>`, `<ul>`, `<div>`).

Common situations: Copy-pasting a binding from a `<select>` onto a different element type, or dynamically swapping the element type while the binding is active.

Related errors


AI-assisted analysis of SignalR/SignalR@693053b89a (2026-08-13). Data as JSON: /api/errors/962a6d3cd0b467be. Report an issue: GitHub.