diff --git a/common/components/search_select.py b/common/components/search_select.py
index 450d552..a14dac3 100644
--- a/common/components/search_select.py
+++ b/common/components/search_select.py
@@ -13,8 +13,11 @@ This module imports only from ``common.components`` — it has no Django-forms o
``
``, which has no ``disabled`` state of its own. To disable it, set
``disabled`` on the inner search ```` (``[data-search-select-search]``);
the wrapper then greys itself via the ``has-[:disabled]:`` utilities in
-``_CONTAINER_CLASS``. Callers toggle only the control's ``disabled`` — never
-styles. (See ``ts/add_purchase.ts`` gating ``related_game`` on the type field.)
+``_CONTAINER_CLASS``. The inner input is excluded from the global
+disabled-input surface (``common/input.css``) so it stays transparent — the
+widget reads as one faded element, not a nested box. Callers toggle only the
+control's ``disabled`` — never styles. (See ``ts/add_purchase.ts`` gating
+``related_game`` on the type field.)
Option sourcing follows two axes. *Population*: options are either rendered
inline up front (``options=``, no ``search_url``) or fetched from ``search_url``.
diff --git a/common/input.css b/common/input.css
index a59fd09..8e2fa5d 100644
--- a/common/input.css
+++ b/common/input.css
@@ -127,11 +127,14 @@
}
}
-form input:disabled,
+/* Standalone form controls get a distinct disabled surface. The SearchSelect's
+ inner search box is excluded: it's a composite widget that owns its disabled
+ look on the wrapper (has-[:disabled] in _CONTAINER_CLASS), so painting the
+ inner input here would render a nested box inside the wrapper. */
+form input:disabled:not([data-search-select-search]),
select:disabled,
textarea:disabled {
@apply cursor-not-allowed bg-neutral-secondary-strong text-fg-disabled;
-
}
.errorlist {
diff --git a/e2e/test_widgets_e2e.py b/e2e/test_widgets_e2e.py
index 2cf1925..7c9dc0d 100644
--- a/e2e/test_widgets_e2e.py
+++ b/e2e/test_widgets_e2e.py
@@ -174,6 +174,10 @@ def test_add_purchase_type_game_disables_related_game_search(
expect(search).to_be_disabled()
# The component greys itself via has-[:disabled] when the input is disabled.
assert wrapper.evaluate("el => getComputedStyle(el).opacity") == "0.5"
+ # The disabled inner input stays transparent (excluded from the global
+ # disabled-input surface) so the widget reads as one element, not a nested
+ # box. transparent is mode-independent, so this holds in light and dark.
+ assert search.evaluate("el => getComputedStyle(el).backgroundColor") == "rgba(0, 0, 0, 0)"
page.select_option("#id_type", "dlc")
expect(search).to_be_enabled()
diff --git a/games/static/base.css b/games/static/base.css
index e036247..aa346d1 100644
--- a/games/static/base.css
+++ b/games/static/base.css
@@ -4400,7 +4400,7 @@
border-left-color: var(--color-slate-500);
}
}
-form input:disabled, select:disabled, textarea:disabled {
+form input:disabled:not([data-search-select-search]), select:disabled, textarea:disabled {
cursor: not-allowed;
background-color: var(--color-neutral-secondary-strong);
color: var(--color-fg-disabled);