npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swiss-ui/angular

v0.1.0

Published

Angular components for Swiss UI design system

Readme

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