JavaScript/TypeScript utilities for Swiss UI design system.
npm install @swiss-ui/utilsMerges CSS class names with support for strings, conditional objects, arrays, and falsy values.
import { cn } from '@swiss-ui/utils/cn'
cn('btn', 'btn--primary')
// 'btn btn--primary'
cn('btn', { 'btn--active': isActive, 'btn--disabled': false })
// 'btn btn--active' (if isActive is true)
cn('btn', ['btn--sm', undefined, false])
// 'btn btn--sm'Read, write, and generate overrides for design tokens.
import { getCssVar, setCssVar, createThemeVars } from '@swiss-ui/utils/tokens'
import type { SwissToken } from '@swiss-ui/utils/tokens'
// Read computed value at runtime
const primary = getCssVar('--color-text-primary')
// Set on :root
setCssVar('--color-background-primary', '#ffffff')
// Set on a specific element
setCssVar('--color-text-primary', '#000000', myElement)
// Generate inline styles for token overrides
const style = createThemeVars({
'--color-text-primary': '#1a1a1a',
'--color-background-primary': '#f5f5f5',
})
// Use as: <div style={style}>...</div>CVA-inspired helper for building type-safe component variants. No dependencies.
import { swiss } from '@swiss-ui/utils/variants'
const button = swiss({
base: 'swiss-button',
variants: {
size: {
sm: 'swiss-button--sm',
md: 'swiss-button--md',
lg: 'swiss-button--lg',
},
variant: {
primary: 'swiss-button--primary',
ghost: 'swiss-button--ghost',
},
},
compoundVariants: [
{ size: 'sm', variant: 'ghost', class: 'swiss-button--sm-ghost' },
],
defaultVariants: { size: 'md', variant: 'primary' },
})
button() // 'swiss-button swiss-button--md swiss-button--primary'
button({ size: 'sm' }) // 'swiss-button swiss-button--sm swiss-button--primary'
button({ size: 'sm', variant: 'ghost' }) // includes 'swiss-button--sm-ghost'Framework-agnostic media query helpers. SSR-safe.
import {
createMediaQuery,
matchesBreakpoint,
onBreakpointChange,
} from '@swiss-ui/utils/responsive'
import type { SwissBreakpoint } from '@swiss-ui/utils/responsive'
// Get a media query string
createMediaQuery('md') // '(min-width: 768px)'
// Synchronous viewport check
if (matchesBreakpoint('lg')) {
// desktop layout
}
// Subscribe to breakpoint changes
const unsubscribe = onBreakpointChange('md', (matches) => {
console.log('md breakpoint active:', matches)
})
// Later:
unsubscribe()Available breakpoints: sm (640px), md (768px), lg (1024px), xl (1280px), 2xl (1536px).
import {
generateId,
getFocusableElements,
focusTrap,
isReducedMotion,
} from '@swiss-ui/utils/a11y'
// Generate unique IDs for aria attributes
const labelId = generateId('dialog') // 'dialog-1'
const descId = generateId() // 'swiss-2'
// Get all focusable elements inside a container
const focusable = getFocusableElements(modalElement)
// Trap focus inside a modal
const trap = focusTrap(modalElement)
trap.activate() // moves focus to first focusable element
trap.deactivate() // returns focus to previously focused element
// Respect user motion preferences
if (!isReducedMotion()) {
startAnimation()
}import { setDataTheme, getDataTheme, toggleDataTheme } from '@swiss-ui/utils/dom'
// Set theme
setDataTheme('dark') // sets data-theme="dark" on :root
setDataTheme('light', myElement) // sets on a specific element
// Read current theme
getDataTheme() // 'dark' | 'light' | null
getDataTheme(myElement)
// Toggle theme
const newTheme = toggleDataTheme() // returns 'light' | 'dark'Each module is a separate entry point. Import only what you use:
import { cn } from '@swiss-ui/utils/cn'
import { swiss } from '@swiss-ui/utils/variants'
import { getCssVar } from '@swiss-ui/utils/tokens'Or import everything from the main entry (bundlers will tree-shake unused exports):
import { cn, swiss, getCssVar } from '@swiss-ui/utils'AGPL-3.0-only