RGB logo

RGB Studios.org

A web development company

Justin Golden — 9/16/2022

A Simple Svelte Tabs Component

webdev
component
Photo credit @rssemfam on Unsplash

The Problem

Most tab components I found either involve getting an entire UI kit, a lot of boilerplate code per implementation, or more likely, the code looks like this:

(This was my old code; I had to zoom out twice to get this screenshot)

The Props

We’ll simply export a list of tabs and then the index of the activeTab:

export let tabs, activeTab = 0;

The HTML

The Tabs

We want to loop through each tab and render them as a button with the tab name and with class tab for styling later.

{#each tabs as tab, idx}
	<button class="tab">
		{tab.name}
	</button>
{/each}

Let’s apply an active class if the tab is active:

class:active={idx === activeTab}

And let’s set the activeTab on click:

on:click={() => (activeTab = idx)}

So far we’ve got:

{#each tabs as tab, idx}
	<button class="tab" class:active={idx === activeTab} on:click={() => (activeTab = idx)}>
		{tab.name}
	</button>
{/each}
The Tab Content

Now let’s render the content for each tab. We will only show the content for the active tab, and it will be a component like so:

{#each tabs as tab, idx}
	{#if idx === activeTab}
		<svelte:component this={tab.content} />
	{/if}
{/each}

We simply pass in a component into tab.component. I’ll show you under Usage.

The CSS

Let’s style our tabs, and make sure to style our active tabs differently.

<style lang="postcss">
	.tab {
		background-color: #f4f4f5;
		padding: 1rem;
	}
	.tab.active {
		background-color: #e4e4e7;
	}
</style>

You can of course add your own styles; this is just a bare minimum to get the component going. I’ve got some default styles in my project so my tabs may look a bit different from yours.

All Together Now

Here’s the full file: 25 lines!

<script>
	export let data = {}

	$: tabs = data.tabs;
	$: activeTab = data.activeTab ?? 0;
</script>

{#each tabs as tab, idx}
	<button class="tab" class:active={idx === activeTab} on:click={() => (activeTab = idx)}>
		{tab.name}
	</button>
{/each}

{#each tabs as tab, idx}
	{#if idx === activeTab}
		<svelte:component this={tab.content} />
	{/if}
{/each}

<style lang="postcss">
	.tab {
		background-color: #f4f4f5;
		padding: 1rem;
	}
	.tab.active {
		background-color: #e4e4e7;
	}
</style>

Usage

Here’s how you would use the tabs component in your app:

import Tabs from '$lib/components/Tabs.svelte'; (or wherever you saved it)

<Tabs
	tabs={[
		{ name: 'Home', content: PomodoroHome },
		{ name: 'About', content: PomodoroAbout },
		{ name: 'Settings', content: PomodoroSettings }
	]} />

This of course has you importing the components from other files. I find it best practice to split things up into smaller files when possible, so long as the file structure is organized and it’s not cluttered.

Expanding

Transition

To add a transition, simply import from svelte/transition:

import { fade } from 'svelte/transition';

Then wrap the current tab in a div with in:fade:

<div in:fade>
	<svelte:component this={tab.content} />
</div>
Icon

You could add an icon to each tab:

<button class="tab" class:active={idx === activeTab} on:click={() => (activeTab = idx)}>
	<Icon name={tab.icon} />
	{tab.name}
</button>

If you have an Icon component, and then implement it like so:

<Tabs
	tabs={[
		{ name: 'Home', icon: 'home', content: PomodoroHome },
		{ name: 'About', icon: 'info_circle', content: PomodoroAbout },
		{ name: 'Settings', icon: 'cog', content: PomodoroSettings }
	]} />

I couldn’t find any good examples of tab components in Svelte that weren’t either unnecessarily complex (involving multiple files, dozens of lines of CSS, getting and registering context and writables) or just hard coded and involving too much copy-paste boilerplate code to use every implementation.

I hope this article helped you out. If so, feel free to share or check out our other stuff : )


More Blog Articles
  Share   Tweet   Pin   Share   Post   Post   Share   Email