Components
vue
Tabs

Tabs

This is Tab 1
---
import Tabs from "@technologyadvice/components/ui/tabs/Tabs.vue"

// define the tab's titles
const tabs = [{ title: "Tab 1" }, { title: "Tab 2" }, { title: "Tab 3" }]
---

<Tabs id="tabs-sample-1" tabs={tabs} client:visible>
  <!-- tab1 -->
  <div class="p-4" slot="tab-content-1">
    <span>This is Tab 1</span>
  </div>

  <!-- tab2 -->
  <div class="p-4" slot="tab-content-2">
    <span>This is Tab 2</span>
  </div>

  <!-- tab3 -->
  <div class="p-4" slot="tab-content-3">
    <span>This is Tab 3</span>
  </div>
</Tabs>
<style is:global>
  /* This is just to force full width on Astro live preview */
  div > * :has(.astro-tabs) {
    width: 100%;
  }
</style>
  • <Tabs/> : The main Tabs component
  • props.id : STRING value required for the component
  • props.tabs : ARRAY of tab collection to build the tabs header/nav
  • props.tabs.title : Set the tab header/nav title
  • default tab : Utilise v-model for setting default tab
  • props.headerClass : Additional Classes for astro-tabs__header
  • props.dropdownClass : Additional Classes for astro-tabs__dropdown
  • Icons : Utilise named Slot with this pattern — tab-icon-{tab position} e.g. <IconName slot="tab-icon-1"> to set the tab icon for the first tab.
  • Contents : Utilise named Slot with this pattern — tab-content-{tab position} e.g. <div class="p-4" slot="tab-content-1"> to set the tab content for the first tab.

States

Mobile

Select screen size:

You can also drag the container horizontally to resize!

This is Tab 1
---
import Tabs from "@technologyadvice/components/ui/tabs/Tabs.vue"

// define the tab's titles
const tabs = [{ title: "Tab 1" }, { title: "Tab 2" }, { title: "Tab 3" }]
let screenSizes = [
  { xs: "320px" },
  { sm: "384px" },
  { md: "448px" },
  { lg: "512px" },
  { xl: "576px" },
  { "2xl": "672px" },
  { "3xl": "768px" },
]
---

<astro-responsive-container>
  <p class="mb-4">Select screen size:</p>
  <div class="screen-options flex flex-wrap gap-4 mb-4">
    {
      screenSizes.map((screen) =>
        Object.entries(screen).map(([key, value]) => (
          <span key={key}>
            <input
              type="radio"
              id={key}
              name="responsive-screens"
              value={value}
            />
            <label for={key} class="cursor-pointer">
              <strong>{key}</strong> ({value})
            </label>
          </span>
        )),
      )
    }
  </div>
  <p class="mb-4">You can also drag the container horizontally to resize!</p>
  <div
    class:list={[
      "responsive-container",
      "border-[1px] border-dashed border-ui-warning-500",
      "overflow-hidden",
      "p-4",
      "resize-x",
      "max-w-full",
    ]}
  >
    <Tabs
      id="tabs-sample-2"
      tabs={tabs}
      dropdownClass="flex justify-center"
      client:visible
    >
      <!-- tab1 -->
      <div class="p-4" slot="tab-content-1">
        <span>This is Tab 1</span>
      </div>

      <!-- tab2 -->
      <div class="p-4" slot="tab-content-2">
        <span>This is Tab 2</span>
      </div>

      <!-- tab3 -->
      <div class="p-4" slot="tab-content-3">
        <span>This is Tab 3</span>
      </div>
    </Tabs>
  </div><!-- /.responsive-container -->
</astro-responsive-container>
<script>
  class AstroResponsiveContainer extends HTMLElement {
    constructor() {
      super()
      this.radios = this.querySelectorAll('input[name="responsive-screens"]')
      this.container = this.querySelector(".responsive-container")
    }
    connectedCallback() {
      if (this.radios) {
        this.radios.forEach((radio) => {
          radio.addEventListener("change", (event) => {
            if (event.target.checked) {
              this.setContainerWidth(event.target)
            }
          })
        })
        //set default
        this.radios[0].checked = true
        this.setContainerWidth(this.radios[0])
      }
    }
    setContainerWidth(radio) {
      this.container.style.width = radio.value
    }
  }
  customElements.define("astro-responsive-container", AstroResponsiveContainer)
