diff --git a/packages/@headlessui-vue/src/components/tabs/tabs.test.ts b/packages/@headlessui-vue/src/components/tabs/tabs.test.ts
index f0a3aa6904..af8636c14f 100644
--- a/packages/@headlessui-vue/src/components/tabs/tabs.test.ts
+++ b/packages/@headlessui-vue/src/components/tabs/tabs.test.ts
@@ -1,4 +1,5 @@
-import { nextTick, ref } from 'vue'
+import { createSSRApp, nextTick, ref } from 'vue'
+import { renderToString } from 'vue/server-renderer'
import { createRenderTemplate, render } from '../../test-utils/vue-testing-library'
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from './tabs'
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
@@ -554,6 +555,60 @@ describe('Rendering', () => {
assertTabs({ active: 2 })
})
})
+
+ describe('SSR', () => {
+ it('should be possible to server side render the first Tab and Panel', async () => {
+ let app = createSSRApp({
+ components: { TabGroup, TabList, Tab, TabPanels, TabPanel },
+ template: html`
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+
+
+ Content 1
+ Content 2
+ Content 3
+
+
+ `,
+ })
+
+ let contents = await renderToString(app)
+ expect(contents).toContain(`Content 1`)
+ expect(contents).not.toContain(`Content 2`)
+ expect(contents).not.toContain(`Content 3`)
+ })
+
+ it('should be possible to server side render the defaultIndex Tab and Panel', async () => {
+ let app = createSSRApp({
+ components: { TabGroup, TabList, Tab, TabPanels, TabPanel },
+ template: html`
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+
+
+ Content 1
+ Content 2
+ Content 3
+
+
+ `,
+ })
+
+ let contents = await renderToString(app)
+ expect(contents).not.toContain(`Content 1`)
+ expect(contents).toContain(`Content 2`)
+ expect(contents).not.toContain(`Content 3`)
+ })
+ })
})
describe('`selectedIndex`', () => {
diff --git a/packages/@headlessui-vue/src/components/tabs/tabs.ts b/packages/@headlessui-vue/src/components/tabs/tabs.ts
index 4cc35ed10b..dd38c2ca08 100644
--- a/packages/@headlessui-vue/src/components/tabs/tabs.ts
+++ b/packages/@headlessui-vue/src/components/tabs/tabs.ts
@@ -58,6 +58,10 @@ function useTabsContext(component: string) {
return context
}
+let TabsSSRContext = Symbol('TabsSSRContext') as InjectionKey<
+ Ref<{ tabs: string[]; panels: string[] } | null>
+>
+
// ---
export let TabGroup = defineComponent({
@@ -84,7 +88,7 @@ export let TabGroup = defineComponent({
)
let api = {
- selectedIndex,
+ selectedIndex: computed(() => selectedIndex.value ?? props.defaultIndex ?? null),
orientation: computed(() => (props.vertical ? 'vertical' : 'horizontal')),
activation: computed(() => (props.manual ? 'manual' : 'auto')),
tabs,
@@ -116,6 +120,16 @@ export let TabGroup = defineComponent({
provide(TabsContext, api)
+ let SSRCounter = ref({ tabs: [], panels: [] })
+ let mounted = ref(false)
+ onMounted(() => {
+ mounted.value = true
+ })
+ provide(
+ TabsSSRContext,
+ computed(() => (mounted.value ? null : SSRCounter.value))
+ )
+
watchEffect(() => {
if (api.tabs.value.length <= 0) return
if (props.selectedIndex === null && selectedIndex.value !== null) return
@@ -231,7 +245,22 @@ export let Tab = defineComponent({
onMounted(() => api.registerTab(internalTabRef))
onUnmounted(() => api.unregisterTab(internalTabRef))
- let myIndex = computed(() => api.tabs.value.indexOf(internalTabRef))
+ let SSRContext = inject(TabsSSRContext)!
+ let mySSRIndex = computed(() => {
+ if (SSRContext.value) {
+ let mySSRIndex = SSRContext.value.tabs.indexOf(props.id)
+ if (mySSRIndex === -1) return SSRContext.value.tabs.push(props.id) - 1
+ return mySSRIndex
+ }
+
+ return -1
+ })
+
+ let myIndex = computed(() => {
+ let myIndex = api.tabs.value.indexOf(internalTabRef)
+ if (myIndex === -1) return mySSRIndex.value
+ return myIndex
+ })
let selected = computed(() => myIndex.value === api.selectedIndex.value)
function activateUsing(cb: () => FocusResult) {
@@ -391,7 +420,22 @@ export let TabPanel = defineComponent({
onMounted(() => api.registerPanel(internalPanelRef))
onUnmounted(() => api.unregisterPanel(internalPanelRef))
- let myIndex = computed(() => api.panels.value.indexOf(internalPanelRef))
+ let SSRContext = inject(TabsSSRContext)!
+ let mySSRIndex = computed(() => {
+ if (SSRContext.value) {
+ let mySSRIndex = SSRContext.value.panels.indexOf(props.id)
+ if (mySSRIndex === -1) return SSRContext.value.panels.push(props.id) - 1
+ return mySSRIndex
+ }
+
+ return -1
+ })
+
+ let myIndex = computed(() => {
+ let myIndex = api.panels.value.indexOf(internalPanelRef)
+ if (myIndex === -1) return mySSRIndex.value
+ return myIndex
+ })
let selected = computed(() => myIndex.value === api.selectedIndex.value)
return () => {