A modern, customizable switch component library for Svelte 5 with support for both binary and multi-step switches.
š® Live Demo & Documentation
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 countWhen 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.
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 colorWhen 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 weight
Default labels automatically:
items
array content when availableitemsCount
size
proplabelPosition
for positioning logicMIT
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