</script>
<style>
  .responsive-container {
    background-image: url("data:image/svg+xml,%0A%3Csvg width='544' height='802' viewBox='0 0 544 802' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.03 7.66C.57 10.18.46 11.7 1.47 14.63c1.27 3.68 2.19 4.21 27.49 16.87 203.58 101.34 251.9 294.64 263.6 336.3 8.94 32.13 22.85 88.8 22.2 90.29-.22.28-23.28 9.8-51.31 21.15l-50.93 20.71-1.58 3.72c-.96 2.13-1.28 4.88-.86 6.11.43 1.23 3.76 5.69 7.5 9.8 3.68 4.23 16.19 18.43 27.7 31.6 235.31 269.05 217.7 249.2 221.15 250.02 3.93.97 7.27-.39 9.16-3.79.9-1.68 9.81-54.14 19.82-116.6 10.01-62.47 25.02-156.47 33.46-208.77 8.4-52.39 15.08-96.57 14.77-98.37-.46-2.59-1.2-3.5-4.41-5.14-4.8-2.26-.26-3.83-58.75 19.8-25.7 10.44-46.97 18.71-47.32 18.62-.29-.22-2.28-6.62-4.28-14.28-5.92-22.4-38.2-118.31-50.04-148.71C362.1 201.18 305.25 40.54 131.38 7.97A417.34 417.34 0 0 0 49.62.9C34.12 1.05 9.1 2.7 5.88 3.8c-1.04.36-2.76 2.11-3.85 3.86z' fill='%23C9D6E2' fill-rule='evenodd'/%3E%3C/svg%3E");
    background-position: calc(100% - 0.25em) calc(100% - 0.5em);
    background-size: 2em auto;
    background-repeat: no-repeat;
  }
</style>

Default Opened Tab

This is Tab 3
---
import Tabs from "@technologyadvice/components/ui/tabs/Tabs.vue"

// define the tab's titles
const tabs = [
  { title: "Tab 1" },
  { title: "Tab 2" },
  { title: "Tab 3" },
  { title: "Tab 4" },
]
let selectedTab = "tab3"
---

<Tabs id="tabs-sample-3" tabs={tabs} selectedTab={selectedTab} client:visible>
  <!-- tab1 -->
  <div class="p-4" slot="tab-content-1">
    <span>This is Tab 1</span>
  </div>

  <!-- tab2 -->
  <div class="p-4" slot="tab-content-2">
    <span>This is Tab 2</span>
  </div>

  <!-- tab3 -->
  <div class="p-4" slot="tab-content-3">
    <span>This is Tab 3</span>
  </div>

  <!-- tab4 -->
  <div class="p-4" slot="tab-content-4">
    <span>This is Tab 4</span>
  </div>
</Tabs>

With Icons

This is Tab 1
---
import Tabs from "@technologyadvice/components/ui/tabs/Tabs.vue"
import { Download, Smile, ShoppingCart, ExternalLink } from "lucide-vue-next"
// define the tab's titles
const tabs = [
  { title: "Tab 1" },
  { title: "Tab 2" },
  { title: "Tab 3" },
  { title: "Tab 4" },
]
---

<Tabs id="tabs-sample-4" client:visible tabs={tabs}>
  <!-- tab1 -->
  <Download slot="tab-icon-1" />
  <div class="p-4" slot="tab-content-1">
    <span>This is Tab 1</span>
  </div>

  <!-- tab2 -->
  <Smile slot="tab-icon-2" />
  <div class="p-4" slot="tab-content-2">
    <span>This is Tab 2</span>
  </div>

  <!-- tab3 -->
  <ShoppingCart slot="tab-icon-3" />
  <div class="p-4" slot="tab-content-3">
    <span>This is Tab 3</span>
  </div>

  <!-- tab4 -->
  <ExternalLink slot="tab-icon-4" />
  <div class="p-4" slot="tab-content-4">
    <span>This is Tab 4</span>
  </div>
</Tabs>

Center Aligned

Boots Boots Boots Boots Boots Boots
---
import Tabs from "@technologyadvice/components/ui/tabs/Tabs.vue"
import {
  Download,
  Smile,
  ShoppingCart,
  Camera,
  ExternalLink,
} from "lucide-vue-next"

// define the tab's titles
const tabs = [
  { title: "Boots" },
  { title: "Sneakers" },
  { title: "Flip-flops" },
  { title: "Jeans" },
  { title: "Cap" },
]
---

<Tabs
  id="tabs-sample-5"
  headerClass="justify-center"
  tabs={tabs}
  client:visible
>
  <!-- tab1 -->
  <Download slot="tab-icon-1" />
  <div class="p-4" slot="tab-content-1">
    <span>Boots </span>
    <span>Boots </span>
    <span>Boots </span>
    <span>Boots </span>
    <span>Boots </span>
    <span>Boots </span>
  </div>

  <!-- tab2 -->
  <Smile slot="tab-icon-2" />
  <div class="p-4" slot="tab-content-2">
    <p class="text-red-500">Sneakers: 0</p>
    <p class="text-red-500">Sneakers: 0</p>
    <p class="text-red-500">Sneakers: 0</p>
    <p class="text-red-500">Sneakers: 0</p>
    <p class="text-red-500">Sneakers: 0</p>
  </div>

  <!-- tab3 -->
  <Camera slot="tab-icon-3" />
  <div class="p-4" slot="tab-content-3">
    <span>Flip-flops</span>
    <span>Flip-flops</span>
    <span>Flip-flops</span>
    <span>Flip-flops</span>
  </div>

  <!-- tab4 -->
  <ExternalLink slot="tab-icon-4" />
  <div class="p-4" slot="tab-content-4">
    {Array.from({ length: 20 }).map(() => <p>Jeans: 40</p>)}
  </div>

  <!-- tab5 -->
  <ShoppingCart slot="tab-icon-5" />
  <div class="p-4" slot="tab-content-5">
    <span>Cap: Out of stock</span>
  </div>
