Vue 3 components for the Swiss UI design system.
npm install @swiss-ui/vue vue
Every component ships in two modes:
Headless (@swiss-ui/vue) — logic, state, ARIA, no CSS classes
Styled (@swiss-ui/vue/styled) — headless wrappers with swiss-* classes from @swiss-ui/core
<script setup lang="ts">
import { SwissButton , SwissInput } from ' @swiss-ui/vue'
</script >
<template >
<SwissButton variant="solid" size="md">Submit</SwissButton >
<SwissInput v-model =" value " placeholder="Enter text" />
</template >
<script setup lang="ts">
import { SwissButton } from ' @swiss-ui/vue/styled'
</script >
<template >
<SwissButton variant="solid">Styled Button</SwissButton >
</template >
Global registration via plugin
import { createApp } from 'vue'
import { createSwissUI } from '@swiss-ui/vue/plugin'
import App from './App.vue'
const app = createApp ( App )
app . use ( createSwissUI ( { prefix : 'Swiss' } ) )
app . mount ( '#app' )
Plugin options:
Option
Type
Default
Description
prefix
string
'Swiss'
Component name prefix. Pass '' to remove it
styled
boolean
false
Register styled variants
Component
Props
Slots
SwissBox
is
default
SwissTextPrimitive
is, size, weight, align, truncate
default
Component
Props
Slots
SwissContainer
size
default
SwissStack
gap, align, justify, direction
default
SwissGrid
cols, gap, align
default
SwissGridItem
colSpan, rowSpan
default
Component
Props
Slots
SwissHeading
is, level, size, weight, align
default
SwissText
is, size, weight, align, color, truncate
default
Component
Props
Emits
Slots
SwissButton
is, variant, size, loading, disabled, type
click
default, leftIcon, rightIcon
SwissInput
modelValue, size, disabled, invalid, type, placeholder
update:modelValue, change, blur, focus
leftAddon, rightAddon
SwissTextarea
modelValue, resize, rows, autoResize, disabled, invalid
update:modelValue, change, blur, focus
—
SwissSelect
modelValue, size, disabled, invalid, placeholder
update:modelValue, change, blur
default (option elements)
SwissCheckbox
modelValue, indeterminate, disabled, invalid, label, value
update:modelValue, change
default
SwissRadioGroup
modelValue, name, disabled, orientation
update:modelValue, change
default
SwissRadio
value, disabled, label
—
default
SwissSwitch
modelValue, disabled, label
update:modelValue, change
default
Component
Props
Slots
SwissBadge
variant, color
default
SwissAlert
variant
icon, title, description, actions, default
SwissSpinner
size, color, label
—
Component
Props
Emits
Slots
SwissModal
open
update:open, open, close
default, portal
SwissModalOverlay
—
—
default
SwissModalContent
—
—
default
SwissModalHeader
—
—
default
SwissModalBody
—
—
default
SwissModalFooter
—
—
default
SwissModalCloseButton
—
—
default
SwissTooltip
open, placement, delay, disabled, content
update:open
default (trigger), content
SwissDropdown
open
update:open, open, close
default, portal
SwissDropdownTrigger
—
—
default
SwissDropdownContent
—
—
default
SwissDropdownItem
disabled, onSelect
select
default
SwissDropdownSeparator
—
—
—
SwissDropdownLabel
—
—
default
<script setup lang="ts">
import { ref } from ' vue'
import {
SwissModal ,
SwissModalOverlay ,
SwissModalContent ,
SwissModalHeader ,
SwissModalBody ,
SwissModalFooter ,
SwissModalCloseButton ,
SwissButton ,
} from ' @swiss-ui/vue'
const open = ref (false )
</script >
<template >
<SwissButton @click =" open = true " >Open Modal</SwissButton >
<SwissModal v-model :open =" open " >
<template #portal >
<SwissModalOverlay >
<SwissModalContent >
<SwissModalHeader >
<h2 >Confirm Action</h2 >
<SwissModalCloseButton />
</SwissModalHeader >
<SwissModalBody >
<p >Are you sure you want to continue?</p >
</SwissModalBody >
<SwissModalFooter >
<SwissButton variant="ghost" @click =" open = false " >Cancel</SwissButton >
<SwissButton variant="solid">Confirm</SwissButton >
</SwissModalFooter >
</SwissModalContent >
</SwissModalOverlay >
</template >
</SwissModal >
</template >
<script setup lang="ts">
import {
SwissDropdown ,
SwissDropdownTrigger ,
SwissDropdownContent ,
SwissDropdownItem ,
SwissDropdownSeparator ,
} from ' @swiss-ui/vue'
</script >
<template >
<SwissDropdown >
<SwissDropdownTrigger >Actions</SwissDropdownTrigger >
<template #portal >
<SwissDropdownContent >
<SwissDropdownItem @select =" handleEdit " >Edit</SwissDropdownItem >
<SwissDropdownItem @select =" handleDuplicate " >Duplicate</SwissDropdownItem >
<SwissDropdownSeparator />
<SwissDropdownItem :disabled =" true " >Delete</SwissDropdownItem >
</SwissDropdownContent >
</template >
</SwissDropdown >
</template >
<script setup lang="ts">
import { ref } from ' vue'
import { SwissRadioGroup , SwissRadio } from ' @swiss-ui/vue'
const size = ref (' md' )
</script >
<template >
<SwissRadioGroup v-model =" size " name="size" orientation="horizontal">
<SwissRadio value="sm" label="Small" />
<SwissRadio value="md" label="Medium" />
<SwissRadio value="lg" label="Large" />
</SwissRadioGroup >
</template >
SwissButton accepts any component via :is, including RouterLink:
<script setup lang="ts">
import { RouterLink } from ' vue-router'
import { SwissButton } from ' @swiss-ui/vue'
</script >
<template >
<SwissButton :is =" RouterLink " to="/dashboard" variant="link">
Go to Dashboard
</SwissButton >
</template >
All components follow WCAG 2.1 AA:
Keyboard navigable: Tab, Enter, Space, Arrow keys, Escape, Home, End
Proper ARIA roles: dialog, alert, switch, radiogroup, menu, menuitem, tooltip
Focus trap in SwissModal — focus stays inside the dialog while open
SwissSpinner includes a visible label via aria-label and <title>
SwissCheckbox supports indeterminate state via aria-checked="mixed"
SwissAlert uses aria-live="assertive" for errors, "polite" for others
Overlay components (SwissModal, SwissTooltip, SwissDropdown) use <Teleport to="body"> with an onMounted guard, making them safe for SSR. No document access happens during server-side rendering.
npm run build # compile to dist/
npm run test # run Vitest
npm run typecheck # vue-tsc check