Skip to content

swiss-ui/angular

Repository files navigation

@swiss-ui/angular

Angular components for the Swiss UI design system.

Installation

npm install @swiss-ui/angular

Architecture

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';

Bootstrap (standalone)

import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [provideAnimations()],
});

Components

Primitives

SwissBoxComponent

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>

Layout

SwissContainerComponent

Input Type Default
size 'sm' | 'md' | 'lg' | 'xl' | 'full' 'lg'
<swiss-container size="xl">
  Page content
</swiss-container>

SwissStackComponent

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>

Typography

SwissHeadingComponent

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>

SwissTextComponent

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>

Controls

SwissButtonComponent

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>

SwissInputComponent

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" />

SwissTextareaComponent

Input Type Default
resize 'none' | 'both' | 'horizontal' | 'vertical' 'vertical'
rows number 3
autoResize boolean false
disabled boolean false
invalid boolean false
placeholder string ''

SwissSelectComponent

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>

SwissCheckboxComponent

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>

SwissSwitchComponent

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" />

Feedback

SwissBadgeComponent

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>

SwissAlertComponent

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>

SwissSpinnerComponent

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" />

Overlay

SwissModalComponent

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>

SwissTooltipComponent

Model Type
open boolean
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>

SwissDropdownComponent

Model Type
open boolean

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>

Services

SwissThemeService

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

SwissOverlayService

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

Directives

import {
  SwissFocusTrapDirective,
  SwissClickOutsideDirective,
  SwissEscapeKeyDirective,
  SwissAutoPlacementDirective,
} from '@swiss-ui/angular/directives';
Directive Selector Events
SwissFocusTrapDirective [swissFocusTrap]
SwissClickOutsideDirective [swissClickOutside] swissClickOutside
SwissEscapeKeyDirective [swissEscapeKey] swissEscapeKey
SwissAutoPlacementDirective [swissAutoPlacement] placementChange

Forms Integration

Reactive Forms

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);
  }
}

Template-driven Forms

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 });
  }
}

Accessibility

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)

Entrypoints

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

About

Angular components for the Swiss UI design system.

Topics

Resources

License

Stars

Watchers

Forks

Contributors