Skip to content
API 风格:

Tabs ​

Switch between peer content panels. Strictly aligned with the WAI-ARIA APG tabs pattern: tablist/tab/tabpanel role wiring, roving tabindex and arrow-key navigation.

Basic Usage ​

Basic Usage
Prose content.
vue
<script setup lang="ts">
</script>

<template>
  <LhTabs>
    <LhTabPane name="prose" label="Prose">Prose content.</LhTabPane>
    <LhTabPane name="poem" label="Poetry">Poetry content.</LhTabPane>
    <LhTabPane name="calligraphy" label="Calligraphy">Calligraphy content.</LhTabPane>
  </LhTabs>
</template>
vue
<script>
export default {}
</script>

<template>
  <LhTabs>
    <LhTabPane name="prose" label="Prose">Prose content.</LhTabPane>
    <LhTabPane name="poem" label="Poetry">Poetry content.</LhTabPane>
    <LhTabPane name="calligraphy" label="Calligraphy">Calligraphy content.</LhTabPane>
  </LhTabs>
</template>

Card Type and Disabled Panes ​

Card Type and Disabled Panes
Ink content.
vue
<script setup lang="ts">
</script>

<template>
  <LhTabs type="card">
    <LhTabPane name="ink" label="Ink">Ink content.</LhTabPane>
    <LhTabPane name="cinnabar" label="Cinnabar" disabled>Disabled content.</LhTabPane>
    <LhTabPane name="gold" label="Gold">Gold content.</LhTabPane>
  </LhTabs>
</template>
vue
<script>
export default {}
</script>

<template>
  <LhTabs type="card">
    <LhTabPane name="ink" label="Ink">Ink content.</LhTabPane>
    <LhTabPane name="cinnabar" label="Cinnabar" disabled>Disabled content.</LhTabPane>
    <LhTabPane name="gold" label="Gold">Gold content.</LhTabPane>
  </LhTabs>
</template>

Bottom Position ​

Bottom Position
Mountain content.
vue
<script setup lang="ts">
</script>

<template>
  <LhTabs tab-position="bottom">
    <LhTabPane name="mountain" label="Mountain">Mountain content.</LhTabPane>
    <LhTabPane name="water" label="Water">Water content.</LhTabPane>
  </LhTabs>
</template>
vue
<script>
export default {}
</script>

<template>
  <LhTabs tab-position="bottom">
    <LhTabPane name="mountain" label="Mountain">Mountain content.</LhTabPane>
    <LhTabPane name="water" label="Water">Water content.</LhTabPane>
  </LhTabs>
</template>

Closable and Addable ​

Closable and Addable
First scroll.
vue
<script setup lang="ts">
import { ref } from 'vue'
const panes = ref([
  { name: 's1', label: 'Scroll 1', content: 'First scroll.' },
  { name: 's2', label: 'Scroll 2', content: 'Second scroll.' },
])
let index = 2

const removePane = (name) => {
  const target = panes.value.find((pane) => pane.name === name)
  if (target) panes.value.splice(panes.value.indexOf(target), 1)
}
const addPane = () => {
  index += 1
  panes.value.push({ name: `s${index}`, label: `Scroll ${index}`, content: 'New scroll.' })
}
</script>

<template>
  <LhTabs closable addable @tab-remove="removePane" @tab-add="addPane">
    <LhTabPane v-for="pane in panes" :key="pane.name" :name="pane.name" :label="pane.label">
      {{ pane.content }}
    </LhTabPane>
  </LhTabs>
</template>
vue
<script>
export default {
  data() {
    return {
      panes: [
        { name: 's1', label: 'Scroll 1', content: 'First scroll.', disabled: false },
        { name: 's2', label: 'Scroll 2', content: 'Second scroll.', disabled: false },
      ],
      index: 2,
    }
  },
  methods: {
    removePane(name) {
      const target = this.panes.find((pane) => pane.name === name)
      if (target) this.panes.splice(this.panes.indexOf(target), 1)
    },
    addPane() {
      this.index += 1
      this.panes.push({ name: `s${this.index}`, label: `Scroll ${this.index}`, content: 'New scroll.', disabled: false })
    },
  },
}
</script>

