Tabs

Components

Tabs

shadcn's Tabs with goo. The active tab is a blob that slides between triggers and lands with a jelly overshoot.

Make changes to your account here.
Click a tab, or focus the list and use the arrow keys.

Customize

Preset
Speed 1.00×
Variant

GoopTabs wraps the same reka-ui Tabs primitive as shadcn-vue's Tabs, with the same API. Keyboard navigation and ARIA work the same. Swap the imports and you're done.

Installation

Nuxt

pnpm add @goopui/nuxt
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@goopui/nuxt'],
})

The module auto-imports every GoopTabs* part, adds goop.css, and points Tailwind at the component classes. It needs Tailwind CSS v4 and the shadcn tokens (--muted, --background, --foreground, --input, --ring, --radius).

Vue

pnpm add @goopui/vue
main.css
@import "tailwindcss";
@import "@goopui/vue/goop.css";

Usage

<script setup lang="ts">
// Nuxt auto-imports these. In plain Vue, import them:
import { GoopTabs, GoopTabsContent, GoopTabsList, GoopTabsTrigger } from '@goopui/vue'
</script>

<template>
  <GoopTabs default-value="account">
    <GoopTabsList>
      <GoopTabsTrigger value="account">Account</GoopTabsTrigger>
      <GoopTabsTrigger value="password">Password</GoopTabsTrigger>
    </GoopTabsList>
    <GoopTabsContent value="account">Make changes to your account here.</GoopTabsContent>
    <GoopTabsContent value="password">Change your password here.</GoopTabsContent>
  </GoopTabs>
</template>

All the goo lives on GoopTabsList. GoopTabs, GoopTabsTrigger, and GoopTabsContent are shadcn's.

Behavior

  • The blob is the active tab's background. It slides to the new trigger with momentum, stretching with a neck of lumps while it travels, and lands with a jelly overshoot (momentum, wobble).
  • It follows everything. Clicks, the arrow keys, and programmatic changes. It tracks whichever trigger has data-state="active".
  • Labels stay sharp. The blob is aria-hidden and sits behind the real triggers. Text is never filtered.
  • Server render. SSR renders shadcn's markup, and the active trigger is styled until the blob attaches in the browser.

Vertical

orientation="vertical" is reka-ui's. The blob travels along the column:

<GoopTabs default-value="account" orientation="vertical">
  <GoopTabsList>
    <GoopTabsTrigger value="account">Account</GoopTabsTrigger>
    <GoopTabsTrigger value="password">Password</GoopTabsTrigger>
  </GoopTabsList>
</GoopTabs>

Line variant

variant="line" is shadcn's underline style. It keeps the underline and has no goo, since there's no filled surface to melt:

<GoopTabsList variant="line">...</GoopTabsList>

Tuning

Turn off the jelly or the stretch, change the feel, or go plain shadcn:

<GoopTabsList preset="wild">...</GoopTabsList>
<GoopTabsList :wobble="false" :momentum="false">...</GoopTabsList>
<GoopTabsList :goo="false">...</GoopTabsList>

:indicator="false" keeps the list's goo setting but gives the active tab shadcn's flat background. :goo="false" renders exactly shadcn's TabsList and TabsTrigger.

Driving it from code

GoopTabsList exposes handle, the live indicator handle (null before mount, when goo is off, or on the line variant).

Props

GoopTabs, GoopTabsTrigger, and GoopTabsContent take shadcn-vue's props. GoopTabsList takes reka-ui's TabsList props, class, and these:

GoopTabsList props
PropTypeDefaultNotes
variant'default' | 'line''default'Same as shadcn. 'line' keeps shadcn's underline and has no goo.
goobooleantrueMaster switch. false renders a plain shadcn TabsList.
indicatorbooleantrueThe active tab's background is a goo blob that slides between triggers. false gives shadcn's flat background.
wobblebooleantrueJelly overshoot when the blob lands.
momentumbooleantrueThe blob stretches and necks to a trailing tail while it travels.
speednumber1Animation speed multiplier.
preset'subtle' | 'playful' | 'wild'app config, then playfulThe overall feel.
renderer'svg' | 'canvas''svg'How the goo is drawn.

Accessibility

It's reka-ui's Tabs: a tablist with tab and tabpanel roles, roving focus, and arrow, Home, and End keys. Under reduced motion the blob snaps to the new tab with no stretch or overshoot, and still marks the active tab. See Accessibility & reduced motion.