Angular components for the Swiss UI design system.
npm install @swiss-ui/angular
Every component ships in two modes:
Headless — logic, state, accessibility, ARIA. No classes by default.
Styled — wraps headless, adds swiss-* classes from @swiss-ui/core.
// Headless (zero runtime styles)
import { SwissButtonComponent } from '@swiss-ui/angular' ;
// With swiss-* classes
import { StyledButtonComponent } from '@swiss-ui/angular/styled' ;
import { bootstrapApplication } from '@angular/platform-browser' ;
import { provideAnimations } from '@angular/platform-browser/animations' ;
import { AppComponent } from './app/app.component' ;
bootstrapApplication ( AppComponent , {
providers : [ provideAnimations ( ) ] ,
} ) ;
Polymorphic container element.
Input
Type
Default
Description
el
BoxElement
'div'
HTML tag to render
role
string | null
null
ARIA role
ariaLabel
string | null
null
ARIA label
< swiss-box el ="section " role ="region " ariaLabel ="Main content ">
Content
</ swiss-box >
Input
Type
Default
size
'sm' | 'md' | 'lg' | 'xl' | 'full'
'lg'
< swiss-container size ="xl ">
Page content
</ swiss-container >
Input
Type
Default
direction
'row' | 'column' | 'row-reverse' | 'column-reverse'
'row'
align
'start' | 'center' | 'end' | 'stretch' | 'baseline'
'start'
justify
'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'
'start'
gap
string
'0'
wrap
boolean
false
< swiss-stack direction ="column " gap ="1rem " align ="center ">
< swiss-button > One</ swiss-button >
< swiss-button > Two</ swiss-button >
</ swiss-stack >
SwissGridComponent / SwissGridItemComponent
Input
Type
Default
cols
number
12
gap
string
'0'
align
'start' | 'center' | 'end' | 'stretch'
'stretch'
SwissGridItemComponent:
Input
Type
Default
colSpan
number | null
null
rowSpan
number | null
null
< swiss-grid cols ="3 " gap ="1rem ">
< swiss-grid-item colSpan ="2 "> Wide cell</ swiss-grid-item >
< swiss-grid-item > Narrow cell</ swiss-grid-item >
</ swiss-grid >
Input
Type
Default
level
1 | 2 | 3 | 4 | 5 | 6
1
size
'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | null
null
weight
'normal' | 'medium' | 'semibold' | 'bold' | null
null
align
'left' | 'center' | 'right' | 'justify' | null
null
< swiss-heading level ="2 " size ="xl " weight ="bold "> Page Title</ swiss-heading >
Input
Type
Default
el
'p' | 'span' | 'label' | 'small' | 'strong' | 'em' | 'div'
'p'
size
'xs' | 'sm' | 'md' | 'lg' | 'xl' | null
null
weight
'normal' | 'medium' | 'semibold' | 'bold' | null
null
align
TextAlign | null
null
color
'default' | 'muted' | 'subtle' | 'disabled' | 'primary' | 'success' | 'warning' | 'error' | null
null
truncate
boolean
false
< swiss-text size ="sm " color ="muted " truncate > Long text that will be truncated</ swiss-text >
Input
Type
Default
el
'button' | 'a'
'button'
variant
'solid' | 'outline' | 'ghost' | 'link'
'solid'
size
'sm' | 'md' | 'lg'
'md'
loading
boolean
false
disabled
boolean
false
type
'button' | 'submit' | 'reset'
'button'
href
string | null
null
Content projection:
[swissButtonLeftIcon] — icon before label
[swissButtonRightIcon] — icon after label
< swiss-button variant ="outline " size ="lg ">
< span swissButtonLeftIcon > +</ span >
Add item
</ swiss-button >
Implements ControlValueAccessor. Compatible with FormControl and ngModel.
Input
Type
Default
type
InputType
'text'
size
'sm' | 'md' | 'lg'
'md'
disabled
boolean
false
invalid
boolean
false
placeholder
string
''
describedBy
string | null
null
ariaLabel
string | null
null
Model
Type
Description
value
string
Two-way binding via model()
Content projection:
[swissInputLeftAddon]
[swissInputRightAddon]
Reactive Forms:
< swiss-input [formControl] ="emailControl " type ="email " placeholder ="Email " />
Template-driven:
< swiss-input [(ngModel)] ="email " type ="email " />
Input
Type
Default
resize
'none' | 'both' | 'horizontal' | 'vertical'
'vertical'
rows
number
3
autoResize
boolean
false
disabled
boolean
false
invalid
boolean
false
placeholder
string
''
Input
Type
Default
size
'sm' | 'md' | 'lg'
'md'
disabled
boolean
false
invalid
boolean
false
< swiss-select [formControl] ="countryControl ">
< option value ="ch "> Switzerland</ option >
< option value ="de "> Germany</ option >
</ swiss-select >
Input
Type
Default
indeterminate
boolean
false
disabled
boolean
false
invalid
boolean
false
value
string
''
name
string | null
null
Model
Type
checked
boolean
< swiss-checkbox [formControl] ="agreeControl "> I agree to the terms</ swiss-checkbox >
SwissRadioGroupComponent + SwissRadioComponent
SwissRadioGroupComponent implements ControlValueAccessor. Child SwissRadioComponent instances connect via SWISS_RADIO_GROUP_TOKEN.
Input (group)
Type
Default
orientation
'horizontal' | 'vertical'
'vertical'
disabled
boolean
false
name
string
auto-generated
Input (radio)
Type
Required
value
unknown
yes
disabled
boolean
no
< swiss-radio-group [formControl] ="sizeControl " orientation ="horizontal ">
< swiss-radio value ="sm "> Small</ swiss-radio >
< swiss-radio value ="md "> Medium</ swiss-radio >
< swiss-radio value ="lg "> Large</ swiss-radio >
</ swiss-radio-group >
Input
Type
Default
disabled
boolean
false
ariaLabel
string | null
null
describedBy
string | null
null
Model
Type
checked
boolean
< swiss-switch [formControl] ="darkModeControl " ariaLabel ="Enable dark mode " />
Input
Type
Default
variant
'solid' | 'outline' | 'subtle'
'subtle'
color
'default' | 'primary' | 'success' | 'warning' | 'error' | 'info'
'default'
< swiss-badge variant ="solid " color ="success "> Active</ swiss-badge >
Input
Type
Default
variant
'info' | 'success' | 'warning' | 'error'
'info'
Content projection:
[swissAlertIcon]
[swissAlertTitle]
[swissAlertDescription]
[swissAlertActions]
< swiss-alert variant ="warning ">
< span swissAlertTitle > Warning</ span >
< span swissAlertDescription > Please review before proceeding.</ span >
</ swiss-alert >
Input
Type
Default
size
'xs' | 'sm' | 'md' | 'lg' | 'xl'
'md'
label
string
'Loading...'
color
string | null
null
Respects prefers-reduced-motion.
< swiss-spinner size ="lg " label ="Saving changes " />
Model
Type
Description
open
boolean
Controls visibility
Input
Type
Default
closeOnEscape
boolean
true
closeOnBackdrop
boolean
true
Composed via InjectionToken — child components (swiss-modal-header, swiss-modal-body, etc.) connect automatically.
Composed parts:
SwissModalOverlayComponent (swiss-modal-overlay)
SwissModalContentComponent (swiss-modal-content)
SwissModalHeaderComponent (swiss-modal-header)
SwissModalBodyComponent (swiss-modal-body)
SwissModalFooterComponent (swiss-modal-footer)
SwissModalCloseButtonComponent (swiss-modal-close-button)
< button (click) ="isOpen.set(true) "> Open</ button >
< swiss-modal [(open)] ="isOpen ">
< swiss-modal-header >
< swiss-heading level ="2 "> Confirm action</ swiss-heading >
< swiss-modal-close-button > x</ swiss-modal-close-button >
</ swiss-modal-header >
< swiss-modal-body >
< swiss-text > Are you sure you want to continue?</ swiss-text >
</ swiss-modal-body >
< swiss-modal-footer >
< swiss-button variant ="ghost " (click) ="isOpen.set(false) "> Cancel</ swiss-button >
< swiss-button variant ="solid "> Confirm</ swiss-button >
</ swiss-modal-footer >
</ swiss-modal >
Input
Type
Default
placement
Placement
'bottom'
delay
number
200
disabled
boolean
false
Content projection:
[swissTooltipTrigger]
[swissTooltipContent]
< swiss-tooltip placement ="top ">
< button swissTooltipTrigger > Hover me</ button >
< span swissTooltipContent > Helpful information</ span >
</ swiss-tooltip >
Keyboard: ArrowDown, ArrowUp, Home, End, Escape.
Composed parts:
SwissDropdownTriggerComponent (swiss-dropdown-trigger)
SwissDropdownContentComponent (swiss-dropdown-content)
SwissDropdownItemComponent (swiss-dropdown-item)
SwissDropdownSeparatorComponent (swiss-dropdown-separator)
SwissDropdownLabelComponent (swiss-dropdown-label)
< swiss-dropdown >
< swiss-dropdown-trigger > Actions</ swiss-dropdown-trigger >
< swiss-dropdown-content >
< swiss-dropdown-label > File</ swiss-dropdown-label >
< swiss-dropdown-item > New</ swiss-dropdown-item >
< swiss-dropdown-item > Open</ swiss-dropdown-item >
< swiss-dropdown-separator />
< swiss-dropdown-item > Delete</ swiss-dropdown-item >
</ swiss-dropdown-content >
</ swiss-dropdown >
import { SwissThemeService } from '@swiss-ui/angular/services' ;
@Component ( { ... } )
export class AppComponent {
private theme = inject ( SwissThemeService ) ;
readonly currentTheme = this . theme . theme ; // Signal<'light' | 'dark'>
readonly isDark = this . theme . isDark ; // Signal<boolean>
toggleTheme ( ) {
this . theme . toggle ( ) ;
}
}
import { SwissOverlayService } from '@swiss-ui/angular/services' ;
@Component ( { ... } )
export class AppComponent {
private overlay = inject ( SwissOverlayService ) ;
openDialog ( ) {
const ref = this . overlay . open ( MyDialogComponent ) ;
// ref.destroy() to close programmatically
}
}
import {
SwissFocusTrapDirective ,
SwissClickOutsideDirective ,
SwissEscapeKeyDirective ,
SwissAutoPlacementDirective ,
} from '@swiss-ui/angular/directives' ;
Directive
Selector
Events
SwissFocusTrapDirective
[swissFocusTrap]
—
SwissClickOutsideDirective
[swissClickOutside]
swissClickOutside
SwissEscapeKeyDirective
[swissEscapeKey]
swissEscapeKey
SwissAutoPlacementDirective
[swissAutoPlacement]
placementChange
import { Component , inject } from '@angular/core' ;
import { FormBuilder , Validators , ReactiveFormsModule } from '@angular/forms' ;
import { SwissInputComponent , SwissButtonComponent , SwissCheckboxComponent } from '@swiss-ui/angular' ;
@Component ( {
standalone : true ,
imports : [ ReactiveFormsModule , SwissInputComponent , SwissButtonComponent , SwissCheckboxComponent ] ,
template : `
<form [formGroup]="form" (ngSubmit)="submit()">
<swiss-input formControlName="email" type="email" placeholder="Email"
[invalid]="form.controls.email.invalid && form.controls.email.touched" />
<swiss-checkbox formControlName="agree">Accept terms</swiss-checkbox>
<swiss-button type="submit" [disabled]="form.invalid">Submit</swiss-button>
</form>
` ,
} )
export class SignupComponent {
private fb = inject ( FormBuilder ) ;
form = this . fb . group ( {
email : [ '' , [ Validators . required , Validators . email ] ] ,
agree : [ false , Validators . requiredTrue ] ,
} ) ;
submit ( ) {
if ( this . form . valid ) console . log ( this . form . value ) ;
}
}
import { Component } from '@angular/core' ;
import { FormsModule } from '@angular/forms' ;
import { SwissInputComponent , SwissSwitchComponent } from '@swiss-ui/angular' ;
@Component ( {
standalone : true ,
imports : [ FormsModule , SwissInputComponent , SwissSwitchComponent ] ,
template : `
<form #f="ngForm" (ngSubmit)="submit(f)">
<swiss-input [(ngModel)]="name" name="name" required placeholder="Name" />
<swiss-switch [(ngModel)]="notifications" name="notifications">Notifications</swiss-switch>
<button type="submit">Save</button>
</form>
` ,
} )
export class ProfileComponent {
name = '' ;
notifications = true ;
submit ( form : NgForm ) {
if ( form . valid ) console . log ( { name : this . name , notifications : this . notifications } ) ;
}
}
All components follow WCAG 2.1 AA:
Keyboard navigation on all interactive elements
ARIA roles, states, and properties on all components
Focus management in Modal (focus trap, restore on close)
role="switch" with aria-checked on Switch
role="radiogroup" with linked radio inputs
aria-invalid, aria-describedby on form controls
aria-modal, aria-labelledby, aria-describedby on dialogs
Screen-reader-only labels on Spinner
prefers-reduced-motion respected in Spinner and Modal animations
SSR-safe (no document/window access without isPlatformBrowser)
Import path
Contents
@swiss-ui/angular
All headless components, directives, services, tokens
@swiss-ui/angular/styled
Styled variants with swiss-* CSS classes
@swiss-ui/angular/directives
Directives only
@swiss-ui/angular/services
Services only