Skip to content

bug: a multi-line class attribute on an Ionic component throws InvalidCharacterError in Vue #31393

Description

@maximilianschmid

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

  1. ionic start multilineClass blank --type=vue
  2. Install @ionic/vue@9.0.0 / @ionic/vue-router@9.0.0 / @ionic/core@9.0.0.
  3. Put this in a page:
<template>
<ion-contentclass=" first-class second-class"
>
content
</ion-content>
</template>
<script setup>import { IonContent } from'@ionic/vue'</script>
  1. 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):

constgetComponentClasses=(classes)=>{returnclasses?.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

constgetComponentClasses=(classes)=>{returnclasses?.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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions