Tabs
Tabs in a flat sunken track (default), standalone pills, or underlined (line). Horizontal and vertical.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
export default function Demo() { return ( <Tabs defaultValue="overview" className="w-[420px]"> <TabsList> <TabsTrigger value="overview">Overview</TabsTrigger> <TabsTrigger value="holdings">Holdings</TabsTrigger> <TabsTrigger value="activity">Activity</TabsTrigger> </TabsList> <TabsContent value="overview"> NAV 59.9998. 4 tokens, 412 holders, rebalanced 2 days ago. </TabsContent> <TabsContent value="holdings">Four tokens, weighted by AUM.</TabsContent> <TabsContent value="activity">No recent trades.</TabsContent> </Tabs> );}<script setup lang="ts">import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";</script>
<template> <Tabs default-value="overview" class="w-[420px]"> <TabsList> <TabsTrigger value="overview">Overview</TabsTrigger> <TabsTrigger value="holdings">Holdings</TabsTrigger> <TabsTrigger value="activity">Activity</TabsTrigger> </TabsList> <TabsContent value="overview"> NAV 59.9998. 4 tokens, 412 holders, rebalanced 2 days ago. </TabsContent> <TabsContent value="holdings">Four tokens, weighted by AUM.</TabsContent> <TabsContent value="activity">No recent trades.</TabsContent> </Tabs></template><script lang="ts"> import * as Tabs from "$lib/components/ui/tabs";</script>
<Tabs.Root value="overview" class="w-[420px]"> <Tabs.List> <Tabs.Trigger value="overview">Overview</Tabs.Trigger> <Tabs.Trigger value="holdings">Holdings</Tabs.Trigger> <Tabs.Trigger value="activity">Activity</Tabs.Trigger> </Tabs.List> <Tabs.Content value="overview">NAV 59.9998. 4 tokens, 412 holders, rebalanced 2 days ago.</Tabs.Content> <Tabs.Content value="holdings">Four tokens, weighted by AUM.</Tabs.Content> <Tabs.Content value="activity">No recent trades.</Tabs.Content></Tabs.Root>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/tabspnpm dlx shadcn@latest add @edmi-ui/tabsyarn dlx shadcn@latest add @edmi-ui/tabsbunx shadcn@latest add @edmi-ui/tabsPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/react class-variance-authorityTerminal window pnpm add @base-ui/react class-variance-authorityTerminal window yarn add @base-ui/react class-variance-authorityTerminal window bun add @base-ui/react class-variance-authorityAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/tabspnpm dlx shadcn-vue@latest add @edmi-ui/tabsyarn dlx shadcn-vue@latest add @edmi-ui/tabsbunx shadcn-vue@latest add @edmi-ui/tabsPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui class-variance-authority @vueuse/coreTerminal window pnpm add reka-ui class-variance-authority @vueuse/coreTerminal window yarn add reka-ui class-variance-authority @vueuse/coreTerminal window bun add reka-ui class-variance-authority @vueuse/coreAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/tabs.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/tabs.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/tabs.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/tabs.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
<Tabs defaultValue="overview" className="w-[420px]"> <TabsList> <TabsTrigger value="overview">Overview</TabsTrigger> <TabsTrigger value="holdings">Holdings</TabsTrigger> <TabsTrigger value="activity">Activity</TabsTrigger> </TabsList> <TabsContent value="overview"> NAV 59.9998. 4 tokens, 412 holders, rebalanced 2 days ago. </TabsContent> <TabsContent value="holdings">Four tokens, weighted by AUM.</TabsContent> <TabsContent value="activity">No recent trades.</TabsContent></Tabs><script setup lang="ts">import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";</script>
<template> <Tabs default-value="overview" class="w-[420px]"> <TabsList> <TabsTrigger value="overview">Overview</TabsTrigger> <TabsTrigger value="holdings">Holdings</TabsTrigger> <TabsTrigger value="activity">Activity</TabsTrigger> </TabsList> <TabsContent value="overview"> NAV 59.9998. 4 tokens, 412 holders, rebalanced 2 days ago. </TabsContent> <TabsContent value="holdings">Four tokens, weighted by AUM.</TabsContent> <TabsContent value="activity">No recent trades.</TabsContent> </Tabs></template><script lang="ts"> import * as Tabs from "$lib/components/ui/tabs";</script>
<Tabs.Root value="overview" class="w-[420px]"> <Tabs.List> <Tabs.Trigger value="overview">Overview</Tabs.Trigger> <Tabs.Trigger value="holdings">Holdings</Tabs.Trigger> <Tabs.Trigger value="activity">Activity</Tabs.Trigger> </Tabs.List> <Tabs.Content value="overview">NAV 59.9998. 4 tokens, 412 holders, rebalanced 2 days ago.</Tabs.Content> <Tabs.Content value="holdings">Four tokens, weighted by AUM.</Tabs.Content> <Tabs.Content value="activity">No recent trades.</Tabs.Content></Tabs.Root>Component source
Section titled “Component source”"use client";
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";import { cva, type VariantProps } from "class-variance-authority";import { cn } from "cn";import * as React from "react";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
function Tabs({ className, orientation = "horizontal", ...props}: TabsPrimitive.Root.Props) { return ( <TabsPrimitive.Root data-slot="tabs" data-orientation={orientation} className={cn( "group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className, )} {...props} /> );}
// Flat by default: the active tab is a --tab-active fill + 1px border, with or without a track.// ✦ `elevation` (on TabsList, flows to the triggers): raised/floating make ONLY the active tab rise (bevel);// the list/track itself never gets the bevel. The `line` variant has no track and ignores elevation.const tabsListVariants = cva( "group/tabs-list text-muted-foreground group-data-[orientation=vertical]/tabs:flex-col", { variants: { variant: { default: "inline-flex w-fit items-center justify-center gap-0.5 rounded-lg border border-border bg-muted p-[3px] shadow-[inset_0_1px_2px_rgb(0_0_0/0.04)] group-data-[orientation=vertical]/tabs:h-fit", line: "flex gap-[22px] border-border group-data-[orientation=horizontal]/tabs:border-b group-data-[orientation=vertical]/tabs:gap-1 group-data-[orientation=vertical]/tabs:border-l", // ✦ no track pills: "inline-flex w-fit items-center gap-1 group-data-[orientation=vertical]/tabs:h-fit", }, }, defaultVariants: { variant: "default", }, },);
const raisedActive = "data-[active]:border-transparent data-[active]:[background-image:var(--r1-s-face)] data-[active]:[background-origin:border-box] data-[active]:shadow-btn-raised-neutral";
const tabsTriggerVariants = cva( "relative inline-flex items-center justify-center gap-1.5 text-[13.5px] font-medium whitespace-nowrap text-muted-foreground transition-colors outline-none hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { variant: { default: "h-[30px] flex-1 rounded-[7px] border border-transparent px-3.5 data-[active]:border-border data-[active]:bg-tab-active data-[active]:text-foreground", line: "-mb-px border-b-2 border-transparent px-0.5 pb-[11px] data-[active]:border-foreground data-[active]:text-foreground group-data-[orientation=vertical]/tabs:mb-0 group-data-[orientation=vertical]/tabs:-ml-px group-data-[orientation=vertical]/tabs:border-b-0 group-data-[orientation=vertical]/tabs:border-l-2 group-data-[orientation=vertical]/tabs:py-1 group-data-[orientation=vertical]/tabs:pr-0 group-data-[orientation=vertical]/tabs:pl-3", pills: "h-8 rounded-[7px] border border-transparent px-3 data-[active]:border-border data-[active]:bg-tab-active data-[active]:text-foreground", }, // ✦ the TabsList elevation, passed down to each trigger elevation: { flat: "", raised: "" }, }, compoundVariants: [ { variant: "default", elevation: "raised", class: raisedActive }, { variant: "pills", elevation: "raised", class: raisedActive }, ], defaultVariants: { variant: "default", elevation: "flat" }, },);
const TabsListContext = React.createContext<{ variant: "default" | "line" | "pills"; elevation: "flat" | "raised";}>({ variant: "default", elevation: "flat" });
function TabsList({ className, variant = "default", elevation, activateOnFocus = true, ...props}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants> & { /** ✦ depth: raised +1 / floating +2 make the active tab rise (ignored by `line`). */ elevation?: Elevation; }) { const level = useElevation(elevation, "control"); return ( <TabsListContext.Provider value={{ variant: variant ?? "default", elevation: level === "flat" || level === "sunken" ? "flat" : "raised", }} > <TabsPrimitive.List data-slot="tabs-list" data-variant={variant} activateOnFocus={activateOnFocus} className={cn(tabsListVariants({ variant }), className)} {...props} /> </TabsListContext.Provider> );}
function TabsTrigger({ className, elevation, ...props}: TabsPrimitive.Tab.Props & { elevation?: Elevation }) { const context = React.useContext(TabsListContext); const own = elevation && elevation !== "auto" ? elevation === "raised" || elevation === "floating" ? "raised" : "flat" : undefined; return ( <TabsPrimitive.Tab data-slot="tabs-trigger" className={cn( tabsTriggerVariants({ variant: context.variant, elevation: own ?? context.elevation, }), className, )} {...props} /> );}
function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) { return ( <TabsPrimitive.Panel data-slot="tabs-content" className={cn("flex-1 text-sm outline-none", className)} {...props} /> );}
export { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants, tabsTriggerVariants,};<script setup lang="ts">import type { TabsRootEmits, TabsRootProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { TabsRoot, useForwardPropsEmits } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<TabsRootProps & { class?: HTMLAttributes['class'] }>()const emits = defineEmits<TabsRootEmits>()
const delegatedProps = reactiveOmit(props, 'class')const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <TabsRoot v-slot="slotProps" data-slot="tabs" :data-orientation="forwarded.orientation || 'horizontal'" v-bind="forwarded" :class="cn('group/tabs flex gap-2 data-[orientation=horizontal]:flex-col', props.class)" > <slot v-bind="slotProps" /> </TabsRoot></template><script setup lang="ts">import type { TabsContentProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { TabsContent } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<TabsContentProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')</script>
<template> <TabsContent data-slot="tabs-content" :class="cn('flex-1 text-sm outline-none', props.class)" v-bind="delegatedProps" > <slot /> </TabsContent></template><script setup lang="ts">import type { TabsListProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import type { TabsListVariants } from '.'import { reactiveOmit } from '@vueuse/core'import { computed, provide } from 'vue'import { TabsList } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'import { type Elevation, useElevation } from '@/registry/edmi/ui/elevation'import { tabsListVariants } from '.'
const props = withDefaults(defineProps<TabsListProps & { class?: HTMLAttributes['class'] variant?: TabsListVariants['variant'] /** ✦ depth: raised +1 / floating +2 make the active trigger rise (default / pills). Triggers may override. */ elevation?: Elevation}>(), { variant: 'default', elevation: undefined,})
const level = useElevation(() => props.elevation, 'control')const raised = computed(() => level.value === 'raised' || level.value === 'floating')
provide('tabsList', { get variant() { return props.variant }, get raised() { return raised.value },})
const delegatedProps = reactiveOmit(props, 'class', 'variant', 'elevation')</script>
<template> <TabsList data-slot="tabs-list" :data-variant="variant" v-bind="delegatedProps" :class="cn(tabsListVariants({ variant }), props.class)" > <slot /> </TabsList></template><script setup lang="ts">import type { TabsTriggerProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { TabsTrigger, useForwardProps } from 'reka-ui'import { computed, inject } from 'vue'import { cn } from '@/registry/edmi/lib/utils'import type { Elevation } from '@/registry/edmi/ui/elevation'
const props = withDefaults(defineProps<TabsTriggerProps & { class?: HTMLAttributes['class'] /** ✦ overrides the TabsList `elevation` for this trigger (raised/floating = the active trigger rises) */ elevation?: Elevation}>(), { elevation: undefined })
const context = inject<{ raised?: boolean } | null>('tabsList', null)
const delegatedProps = reactiveOmit(props, 'class', 'elevation')
const resolvedRaised = computed(() => props.elevation && props.elevation !== 'auto' ? props.elevation === 'raised' || props.elevation === 'floating' : !!context?.raised,)
const forwardedProps = useForwardProps(delegatedProps)</script>
<template> <TabsTrigger data-slot="tabs-trigger" :class="cn( 'relative inline-flex items-center justify-center gap-1.5 text-[13.5px] font-medium whitespace-nowrap text-muted-foreground transition-colors outline-none hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4', // default (in a track) 'group-data-[variant=default]/tabs-list:h-[30px] group-data-[variant=default]/tabs-list:flex-1 group-data-[variant=default]/tabs-list:rounded-[7px] group-data-[variant=default]/tabs-list:border group-data-[variant=default]/tabs-list:border-transparent group-data-[variant=default]/tabs-list:px-3.5 group-data-[variant=default]/tabs-list:data-[state=active]:border-border group-data-[variant=default]/tabs-list:data-[state=active]:bg-tab-active group-data-[variant=default]/tabs-list:data-[state=active]:text-foreground', // pills (✦ no track) 'group-data-[variant=pills]/tabs-list:h-8 group-data-[variant=pills]/tabs-list:rounded-[7px] group-data-[variant=pills]/tabs-list:border group-data-[variant=pills]/tabs-list:border-transparent group-data-[variant=pills]/tabs-list:px-3 group-data-[variant=pills]/tabs-list:data-[state=active]:border-border group-data-[variant=pills]/tabs-list:data-[state=active]:bg-tab-active group-data-[variant=pills]/tabs-list:data-[state=active]:text-foreground', // line 'group-data-[variant=line]/tabs-list:-mb-px group-data-[variant=line]/tabs-list:border-b-2 group-data-[variant=line]/tabs-list:border-transparent group-data-[variant=line]/tabs-list:px-0.5 group-data-[variant=line]/tabs-list:pb-[11px] group-data-[variant=line]/tabs-list:data-[state=active]:border-foreground group-data-[variant=line]/tabs-list:data-[state=active]:text-foreground group-data-[orientation=vertical]/tabs:group-data-[variant=line]/tabs-list:mb-0 group-data-[orientation=vertical]/tabs:group-data-[variant=line]/tabs-list:-ml-px group-data-[orientation=vertical]/tabs:group-data-[variant=line]/tabs-list:border-b-0 group-data-[orientation=vertical]/tabs:group-data-[variant=line]/tabs-list:border-l-2 group-data-[orientation=vertical]/tabs:group-data-[variant=line]/tabs-list:py-1 group-data-[orientation=vertical]/tabs:group-data-[variant=line]/tabs-list:pr-0 group-data-[orientation=vertical]/tabs:group-data-[variant=line]/tabs-list:pl-3', // elevation ✦ raised/floating: only the active trigger rises (default + pills; line ignores it) resolvedRaised && 'group-data-[variant=default]/tabs-list:data-[state=active]:border-transparent group-data-[variant=default]/tabs-list:data-[state=active]:[background-image:var(--r1-s-face)] group-data-[variant=default]/tabs-list:data-[state=active]:[background-origin:border-box] group-data-[variant=default]/tabs-list:data-[state=active]:shadow-btn-raised-neutral group-data-[variant=pills]/tabs-list:data-[state=active]:border-transparent group-data-[variant=pills]/tabs-list:data-[state=active]:[background-image:var(--r1-s-face)] group-data-[variant=pills]/tabs-list:data-[state=active]:[background-origin:border-box] group-data-[variant=pills]/tabs-list:data-[state=active]:shadow-btn-raised-neutral', props.class, )" v-bind="forwardedProps" > <slot /> </TabsTrigger></template>import type { VariantProps } from "class-variance-authority";import { cva } from "class-variance-authority";
export { default as Tabs } from "./Tabs.vue";export { default as TabsContent } from "./TabsContent.vue";export { default as TabsList } from "./TabsList.vue";export { default as TabsTrigger } from "./TabsTrigger.vue";
// Flat by default: the active tab is a --tab-active fill + 1px border (with a track for `default`, without for `pills`).// `line` has no track; it underlines the active tab. `elevation` ✦ (on TabsList or a trigger) raised/floating make ONLY the active// `default`/`pills` trigger rise (bevel); `line` ignores it.export const tabsListVariants = cva( "group/tabs-list text-muted-foreground group-data-[orientation=vertical]/tabs:flex-col", { variants: { variant: { default: "inline-flex w-fit items-center justify-center gap-0.5 rounded-lg border border-border bg-muted p-[3px] shadow-[inset_0_1px_2px_rgb(0_0_0/0.04)] group-data-[orientation=vertical]/tabs:h-fit", // ✦ no track pills: "inline-flex w-fit items-center gap-1 group-data-[orientation=vertical]/tabs:h-fit", line: "flex gap-[22px] border-border group-data-[orientation=horizontal]/tabs:border-b group-data-[orientation=vertical]/tabs:gap-1 group-data-[orientation=vertical]/tabs:border-l", }, }, defaultVariants: { variant: "default", }, },);
export type TabsListVariants = VariantProps<typeof tabsListVariants>;<script lang="ts"> import { Tabs as TabsPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), value = $bindable(""), class: className, ...restProps }: TabsPrimitive.RootProps = $props();</script>
<TabsPrimitive.Root bind:ref bind:value data-slot="tabs" class={cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className)} {...restProps}/><script lang="ts" module> import { getContext, setContext } from "svelte"; import { tv, type VariantProps } from "tailwind-variants";
// Flat by default: the active tab is `--tab-active` + 1px border (with or without a track). // `default` = track, `line` = underline, `pills` ✦ = no track. elevation ✦ raised/floating make ONLY the // active tab rise (default/pills; `line` ignores it); the level is passed down to every trigger. export const tabsListVariants = tv({ base: "group/tabs-list text-muted-foreground group-data-[orientation=vertical]/tabs:flex-col", variants: { variant: { default: "inline-flex w-fit items-center justify-center gap-0.5 rounded-lg border border-border bg-muted p-[3px] shadow-[inset_0_1px_2px_rgb(0_0_0/0.04)] group-data-[orientation=vertical]/tabs:h-fit", line: "flex gap-[22px] border-border group-data-[orientation=horizontal]/tabs:border-b group-data-[orientation=vertical]/tabs:gap-1 group-data-[orientation=vertical]/tabs:border-l", // ✦ no track pills: "inline-flex w-fit items-center gap-1", }, }, defaultVariants: { variant: "default", }, });
export type TabsListVariant = VariantProps<typeof tabsListVariants>["variant"];
interface TabsListContext { variant: NonNullable<TabsListVariant>; raised: boolean; }
export function setTabsListCtx(ctx: TabsListContext) { setContext("tabsList", ctx); }
export function getTabsListCtx() { return getContext<TabsListContext | undefined>("tabsList"); }</script>
<script lang="ts"> import { Tabs as TabsPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js";
let { ref = $bindable(null), variant = "default", elevation = "auto", class: className, ...restProps }: TabsPrimitive.ListProps & { variant?: TabsListVariant; /** ✦ depth: raised +1 / floating +2 make the active trigger rise (default/pills). */ elevation?: Elevation; } = $props();
const level = useElevation(() => elevation, "control"); const raised = $derived(level.current === "raised" || level.current === "floating");
setTabsListCtx({ get variant() { return variant ?? "default"; }, get raised() { return raised; }, });</script>
<TabsPrimitive.List bind:ref data-slot="tabs-list" data-variant={variant} class={cn(tabsListVariants({ variant }), className)} {...restProps}/><script lang="ts" module> import { tv } from "tailwind-variants";
// tabs.trigger elevation: only the active trigger rises (bevel). const raisedActive = "data-[state=active]:border-transparent data-[state=active]:[background-image:var(--r1-s-face)] data-[state=active]:[background-origin:border-box] data-[state=active]:shadow-btn-raised-neutral";
export const tabsTriggerVariants = tv({ base: "relative inline-flex items-center justify-center gap-1.5 text-[13.5px] font-medium whitespace-nowrap text-muted-foreground transition-colors outline-none hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", variants: { variant: { default: "h-[30px] flex-1 rounded-[7px] border border-transparent px-3.5 data-[state=active]:border-border data-[state=active]:bg-tab-active data-[state=active]:text-foreground", line: "-mb-px border-b-2 border-transparent px-0.5 pb-[11px] data-[state=active]:border-foreground data-[state=active]:text-foreground group-data-[orientation=vertical]/tabs:mb-0 group-data-[orientation=vertical]/tabs:-ml-px group-data-[orientation=vertical]/tabs:border-b-0 group-data-[orientation=vertical]/tabs:border-l-2 group-data-[orientation=vertical]/tabs:py-1 group-data-[orientation=vertical]/tabs:pr-0 group-data-[orientation=vertical]/tabs:pl-3", // ✦ no track pills: "h-8 rounded-[7px] border border-transparent px-3 data-[state=active]:border-border data-[state=active]:bg-tab-active data-[state=active]:text-foreground", }, elevation: { flat: "", raised: "" }, }, compoundVariants: [ { variant: "default", elevation: "raised", class: raisedActive }, { variant: "pills", elevation: "raised", class: raisedActive }, ], defaultVariants: { variant: "default", elevation: "flat" }, });</script>
<script lang="ts"> import { Tabs as TabsPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js"; import type { Elevation } from "$lib/registry/ui/elevation/index.js"; import { getTabsListCtx } from "./tabs-list.svelte";
let { ref = $bindable(null), class: className, elevation = "auto", ...restProps }: TabsPrimitive.TriggerProps & { /** ✦ overrides the TabsList `elevation` (raised/floating = the active trigger rises). */ elevation?: Elevation; } = $props();
const ctx = getTabsListCtx(); const raised = $derived( elevation !== "auto" ? elevation === "raised" || elevation === "floating" : (ctx?.raised ?? false) );</script>
<TabsPrimitive.Trigger bind:ref data-slot="tabs-trigger" class={cn( tabsTriggerVariants({ variant: ctx?.variant ?? "default", elevation: raised ? "raised" : "flat" }), className )} {...restProps}/><script lang="ts"> import { Tabs as TabsPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.ContentProps = $props();</script>
<TabsPrimitive.Content bind:ref data-slot="tabs-content" class={cn("flex-1 text-sm outline-none", className)} {...restProps}/>import Root from "./tabs.svelte";import Content from "./tabs-content.svelte";import List, { type TabsListVariant, tabsListVariants,} from "./tabs-list.svelte";import Trigger, { tabsTriggerVariants } from "./tabs-trigger.svelte";
export { Content, Content as TabsContent, List, List as TabsList, Root, // Root as Tabs, type TabsListVariant, Trigger, Trigger as TabsTrigger, tabsListVariants, tabsTriggerVariants,};import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
export default function Demo() { return ( <Tabs defaultValue="positions" className="w-[420px]"> <TabsList variant="line"> <TabsTrigger value="positions">Positions</TabsTrigger> <TabsTrigger value="created">Created</TabsTrigger> <TabsTrigger value="history">History</TabsTrigger> </TabsList> <TabsContent value="positions">Open positions.</TabsContent> <TabsContent value="created">Indexes you created.</TabsContent> <TabsContent value="history">Closed positions.</TabsContent> </Tabs> );}<script setup lang="ts">import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";</script>
<template> <Tabs default-value="positions" class="w-[420px]"> <TabsList variant="line"> <TabsTrigger value="positions">Positions</TabsTrigger> <TabsTrigger value="created">Created</TabsTrigger> <TabsTrigger value="history">History</TabsTrigger> </TabsList> <TabsContent value="positions">Open positions.</TabsContent> <TabsContent value="created">Indexes you created.</TabsContent> <TabsContent value="history">Closed positions.</TabsContent> </Tabs></template><script lang="ts"> import * as Tabs from "$lib/components/ui/tabs";</script>
<Tabs.Root value="positions" class="w-[420px]"> <Tabs.List variant="line"> <Tabs.Trigger value="positions">Positions</Tabs.Trigger> <Tabs.Trigger value="created">Created</Tabs.Trigger> <Tabs.Trigger value="history">History</Tabs.Trigger> </Tabs.List> <Tabs.Content value="positions">Open positions.</Tabs.Content> <Tabs.Content value="created">Indexes you created.</Tabs.Content> <Tabs.Content value="history">Closed positions.</Tabs.Content></Tabs.Root>Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Flat by default. Set raised on TabsList: only the ACTIVE trigger rises (bevel); the list or track itself never does. It flows to the triggers (a trigger can override it) and line ignores it.
Flat (0)
Raised (+1): only the active tab rises
Flat (0)
Raised (+1): only the active tab rises
Flat (0)
Raised (+1): only the active tab rises
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
const levels = [ { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1): only the active tab rises" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-6"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <div className="flex flex-wrap items-start gap-6"> {(["default", "pills"] as const).map((variant) => ( <Tabs key={variant} defaultValue="overview" className="w-[360px]"> <TabsList variant={variant} elevation={value}> <TabsTrigger value="overview">Overview</TabsTrigger> <TabsTrigger value="holdings">Holdings</TabsTrigger> <TabsTrigger value="activity">Activity</TabsTrigger> </TabsList> <TabsContent value="overview">{variant} tabs.</TabsContent> <TabsContent value="holdings"> Four tokens, weighted by AUM. </TabsContent> <TabsContent value="activity">No recent trades.</TabsContent> </Tabs> ))} </div> </div> ))} </div> );}<script setup lang="ts">import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
const levels = [{ value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1): only the active tab rises" }] as const;const variants = ["default", "pills"] as const;</script>
<template> <div class="flex flex-col gap-6"> <div v-for="level in levels" :key="level.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ level.label }}</p> <div class="flex flex-wrap items-start gap-6"> <Tabs v-for="variant in variants" :key="variant" default-value="overview"> <TabsList :variant="variant" :elevation="level.value"> <TabsTrigger value="overview">Overview</TabsTrigger> <TabsTrigger value="holdings">Holdings</TabsTrigger> <TabsTrigger value="activity">Activity</TabsTrigger> </TabsList> </Tabs> </div> </div> </div></template><script lang="ts"> import * as Tabs from "$lib/components/ui/tabs";
const levels = [{ value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1): only the active tab rises" }] as const; const variants = ["default", "pills"] as const;</script>
<div class="flex flex-col gap-6"> {#each levels as level (level.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{level.label}</p> <div class="flex flex-wrap items-start gap-6"> {#each variants as variant (variant)} <Tabs.Root value="overview"> <Tabs.List {variant} elevation={level.value}> <Tabs.Trigger value="overview">Overview</Tabs.Trigger> <Tabs.Trigger value="holdings">Holdings</Tabs.Trigger> <Tabs.Trigger value="activity">Activity</Tabs.Trigger> </Tabs.List> </Tabs.Root> {/each} </div> </div> {/each}</div>| Prop | Type | Default |
|---|---|---|
Tabs defaultValue / value |
string |
none |
Tabs orientation |
horizontal | vertical |
horizontal |
TabsList variant |
default | line | pills |
default |
TabsTrigger disabled |
boolean |
false |
elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
Parts: Tabs, TabsList, TabsTrigger, TabsContent.
✦ Edmi additions
Section titled “✦ Edmi additions”- Inside a track the active tab is flat:
--tab-activefill and a 1px border, no gradient, no lip. pillsvariant (no track) is an inline row of pills; active =--tab-active+ 1px border.linevariant underlines the active tab with the foreground colour.