tabs.html
<p-tabs :titles="['First Tab', 'Second Tab', 'Third Tab']">
  <template #tabpanel-1>
    First tab content
  </template>
  <template #tabpanel-2>
    second tab content
  </template>
  <template #tabpanel-3>
    third tab content
  </template>
</p-tabs>
index.scss
/**
 * this is a component specific property for animating.
 */
@property --tab__button-box-shadow {
  syntax: "<length>";
  inherits: false;
  initial-value: 0;
}

/**
 * @uses PTabs
 */
.tabs {
  $b: &;

  &__tablist {
    display: flex;
    gap: 3rem;
    border: solid var(--color-gray-300);
    border-width: 0 0 2px;
  }

  &__button {
    background: #0000;
    border: 0;
    box-shadow: 0 var(--tab__button-box-shadow) var(--accent-color) inset;
    color: inherit;
    cursor: pointer;
    font: inherit;
    font-size: 1.5rem;
    outline-offset: 2px;
    padding: 0 0 8px;
    transition:
      color var(--root-duration-fast) var(--root-ease-out),
      --tab__button-box-shadow var(--root-duration-fast) var(--root-ease-out);

    &:hover {
      --tab__button-box-shadow: -2px;
      box-shadow: 0 var(--tab__button-box-shadow) var(--accent-color-400) inset;
      color: var(--color-gray-900);
    }

    &.-selected {
      --tab__button-box-shadow: -8px;
      color: var(--color-gray-900);
    }
  }

  &.-vertical {
    display: grid;
    grid: auto / auto 1fr;

    #{$b}__tablist {
      flex-direction: column;
      border-width: 0 2px 0 0;
      gap: 2rem;
    }

    #{$b}__button {
      padding: 0 1rem 0 0;
      text-align: right;
      box-shadow: var(--tab__button-box-shadow) 0 var(--accent-color) inset;
    }
  }

  &.-solid {
    #{$b}__tablist {
      border: 0;
      gap: .5rem;
    }
    #{$b}__button {
      white-space: nowrap;
      background: var(--color-gray-100);
      border-radius: .5rem;
      box-shadow: 0px 1px 1px 0px var(--color-gray-200);
      padding: .5rem 1rem;
      margin-bottom: .5rem;

      &:hover {
        background: #fff;
        box-shadow: var(--root-box-shadow-low);
      }

      &.-selected {
        background: #fff;
        border-radius: 8px 8px 0 0;
        box-shadow: 0 .5rem 0 #fff;
      }
    }

    #{$b}__panel {
      background: #fff;
    }
  }
}
PTabs.vue
<script setup>
  import { computed, ref, useTemplateRef, isRef, isReactive, watchEffect } from 'vue'
  import focusWithArrows from '/resources/js/composables/focusWithArrows'

  const props = defineProps({
    titles: { type: Array, required: true },
  })

  const tablist = useTemplateRef('tablist')
  const visibleTab = ref(0)

  const focusedElement = focusWithArrows(tablist)

  watchEffect(() => {
    if (focusedElement.value) {
      visibleTab.value = tablist.value.indexOf(focusedElement.value)
    }
  })

  const tabs = computed(() => props.titles.map((title, i) => ({
    title,
    selected: i === visibleTab.value,
    tabindex: i === visibleTab.value ? 0 : -1,

    buttonId: `tab-${i + 1}`,
    panelId: `tabpanel-${i + 1}`,
  })))
</script>

<template>
  <div class="tabs">
    <div class="tabs__tablist" role="tablist" aria-label="Sample Tabs">
      <button
        class="tabs__button"
        :class="{ '-selected': tab.selected }"
        v-for="tab in tabs"
        role="tab"
        :aria-selected="tab.selected"
        :aria-controls="tab.panelId"
        :id="tab.buttonId"
        ref="tablist"
        :tabindex="tab.tabindex"
        v-text="tab.title"
      ></button>
    </div>

    <div
        class="tabs__panel"
        v-for="tab in tabs"
        role="tabpanel"
        :tabindex="tab.tabindex"
        :id="tab.panelId"
        :aria-labelledby="tab.buttonId"
        :hidden="!tab.selected"
      >
      <slot :name="tab.panelId" />
    </div>
  </div>
</template>