A modern, customizable switch component library for Svelte 5 with support for both binary and multi-step switches.
๐ฎ Live Demo & Documentation
- Binary Switch: Classic on/off toggle functionality
- Multi-Step Switch: Support for 3+ step switches (like volume controls, temperature settings)
- Orientation Support: Both horizontal and vertical layouts
- Custom Sizing: Scalable to any size while maintaining proportions
- Custom Content: Slot support for icons, text, or any content in the thumb
- Step Styling: Individual styling for each step in multi-step switches
- Label Support: Optional labels with flexible rendering modes (absolute/block positioning)
- Interactive Labels: Clickable labels for enhanced user experience in vertical multi-step switches
- Accessibility: Full keyboard navigation and ARIA support
- TypeScript: Complete type safety with TypeScript interfaces
- No Dependencies: Zero external dependencies, just peer dependency on Svelte 5
npm install @keenmate/svelte-switch<script>
import { Switch } from '@keenmate/svelte-switch';
let checked = $state(false);
</script>
<Switch bind:checked /><Switch
bind:checked
labels={['OFF', 'ON']}
/><Switch
bind:checked
orientation="vertical"
size={60}
/><Switch size={60}>
{#snippet children({ currentIndex, item, isSelected })}
<span>{isSelected ? 'โ' : 'โ'}</span>
{/snippet}
</Switch>The thumbTemplate snippet provides enhanced context for more sophisticated thumb content:
<!-- Step counter example -->
<Switch items={['Off', 'On']}>
{#snippet thumbTemplate({ currentIndex, currentItem, itemsCount })}
<div style="font-size: 0.8rem; text-align: center;">
<div>Step {currentIndex + 1}/{itemsCount}</div>
<div>{currentItem}</div>
</div>
{/snippet}
</Switch>
<!-- MultiSwitch with progress indicator -->
<MultiSwitch bind:selectedIndex itemsCount={4}>
{#snippet thumbTemplate({ currentIndex, currentItem, itemsCount })}
<span style="font-size: 0.7rem;">
{currentIndex + 1}/{itemsCount}
</span>
{/snippet}
</MultiSwitch>Template Comparison:
children:{ currentIndex, item, isSelected }- Basic content for all stepsthumbTemplate:{ currentIndex, currentItem, itemsCount }- Enhanced context with total count
When using switches in non-reactive environments (web components, plain JavaScript, or any context where Svelte's reactivity system isn't available), the disableThumbRender property prevents stale template content:
// Disable thumb template rendering in non-reactive environments
switchInstance.update({
disableThumbRender: true
});This property is primarily designed for non-reactive environments where template functions are called only once and won't update with state changes. It ensures only static styling is rendered, avoiding stale dynamic content.
<script>
import { MultiSwitch } from '@keenmate/svelte-switch';
let selectedIndex = $state(0);
</script>
<MultiSwitch
bind:selectedIndex
itemsCount={4}
size={80}
>
{#snippet children({ currentIndex, item, isSelected })}
<span>{['Low', 'Med', 'High', 'Max'][currentIndex]}</span>
{/snippet}
</MultiSwitch><!-- Automatic labels with absolute positioning (default) -->
<MultiSwitch
bind:selectedIndex
items={['Small', 'Medium', 'Large']}
shouldDisplayLabels={true}
labelPosition="right"
orientation="vertical"
size={70}
/>
<!-- Block positioning - labels take up actual space in layout -->
<MultiSwitch
bind:selectedIndex
items={['Small', 'Medium', 'Large']}
shouldDisplayLabels={true}
labelPosition="bottom"
labelRenderMode="block"
size={70}
/>
<!-- Labels are clickable in vertical orientation (left/right positions) -->
<MultiSwitch
bind:selectedIndex
itemsCount={4}
shouldDisplayLabels={true}
labelPosition="right"
orientation="vertical"
size={70}
/><script>
const productSizes = [
{ name: 'Small', value: 'S', stock: 10 },
{ name: 'Medium', value: 'M', stock: 5 },
{ name: 'Large', value: 'L', stock: 2 }
];
</script>
<MultiSwitch
bind:selectedIndex
items={productSizes}
labelMember="name"
shouldDisplayLabels={true}
labelPosition="right"
orientation="vertical"
size={70}
/><script>
const products = [
{ name: 'Basic', price: 10 },
{ name: 'Pro', price: 25 },
{ name: 'Enterprise', price: 50 }
];
</script>
<MultiSwitch
bind:selectedIndex
items={products}
labelCallback={(item, index) => `${item.name} - $${item.price}`}
shouldDisplayLabels={true}
labelPosition="right"
orientation="vertical"
size={70}
/><MultiSwitch
bind:selectedIndex
items={['Small', 'Medium', 'Large']}
shouldDisplayLabels={true}
labelPosition="right"
orientation="vertical"
size={70}
>
{#snippet labelTemplate({ item, isSelected })}
<span style="color: {isSelected ? '#333' : '#666'}; font-weight: {isSelected ? 'bold' : 'normal'}">
{item} Size
</span>
{/snippet}
</MultiSwitch><script>
const temperatureStyles = [
{ backgroundColor: '#3b82f6', thumbColor: '#1e40af', thumbBorderColor: '#2563eb' }, // Cold
{ backgroundColor: '#10b981', thumbColor: '#047857', thumbBorderColor: '#059669' }, // Warm
{ backgroundColor: '#f59e0b', thumbColor: '#d97706', thumbBorderColor: '#f59e0b' }, // Hot
{ backgroundColor: '#ef4444', thumbColor: '#dc2626', thumbBorderColor: '#ef4444' } // Very Hot
];
</script>
<MultiSwitch
bind:selectedIndex
itemsCount={4}
size={70}
itemStyles={temperatureStyles}
>
{#snippet children({ currentIndex, item, isSelected })}
<span>{['โ๏ธ', '๐ก๏ธ', '๐ฅ', '๐'][currentIndex]}</span>
{/snippet}
</MultiSwitch>| Prop | Type | Default | Description |
|---|---|---|---|
checked |
boolean |
false |
Bindable checked state |
isDisabled |
boolean |
false |
Disable the switch |
orientation |
'horizontal' | 'vertical' |
'horizontal' |
Switch orientation |
size |
number |
50 |
Switch size in pixels |
labels |
string[] |
- | Optional labels for switch states (e.g., ['OFF', 'ON']) |
onToggle |
(checked: boolean) => void |
- | Toggle event handler |
children |
Snippet<[{ currentIndex: number, item: any, isSelected: boolean }]> |
- | Custom content for thumb |
thumbTemplate |
Snippet<[{ currentIndex: number, currentItem: any, itemsCount: number }]> |
- | Enhanced thumb content with extended context |
disableThumbRender |
boolean |
false |
Disable rendering children template in thumb (primarily for non-reactive environments) |
| Method | Parameters | Description |
|---|---|---|
update() |
{ checked?, isDisabled?, orientation?, size?, items?, itemStyles?, onToggle?, disableThumbRender? } |
Updates component properties from external JavaScript/HTML |
| Prop | Type | Default | Description |
|---|---|---|---|
selectedIndex |
number |
0 |
Bindable selected step index |
isDisabled |
boolean |
false |
Disable the switch |
orientation |
'horizontal' | 'vertical' |
'horizontal' |
Switch orientation |
size |
number |
50 |
Switch size in pixels |
itemsCount |
number |
3 |
Number of steps |
items |
any[] |
null |
Array of data items (optional) |
itemStyles |
StepStyle[] | StepStyle |
[] |
Custom styling for each step (array) or all steps (object) |
shouldDisplayLabels |
boolean |
false |
Enable automatic label display (v1.3.0+) |
labelPosition |
'top' | 'bottom' | 'left' | 'right' |
'bottom' |
Label position (horizontal: all positions, vertical: left/right only) |
labelRenderMode |
'absolute' | 'block' |
'absolute' |
Label rendering mode - absolute (may overlap) or block (takes space) (v1.4.0+) |
labelMember |
string |
- | Property name to extract label text from items (e.g., "name" reads item.name) (v1.4.0+) |
labelCallback |
(item: any, index: number) => string |
- | Custom function to generate label text with item and index access (v1.4.0+) |
onItemChange |
(index: number) => void |
- | Item change event handler |
children |
Snippet<[{ currentIndex: number, item: any, isSelected: boolean }]> |
- | Custom content for thumb |
thumbTemplate |
Snippet<[{ currentIndex: number, currentItem: any, itemsCount: number }]> |
- | Enhanced thumb content with extended context |
labelTemplate |
Snippet<[{ currentIndex: number, item: any, isSelected: boolean }]> |
- | Custom label content template (optional, uses default if not provided) |
disableThumbRender |
boolean |
false |
Disable rendering children template in thumb (primarily for non-reactive environments) |
| Method | Parameters | Description |
|---|---|---|
update() |
{ selectedIndex?, isDisabled?, orientation?, size?, itemsCount?, items?, itemStyles?, shouldDisplayLabels?, labelPosition?, labelRenderMode?, labelMember?, labelCallback?, onItemChange?, disableThumbRender? } |
Updates component properties from external JavaScript/HTML |
interface StepStyle {
backgroundColor?: string; // Switch/MultiSwitch background color
thumbColor?: string; // Thumb element color
thumbBorderColor?: string; // Thumb border color
}When using the components directly from vanilla JavaScript or HTML (not within Svelte), you can use the update() method to programmatically change component properties:
// Create Switch instance
const switchInstance = new Switch({
target: document.getElementById('switch-container'),
props: { checked: false, size: 50 }
});
// Update properties externally
switchInstance.update({
checked: true,
size: 80,
isDisabled: false
});
// Create MultiSwitch instance
const multiSwitchInstance = new MultiSwitch({
target: document.getElementById('multiswitch-container'),
props: { selectedIndex: 0, itemsCount: 4 }
});
// Update properties externally
multiSwitchInstance.update({
selectedIndex: 2,
size: 70,
itemsCount: 5,
itemStyles: [
{ backgroundColor: '#ff0000', thumbColor: '#ffffff', thumbBorderColor: '#cc0000' },
{ backgroundColor: '#00ff00', thumbColor: '#000000', thumbBorderColor: '#00cc00' }
]
});This mechanism addresses Svelte 5 reactivity issues when components are used outside of Svelte environments.
- Arrow Keys: Navigate between steps (all directions supported)
- Tab: Focus management with proper tab order
- Enter/Space: Toggle binary switch
The component uses CSS custom properties for dynamic styling. All calculations are handled in SCSS for optimal performance:
--scale: Size scaling factor--steps: Number of steps (MultiSwitch)--current-bg-color: Dynamic background color--current-thumb-color: Dynamic thumb color--current-thumb-border-color: Dynamic thumb border color
When using shouldDisplayLabels={true} without labelTemplate, you can customize the default label appearance using SCSS variables:
// Override default label variables
$default-label-color: #666; // Normal label color
$default-label-active-color: #333; // Active label color
$default-label-font-size: 14px; // Base font size
$default-label-font-weight: normal; // Normal font weight
$default-label-active-font-weight: bold; // Active label font weightDefault labels automatically:
- Display
itemsarray content when available - Fallback to "Item 1", "Item 2", etc. when using
itemsCount - Apply bold font-weight to the active/selected label
- Scale font-size with the component's
sizeprop - Respect
labelPositionfor positioning logic
- Svelte 5.0.0 or higher
- Modern browser with CSS custom properties support
MIT
Explore all features with live examples, interactive controls, and comprehensive documentation.
See CHANGELOG.md for detailed release notes and version history.
Issues and pull requests are welcome at https://github.com/keenmate/svelte-switch