Svelte 5 components for the Swiss UI design system.
npm install @swiss-ui/svelte svelte
Every component ships in two modes:
Headless (@swiss-ui/svelte) — logic, state, ARIA, no classes by default
Styled (@swiss-ui/svelte/styled) — wraps headless with swiss-* classes from @swiss-ui/core
<!-- Headless: bring your own styles -->
<script >
import { SwissButton } from ' @swiss-ui/svelte'
</script >
<SwissButton variant =" solid" >Save</SwissButton >
<!-- Styled: uses Swiss UI design tokens -->
<script >
import { StyledSwissButton } from ' @swiss-ui/svelte/styled'
</script >
<StyledSwissButton variant =" solid" >Save</StyledSwissButton >
All components use Svelte 5 runes exclusively:
$state() for reactive state
$derived() for computed values
$effect() for side effects
$props() with typed interfaces
$bindable() for two-way binding
Snippets ({#snippet} / {@render}) instead of named slots
setContext / getContext with Symbol keys for compound components
Component
Props
Description
SwissBox
el, class
Polymorphic box element
SwissTextPrimitive
el, class
Polymorphic text element
Component
Props
Description
SwissContainer
size (sm|md|lg|xl|full)
Constrained width container
SwissStack
direction, gap, align, justify
Flex stack
SwissGrid
cols, gap, align
CSS grid wrapper
SwissGridItem
colSpan, rowSpan
Grid cell
Component
Props
Description
SwissHeading
el, level (1–6), size, weight, align
Heading h1–h6
SwissText
el, size, weight, align, color, truncate
Text paragraph or span
Component
Props
Snippets
Bindable
SwissButton
variant, size, loading, disabled, el, type
leftIcon, rightIcon
—
SwissInput
size, type, disabled, invalid, id, describedBy
leftAddon, rightAddon
bind:value
SwissTextarea
rows, resize, autoResize, disabled, invalid
—
bind:value
SwissSelect
size, disabled, invalid, id
children (options)
bind:value
SwissCheckbox
indeterminate, disabled, invalid, id, name, value
children (label)
bind:checked
SwissRadio
value, disabled, invalid, id
children (label)
—
SwissRadioGroup
name, disabled, orientation
children
bind:value
SwissSwitch
disabled, id, name
children (label)
bind:checked
Component
Props
Snippets
SwissBadge
variant (solid|outline|subtle), color
children
SwissAlert
variant (info|success|warning|error)
icon, title, description, actions
SwissSpinner
size, color, label
—
Compound component. Manages focus trap, Escape key, scroll lock, and ARIA.
Part
Props
Bindable
SwissModal
onclose
bind:open
SwissModalOverlay
closeOnClick
—
SwissModalContent
—
—
SwissModalHeader
—
—
SwissModalBody
—
—
SwissModalFooter
—
—
SwissModalCloseButton
—
—
<script >
import {
SwissModal ,
SwissModalOverlay ,
SwissModalContent ,
SwissModalHeader ,
SwissModalBody ,
SwissModalFooter ,
SwissModalCloseButton ,
} from ' @swiss-ui/svelte'
let open = $state (false )
</script >
<button onclick ={() => open = true }>Open</button >
<SwissModal bind:open >
<SwissModalOverlay />
<SwissModalContent >
<SwissModalHeader >
Title
<SwissModalCloseButton />
</SwissModalHeader >
<SwissModalBody >Content goes here</SwissModalBody >
<SwissModalFooter >
<SwissButton onclick ={() => open = false }>Close</SwissButton >
</SwissModalFooter >
</SwissModalContent >
</SwissModal >
Props
Snippets
Bindable
placement, delay, disabled
children (trigger), content
bind:open
<script >
import { SwissTooltip } from ' @swiss-ui/svelte'
</script >
<SwissTooltip placement =" top" >
<button >Hover me</button >
{#snippet content ()}
Tooltip text
{/ snippet }
</SwissTooltip >
Compound component with full keyboard navigation (Arrow, Enter, Escape, Home, End).
Part
Props
Bindable
SwissDropdown
—
bind:open
SwissDropdownTrigger
—
—
SwissDropdownContent
placement
—
SwissDropdownItem
disabled, onselect
—
SwissDropdownSeparator
—
—
SwissDropdownLabel
—
—
<script >
import {
SwissDropdown ,
SwissDropdownTrigger ,
SwissDropdownContent ,
SwissDropdownItem ,
SwissDropdownSeparator ,
SwissDropdownLabel ,
} from ' @swiss-ui/svelte'
</script >
<SwissDropdown >
<SwissDropdownTrigger >Menu</SwissDropdownTrigger >
<SwissDropdownContent >
<SwissDropdownLabel >Actions</SwissDropdownLabel >
<SwissDropdownItem onselect ={() => console .log (' edit' )}>Edit</SwissDropdownItem >
<SwissDropdownItem onselect ={() => console .log (' copy' )}>Copy</SwissDropdownItem >
<SwissDropdownSeparator />
<SwissDropdownItem disabled >Delete</SwissDropdownItem >
</SwissDropdownContent >
</SwissDropdown >
Import from @swiss-ui/svelte/actions:
Action
Parameters
Description
use:portal
target?: string | HTMLElement
Renders element into a different DOM node
use:focusTrap
active: boolean
Traps keyboard focus within element
use:clickOutside
handler: () => void
Fires handler on click outside element
use:escapeKey
handler: () => void
Fires handler on Escape keydown
use:autoPlacement
{ trigger, placement, offset }
Positions element relative to trigger
<script >
import { portal , focusTrap , clickOutside , escapeKey } from ' @swiss-ui/svelte/actions'
let open = $state (false )
</script >
{#if open }
<div
use:portal ={' body' }
use:focusTrap ={open }
use:clickOutside ={() => open = false }
use:escapeKey ={() => open = false }
>
Popup content
</div >
{/if }
<script >
import { SwissRadioGroup , SwissRadio } from ' @swiss-ui/svelte'
let selected = $state (' a' )
</script >
<SwissRadioGroup bind:value ={selected } orientation =" horizontal" >
<SwissRadio value =" a" >Option A</SwissRadio >
<SwissRadio value =" b" >Option B</SwissRadio >
<SwissRadio value =" c" disabled >Option C</SwissRadio >
</SwissRadioGroup >
<!-- Headless: data attributes for styling -->
<SwissButton variant =" solid" size =" md" data-variant =" solid" >
Click
</SwissButton >
<!-- Styled: adds swiss-button class -->
<StyledSwissButton variant =" solid" >
Click
</StyledSwissButton >
All components follow WCAG 2.1 AA:
Semantic HTML elements and ARIA roles
aria-invalid, aria-describedby on form fields
aria-modal, aria-labelledby, aria-describedby on dialogs
aria-checked="mixed" on indeterminate checkboxes
role="switch" with aria-checked on SwissSwitch
role="radiogroup" with aria-orientation on SwissRadioGroup
Full keyboard navigation on Dropdown (Arrow, Enter, Escape, Home, End)
Focus trap on Modal with first-element focus on open
Focus returns to trigger on Modal/Dropdown close
All components are SSR-safe:
Svelte actions run only in the browser (mount-time)
$effect() blocks that access document run only client-side
Portal renders nothing during SSR
document.body.style modifications are guarded by $effect()
npm run build # svelte-package → dist/
npm run dev # watch mode
npm run check # svelte-check
npm test # vitest run
Individual Component Imports
import { SwissButton } from '@swiss-ui/svelte/SwissButton'
import { SwissModal } from '@swiss-ui/svelte/SwissModal'
import { SwissDropdown } from '@swiss-ui/svelte/SwissDropdown'