Tabs

Tabs to switch content, click on a tab to check background animation.

Product Tab

dummy image

Services tab

dummy image

Playground tab

dummy image

Content tab

dummy image

Random tab

dummy image

Installation

Install Dependencies

npm i svelte-motion clsx tailwind-merge

Add util file

src/lib/utils/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
        
export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}

Copy the source code

src/lib/components/ui/Tabs/Tabs.svelte
<script lang="ts">
	import { cn } from '@/utils';
	import { AnimatePresence, Motion } from 'svelte-motion';
	import FadeInDiv from './FadeInDiv.svelte';

	type Tab = {
		title: string;
		value: string;
		content?: string | any;
	};

	export let propTabs: Tab[];
	export let containerClassName: string | undefined = undefined;
	export let activeTabClassName: string | undefined = undefined;
	export let tabClassName: string | undefined = undefined;
	export let contentClassName: string | undefined = undefined;

	let active: Tab = propTabs[0];
	let tabs: Tab[] = propTabs;

	const moveSelectedTabToTop = (idx: number) => {
		const newTabs = [...propTabs];
		const selectedTab = newTabs.splice(idx, 1);
		newTabs.unshift(selectedTab[0]);
		tabs = newTabs;
		active = newTabs[0];
	};

	let hovering = false;
</script>

<div
	class={cn(
		'no-visible-scrollbar relative flex w-full max-w-full flex-row items-center justify-start overflow-auto [perspective:1000px] sm:overflow-visible',
		containerClassName
	)}
>
	{#each propTabs as tab, idx (tab.title)}
		<button
			on:click={() => {
				moveSelectedTabToTop(idx);
			}}
			on:mouseenter={() => (hovering = true)}
			on:mouseleave={() => (hovering = false)}
			class={cn('relative rounded-full px-4 py-2', tabClassName)}
			style="transform-style: preserve-3d;"
		>
			{#if active.value === tab.value}
				<Motion
					let:motion
					layoutId="clickedbutton"
					transition={{ type: 'spring', bounce: 0.3, duration: 0.6 }}
				>
					<div
						use:motion
						class={cn(
							'absolute inset-0 rounded-full bg-gray-200 dark:bg-zinc-800 ',
							activeTabClassName
						)}
					/>
				</Motion>
			{/if}

			<span class="relative block text-black dark:text-white">
				{tab.title}
			</span>
		</button>
	{/each}
</div>
<FadeInDiv {tabs} {hovering} className={cn('', contentClassName)} />
src/lib/components/ui/Tabs/FadeInDiv.svelte
<script lang="ts">
	import { cn } from '@/utils';
	import { Motion } from 'svelte-motion';

	type Tab = {
		title: string;
		value: string;
		content?: any | undefined;
	};

	export let className: string | undefined = undefined;
	export let tabs: Tab[];
	export let hovering: boolean | undefined;

	const isActive = (tab: Tab) => {
		return tab.value === tabs[0].value;
	};
</script>

<div class="relative h-full w-full">
	{#each tabs as tab, idx (tab.value)}
		<Motion
			let:motion
			layoutId={tab.value}
			animate={{
				y: isActive(tab) ? [0, 40, 0] : 0
			}}
		>
			<div
				use:motion
				style={`scale: ${1 - idx * 0.1}; top: ${hovering ? `${idx * -60}px` : 0}; z-index: ${-idx}; opacity: ${idx < 3 ? 1 - idx * 0.1 : 0};`}
				class={cn('absolute left-0 top-0 h-full w-full overflow-hidden', className)}
			>
				{@html tab.content}
			</div>
		</Motion>
	{/each}
</div>

Copy to app.pcss to hide scrollbar (optional)

src/app.pcss
.no-visible-scrollbar {
	scrollbar-width: none;
	-ms-overflow-style: none;
	-webkit-overflow-scrolling: touch;
}

.no-visible-scrollbar::-webkit-scrollbar {
	display: none;
}
src/lib/components/ui/Tabs/index.ts
import Tabs from './Tabs.svelte';
import FadeInDiv from './FadeInDiv.svelte';

export { Tabs, FadeInDiv };

Props

Tabs
Prop Type Description
propTabs {title: string, value: string, content?: string | any}[] An array of Tab objects.
containerClassName string | undefined CSS class name for the container div.
activeTabClassName string | undefined CSS class name for the active tab.
tabClassName string | undefined CSS class name for the tab.
contentClassName string | undefined CSS class name for the content div.
FadeInDiv
Prop Type Description
className string | undefined CSS class name for the FadeInDiv.
tabs {title: string, value: string, content?: string | any}[] An array of Tab objects. Each Tab object has title, value, and content properties.
active {title: string, value: string, content?: string | any} The active tab.
hovering boolean | undefined Whether the mouse is hovering over the component.