Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to the exciting world of TypeScript with Svelte! ๐ In this comprehensive guide, weโll explore how Svelteโs compile-time magic works perfectly with TypeScriptโs type safety to create lightning-fast, type-safe applications.
Youโll discover how TypeScript and Svelte combine to give you the best of both worlds: Svelteโs incredible performance and TypeScriptโs bulletproof type safety. Whether youโre building web applications ๐, interactive dashboards ๐, or component libraries ๐, understanding TypeScript with Svelte is essential for modern web development.
By the end of this tutorial, youโll feel confident using TypeScript with Svelte in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding TypeScript with Svelte
๐ค What is TypeScript with Svelte?
TypeScript with Svelte is like having a super-powered development experience ๐. Think of it as having a personal assistant that checks your code while Svelteโs compiler works its magic to create incredibly fast applications.
In TypeScript terms, Svelte provides compile-time optimization while TypeScript provides compile-time type checking. This means you can:
- โจ Catch errors before they reach your users
- ๐ Get amazing performance from Svelteโs compiler
- ๐ก๏ธ Enjoy type safety throughout your components
- ๐ก Benefit from excellent IDE support and autocompletion
๐ก Why Use TypeScript with Svelte?
Hereโs why developers love this combination:
- Compile-Time Safety ๐: Both TypeScript and Svelte catch issues at build time
- Amazing Performance โก: Svelte generates vanilla JavaScript with minimal runtime overhead
- Developer Experience ๐ป: Incredible IDE support and debugging capabilities
- Smaller Bundle Sizes ๐ฆ: Svelteโs compiler removes unused code automatically
Real-world example: Imagine building a real-time chat application ๐ฌ. With TypeScript and Svelte, you get type-safe message handling with incredible performance!
๐ง Basic Syntax and Usage
๐ Setting Up TypeScript with Svelte
Letโs start with setting up your Svelte project with TypeScript:
# ๐ฏ Create a new Svelte project with TypeScript
npm create svelte@latest my-svelte-app
cd my-svelte-app
# ๐ฆ Choose TypeScript template during setup
npm install
๐จ Your First TypeScript Svelte Component
Hereโs a simple TypeScript-powered Svelte component:
<!-- ๐ HelloWorld.svelte -->
<script lang="ts">
// ๐ TypeScript in Svelte!
export let name: string = "World"; // ๐ฏ Typed prop with default
export let count: number = 0; // ๐ข Number prop
// ๐จ Typed reactive statement
$: greeting = `Hello, ${name}! ๐`;
$: isHighCount = count > 5; // ๐ฅ Boolean derived from count
// ๐ฎ Typed event handler
const handleClick = (): void => {
count += 1;
console.log(`Clicked! Count is now: ${count} ๐`);
};
// ๐จ Interface for complex data
interface UserData {
id: number;
name: string;
emoji: string;
}
const user: UserData = {
id: 1,
name: "TypeScript Fan",
emoji: "๐"
};
</script>
<div class="container">
<h1>{greeting}</h1>
<p>User: {user.emoji} {user.name}</p>
<button
on:click={handleClick}
class:active={isHighCount}
>
Clicked {count} times! ๐ฏ
</button>
{#if isHighCount}
<p>๐ฅ You're on fire! Great clicking!</p>
{/if}
</div>
<style>
.container {
text-align: center;
padding: 2rem;
}
.active {
background-color: #ff6b6b;
color: white;
}
</style>
๐ก Explanation: Notice how we use lang="ts"
in the script tag! This tells Svelte to use TypeScript for type checking.
๐ฏ Common TypeScript Patterns in Svelte
Here are patterns youโll use daily:
<!-- ๐๏ธ Advanced Component with Types -->
<script lang="ts">
// ๐จ Union types for component states
type LoadingState = "idle" | "loading" | "success" | "error";
type Theme = "light" | "dark" | "auto";
// ๐ Props with complex types
export let items: Array<{id: string; name: string; emoji: string}> = [];
export let theme: Theme = "light";
export let onItemClick: (id: string) => void = () => {};
let loadingState: LoadingState = "idle";
// ๐ Async function with proper typing
const loadData = async (): Promise<void> => {
loadingState = "loading";
try {
// ๐ฏ Simulated API call
await new Promise(resolve => setTimeout(resolve, 1000));
loadingState = "success";
} catch (error) {
loadingState = "error";
console.error("Failed to load data:", error);
}
};
</script>
๐ก Practical Examples
๐ Example 1: TypeScript Shopping Cart Component
Letโs build a real shopping cart with TypeScript:
<!-- ๐๏ธ ShoppingCart.svelte -->
<script lang="ts">
// ๐ Product interface
interface Product {
id: string;
name: string;
price: number;
emoji: string;
category: "electronics" | "clothing" | "books" | "food";
}
// ๐งพ Cart item extends product with quantity
interface CartItem extends Product {
quantity: number;
}
// ๐ฆ Props
export let products: Product[] = [];
// ๐ฏ Component state
let cart: CartItem[] = [];
let isCheckingOut: boolean = false;
// ๐ฐ Computed total with type safety
$: total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
$: itemCount = cart.reduce((sum, item) => sum + item.quantity, 0);
// โ Add to cart function
const addToCart = (product: Product): void => {
const existingItem = cart.find(item => item.id === product.id);
if (existingItem) {
// ๐ Update existing item
cart = cart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
} else {
// ๐ Add new item
cart = [...cart, { ...product, quantity: 1 }];
}
console.log(`Added ${product.emoji} ${product.name} to cart! ๐`);
};
// โ Remove from cart
const removeFromCart = (productId: string): void => {
cart = cart.filter(item => item.id !== productId);
};
// ๐ณ Checkout process
const checkout = async (): Promise<void> => {
isCheckingOut = true;
try {
// ๐ฏ Simulate payment processing
await new Promise(resolve => setTimeout(resolve, 2000));
console.log(`๐ Order completed! Total: $${total.toFixed(2)}`);
cart = []; // ๐งน Clear cart
} catch (error) {
console.error("Checkout failed:", error);
} finally {
isCheckingOut = false;
}
};
</script>
<div class="shopping-cart">
<h2>๐ TypeScript Shopping Cart</h2>
<!-- ๐ฆ Products list -->
<div class="products">
<h3>๐๏ธ Available Products</h3>
{#each products as product (product.id)}
<div class="product-card">
<span class="emoji">{product.emoji}</span>
<span class="name">{product.name}</span>
<span class="price">${product.price.toFixed(2)}</span>
<button on:click={() => addToCart(product)}>
Add to Cart โ
</button>
</div>
{/each}
</div>
<!-- ๐งพ Cart display -->
<div class="cart">
<h3>๐ Your Cart ({itemCount} items)</h3>
{#if cart.length === 0}
<p>Your cart is empty ๐ข</p>
{:else}
{#each cart as item (item.id)}
<div class="cart-item">
<span>{item.emoji} {item.name}</span>
<span>Qty: {item.quantity}</span>
<span>${(item.price * item.quantity).toFixed(2)}</span>
<button on:click={() => removeFromCart(item.id)}>โ</button>
</div>
{/each}
<div class="total">
<strong>Total: ${total.toFixed(2)} ๐ฐ</strong>
</div>
<button
class="checkout-btn"
on:click={checkout}
disabled={isCheckingOut}
>
{isCheckingOut ? "Processing... โณ" : "Checkout ๐ณ"}
</button>
{/if}
</div>
</div>
<style>
.shopping-cart {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.products, .cart {
margin: 2rem 0;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
}
.product-card, .cart-item {
display: flex;
gap: 1rem;
align-items: center;
padding: 0.5rem;
margin: 0.5rem 0;
background: #f9f9f9;
border-radius: 4px;
}
.checkout-btn {
background: #28a745;
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.checkout-btn:disabled {
background: #6c757d;
cursor: not-allowed;
}
</style>
๐ฏ Try it yourself: Add a quantity selector and discount codes feature!
๐ฎ Example 2: Real-Time Game Score Dashboard
Letโs create a live game dashboard:
<!-- ๐ GameDashboard.svelte -->
<script lang="ts">
// ๐ฎ Game interfaces
interface Player {
id: string;
name: string;
avatar: string; // emoji
score: number;
level: number;
status: "online" | "offline" | "in-game";
}
interface GameMatch {
id: string;
players: Player[];
startTime: Date;
gameType: "quick" | "ranked" | "tournament";
status: "waiting" | "active" | "finished";
}
// ๐ Component state
let players: Player[] = [
{ id: "1", name: "TypeScript Hero", avatar: "๐", score: 1500, level: 42, status: "online" },
{ id: "2", name: "Svelte Master", avatar: "โก", score: 1200, level: 38, status: "in-game" },
{ id: "3", name: "Code Ninja", avatar: "๐ฅท", score: 1800, level: 55, status: "online" }
];
let currentMatch: GameMatch | null = null;
let isMatchmaking: boolean = false;
// ๐ฏ Reactive computations
$: onlinePlayers = players.filter(p => p.status === "online");
$: topPlayer = players.reduce((top, player) =>
player.score > top.score ? player : top
);
$: averageLevel = Math.round(
players.reduce((sum, p) => sum + p.level, 0) / players.length
);
// ๐ฎ Start matchmaking
const startMatchmaking = async (): Promise<void> => {
if (onlinePlayers.length < 2) {
alert("Need at least 2 players online! ๐ฅ");
return;
}
isMatchmaking = true;
try {
// ๐ Simulate matchmaking
await new Promise(resolve => setTimeout(resolve, 2000));
currentMatch = {
id: `match-${Date.now()}`,
players: onlinePlayers.slice(0, 2), // Take first 2 players
startTime: new Date(),
gameType: "quick",
status: "active"
};
// ๐ฏ Update player status
players = players.map(player =>
currentMatch!.players.some(p => p.id === player.id)
? { ...player, status: "in-game" }
: player
);
console.log("๐ฎ Match started!", currentMatch);
} catch (error) {
console.error("Matchmaking failed:", error);
} finally {
isMatchmaking = false;
}
};
// ๐ End match
const endMatch = (): void => {
if (!currentMatch) return;
// ๐ Award points to winner (random for demo)
const winner = currentMatch.players[Math.floor(Math.random() * currentMatch.players.length)];
players = players.map(player => {
if (player.id === winner.id) {
return {
...player,
score: player.score + 100,
level: player.level + 1,
status: "online"
};
}
return currentMatch!.players.some(p => p.id === player.id)
? { ...player, status: "online" }
: player;
});
console.log(`๐ ${winner.avatar} ${winner.name} won the match!`);
currentMatch = null;
};
</script>
<div class="dashboard">
<h1>๐ฎ Real-Time Game Dashboard</h1>
<!-- ๐ Stats overview -->
<div class="stats">
<div class="stat-card">
<h3>๐ฅ Online Players</h3>
<p>{onlinePlayers.length}</p>
</div>
<div class="stat-card">
<h3>๐ Top Player</h3>
<p>{topPlayer.avatar} {topPlayer.name}</p>
<small>{topPlayer.score} points</small>
</div>
<div class="stat-card">
<h3>๐ Avg Level</h3>
<p>{averageLevel}</p>
</div>
</div>
<!-- ๐ฅ Players list -->
<div class="players">
<h2>๐ฏ Players</h2>
{#each players as player (player.id)}
<div class="player-card" class:in-game={player.status === "in-game"}>
<span class="avatar">{player.avatar}</span>
<div class="player-info">
<h4>{player.name}</h4>
<p>Level {player.level} โข {player.score} points</p>
<span class="status {player.status}">{player.status}</span>
</div>
</div>
{/each}
</div>
<!-- ๐ฎ Match controls -->
<div class="match-controls">
{#if currentMatch}
<div class="current-match">
<h3>๐ฏ Current Match</h3>
<p>Game Type: {currentMatch.gameType}</p>
<p>Players: {currentMatch.players.map(p => `${p.avatar} ${p.name}`).join(" vs ")}</p>
<button on:click={endMatch}>End Match ๐</button>
</div>
{:else}
<button
on:click={startMatchmaking}
disabled={isMatchmaking || onlinePlayers.length < 2}
>
{isMatchmaking ? "Finding Match... ๐" : "Start Matchmaking ๐ฎ"}
</button>
{/if}
</div>
</div>
<style>
.dashboard {
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin: 2rem 0;
}
.stat-card {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
text-align: center;
}
.players {
margin: 2rem 0;
}
.player-card {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
margin: 0.5rem 0;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
transition: all 0.3s ease;
}
.player-card.in-game {
background: #fff3cd;
border-color: #ffeaa7;
}
.avatar {
font-size: 2rem;
}
.status {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: bold;
}
.status.online { background: #d4edda; color: #155724; }
.status.offline { background: #f8d7da; color: #721c24; }
.status.in-game { background: #d1ecf1; color: #0c5460; }
.match-controls {
text-align: center;
margin: 2rem 0;
}
.current-match {
background: #e7f3ff;
padding: 1.5rem;
border-radius: 8px;
margin: 1rem 0;
}
</style>
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: TypeScript Stores in Svelte
When youโre ready to level up, try Svelte stores with TypeScript:
// ๐ช stores.ts - Type-safe Svelte stores
import { writable, derived, readable } from 'svelte/store';
// ๐ฏ User store with TypeScript
interface User {
id: string;
name: string;
email: string;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
export const user = writable<User | null>(null);
// ๐จ Theme store derived from user
export const theme = derived(
user,
($user): 'light' | 'dark' => $user?.preferences.theme ?? 'light'
);
// โฐ Real-time clock store
export const currentTime = readable<Date>(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return () => clearInterval(interval);
});
// ๐ Shopping cart store with complex types
interface CartState {
items: Array<{
id: string;
name: string;
price: number;
quantity: number;
}>;
total: number;
discount: number;
}
const createCartStore = () => {
const { subscribe, set, update } = writable<CartState>({
items: [],
total: 0,
discount: 0
});
return {
subscribe,
addItem: (item: Omit<CartState['items'][0], 'quantity'>) => {
update(state => {
const existingItem = state.items.find(i => i.id === item.id);
if (existingItem) {
existingItem.quantity += 1;
} else {
state.items.push({ ...item, quantity: 1 });
}
state.total = state.items.reduce((sum, i) => sum + (i.price * i.quantity), 0);
return state;
});
},
clear: () => set({ items: [], total: 0, discount: 0 })
};
};
export const cart = createCartStore();
๐๏ธ Advanced Topic 2: Component Typing with Slots
For the brave developers, hereโs advanced component typing:
<!-- ๐จ AdvancedModal.svelte -->
<script lang="ts">
import type { SvelteComponent } from 'svelte';
// ๐ฏ Complex prop types
interface ModalAction {
label: string;
variant: 'primary' | 'secondary' | 'danger';
action: () => void;
disabled?: boolean;
}
export let isOpen: boolean = false;
export let title: string;
export let size: 'small' | 'medium' | 'large' = 'medium';
export let actions: ModalAction[] = [];
export let onClose: () => void = () => {};
export let allowBackdropClose: boolean = true;
// ๐ช Slot props typing
interface ModalSlotProps {
closeModal: () => void;
modalId: string;
}
const modalId = `modal-${Math.random().toString(36).substr(2, 9)}`;
const closeModal = (): void => {
isOpen = false;
onClose();
};
const handleBackdropClick = (event: MouseEvent): void => {
if (allowBackdropClose && event.target === event.currentTarget) {
closeModal();
}
};
// ๐ฏ Typed slot props
const slotProps: ModalSlotProps = {
closeModal,
modalId
};
</script>
{#if isOpen}
<div
class="modal-backdrop"
on:click={handleBackdropClick}
on:keydown={(e) => e.key === 'Escape' && closeModal()}
role="dialog"
aria-labelledby="{modalId}-title"
aria-modal="true"
>
<div class="modal-content {size}">
<header class="modal-header">
<h2 id="{modalId}-title">{title}</h2>
<button class="close-btn" on:click={closeModal}>โ</button>
</header>
<main class="modal-body">
<!-- ๐ช Named slot with typed props -->
<slot name="content" {closeModal} {modalId} />
<!-- ๐ฏ Default slot with props -->
<slot {...slotProps} />
</main>
{#if actions.length > 0}
<footer class="modal-footer">
{#each actions as action}
<button
class="action-btn {action.variant}"
disabled={action.disabled}
on:click={action.action}
>
{action.label}
</button>
{/each}
</footer>
{/if}
</div>
</div>
{/if}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Forgetting lang="ts"
in Script Tags
<!-- โ Wrong way - no TypeScript! -->
<script>
let name: string = "Hello"; // โ ๏ธ Type annotation ignored!
</script>
<!-- โ
Correct way - TypeScript enabled! -->
<script lang="ts">
let name: string = "Hello"; // โ
Type safety enabled!
</script>
๐คฏ Pitfall 2: Incorrect Event Handler Typing
<!-- โ Dangerous - untyped events! -->
<script lang="ts">
const handleClick = (event) => { // ๐ฅ `event` is `any`!
console.log(event.target.value); // Might not exist!
};
</script>
<!-- โ
Safe - properly typed events! -->
<script lang="ts">
const handleClick = (event: MouseEvent): void => {
const target = event.target as HTMLButtonElement;
console.log("Button clicked:", target.textContent); // โ
Type safe!
};
const handleInput = (event: Event): void => {
const target = event.target as HTMLInputElement;
console.log("Input value:", target.value); // โ
Safe access!
};
</script>
๐ง Pitfall 3: Store Typing Issues
// โ Wrong way - losing type safety in stores!
import { writable } from 'svelte/store';
const userData = writable(null); // ๐ฅ Type is `any`!
// โ
Correct way - explicit store typing!
import { writable } from 'svelte/store';
interface User {
id: string;
name: string;
}
const userData = writable<User | null>(null); // โ
Typed store!
๐ ๏ธ Best Practices
- ๐ฏ Always Use
lang="ts"
: Enable TypeScript in all your script tags - ๐ Type Your Props: Explicitly type all component props and their defaults
- ๐ก๏ธ Type Event Handlers: Use proper event types for all event handlers
- ๐จ Interface Everything: Create interfaces for complex data structures
- โจ Leverage Reactive Statements: Use
$:
with proper TypeScript inference - ๐ช Type Your Stores: Always provide explicit types for Svelte stores
- ๐ช Type Slot Props: Define interfaces for slot prop types
๐งช Hands-On Exercise
๐ฏ Challenge: Build a TypeScript Todo App with Svelte
Create a fully type-safe todo application with these features:
๐ Requirements:
- โ Todo items with title, completed status, priority, and due date
- ๐ท๏ธ Categories for todos (work, personal, shopping)
- ๐ค User assignment feature
- ๐ Sorting and filtering capabilities
- ๐จ Each todo needs an emoji based on category!
- ๐พ Local storage persistence
๐ Extra Credit:
- Add drag-and-drop reordering
- Implement todo search functionality
- Create a statistics dashboard
- Add keyboard shortcuts
๐ก Solution
๐ Click to see solution
<!-- ๐ TodoApp.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
// ๐ฏ Todo interfaces
interface Todo {
id: string;
title: string;
completed: boolean;
priority: 'low' | 'medium' | 'high';
category: 'work' | 'personal' | 'shopping';
dueDate?: Date;
assignee?: string;
createdAt: Date;
}
type FilterType = 'all' | 'active' | 'completed';
type SortType = 'created' | 'priority' | 'dueDate' | 'title';
// ๐ฆ Component state
let todos: Todo[] = [];
let newTodoTitle: string = '';
let selectedCategory: Todo['category'] = 'personal';
let selectedPriority: Todo['priority'] = 'medium';
let filter: FilterType = 'all';
let sortBy: SortType = 'created';
let searchTerm: string = '';
// ๐จ Category emojis
const categoryEmojis: Record<Todo['category'], string> = {
work: '๐ผ',
personal: '๐ ',
shopping: '๐'
};
// ๐ฏ Priority colors
const priorityColors: Record<Todo['priority'], string> = {
low: '#28a745',
medium: '#ffc107',
high: '#dc3545'
};
// ๐ Reactive filtering and sorting
$: filteredTodos = todos
.filter(todo => {
// ๐ฏ Filter by completion status
if (filter === 'active' && todo.completed) return false;
if (filter === 'completed' && !todo.completed) return false;
// ๐ Filter by search term
if (searchTerm && !todo.title.toLowerCase().includes(searchTerm.toLowerCase())) {
return false;
}
return true;
})
.sort((a, b) => {
switch (sortBy) {
case 'priority':
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
case 'dueDate':
if (!a.dueDate && !b.dueDate) return 0;
if (!a.dueDate) return 1;
if (!b.dueDate) return -1;
return a.dueDate.getTime() - b.dueDate.getTime();
case 'title':
return a.title.localeCompare(b.title);
default:
return b.createdAt.getTime() - a.createdAt.getTime();
}
});
// ๐ Statistics
$: completedCount = todos.filter(t => t.completed).length;
$: totalCount = todos.length;
$: completionRate = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
// โ Add new todo
const addTodo = (): void => {
if (!newTodoTitle.trim()) return;
const newTodo: Todo = {
id: Date.now().toString(),
title: newTodoTitle.trim(),
completed: false,
priority: selectedPriority,
category: selectedCategory,
createdAt: new Date()
};
todos = [newTodo, ...todos];
newTodoTitle = '';
saveTodos();
console.log(`โ
Added todo: ${categoryEmojis[newTodo.category]} ${newTodo.title}`);
};
// โ
Toggle completion
const toggleTodo = (id: string): void => {
todos = todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
);
saveTodos();
};
// โ Delete todo
const deleteTodo = (id: string): void => {
const todo = todos.find(t => t.id === id);
todos = todos.filter(t => t.id !== id);
saveTodos();
if (todo) {
console.log(`๐๏ธ Deleted: ${categoryEmojis[todo.category]} ${todo.title}`);
}
};
// ๐พ Local storage functions
const saveTodos = (): void => {
localStorage.setItem('typescript-todos', JSON.stringify(todos));
};
const loadTodos = (): void => {
const saved = localStorage.getItem('typescript-todos');
if (saved) {
try {
const parsed = JSON.parse(saved);
todos = parsed.map((todo: any) => ({
...todo,
createdAt: new Date(todo.createdAt),
dueDate: todo.dueDate ? new Date(todo.dueDate) : undefined
}));
} catch (error) {
console.error('Failed to load todos:', error);
}
}
};
// ๐ฎ Keyboard shortcuts
const handleKeydown = (event: KeyboardEvent): void => {
if (event.ctrlKey || event.metaKey) {
switch (event.key) {
case 'k':
event.preventDefault();
document.getElementById('search-input')?.focus();
break;
case 'n':
event.preventDefault();
document.getElementById('new-todo-input')?.focus();
break;
}
}
};
// ๐ Initialize on mount
onMount(() => {
loadTodos();
});
</script>
<svelte:window on:keydown={handleKeydown} />
<div class="todo-app">
<header class="app-header">
<h1>๐ TypeScript Todo App</h1>
<div class="stats">
<span>๐ {completedCount}/{totalCount} completed ({completionRate}%)</span>
</div>
</header>
<!-- ๐ Search and filters -->
<div class="controls">
<input
id="search-input"
type="text"
placeholder="๐ Search todos... (Ctrl+K)"
bind:value={searchTerm}
/>
<select bind:value={filter}>
<option value="all">All Todos</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
<select bind:value={sortBy}>
<option value="created">Sort by Created</option>
<option value="priority">Sort by Priority</option>
<option value="dueDate">Sort by Due Date</option>
<option value="title">Sort by Title</option>
</select>
</div>
<!-- โ Add new todo -->
<div class="add-todo">
<input
id="new-todo-input"
type="text"
placeholder="What needs to be done? (Ctrl+N)"
bind:value={newTodoTitle}
on:keydown={(e) => e.key === 'Enter' && addTodo()}
/>
<select bind:value={selectedCategory}>
<option value="personal">๐ Personal</option>
<option value="work">๐ผ Work</option>
<option value="shopping">๐ Shopping</option>
</select>
<select bind:value={selectedPriority}>
<option value="low">๐ข Low</option>
<option value="medium">๐ก Medium</option>
<option value="high">๐ด High</option>
</select>
<button on:click={addTodo} disabled={!newTodoTitle.trim()}>
Add Todo โ
</button>
</div>
<!-- ๐ Todos list -->
<div class="todos-list">
{#if filteredTodos.length === 0}
<div class="empty-state">
{#if searchTerm}
<p>๐ No todos found matching "{searchTerm}"</p>
{:else if filter === 'completed'}
<p>๐ No completed todos yet!</p>
{:else if filter === 'active'}
<p>โจ All caught up! No active todos.</p>
{:else}
<p>๐ No todos yet. Add one above!</p>
{/if}
</div>
{:else}
{#each filteredTodos as todo (todo.id)}
<div class="todo-item" class:completed={todo.completed}>
<input
type="checkbox"
checked={todo.completed}
on:change={() => toggleTodo(todo.id)}
/>
<div class="todo-content">
<div class="todo-header">
<span class="category-emoji">{categoryEmojis[todo.category]}</span>
<span class="todo-title" class:completed={todo.completed}>
{todo.title}
</span>
<span
class="priority-badge"
style="background-color: {priorityColors[todo.priority]}"
>
{todo.priority}
</span>
</div>
<div class="todo-meta">
<small>
Category: {todo.category} โข
Created: {todo.createdAt.toLocaleDateString()}
{#if todo.dueDate}
โข Due: {todo.dueDate.toLocaleDateString()}
{/if}
</small>
</div>
</div>
<button class="delete-btn" on:click={() => deleteTodo(todo.id)}>
๐๏ธ
</button>
</div>
{/each}
{/if}
</div>
</div>
<style>
.todo-app {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.app-header {
text-align: center;
margin-bottom: 2rem;
}
.stats {
color: #666;
font-size: 0.9rem;
}
.controls {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.controls input, .controls select {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
flex: 1;
min-width: 150px;
}
.add-todo {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.add-todo input {
flex: 2;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.add-todo select, .add-todo button {
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
}
.add-todo button {
background: #007bff;
color: white;
border-color: #007bff;
font-weight: 600;
}
.add-todo button:disabled {
background: #6c757d;
border-color: #6c757d;
cursor: not-allowed;
}
.todos-list {
margin-top: 2rem;
}
.empty-state {
text-align: center;
padding: 3rem;
color: #666;
font-style: italic;
}
.todo-item {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 1rem;
margin-bottom: 0.5rem;
background: white;
border: 1px solid #e9ecef;
border-radius: 8px;
transition: all 0.2s ease;
}
.todo-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.todo-item.completed {
opacity: 0.7;
background: #f8f9fa;
}
.todo-content {
flex: 1;
}
.todo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.category-emoji {
font-size: 1.2rem;
}
.todo-title {
flex: 1;
font-weight: 500;
}
.todo-title.completed {
text-decoration: line-through;
color: #6c757d;
}
.priority-badge {
color: white;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.todo-meta {
color: #6c757d;
font-size: 0.8rem;
}
.delete-btn {
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
opacity: 0.7;
transition: opacity 0.2s ease;
}
.delete-btn:hover {
opacity: 1;
}
input[type="checkbox"] {
width: 1.2rem;
height: 1.2rem;
margin-top: 0.1rem;
}
</style>
๐ Key Takeaways
Youโve mastered TypeScript with Svelte! Hereโs what you can now do:
- โ Set up TypeScript in Svelte projects with confidence ๐ช
- โ Create type-safe components with proper prop typing ๐ก๏ธ
- โ Use reactive statements with TypeScript inference ๐ฏ
- โ Handle events safely with proper event typing ๐
- โ Build complex applications with Svelte and TypeScript! ๐
Remember: TypeScript and Svelte together create an incredible developer experience! You get compile-time safety with runtime performance. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered TypeScript with Svelte!
Hereโs what to do next:
- ๐ป Practice building the todo app from the exercise
- ๐๏ธ Create a Svelte component library with TypeScript
- ๐ Explore SvelteKit for full-stack TypeScript development
- ๐ Build a real project combining both technologies!
Remember: Every Svelte expert started with their first component. Keep building, keep learning, and most importantly, have fun creating amazing applications! ๐
Happy coding! ๐๐โจ