A Beginner's Guide to Reusable Type-Safe Components in Svelte 5


Introduction
Generics are an incredibly useful feature in TypeScript that let you build components with APIs that are both reusable and safe. Think of them as a way to create functions, classes, or Svelte components that work with various types instead of being locked into one. This means your components can adapt to different types without losing their type-checking powers—pretty handy when you're dealing with collections of data or custom callbacks.
In this article, we'll dive into how you can use generics in Svelte 5 to create reusable components that feel like a dream to use. We’ll also sprinkle in snippets—a feature that complements generics by giving you more control over how things are displayed. By the end, you’ll have a better idea of how these tools make your code cleaner, easier to maintain, and a joy to work with.
Generics and snippets are perfect for developers who want to make their components more versatile without sacrificing readability or safety. As your projects grow, these tools can help you avoid duplicating code while maintaining a clear structure in your codebase. Let’s start by understanding the basics of generics and how they fit into Svelte development.
Getting Started with Generics
Generics are like placeholders for types that get replaced when the function or component is used. Here’s a quick example to get the idea across:
function getFirst<T>(arr: T[]): T {
return arr[0];
}
const firstNumber = getFirst([1, 2, 3]); // TypeScript figures out T is a number
const firstString = getFirst(['Alice', 'Bob']); // Here, T becomes a string
Notice how the type of T
is inferred based on the array you pass in? This keeps things both flexible and safe. You can apply the same concept to Svelte components to make them adaptable to whatever data they need to handle.
Generics are especially handy for scenarios where you’re building utility functions or components that work with multiple data types. They allow you to avoid repetitive code while maintaining strong type guarantees. Let’s see how this concept applies to Svelte components, where generics can bring additional flexibility to UI development.
Generics in Svelte Components
In Svelte 5, generics are easy to use with the generics
attribute in your <script>
tag. Let’s take a look at a simple List
component that can display any type of item:
<script lang="ts" generics="T">
let { items }: { items: T[] } = $props();
</script>
<ul>
{#each items as item}
<li>{item}</li>
{/each}
</ul>
Here, the items
prop is flexible and adapts to whatever type the parent component specifies. This makes the List
component incredibly reusable. You don’t need to write separate components for numbers, strings, or objects—just let the parent decide.
The real beauty of this approach is that it scales effortlessly. Whether you’re displaying a list of strings, numbers, or complex objects, the component remains the same. Plus, you get the added bonus of TypeScript’s type-checking to catch potential bugs during development.
Example 1: Displaying Strings
<script lang="ts">
import List from './List.svelte';
let names = ['Alice', 'Bob', 'Charlie'];
</script>
<List items={names} />
Example 2: Displaying Numbers
<script lang="ts">
import List from './List.svelte';
let numbers = [1, 2, 3, 4];
</script>
<List items={numbers} />
Generics make this process seamless. You get type safety and flexibility without writing repetitive code. Plus, TypeScript gives you immediate feedback if something doesn’t line up, which means fewer bugs and more confidence in your code.
Another advantage of using generics is that they make your components future-proof. As your application evolves, you can easily adapt these components to handle new data types without rewriting them. This saves time and ensures consistency across your project.
Making Components Interactive with Callbacks
Generics show their true power when paired with callbacks. Using them alone provides type safety, but the real value comes when your components start interacting with data. Here’s an example of a List
component that lets you handle item clicks:
<script lang="ts" generics="T">
let { items, onSelect }: { items: T[]; onSelect: (item: T) => void } = $props();
</script>
<ul>
{#each items as item}
<li onclick={() => onSelect(item)}>{item}</li>
{/each}
</ul>
With this setup, the parent component controls what happens when an item is clicked, and TypeScript ensures that the onSelect
function gets the right type of item.
Example 3: Handling User Objects
<script lang="ts">
import List from './List.svelte';
type User = { id: number; name: string };
let users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
function handleSelect(user: User) {
console.log('Selected user:', user.name);
}
</script>
<List items={users} onSelect={handleSelect} />
This approach keeps things clean and ensures that your interactions are consistent with the data type you’re working with. Whether it’s a User
object or something else entirely, TypeScript has your back.
Interactive components like this are invaluable for building dynamic interfaces. You can use the same pattern for forms, dashboards, or e-commerce applications where user interaction is key.
Adding Flexibility with Snippets
Generics are great for handling data, but what if you want to customize how each item looks? That’s where snippets come in. They let you define how items should be rendered, giving the parent component full control over presentation.
Snippets are like templates that you can pass to a child component. They make it easy to tailor the appearance of your data without modifying the component itself. This is especially useful when working with complex UIs that need to display the same data in different ways.
Updating the List
Component
<script lang="ts" generics="T">
import { Snippet } from 'svelte';
type ListProps<T> = {
items: T[];
itemTemplate?: Snippet<[T]>;
};
let { items, itemTemplate }: ListProps<T> = $props();
</script>
<ul>
{#each items as item}
<li>
{#if itemTemplate}
{@render itemTemplate(item)}
{:else}
{item}
{/if}
</li>
{/each}
</ul>
Example 4: Customizing the Display
<script lang="ts">
import List from './List.svelte';
type User = { id: number; name: string };
let users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
</script>
<List items={users}>
{#snippet itemTemplate(user: User)}
<strong>{user.name}</strong> (ID: {user.id})
{/snippet}
</List>
This gives you the best of both worlds: reusable components and fully customizable rendering. It’s especially useful when you’re working on complex applications where different contexts might need different visual treatments.
Wrapping It Up
Generics and snippets in Svelte 5 are a game-changer for building flexible, type-safe components. Generics let your components adapt to different types of data while maintaining strict type checking. Snippets, on the other hand, let you customize how your components look without sacrificing reusability.
Whether you’re building a simple list or a complex UI, these tools make your job easier and your code cleaner. Start using generics and snippets today, and see how they can transform your workflow!
These techniques aren’t just for advanced users. Even if you’re new to Svelte or TypeScript, mastering generics and snippets can greatly enhance your productivity. So why wait? Dive in and make your components smarter, more adaptable, and more fun to work with!
Cover photo by: Online illustrations by Storyset
Subscribe to my newsletter
Read articles from George Daskalakis directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

George Daskalakis
George Daskalakis
I'm George, a full-stack developer with 5 years of experience in web development. I specialize in creating seamless user experiences with frameworks like Angular, Vue.js, React, and recently, Svelte/SvelteKit. On the backend, I work primarily with Node.js and have expertise in AWS and serverless architectures. Outside of coding, I enjoy gaming, playing guitar, and exploring low-level languages like Rust and C++ as well as game development.