SVG Icons in Svelte and SvelteKit: The Complete 2026 Guide
How to use SVG icons in Svelte and SvelteKit — lucide-svelte, @iconify/svelte, unplugin-icons, inline SVGs, and dark mode with Tailwind. Full code patterns included.
Svelte’s reactivity model makes SVG icons uniquely clean to work with: no useState, no useEffect, just reactive props flowing straight to SVG attributes. lucide-svelte, @iconify/svelte, and unplugin-icons all integrate with zero boilerplate. This guide covers every approach with SvelteKit-specific patterns.
Option 1: lucide-svelte
The official Lucide port for Svelte. Same 1,500+ icons, same visual style, Svelte component output.
npm install lucide-svelte
Basic usage
<script>
import { Search, Settings, Bell } from 'lucide-svelte';
</script>
<div class="flex gap-4 text-gray-700 dark:text-gray-300">
<Search size={20} />
<Settings size={20} />
<Bell size={20} strokeWidth={1.5} />
</div>
Props
| Prop | Type | Default |
|---|---|---|
size | number | 24 |
color | string | currentColor |
strokeWidth | number | 2 |
class | string | — |
Reactive icon props in Svelte
Svelte’s reactivity makes size/color toggling extremely clean:
<script>
import { Heart } from 'lucide-svelte';
let active = false;
</script>
<button on:click={() => active = !active}>
<Heart
size={24}
color={active ? '#ef4444' : 'currentColor'}
fill={active ? '#ef4444' : 'none'}
/>
</button>
No useState, no render cycle — Svelte’s compiled reactivity updates the SVG attributes directly.
Option 2: @iconify/svelte
Access all 250,000+ Iconify icons through a single Svelte component.
npm install @iconify/svelte
<script>
import Icon from '@iconify/svelte';
</script>
<Icon icon="lucide:search" class="w-5 h-5" />
<Icon icon="tabler:dashboard" class="w-5 h-5" />
<Icon icon="ph:heart-duotone" class="w-5 h-5 text-blue-500" />
Offline/SSR mode
For SvelteKit SSR, bundle icons instead of fetching at runtime:
npm install @iconify/json
<script>
import Icon from '@iconify/svelte';
import searchIcon from '@iconify-icons/lucide/search';
</script>
<Icon icon={searchIcon} class="w-5 h-5" />
@iconify/svelte’s default runtime mode fetches icons from a CDN. For SvelteKit with SSR enabled, use the bundled approach above or set mode="svg" to inline the SVG at build time.
Option 3: unplugin-icons with Svelte
unplugin-icons resolves icon components at build time — no imports needed in component files.
npm install -D unplugin-icons @iconify/json
SvelteKit setup
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import Icons from 'unplugin-icons/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit(),
Icons({ compiler: 'svelte' }),
],
});
Usage
<script>
import LucideSearch from '~icons/lucide/search';
import TablerDashboard from '~icons/tabler/dashboard';
</script>
<LucideSearch class="w-5 h-5" />
<TablerDashboard class="w-5 h-5" />
Only the icons you import are bundled — identical tree-shaking behavior to lucide-svelte named imports.
Inline SVG pattern for Svelte
For custom icons not in any library, or icons that need prop-driven path changes, write them inline:
<script>
export let size = 24;
export let color = 'currentColor';
export let strokeWidth = 2;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={color}
stroke-width={strokeWidth}
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 8v4l3 3" />
</svg>
Note the Svelte-specific attribute format: stroke-width (hyphenated) not strokeWidth.
Dark mode with Tailwind
All three options use currentColor by default. Tailwind’s dark mode just works:
<!-- Icon inherits text color from parent -->
<div class="text-gray-700 dark:text-gray-300">
<Search size={20} />
</div>
For CSS variable-based dark mode (without Tailwind):
<style>
:global(:root) { --icon-default: #374151; }
:global(.dark) { --icon-default: #d1d5db; }
</style>
<Search size={20} color="var(--icon-default)" />
Accessibility
<!-- Decorative icon inside labeled button -->
<button>
<Download size={18} aria-hidden="true" />
Export CSV
</button>
<!-- Icon-only button -->
<button aria-label="Delete item">
<Trash2 size={18} aria-hidden="true" />
</button>
<!-- Standalone meaningful icon -->
<Search
size={20}
role="img"
aria-label="Search"
/>
SvelteKit-specific: server-side rendering
All three libraries render correctly during SSR — the SVG markup is included in the initial HTML response. No hydration flash for icons.
Verify by checking your SSR output:
curl http://localhost:5173 | grep -o '<svg[^>]*>' | head -5
You should see <svg> elements in the HTML before any JavaScript runs.
Choosing an approach
| Need | Recommended |
|---|---|
| Lucide icons, simplest setup | lucide-svelte |
| Multiple icon packs | @iconify/svelte (bundled) |
| Auto-import, no per-file imports | unplugin-icons |
| Custom/bespoke icons | Inline SVG Svelte component |