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 elBoxElement'div'HTML tag to render rolestring | nullnullARIA role ariaLabelstring | nullnullARIA 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'gapstring'0'wrapbooleanfalse
< 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 colsnumber12gapstring'0'align'start' | 'center' | 'end' | 'stretch''stretch'
SwissGridItemComponent:
Input Type Default colSpannumber | nullnullrowSpannumber | nullnull
< 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 level1 | 2 | 3 | 4 | 5 | 61size'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | nullnullweight'normal' | 'medium' | 'semibold' | 'bold' | nullnullalign'left' | 'center' | 'right' | 'justify' | nullnull
< 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' | nullnullweight'normal' | 'medium' | 'semibold' | 'bold' | nullnullalignTextAlign | nullnullcolor'default' | 'muted' | 'subtle' | 'disabled' | 'primary' | 'success' | 'warning' | 'error' | nullnulltruncatebooleanfalse
< 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'loadingbooleanfalsedisabledbooleanfalsetype'button' | 'submit' | 'reset''button'hrefstring | nullnull
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 typeInputType'text'size'sm' | 'md' | 'lg''md'disabledbooleanfalseinvalidbooleanfalseplaceholderstring''describedBystring | nullnullariaLabelstring | nullnull
Model Type Description valuestringTwo-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'rowsnumber3autoResizebooleanfalsedisabledbooleanfalseinvalidbooleanfalseplaceholderstring''
Input Type Default size'sm' | 'md' | 'lg''md'disabledbooleanfalseinvalidbooleanfalse
< swiss-select [formControl] ="countryControl "> < option value ="ch "> Switzerland</ option > < option value ="de "> Germany</ option > </ swiss-select > Input Type Default indeterminatebooleanfalsedisabledbooleanfalseinvalidbooleanfalsevaluestring''namestring | nullnull
< 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'disabledbooleanfalsenamestringauto-generated
Input (radio) Type Required valueunknownyes disabledbooleanno
< 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 disabledbooleanfalseariaLabelstring | nullnulldescribedBystring | nullnull
< 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'labelstring'Loading...'colorstring | nullnull
Respects prefers-reduced-motion.
< swiss-spinner size ="lg " label ="Saving changes " /> Model Type Description openbooleanControls visibility
Input Type Default closeOnEscapebooleantruecloseOnBackdropbooleantrue
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 placementPlacement'bottom'delaynumber200disabledbooleanfalse
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]swissClickOutsideSwissEscapeKeyDirective[swissEscapeKey]swissEscapeKeySwissAutoPlacementDirective[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 Switchrole="radiogroup" with linked radio inputsaria-invalid, aria-describedby on form controlsaria-modal, aria-labelledby, aria-describedby on dialogsScreen-reader-only labels on Spinner prefers-reduced-motion respected in Spinner and Modal animationsSSR-safe (no document/window access without isPlatformBrowser) Import path Contents @swiss-ui/angularAll headless components, directives, services, tokens @swiss-ui/angular/styledStyled variants with swiss-* CSS classes @swiss-ui/angular/directivesDirectives only @swiss-ui/angular/servicesServices only