Repository navigation
bug: ion-select shows the wrong text for an option with multiple child nodes #31381
Description
Prerequisites
- I have read the Contributing Guidelines.
- I agree to follow the Code of Conduct.
- I have searched for existing issues that already report this problem, without success.
Ionic Framework Version
v9.x
Current Behavior
ion-select derives the text it displays for the selected option, and that option's
contribution to the button's aria-label, from the option's child nodes. In v9 that
derivation is wrong whenever an option has more than one child node, or has its text
wrapped in an element.
ion-select-option content |
browser renders | v8 | v9 |
|---|---|---|---|
Star |
Star |
Star |
Star |
two sibling text nodes, {'★'}{'Star'} |
★Star |
★Star |
★ Star |
A <b>Star</b> |
A Star |
A Star |
A |
<b>Star</b> |
Star |
Star |
(empty) |
Two distinct defects:
-
A space is inserted between adjacent text nodes. Every framework renders
{icon}{label}as two sibling text nodes with no whitespace between them, so any
option with an emoji, flag, or icon prefix gains a space that is not in the DOM. -
Text wrapped in an element is dropped entirely. An option whose content is
<b>Star</b>,<span>Star</span>, or an i18n component's wrapper element renders as
an empty select with an empty accessible name, silently. This is the more damaging
of the two.
Both affect the visible selected text and the aria-label, and both propagate to every
overlay interface — createAlertInputs, createActionSheetButtons, and
createOverlaySelectOptions read the option through the same helper. In the linked
reproduction, opening the first select shows an alert radio labelled ★ Star.
There is no error or warning; the text is just wrong.
Expected Behavior
The selected text and aria-label should match what the browser renders for the option's
content, which is what v8 produced via textContent: ★Star for two adjacent text nodes,
and A Star for A <b>Star</b>.
Steps to Reproduce
-
git clone https://github.com/ptmkenny/ionic-react-router-6-test.git -
git checkout select-option-text-space -
npm install -
npm run devand open the app (Tab 1). -
Observe: the heading reads 2 of 2 wrong. The page reads each select's shadow DOM
after it renders and prints what the component displays next to what the markup should
produce:❌ Adjacent text nodes option content: {'★'}{'Star'} expected: "★Star" displayed: "★ Star" aria-label: "Adjacent text nodes, ★ Star" ❌ Text wrapped in an element option content: A <b>Star</b> expected: "A Star" displayed: "A" aria-label: "Wrapped in an element, A" -
Optionally open either select: the alert interface shows the same wrong text
(★ Star).
Code Reproduction URL
https://github.com/ptmkenny/ionic-react-router-6-test/tree/select-option-text-space
Ionic Info
Ionic:
Ionic CLI : 7.2.1 (/home/node/.npm/_npx/f6fddb685269761d/node_modules/@ionic/cli)
Ionic Framework : @ionic/react 9.0.0
Capacitor:
Capacitor CLI : 8.0.0
@capacitor/android : not installed
@capacitor/core : 8.0.0
@capacitor/ios : not installed
Utility:
cordova-res : not installed globally
native-run : 2.0.1
System:
NodeJS : v24.19.0 (/usr/local/bin/node)
npm : 11.17.0
NodeJS : v24.19.0 (/usr/local/bin/node)
npm : 11.17.0
OS : Linux 6.18
Additional Information
Root cause analysis by Claude Opus:
textForValue() in core/src/components/select/select.tsx used to be:
return selectOpt ? selectOpt.textContent : null;#31241
(a35e13d59872c2136a3f44871fbb0146d0007c19, resolving
#29890) replaced
option.textContent with getDefaultSlotPlainText() at four call sites — textForValue,
createAlertInputs, createActionSheetButtons, and createOverlaySelectOptions. That
helper is:
const getDefaultSlotPlainText = (option: HTMLIonSelectOptionElement): string => {
const texts = Array.from(option.childNodes)
.filter((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
return !(node as HTMLElement).hasAttribute('slot');
}
`textForValue()` in `core/src/components/select/select.tsx` used to be:
```ts
return selectOpt ? selectOpt.textContent : null;#31241
(a35e13d59872c2136a3f44871fbb0146d0007c19, resolving
#29890) replaced
option.textContent with getDefaultSlotPlainText() at four call sites — textForValue,
createAlertInputs, createActionSheetButtons, and createOverlaySelectOptions. That
helper is:
const getDefaultSlotPlainText = (option: HTMLIonSelectOptionElement): string => {
const texts = Array.from(option.childNodes)
.filter((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
return !(node as HTMLElement).hasAttribute('slot');
}
return node.nodeType === Node.TEXT_NODE;
})
.filter((node) => node.nodeType === Node.TEXT_NODE) // discards every element
.map((n) => n.textContent?.trim())
.filter((t) => t);
return texts.join(' '); // separator not in the DOM
};
Activity
fix(select): derive option text from all child nodes (ionic-team#31382)
ShaneK commentedon Aug 26, 2026
Thanks for your report! This has been fixed in v9.0.1!
ionitron-bot commentedon Sep 25, 2026
Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.