svelte Svelte Themes

Svelte

Svelte 5 components for the Swiss UI design system.

@swiss-ui/svelte

Svelte 5 components for the Swiss UI design system.

Installation

npm install @swiss-ui/svelte svelte

Architecture

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>

Svelte 5 Patterns

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

Components

Primitives

Component Props Description
SwissBox el, class Polymorphic box element
SwissTextPrimitive el, class Polymorphic text element

Layout

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

Typography

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

Controls

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

Feedback

Component Props Snippets
SwissBadge variant (solid|outline|subtle), color children
SwissAlert variant (info|success|warning|error) icon, title, description, actions
SwissSpinner size, color, label

Overlay

SwissModal

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>

SwissTooltip

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>

SwissDropdown

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>

Actions

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}

RadioGroup Example

<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 vs Styled

<!-- 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>

Accessibility

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

SvelteKit SSR

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()

Build

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'

Top categories

Loading Svelte Themes