Prerequisites
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 dev and 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
};
Prerequisites
Ionic Framework Version
v9.x
Current Behavior
ion-selectderives the text it displays for the selected option, and that option'scontribution to the button's
aria-label, from the option's child nodes. In v9 thatderivation is wrong whenever an option has more than one child node, or has its text
wrapped in an element.
ion-select-optioncontentStarStarStarStar{'★'}{'Star'}★Star★Star★ StarA <b>Star</b>A StarA StarA<b>Star</b>StarStarTwo 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 anyoption 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 asan 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 everyoverlay interface —
createAlertInputs,createActionSheetButtons, andcreateOverlaySelectOptionsread the option through the same helper. In the linkedreproduction, 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-labelshould match what the browser renders for the option'scontent, which is what v8 produced via
textContent:★Starfor two adjacent text nodes,and
A StarforA <b>Star</b>.Steps to Reproduce
git clone https://github.com/ptmkenny/ionic-react-router-6-test.gitgit checkout select-option-text-spacenpm installnpm 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:
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
Additional Information
Root cause analysis by Claude Opus:
textForValue()incore/src/components/select/select.tsxused to be:#31241
(
a35e13d59872c2136a3f44871fbb0146d0007c19, resolving#29890) replaced
option.textContentwithgetDefaultSlotPlainText()at four call sites —textForValue,createAlertInputs,createActionSheetButtons, andcreateOverlaySelectOptions. Thathelper is:
#31241
(
a35e13d59872c2136a3f44871fbb0146d0007c19, resolving#29890) replaced
option.textContentwithgetDefaultSlotPlainText()at four call sites —textForValue,createAlertInputs,createActionSheetButtons, andcreateOverlaySelectOptions. Thathelper is: