Repository navigation
bug: a multi-line class attribute on an Ionic component throws InvalidCharacterError in Vue #31393
Description
Ionic Framework Version
- v9.x
- Nightly
(Not present in v8.8.19.)
Current Behavior
In @ionic/vue 9, writing a class attribute across several lines on an Ionic component throws and tears down the render:
InvalidCharacterError: Failed to execute 'add' on 'DOMTokenList':
The token provided ('my-class\n') contains HTML space characters, which are not valid in tokens.
Follow-on errors from the broken render are typically RangeError: Maximum call stack size exceeded and TypeError: Cannot read properties of null (reading 'emitsOptions'), which make the original cause easy to miss.
Multi-line class attributes are valid HTML and valid Vue, are unremarkable in a formatted template, and worked in v8.
Expected Behavior
The class string should be tokenised on any ASCII whitespace, as the HTML parser and DOMTokenList do. The component should render normally.
Steps to Reproduce
ionic start multilineClass blank --type=vue- Install
@ionic/vue@9.0.0/@ionic/vue-router@9.0.0/@ionic/core@9.0.0. - Put this in a page:
<template>
<ion-content
class="
first-class
second-class
"
>
content
</ion-content>
</template>
<script setup>
import { IonContent } from '@ionic/vue'
</script>- Load the page and open the console.
Actual: InvalidCharacterError is thrown and the page fails to render.
Expected: the element gets both classes and renders.
The same template on @ionic/vue@8.8.19 works.
Note this only reproduces through Vue's class binding, not through plain HTML — the HTML parser normalises the attribute before Ionic sees it, so a static .html page with the same markup is fine.
Code Reproduction URL
No Stackblitz link, sorry — but the reproduction is fully inlined in Steps to Reproduce above and needs nothing beyond a stock ionic start … --type=vue app with that snippet pasted into a single page. The behaviour also flips purely on the @ionic/vue version, with no other change. Happy to put it on Stackblitz if that would help triage.
Ionic Info
Ionic:
Ionic CLI : 7.2.1
Ionic Framework : @ionic/vue 9.0.0
Capacitor:
Capacitor CLI : 8.5.0
@capacitor/android : 8.5.0
@capacitor/core : 8.5.0
@capacitor/ios : 8.5.0
System:
NodeJS : v26.7.0
npm : 11.19.0
OS : macOS
Additional Information
Cause
getComponentClasses splits on a literal single space (@ionic/vue/dist/index.js):
const getComponentClasses = (classes) => {
return classes?.split(' ') || [];
};A multi-line attribute value therefore yields tokens such as "first-class\n", which syncElementClasses — new in v9 — passes straight to classList.add():
componentClasses.forEach((c) => {
if (!!c && !element.classList.contains(c)) {
element.classList.add(c); // throws
}
});The !!c guard drops empty tokens but not whitespace-bearing ones. In v8 the split result was never handed to classList.add() — it only fed a filter — so malformed tokens were harmless.
Suggested fix
const getComponentClasses = (classes) => {
return classes?.split(/\s+/).filter(Boolean) || [];
};We fixed this in our own templates by keeping class attributes on one line, and additionally carry the split fix in a local pnpm patch as insurance.
Found while upgrading one app from 8.8.19 to 9.0.0; two more regressions from the same release: #31392 (cumulative Set in syncElementClasses resurrects removed conditional classes) and #31394 (ion-radio value reflect).
Activity
thetaPC commentedon Aug 21, 2026
@maximilianschmid Thank you for reporting the issue! I was able to reproduce it, though not through the steps as written. Vue's compiler condenses static
classattributes, so the multi-line attribute in your example reaches us as"first-class second-class"and renders fine. What actually triggers it is a dynamic:classcarrying a newline or tab between two class names, sincenormalizeClasstrims only the outer edges.From there your analysis is right. The class string is split on a single space rather than any whitespace, so a token like
first-class\nsurvives the split and gets passed toclassList.add(), which rejects it.One note on the fix: the code you patched is bundled into
@ionic/vuefrom@stencil/vue-output-target, so the real change has to land there and come back to us in a version bump. We are handling this together with #31392 since both are in the same class handling code, tracked upstream as stenciljs/output-targets#835.We already have a fix ready for review!
ShaneK commentedon Aug 26, 2026
Thanks for your report! The fix for this was merged into stencil/output-targets and that was updated in #31407. The next release of Ionic Framework should fix this issue for you!
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.