</Tabs>

Right Aligned (VUE Rendering)

Sample rendering within VUE template/component

<script setup lang="ts">
import { onMounted, ref } from "vue"
import Tabs from "@technologyadvice/components/ui/tabs/Tabs.vue"
import { Download, Smile, ShoppingCart } from "lucide-vue-next"

// VUE live code specific
const props = defineProps({
  code: String,
  lang: String,
  filename: String,
})

const tabs = ref([])
let selectedTab = ref("")
onMounted(() => {
  tabs.value = [
    { title: "Flip-flops" },
    { title: "Boots" },
    { title: "Sneakers" },
  ]
  selectedTab.value = "tab2"
})
</script>
<template>
  <Tabs
    id="tabs-sample-6"
    headerClass="justify-end"
    :tabs="tabs"
    v-model:selectedTab="selectedTab"
  >
    <!-- tab1 -->
    <template v-slot:tab-icon-1><Download /></template>
    <template v-slot:tab-content-1>
      <p>Flip-flops: 64</p>
      <p>Flip-flops: 64</p>
      <p>Flip-flops: 64</p>
      <p>Flip-flops: 64</p>
    </template>

    <!-- tab2 -->
    <template v-slot:tab-icon-2><Smile /></template>
    <template v-slot:tab-content-2>
      <p>Boots: 32</p>
      <p>Boots: 32</p>
      <p>Boots: 32</p>
      <p>Boots: 32</p>
    </template>

    <!-- tab3 -->
    <template v-slot:tab-icon-3><ShoppingCart /></template>
    <template v-slot:tab-content-3>
      <p class="text-red-500">Sneakers: 0</p>
      <p class="text-red-500">Sneakers: 0</p>
      <p class="text-red-500">Sneakers: 0</p>
      <p class="text-red-500">Sneakers: 0</p>
    </template>
  </Tabs>
</template>

Passing V-MODEL (VUE Rendering)

Sample rendering within VUE template/component

Passing v-model to and from external element

<script setup lang="ts">
import { onMounted, ref } from "vue"
import Tabs from "@technologyadvice/components/ui/tabs/Tabs.vue"
import Dropdown from "@technologyadvice/components/ui/dropdown/Dropdown.vue"
import { Download, Smile, ShoppingCart } from "lucide-vue-next"

// VUE live code specific
const props = defineProps({
  code: String,
  lang: String,
  filename: String,
})

interface Option {
  value: string
  label: string
}

const tabs = ref([])
let selectedTab = ref("")
const dropDownOptions = ref<Option[]>([])

onMounted(() => {
  tabs.value = [
    { title: "Flip-flops" },
    { title: "Boots" },
    { title: "Sneakers" },
  ]
  selectedTab.value = "tab3"
  dropDownOptions.value = tabs.value.map((tab, index) => {
    return {
      value: "tab" + (index + 1),
      label: tab.title,
    }
  })
})
</script>
<template>
  <p class="mb-4 text-center">
    <em>Passing v-model to and from external element</em>
  </p>
  <Dropdown
    id="sort1"
    placeholder="--- Select ---"
    :default="selectedTab"
    :options="dropDownOptions"
    v-model="selectedTab"
    :class="['astro-tabs__dropdown', 'flex justify-center mb-4']"
  />
  <Tabs
    id="tabs-sample-7"
    headerClass="justify-center"
    :tabs="tabs"
    v-model:selectedTab="selectedTab"
  >
    <!-- tab1 -->
    <template v-slot:tab-icon-1><Download /></template>
    <template v-slot:tab-content-1>
      <p>Flip-flops: 64</p>
      <p>Flip-flops: 64</p>
      <p>Flip-flops: 64</p>
      <p>Flip-flops: 64</p>
    </template>

    <!-- tab2 -->
    <template v-slot:tab-icon-2><Smile /></template>
    <template v-slot:tab-content-2>
      <p>Boots: 32</p>
      <p>Boots: 32</p>
      <p>Boots: 32</p>
      <p>Boots: 32</p>
    </template>

    <!-- tab3 -->
    <template v-slot:tab-icon-3><ShoppingCart /></template>
    <template v-slot:tab-content-3>
      <p class="text-red-500">Sneakers: 0</p>
      <p class="text-red-500">Sneakers: 0</p>
      <p class="text-red-500">Sneakers: 0</p>
      <p class="text-red-500">Sneakers: 0</p>
    </template>
  </Tabs>
</template>