Skip to content

fix(VChip, VTab): render inner wrappers as phrasing content - #23106

Open
BatLeDev wants to merge 2 commits into
vuetifyjs:masterfrom
BatLeDev:fix-vchip-phrasing-content
Open

fix(VChip, VTab): render inner wrappers as phrasing content#23106
BatLeDev wants to merge 2 commits into
vuetifyjs:masterfrom
BatLeDev:fix-vchip-phrasing-content

Conversation

@BatLeDev

@BatLeDev BatLeDev commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Description

fixes #23103

Two components render non-phrasing content inside a span, producing the same W3C error:

Element “div” not allowed as child of element “span” in this context.

VChip defaults its root tag to span, so everything it renders inside must be phrasing content. Four wrappers were hardcoded as divv-chip__filter, v-chip__prepend, v-chip__content and v-chip__append — so every chip was invalid on its own, anywhere on the page. Unlike the same class of defect on VBadge (#23093), there is no tag default to debate here, since the root is already a span.

All four are now span, which is what the sibling v-chip__overlay and v-chip__underlay wrappers already were.

VTab renders its underline as a hardcoded div.v-tab__slider inside VBtn's default slot, which VBtn wraps in a span.v-btn__content — so every tab emits a div inside a span too. It is now a span. Repro: https://vtfy.link/vtab-slider-invalid-nesting

No visual change in either case. VChip.sass declares display: inline-flex explicitly on .v-chip__content and on the .v-chip__filter/.v-chip__prepend/.v-chip__append group, and .v-tab__slider is position: absolute, which blockifies the box whatever the tag name. v-chip__filter is the only wrapper inside a VExpandXTransition; its display is declared the same way, and the added test covers it. The chip's close button is untouched — it is a button element, already valid phrasing content.

None of these elements is reachable from userland (tag only controls the root, and for VTab the root of the underlying VBtn), so there was no workaround short of patching the package.

Added a test per component: the chip root is a span whose subtree contains no div, with filter, prepend and append all rendered; and the tab's v-btn__content contains the slider and no div.

Happy to split this back into two PRs if you'd rather keep them separate.

Markup:

<template>
  <v-app>
    <v-container>
      <v-chip prepend-icon="mdi-tag" text="Plain chip" />
      <v-chip append-icon="mdi-close" text="With append" />
      <v-chip-group filter>
        <v-chip text="Filterable" />
      </v-chip-group>

      <v-tabs v-model="tab">
        <v-tab value="one" text="One" />
        <v-tab value="two" text="Two" />
      </v-tabs>
    </v-container>
  </v-app>
</template>

<script>
  export default {
    name: 'Playground',
    setup () {
      return {
        tab: 'one',
      }
    },
  }
</script>

Run the rendered page through https://validator.w3.org/nu — one div-inside-span error per chip and per tab before, none after.

VChip defaults its root tag to span, so everything it renders inside must be
phrasing content. Four wrappers were hardcoded as div — v-chip__filter,
v-chip__prepend, v-chip__content and v-chip__append — so every chip emitted a
div inside a span, which the W3C validator rejects:

  Element "div" not allowed as child of element "span" in this context.

Unlike the same defect on VBadge (vuetifyjs#23093), no particular nesting is needed to
trigger this: a plain chip is invalid on its own, anywhere on the page.

All four are now span, which is also what the sibling v-chip__overlay and
v-chip__underlay wrappers already were. No visual change: VChip.sass declares
display: inline-flex explicitly on .v-chip__content and on the
.v-chip__filter/.v-chip__prepend/.v-chip__append group, so the tag name
carries no layout meaning. The close button is untouched — it is a button
element, valid phrasing content already.

None of these wrappers is reachable from userland (tag only controls the
root), so there was no workaround short of patching the package.
Same defect as VChip, one component further: VTab renders its underline as
a hardcoded div.v-tab__slider inside VBtn's default slot, which VBtn wraps
in a span.v-btn__content. Every tab therefore emits a div inside a span,
which the W3C validator rejects:

  Element "div" not allowed as child of element "span" in this context.

The slider is now a span. No visual change: .v-tab__slider is
position: absolute, which blockifies the box whatever the tag name. The
element is not reachable from userland — tag only controls the root of the
underlying VBtn — so there was no workaround short of patching the package.
@BatLeDev BatLeDev changed the title fix(VChip): render inner wrappers as span for valid HTML nesting fix(VChip, VTab): render inner wrappers as phrasing content Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug Report][4.1.8] VChip renders divs inside its own span root, producing invalid HTML

1 participant