<template>
  <LhTabs closable addable @tab-remove="removePane" @tab-add="addPane">
    <LhTabPane v-for="pane in panes" :key="pane.name" :name="pane.name" :label="pane.label">
      {{ pane.content }}
    </LhTabPane>
  </LhTabs>
</template>

When the active pane is closed the component switches to a neighbouring enabled pane and emits tab-remove; the data owner removes the pane from its source.

Controlled Usage ​

Controlled Usage
Ink wash content.

Active tab: mo

vue
<script setup lang="ts">
import { ref } from 'vue'
const active = ref('mo')
</script>

<template>
  <LhTabs v-model="active" type="card">
    <LhTabPane name="zh" label="Guofeng">Guofeng content.</LhTabPane>
    <LhTabPane name="mo" label="Ink Wash">Ink wash content.</LhTabPane>
  </LhTabs>
</template>
vue
<script>
export default {
  data() {
    return { active: 'mo' }
  },
}
</script>

<template>
  <LhTabs v-model="active" type="card">
    <LhTabPane name="zh" label="Guofeng">Guofeng content.</LhTabPane>
    <LhTabPane name="mo" label="Ink Wash">Ink wash content.</LhTabPane>
  </LhTabs>
</template>

Tabs API ​

LhTabs Props ​

PropDescriptionTypeDefault
modelValueActive pane name (v-model)string—
typeAppearance"line" | "card"'line'
closableWhether tabs can be closedbooleanfalse
addableWhether to show the add buttonbooleanfalse
tabPositionTab position"top" | "bottom"'top'
sxQuick layout propsLhSxProps—

LhTabs Events ​

EventDescriptionCallback
update:modelValueActive pane changed (v-model)(name: string)
tab-changeActive pane changed(name: string)
tab-removePane closed(name: string)
tab-addAdd button clicked—

LhTabPane Props ​

PropDescriptionTypeDefault
nameUnique pane key (stable, not meant to change dynamically)string—
labelTab titlestring''
disabledWhether the pane is disabledbooleanfalse

Slots ​

SlotDescriptionComponent
defaultTab panes (LhTabPane list)LhTabs
defaultPanel contentLhTabPane

Accessibility ​

Strictly aligned with the WAI-ARIA APG tabs pattern:

  • The header is role="tablist"; every tab is role="tab" (aria-selected + aria-controls); panels are role="tabpanel" (aria-labelledby + tabindex="0")
  • Roving tabindex: only the active tab is Tab-focusable, the rest are tabindex="-1"
  • Arrow keys move and auto-activate among enabled tabs (skipping disabled ones); Home/End jump to the ends
  • With closable, pressing Delete closes the focused tab; the close glyph is an aria-hidden decoration so no button is nested inside a button
  • Disabled tabs cannot be focused or activated; tab-remove is a no-op for them

Type Definitions ​

The TypeScript type structures exported by this component (auto-extracted from packages/components/tabs/src/types.ts and @lh-ui/share/core/* sources; click each entry to expand; see the Props table above for attribute descriptions):

LhTabsProps
ts
export interface LhTabsProps {
  modelValue?: string
  type?: LhTabsType
  closable?: boolean
  addable?: boolean
  tabPosition?: LhTabsPosition
  sx?: LhSxProps
}
LhTabPaneProps
ts
export interface LhTabPaneProps {
  name: string
  label?: string
  disabled?: boolean
}
LhTabsType (from @lh-ui/share/core/tabs)
ts
export type LhTabsType = 'line' | 'card'
LhTabsPosition (from @lh-ui/share/core/tabs)
ts
export type LhTabsPosition = 'top' | 'bottom'

Beauty of tradition, foundation of code