Important
Behavior changes worth checking before you upgrade:
- QImg now renders its
<img>into the SSR/SSG HTML whenever the box shape is known (ratio,initial-ratioorheightset), instead of waiting for hydration. The markup a crawler or a no-JS client sees changes accordingly. Images with none of the three keep deferring to the client, unless you opt in with the newssr-prerenderprop. - The
.q-responsive__fillerelement no longer exists, and neither does QImg's filler node: the ratio now sits on the component root through CSSaspect-ratio. A QVideo with an explicit width resolves its ratio against that width instead of the parent's, and a QResponsive inside a height-clamped parent keeps its ratio by narrowing rather than squishing its content (Chromium and Firefox; WebKit keeps the previous full-width box). - QExpansionItem and QTree no longer render a QSlideTransition around their content; they drive the slide on the content element themselves, so the two extra component instances per item (per tree node) are gone from the tree. Their
durationprop is now a realNumberprop on the component itself rather than a passthrough to the child. - On Chromium, QSlideTransition, QExpansionItem, QTree and the vertical QStep no longer write inline
height/transitionstyles while sliding: the slide runs as a Web Animation over acalc-size(auto, size)keyframe. Onlyoverflow-yis written during the slide. Firefox and Safari keep the previous measuring CSS transition unchanged. - QInput autogrow textareas are sized by the browser through CSS
field-sizing: contentwhere it is supported (Chromium 123+, Safari 26+), so no inlineheight/overflowstyles appear on the textarea any more and amax-heightscrolls natively. Older engines keep the JS measuring fallback. - QParallax registers no scroll or resize listener and no IntersectionObserver where it can ride a view timeline (currently everything but Firefox). The
scrollevent and thecontentslot scope still work, but they are what forces the JS tracking back on, so use the slot only where you need its scope.
New
- feat(QImg): render the image in the SSR HTML; new prop ->
ssr-prerender-> until now QImg only set the image source once the client had mounted, so under SSR/SSG the browser started fetching it after the JS bundle had downloaded and the app had hydrated: the preload scanner never saw it, crawlers and no-JS clients got an empty box, and any hero image behind QImg was a late LCP by construction. The server and the pre-hydration client now render the<img>themselves, visible from the first paint like a native image, with the placeholder underneath; on mount the component reconciles against the element it finds instead of trusting load events, so an image that finished before hydration still goes through the regular load (or error) path. Only images whose box shape is known (ratio,initial-ratioorheight) are server-rendered by default, since otherwise the box would visibly snap from the default ratio to the natural one at hydration; the newssr-prerenderBoolean prop opts the rest in - feat(QDrawer):
panevent with the stage of the swipe gestures -> the swipe-to-open and swipe-to-close gestures ran silently: an app could only learn that the drawer had opened or closed after the fact, through the model and theshow/hideevents, never that a swipe was in progress, so content mounted on open only came in once the drawer had already been dragged fully into view and the swipe itself showed an empty panel. QDrawer now emitspanwith the gesturetype('open'or'close') and itsstage:'start'once the pan is detected,'end'when it crossed the threshold andshow()/hide()ran,'cancel'when the finger let go short of it and the drawer sprang back - feat(QParallax):
refresh()method -> everything the component settles at mount (the scrolling container and the way the media is moved along it, the media size, the position) was only re-checked on the events it listens to, so an ancestor changing itsoverflow, a media swapped without a load event or ascroll-targetthat only appears later left it stale with no way to tell it so.refresh()re-runs all three
Other
- perf(QParallax): the media moves on a view timeline where the browser has one -> the media now rides an
Element.animate()on aViewTimelineof the component root wherever the API exists and nothing scrollable sits between the root and its scroll target. The browser then moves it on its own thread, in step with the scroll whatever the main thread is doing, where the JS tracking wrote its transform a frame behind (one scroll step off in every other compositor frame) and stalled together with the main thread; the keyframes carry the exact numbers the JS tracking used, so the movement itself is unchanged. On that path the component registers no scroll or resize listener and no IntersectionObserver at all, the tracking being created only where thescrollevent or thecontentslot needs the percentage, and there it reads the timeline's own progress instead of measuring. Firefox, and any layout the timeline cannot express (anoverflow: hiddenwrapper, ascroll-targetthat is not the nearest scroll container), keep the JS tracking, itself now measuring once per frame instead of once per scroll event. The media is also no longer left hidden until the first measurement, and a media without a natural size yet no longer measures adisplay: nonebox - perf(QSlideTransition): slide on a Web Animation where
calc-size()is supported -> the height slide (so QExpansionItem, QTree and the vertical QStep) measured the content on every toggle, onescrollHeightread on show and two on hide, each forcing a layout: toggling N nodes in one tick (QTree expand/collapse all, filtering) forced N layouts on show and 2N on hide. Where the engine sizes acalc-size(auto, size)keyframe at layout (Chromium 129+) the slide is now anElement.animate()between 0 and that keyframe: no measurement, no forced layout, the height tracked live while sliding, and an interrupted slide of a kept element played backwards. Engines withoutcalc-size()keep the measuring CSS transition, untouched. Chromium 153 headless, 200 boxes toggled in one tick: forced layouts on show 219 -> 20 and on hide 418 -> 19, layout time 13.7ms -> 8.4ms and 22.2ms -> 6.3ms - perf(QExpansionItem/QTree): slide the content directly, without QSlideTransition -> both components own their content element and its visibility, so the QSlideTransition + Transition pair per item (two component instances, per tree node) only relayed the slide hooks to a
v-show. They now call the slide engine on the element themselves. Chromium 153 headless, a QTree with 780 nodes (155 parents) mounted expanded, median of 12 mounts: 43.1ms -> 32.3ms - perf(QInput): size autogrow textareas with CSS
field-sizingwhere supported -> autogrow textareas now getfield-sizing: contentand skip the JS measuring routine entirely on browsers that implement the property: no rAF layout thrash per keystroke, no inline height/overflow writes, noscrollTopjuggling, and the browser handles amax-heightwith native scrolling. Browsers below the property's floor (Chromium before 123, Safari before 26) keep the routine, which also coalesces to one measurement per frame now (a keystroke used to schedule two) and cancels a pending frame when autogrow turns off or the component unmounts. Height parity between the two paths was verified in Chromium, Firefox and WebKit across plain, labeled, outlined, filled, dense, max-height and custom font-size cases - refactor(ui): size QImg, QResponsive and QVideo ratio boxes with CSS
aspect-ratio-> thepadding-bottompercentage hack needed a dedicated filler element in QImg and QResponsive (plus a width-inheriting wrapper in the latter) and a zero-height root in QVideo.aspect-ratiois under the Baseline floor, so the ratio now sits on the root element itself and those nodes are gone: one element fewer per QImg, two fewer per QResponsive. See the note at the top - perf(use-position-engine): resolve the CSS anchor verdict once at import -> a QMenu or QTooltip setup now reads a boolean constant instead of calling into a cached support probe
Donations
Quasar Framework is an open-source MIT-licensed project made possible due to the generous contributions by sponsors and backers. If Quasar is useful in your workflow and you want to support ongoing maintenance, please consider the following: