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

@vss-software/ui

v0.2.2

Published

Produktuebergreifendes Design-System (Vue-Komponenten)

Readme

@vss-software/ui

Produktuebergreifendes Design-System mit Vue 3 Komponenten fuer die lumen.hr Plattform.

Stil: Professional Calm — dunkle Sidebar (Navy #0B1929), helle Arbeitsflaechen (Gray-50 #F8FAFC), Teal-Akzentfarbe (#00BFA6), Inter Schrift, WCAG 2.1 AA konform.

Installation

npm install @vss-software/ui
# or
pnpm add @vss-software/ui

Peer Dependencies

{
  "vue": "^3.5.0",
  "vue-router": "^4",
  "tailwindcss": "^4.0.0"
}

Setup

Option 1: Vue Plugin (alle Komponenten global registrieren)

import { createApp } from 'vue'
import LumenUI from '@vss-software/ui'
import '@vss-software/ui/styles'

const app = createApp(App)
app.use(LumenUI)
app.mount('#app')

Option 2: Einzelimport (Tree-Shaking)

import { LuButton, LuCard, LuInput } from '@vss-software/ui'
import '@vss-software/ui/styles'

Tailwind Integration

Da @vss-software/ui auf Tailwind CSS v4 basiert, muss das konsumierende Projekt ebenfalls Tailwind v4 verwenden. Die Styles werden ueber @vss-software/ui/styles eingebunden.


Design Tokens

Farben

| Token | Hex | Verwendung | | ------------ | --------- | ------------------------------------- | | navy-900 | #0B1929 | Sidebar-Hintergrund, Login-BG | | navy-800 | #0F2438 | Sidebar Hover-States | | teal-500 | #00BFA6 | Primaere CTAs, Links, aktive Elemente | | teal-400 | #26EDCA | Hover auf dunklem Hintergrund | | teal-700 | #00897B | Pressed-States, dunkle Akzente | | lu-bg | #F8FAFC | Seiten-Hintergrund (gray-50) | | lu-surface | #FFFFFF | Karten, Modals, Tabellen | | lu-border | #E2E8F0 | Trennlinien, Karten-Raender |

Typografie

| Token | Groesse | Verwendung | | -------------------- | ------- | ------------------------- | | --lu-text-display | 30px | Display-Headlines | | --lu-text-h1 | 24px | Seiten-Ueberschriften | | --lu-text-h2 | 20px | Abschnitts-Ueberschriften | | --lu-text-h3 | 16px | Karten-Ueberschriften | | --lu-text-body | 14px | Fliesstext | | --lu-text-body-sm | 13px | Kompakter Text | | --lu-text-caption | 12px | Beschriftungen | | --lu-text-overline | 11px | Labels, Overlines |

Schriftarten: Inter (sans-serif), JetBrains Mono (monospace).

Spacing

4px-Basisraster: 1=4px, 2=8px, 3=12px, 4=16px, 5=20px, 6=24px, 8=32px, 10=40px, 12=48px.

Z-Index Skala

| Token | Wert | Verwendung | | ------------------ | ---- | -------------------- | | z-header | 30 | LuHeader | | z-sidebar | 40 | LuSidebar | | z-dropdown | 50 | LuDropdown, LuSelect | | z-modal-backdrop | 60 | Modal-Backdrop | | z-modal | 70 | LuModal | | z-toast | 80 | LuToast |


Komponenten

Layout

LuAppLayout

Haupt-Application-Shell mit Sidebar, Header und scrollbarem Content-Bereich.

<LuAppLayout
  :nav-items="navGroups"
  :user="{ name: 'Max Mustermann', role: 'HR' }"
  :notification-count="3"
  :current-user-roles="['HR']"
  v-model:sidebar-collapsed="collapsed"
  :user-menu-items="[
    { label: 'Profil', icon: 'User', action: 'profile' },
    { label: 'Abmelden', icon: 'LogOut', action: 'logout' },
  ]"
  @notifications-click="openNotifications"
  @user-menu-action="handleMenuAction"
>
  <template #sidebar-logo>
    <img src="/logo.svg" alt="Logo" />
  </template>

  <template #breadcrumb>
    <LuBreadcrumb :items="[{ label: 'Dashboard', to: '/' }, { label: 'Mitarbeiter' }]" />
  </template>

  <RouterView />
</LuAppLayout>

Props:

| Prop | Typ | Default | Beschreibung | | ------------------- | ----------------------------------------- | ---------------------- | ------------------------------------- | | navItems | NavGroup[] | [] | Navigationsgruppen fuer die Sidebar | | user | { name, role?, roleLabel?, avatarSrc? } | { name: 'Benutzer' } | Aktuelle Benutzerinfo | | notificationCount | Number | 0 | Anzahl ungelesener Benachrichtigungen | | currentUserRoles | String[] | [] | Benutzerrollen fuer RBAC-Filterung | | sidebarCollapsed | Boolean | false | Sidebar eingeklappt (v-model) | | userMenuItems | { label, icon?, action }[] | [] | Eintraege im Benutzer-Dropdown |

Events: notifications-click, user-menu-action(action), update:sidebarCollapsed(boolean)

Slots: default (Content), sidebar-logo, sidebar-bottom, breadcrumb


LuSidebar

Navigations-Sidebar mit RBAC-gefilterter Navigation, Collapse-Modus und responsivem Overlay.

<LuSidebar
  :items="[
    {
      label: 'Hauptmenue',
      items: [
        { icon: 'LayoutDashboard', label: 'Dashboard', to: '/' },
        { icon: 'Users', label: 'Mitarbeiter', to: '/employees', roles: ['HR', 'GF'] },
        { icon: 'Clock', label: 'Zeiterfassung', to: '/time-tracking' },
      ],
    },
  ]"
  v-model:open="sidebarOpen"
  v-model:collapsed="sidebarCollapsed"
  :current-user-roles="['HR']"
>
  <template #logo>
    <img src="/logo.svg" alt="Logo" />
  </template>
</LuSidebar>

Props:

| Prop | Typ | Default | Beschreibung | | ------------------ | ------------ | ------- | ---------------------------------------- | | items | NavGroup[] | [] | Navigationsgruppen mit Items | | open | Boolean | false | Sichtbarkeit auf Mobile/Tablet (v-model) | | collapsed | Boolean | false | Icons-Only-Modus auf Desktop (v-model) | | currentUserRoles | String[] | [] | Rollen fuer RBAC-Filterung |

Events: update:open(boolean), update:collapsed(boolean)

Slots: logo, bottom

NavGroup-Format:

{
  label: 'Gruppenname',
  items: [
    { icon: 'LucideIconName', label: 'Link-Text', to: '/route', roles: ['HR'] }
  ]
}

Items mit roles-Array werden nur angezeigt, wenn der Benutzer mindestens eine passende Rolle hat.


LuHeader

Applikations-Header (64px) mit Hamburger-Toggle, Benachrichtigungen und Benutzer-Dropdown.

<LuHeader
  :user="{ name: 'Max Mustermann', roleLabel: 'HR-Manager', avatarSrc: '/avatar.jpg' }"
  :notification-count="5"
  :pending-corrections-count="2"
  :user-menu-items="menuItems"
  @toggle-sidebar="toggleSidebar"
  @notifications-click="openNotifications"
  @corrections-click="openCorrections"
  @user-menu-action="handleAction"
>
  <template #breadcrumb>
    <LuBreadcrumb />
  </template>
</LuHeader>

Props:

| Prop | Typ | Default | Beschreibung | | ------------------------- | ----------------------------------------- | ---------------------- | ------------------------------------- | | user | { name, role?, roleLabel?, avatarSrc? } | { name: 'Benutzer' } | Benutzerinfo fuer Avatar und Dropdown | | notificationCount | Number | 0 | Badge-Zaehler (max "99+") | | pendingCorrectionsCount | Number | 0 | Amber Badge fuer offene Korrekturen | | userMenuItems | { label, icon?, action }[] | [] | Dropdown-Eintraege |

Events: toggle-sidebar, notifications-click, corrections-click, user-menu-action(action)

Slots: breadcrumb


LuBreadcrumb

Breadcrumb-Navigation — liest Items aus Props oder automatisch aus route.meta.breadcrumb.

<LuBreadcrumb
  :items="[
    { label: 'Dashboard', to: '/' },
    { label: 'Mitarbeiter', to: '/employees' },
    { label: 'Max Mustermann' },
  ]"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ------- | ------------------ | ----------- | ----------------------------------------------------------------- | | items | { label, to? }[] | undefined | Breadcrumb-Pfad. Ohne Prop wird route.meta.breadcrumb verwendet |

Das letzte Item wird als aktuelle Seite dargestellt (aria-current="page").


Formulare

LuInput

Vielseitiges Textfeld mit Label, Fehler/Hinweis, Prefix/Suffix und Passwort-Toggle.

<LuInput
  v-model="email"
  type="email"
  label="E-Mail-Adresse"
  placeholder="[email protected]"
  prefix="@"
  required
  :error="errors.email"
  hint="Ihre geschaeftliche E-Mail-Adresse"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ------------- | --------------------------------------------------------- | -------- | ------------------------------------ | | modelValue | String \| Number | '' | Eingabewert (v-model) | | type | 'text' \| 'password' \| 'number' \| 'email' \| 'search' | 'text' | Input-Typ | | label | String | '' | Label-Text | | placeholder | String | '' | Platzhaltertext | | error | String | '' | Fehlermeldung (aktiviert roten Rand) | | hint | String | '' | Hinweistext (versteckt bei Fehler) | | prefix | String | '' | Prefix-Text links im Input | | suffix | String | '' | Suffix-Text rechts im Input | | disabled | Boolean | false | Deaktiviert | | readonly | Boolean | false | Schreibgeschuetzt | | required | Boolean | false | Pflichtfeld (roter Stern) |

Expose: { focus } — Fokus programmatisch setzen.

Zusaetzliche HTML-Attribute (min, max, step, pattern, maxlength) werden an das native <input> weitergeleitet.


LuTextarea

Mehrzeiliges Textfeld mit Label, Fehler/Hinweis und konfigurierbarer Groessenanpassung.

<LuTextarea
  v-model="notes"
  label="Notizen"
  placeholder="Optionale Anmerkungen..."
  :rows="5"
  resize="vertical"
  :error="errors.notes"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ------------- | -------------------------------- | ------------ | -------------------- | | modelValue | String | '' | Textwert (v-model) | | label | String | '' | Label-Text | | placeholder | String | '' | Platzhaltertext | | error | String | '' | Fehlermeldung | | hint | String | '' | Hinweistext | | disabled | Boolean | false | Deaktiviert | | readonly | Boolean | false | Schreibgeschuetzt | | required | Boolean | false | Pflichtfeld | | rows | Number \| String | 3 | Sichtbare Textzeilen | | resize | 'none' \| 'vertical' \| 'both' | 'vertical' | CSS-Resize-Verhalten |


LuSelect

Dropdown-Select mit Suche, Mehrfachauswahl und gruppierten Optionen.

<!-- Einfache Auswahl -->
<LuSelect
  v-model="department"
  label="Abteilung"
  :options="[
    { value: 'hr', label: 'Personal' },
    { value: 'it', label: 'IT' },
    { value: 'sales', label: 'Vertrieb' },
  ]"
  searchable
/>

<!-- Mehrfachauswahl mit Gruppen -->
<LuSelect
  v-model="selectedRoles"
  label="Rollen"
  multiple
  searchable
  :options="[
    {
      group: 'Management',
      items: [
        { value: 'GF', label: 'Geschaeftsfuehrung' },
        { value: 'HR', label: 'Personal' },
      ],
    },
    {
      group: 'Operativ',
      items: [
        { value: 'TEAMLEITUNG', label: 'Teamleitung' },
        { value: 'MITARBEITER', label: 'Mitarbeiter' },
      ],
    },
  ]"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ------------- | --------- | -------------------- | ---------------------------------------------------------------- | | modelValue | any | null | Ausgewaehlter Wert (v-model) | | options | Array | [] | Flach: [{ value, label }] oder gruppiert: [{ group, items }] | | label | String | '' | Label-Text | | error | String | '' | Fehlermeldung | | placeholder | String | 'Bitte waehlen...' | Platzhaltertext | | searchable | Boolean | false | Suchfeld aktivieren | | multiple | Boolean | false | Mehrfachauswahl mit Chips | | disabled | Boolean | false | Deaktiviert |

Multi-Select zeigt ausgewaehlte Werte als Teal-Chips mit Entfernen-Button. Suche ist akzent-unabhaengig (NFD-Normalisierung).


LuDatePicker

Datumseingabe mit Kalender-Overlay, Monatsnavigation und optionalem Range-Highlighting.

<LuDatePicker
  v-model="startDate"
  label="Startdatum"
  placeholder="TT.MM.JJJJ"
  required
  :range-start="rangeStart"
  :range-end="rangeEnd"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ------------- | --------- | -------------- | --------------------------------------- | | modelValue | String | '' | ISO-Datumsstring YYYY-MM-DD (v-model) | | label | String | '' | Label-Text | | placeholder | String | 'TT.MM.JJJJ' | Platzhaltertext | | error | String | '' | Fehlermeldung | | disabled | Boolean | false | Deaktiviert | | required | Boolean | false | Pflichtfeld | | rangeStart | String | '' | ISO-Datum fuer Range-Start-Hervorhebung | | rangeEnd | String | '' | ISO-Datum fuer Range-Ende-Hervorhebung |

Deutsche Lokalisierung (date-fns de). Woche beginnt am Montag. Anzeige als dd.MM.yyyy.


LuCheckbox

Checkbox mit Boolean- und Array-Modus sowie Indeterminate-State.

<!-- Boolean-Modus -->
<LuCheckbox v-model="accepted" label="AGB akzeptieren" />

<!-- Array-Modus (Mehrfachauswahl) -->
<LuCheckbox v-model="selectedDays" value="MO" label="Montag" />
<LuCheckbox v-model="selectedDays" value="DI" label="Dienstag" />

<!-- Indeterminate ("Alle auswaehlen") -->
<LuCheckbox v-model="allSelected" :indeterminate="partiallySelected" label="Alle" />

Props:

| Prop | Typ | Default | Beschreibung | | --------------- | ------------------ | ----------- | --------------------------- | | modelValue | Boolean \| Array | false | Checked-Status (v-model) | | value | String \| Number | undefined | Wert im Array-Modus | | label | String | '' | Label-Text | | disabled | Boolean | false | Deaktiviert | | indeterminate | Boolean | false | Zeigt Strich statt Haekchen |


LuRadio

Radio-Button fuer Einzelauswahl in Gruppen.

<LuRadio v-model="status" value="active" label="Aktiv" name="status" />
<LuRadio v-model="status" value="inactive" label="Inaktiv" name="status" />

Props:

| Prop | Typ | Default | Beschreibung | | ------------ | ----------------------------- | ------- | --------------------------------------- | | modelValue | String \| Number \| Boolean | '' | Ausgewaehlter Wert der Gruppe (v-model) | | value | String \| Number \| Boolean | — | Wert dieser Radio-Option | | label | String | '' | Label-Text | | name | String | '' | Name-Attribut fuer Gruppierung | | disabled | Boolean | false | Deaktiviert |


LuToggle

Toggle-Switch mit Slide-Animation.

<LuToggle v-model="darkMode" label="Dunkelmodus" />

Props:

| Prop | Typ | Default | Beschreibung | | ------------ | --------- | ------- | ----------------------- | | modelValue | Boolean | false | Toggle-Status (v-model) | | label | String | '' | Label-Text | | disabled | Boolean | false | Deaktiviert |


LuFormGroup

Container fuer konsistentes Label-, Fehler- und Hinweis-Layout um Formularfelder.

<LuFormGroup label="Abteilung" error="Bitte Abteilung waehlen" required>
  <LuSelect v-model="department" :options="departments" />
</LuFormGroup>

Props:

| Prop | Typ | Default | Beschreibung | | ---------- | --------- | ------- | ------------------------------------- | | label | String | '' | Label-Text (uppercase, tracking-wide) | | error | String | '' | Fehlermeldung mit AlertCircle-Icon | | hint | String | '' | Hinweistext (versteckt bei Fehler) | | for | String | '' | HTML for-Attribut | | required | Boolean | false | Roter Stern am Label |

Slots: default — Formularfeld(er)


Datendarstellung

LuTable

Datentabelle mit Sortierung, Zebra-Striping, Loading-Skeleton und Scoped Slots.

<LuTable
  :columns="[
    { key: 'name', label: 'Name', sortable: true },
    { key: 'department', label: 'Abteilung', sortable: true, hideOnMobile: true },
    { key: 'status', label: 'Status' },
    { key: 'actions', label: '', width: '80px' },
  ]"
  :data="employees"
  :loading="isLoading"
  :sort-field="sortField"
  :sort-dir="sortDir"
  @sort="handleSort"
  @row-click="openEmployee"
>
  <template #cell-status="{ value }">
    <LuBadge :variant="value === 'active' ? 'success' : 'neutral'">
      {{ value === 'active' ? 'Aktiv' : 'Inaktiv' }}
    </LuBadge>
  </template>

  <template #cell-actions="{ row }">
    <LuButton variant="ghost" size="sm" @click.stop="editEmployee(row)">
      <template #icon-left><LuIcon name="Pencil" :size="16" /></template>
    </LuButton>
  </template>

  <template #empty>Keine Mitarbeiter gefunden.</template>
</LuTable>

Props:

| Prop | Typ | Default | Beschreibung | | ----------- | ------------------ | ------- | -------------------------------------------------------- | | columns | Column[] | — | Spaltendefinitionen | | data | Object[] | [] | Zeilendaten | | loading | Boolean | false | Zeigt 3 Skeleton-Zeilen | | sortField | String | '' | Aktives Sortierfeld | | sortDir | 'asc' \| 'desc' | 'asc' | Sortierrichtung | | rowKey | String | 'id' | Property fuer Zeilen-Key (Fallback: _id, dann Index) | | rowClass | Function \| null | null | (row, index) => string fuer individuelle Zeilenklassen |

Column-Definition:

{ key: 'name', label: 'Name', sortable: true, hideOnMobile: true, width: '200px' }

Events: sort(field), row-click(row, index)

Slots: cell-{key}({ row, value }), header-{key}({ column }), empty, row({ row, index })


LuPagination

Seitennavigation mit Seiten-Links, Vor/Zurueck und Items-pro-Seite-Auswahl.

<LuPagination
  :current-page="page"
  :total-pages="totalPages"
  :total-items="487"
  :items-per-page="25"
  @change="page = $event"
  @change-per-page="perPage = $event"
/>

Props:

| Prop | Typ | Default | Beschreibung | | -------------- | -------- | ------- | -------------------------- | | currentPage | Number | — | Aktuelle Seite (1-basiert) | | totalPages | Number | — | Gesamtseitenanzahl | | totalItems | Number | 0 | Gesamtanzahl Eintraege | | itemsPerPage | Number | 10 | Eintraege pro Seite |

Events: change(page), change-per-page(perPage)

Zeigt Items-pro-Seite-Selektor (10/25/50), intelligente Seitenwindows mit Ellipsis, und Eintrags-Bereich (z.B. "1-25 von 487 Eintraegen").


Feedback & Overlay

LuModal

Modaler Dialog mit Backdrop, ESC-Schliessen und Bottom-Sheet auf Mobile.

<LuModal v-model="showModal" title="Mitarbeiter bearbeiten" size="lg" persistent>
  <form @submit.prevent="save">
    <LuInput v-model="name" label="Name" />
  </form>

  <template #footer>
    <LuButton variant="secondary" @click="showModal = false">Abbrechen</LuButton>
    <LuButton @click="save" :loading="saving">Speichern</LuButton>
  </template>
</LuModal>

Props:

| Prop | Typ | Default | Beschreibung | | ------------ | ------------------------------ | ------- | ------------------------------------------ | | modelValue | Boolean | — | Sichtbarkeit (v-model) | | title | String | '' | Titel in der Kopfzeile | | size | 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | sm=400px, md=560px, lg=768px, xl=1024px | | persistent | Boolean | false | Verhindert Schliessen durch Backdrop-Klick |

Slots: default (Body, scrollbar), footer (Aktionen)

Features: Focus-Trap (Tab/Shift+Tab), Body-Scroll-Lock, Bottom-Sheet unter 768px, prefers-reduced-motion Support.

Empfehlung: useModal() Composable fuer einfache Zustandsverwaltung.


LuAlert

Inline-Alert fuer kontextbezogene Rueckmeldungen.

<LuAlert
  variant="warning"
  title="Achtung"
  message="Ihre Sitzung laeuft in 5 Minuten ab."
  dismissible
  @dismiss="hideWarning"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ------------- | --------------------------------------------- | -------- | -------------------------- | | variant | 'info' \| 'warning' \| 'error' \| 'success' | 'info' | Semantische Variante | | title | String | '' | Optionale fette Titelzeile | | message | String | '' | Nachrichtentext | | dismissible | Boolean | false | Zeigt Schliessen-Button |

Events: dismiss

Icons pro Variante: success=CircleCheck, warning=TriangleAlert, error=XCircle, info=Info.


LuToast

Toast-Benachrichtigungs-Container. Einmal im App-Root einbinden, Steuerung via useToast().

<!-- In App.vue (einmalig einbinden) -->
<template>
  <LuAppLayout>
    <RouterView />
  </LuAppLayout>
  <LuToast />
</template>
// In beliebiger Komponente
import { useToast } from '@vss-software/ui'

const { addToast } = useToast()

addToast({
  variant: 'success',
  title: 'Gespeichert',
  message: 'Mitarbeiterdaten wurden aktualisiert.',
  duration: 5000,
})

Teleportiert zu <body>. Fixiert unten rechts. Auto-Dismiss nach duration (Standard: 5000ms, 0 = kein Auto-Dismiss).


LuDropdown

Dropdown-Menue mit selektierbaren Optionen, Trennlinien und optionalen Beschreibungen.

<LuDropdown
  label="Exportieren"
  :options="[
    { value: 'csv', label: 'CSV-Export', description: 'Komma-getrennte Werte' },
    { value: 'xlsx', label: 'Excel-Export' },
    { divider: true },
    { value: 'datev', label: 'DATEV-Export', icon: 'FileSpreadsheet' },
  ]"
  @select="handleExport"
/>

Props:

| Prop | Typ | Default | Beschreibung | | --------- | ------------------------------------- | ------------- | ------------------ | | label | String | — | Button-Label | | options | DropdownOption[] | [] | Menue-Optionen | | icon | String \| Object | null | Icon vor dem Label | | variant | 'primary' \| 'secondary' \| 'ghost' | 'secondary' | Button-Variante | | size | 'sm' \| 'md' \| 'lg' | 'sm' | Button-Groesse |

DropdownOption:

{ value: 'id', label: 'Text', description?: 'Zusatzinfo', disabled?: false, icon?: 'LucideName', divider?: false }

Events: select(value)


Allgemeine UI

LuButton

Vielseitiger Button fuer primaere, sekundaere, Danger-, Ghost- und Icon-Aktionen.

<LuButton @click="save" :loading="saving">
  <template #icon-left><LuIcon name="Save" :size="16" /></template>
  Speichern
</LuButton>

<LuButton variant="danger" size="sm">Loeschen</LuButton>

<LuButton variant="ghost">Abbrechen</LuButton>

<LuButton variant="icon" aria-label="Bearbeiten">
  <LuIcon name="Pencil" :size="18" />
</LuButton>

Props:

| Prop | Typ | Default | Beschreibung | | ------------- | ----------------------------------------------------------- | ------------------- | ------------------------------------------- | | variant | 'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'icon' | 'primary' | Visuelle Variante | | size | 'sm' \| 'md' \| 'lg' | 'md' | sm=32px, md=40px, lg=48px Hoehe | | disabled | Boolean | false | Deaktiviert | | loading | Boolean | false | Zeigt Lade-Spinner, deaktiviert Interaktion | | type | 'button' \| 'submit' \| 'reset' | 'button' | HTML button type | | loadingText | String | 'Wird geladen...' | Screenreader-Ansage bei Loading |

Slots: default (Label), icon-left, icon-right


LuCard

Flexible Karte mit Standard- und KPI-Variante.

<!-- Standard -->
<LuCard>
  <template #header>
    <h2>Mitarbeiterliste</h2>
  </template>
  <LuTable :columns="cols" :data="rows" />
  <template #footer>
    <LuPagination :current-page="1" :total-pages="5" />
  </template>
</LuCard>

<!-- KPI -->
<LuCard variant="kpi" value="42" label="Offene Antraege" trend="+12%" trend-direction="up" />

Props:

| Prop | Typ | Default | Beschreibung | | ---------------- | ----------------------------- | ------------ | ---------------------------------------- | | variant | 'standard' \| 'kpi' | 'standard' | Darstellungsvariante | | value | String \| Number | undefined | KPI-Wert (nur kpi-Variante) | | label | String | '' | KPI-Label (nur kpi-Variante) | | trend | String | '' | Trend-Text z.B. "+5%" (nur kpi-Variante) | | trendDirection | 'up' \| 'down' \| 'neutral' | 'neutral' | Trend-Pfeil und Farbe |

Slots (Standard): default (Body), header, footer


LuKpiCard

Kompakte KPI-Karte fuer Dashboards mit Status-Farbgebung und Trend-Indikator.

<LuKpiCard
  label="Krankenquote"
  value="4.2%"
  status="warning"
  :trend="-0.3"
  subtext="vs. Vormonat"
/>

Props:

| Prop | Typ | Default | Beschreibung | | --------- | ------------------------------------------------ | ----------- | -------------------------------------------------------- | | label | String | — | Label (uppercase, xs) | | value | String \| Number | — | KPI-Wert (gross dargestellt) | | status | 'success' \| 'warning' \| 'error' \| 'default' | 'default' | Wert-Textfarbe | | trend | Number | undefined | Trend-Prozent (positiv=TrendingUp, negativ=TrendingDown) | | subtext | String | undefined | Zusatztext unter dem Wert |


LuBadge

Pill-foermiges Badge fuer Status- und Kategorie-Anzeigen.

<LuBadge variant="success">Aktiv</LuBadge>
<LuBadge variant="warning" size="sm">Ausstehend</LuBadge>
<LuBadge variant="error">Abgelehnt</LuBadge>

Props:

| Prop | Typ | Default | Beschreibung | | --------- | ---------------------------------------------------------- | ----------- | ------------ | | variant | 'success' \| 'warning' \| 'error' \| 'info' \| 'neutral' | 'neutral' | Farbvariante | | size | 'sm' \| 'md' | 'md' | Groesse |

Slots: default (Label-Text)


LuAvatar

Avatar mit Initialen-Fallback und optionalem Bild. Farbe wird deterministisch aus dem Namen berechnet.

<LuAvatar name="Max Mustermann" size="lg" />
<LuAvatar name="Anna Schmidt" src="/avatars/anna.jpg" size="md" />

Props:

| Prop | Typ | Default | Beschreibung | | ------ | ------------------------------ | ------- | ------------------------------------------------------- | | name | String | — | Vollstaendiger Name (fuer Initialen und Farbberechnung) | | src | String | '' | Optionale Bild-URL | | size | 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | sm=32px, md=40px, lg=48px, xl=64px |

Bild wird vorab geladen. Bei Fehler werden 2-Buchstaben-Initialen auf farbigem Hintergrund angezeigt (6 deterministische Farben).


LuIcon

Dynamischer Lucide-Icon-Wrapper mit Lazy-Loading und Caching.

<LuIcon name="Clock" :size="24" color="#00BFA6" />
<LuIcon name="Users" />
<LuIcon name="AlertTriangle" :stroke-width="2" />

Props:

| Prop | Typ | Default | Beschreibung | | ------------- | ------------------ | ---------------- | ------------------------------ | | name | String | — | Lucide-Icon-Name in PascalCase | | size | Number \| String | 20 | Groesse in Pixeln | | color | String | 'currentColor' | CSS-Farbwert | | strokeWidth | Number \| String | 1.5 | SVG Strichstaerke |

Icons werden on-demand geladen und im Cache gehalten. Vollstaendige Icon-Liste: lucide.dev/icons


LuSkeleton

Lade-Platzhalter mit Shimmer-Animation.

<LuSkeleton width="200px" height="20px" />
<LuSkeleton width="48px" height="48px" rounded />
<LuSkeleton height="120px" />

Props:

| Prop | Typ | Default | Beschreibung | | --------- | --------- | -------- | -------------------------- | | width | String | '100%' | CSS-Breite | | height | String | '16px' | CSS-Hoehe | | rounded | Boolean | false | Komplett rund (Kreis/Pill) |

Respektiert prefers-reduced-motion.


LuEmptyState

Zentrierter Platzhalter fuer leere Ansichten.

<LuEmptyState
  icon="Users"
  title="Keine Mitarbeiter gefunden"
  description="Passen Sie Ihre Filterkriterien an oder legen Sie einen neuen Mitarbeiter an."
  action-label="Mitarbeiter anlegen"
  :action-handler="createEmployee"
/>

Props:

| Prop | Typ | Default | Beschreibung | | --------------- | ---------- | ------- | -------------------- | | icon | String | '' | Lucide-Icon-Name | | title | String | — | Ueberschrift | | description | String | '' | Beschreibungstext | | actionLabel | String | '' | Button-Text | | actionHandler | Function | null | Button-Click-Handler |

Button nur sichtbar wenn actionLabel und actionHandler gesetzt sind.


LuActionBar

Fixierte Aktionsleiste am unteren Bildschirmrand fuer Bulk-Operationen.

<LuActionBar
  :selected-count="selectedIds.length"
  :loading="processing"
  :show-delete="canDelete"
  :show-export="true"
  :show-status="true"
  :show-department="false"
  @cancel="clearSelection"
  @action-delete="deleteSelected"
  @action-export="exportSelected"
  @action-status="changeStatus"
>
  <template #actions>
    <LuButton variant="secondary" size="sm" @click="customAction">
      Eigene Aktion
    </LuButton>
  </template>
</LuActionBar>

Props:

| Prop | Typ | Default | Beschreibung | | ---------------- | --------- | ------- | ----------------------------- | | selectedCount | Number | — | Anzahl ausgewaehlter Elemente | | loading | Boolean | false | Ladezustand | | showDelete | Boolean | true | Loeschen-Button anzeigen | | showExport | Boolean | true | Export-Button anzeigen | | showStatus | Boolean | true | Status-Button anzeigen | | showDepartment | Boolean | true | Abteilungs-Button anzeigen |

Events: cancel, action-status, action-department, action-export, action-delete

Slots: actions — zusaetzliche eigene Aktionsbuttons

Wird nur angezeigt wenn selectedCount > 0. Slide-Up-Animation. Dunkler Hintergrund (gray-900).


Charts

Alle Chart-Komponenten nutzen Chart.js via vue-chartjs und wenden automatisch die lumen.hr Design-Defaults an.

LuLineChart

<LuLineChart
  :data="{
    labels: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun'],
    datasets: [{ label: 'Ueberstunden', data: [12, 19, 3, 5, 2, 3] }],
  }"
  height="300px"
  aria-label="Ueberstunden pro Monat"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ----------- | ---------------------- | ------------------ | ----------------------------------- | | data | ChartData<'line'> | — | Chart.js Daten | | options | ChartOptions<'line'> | {} | Optionen (deep-merged mit Defaults) | | height | String | '100%' | CSS-Hoehe | | ariaLabel | String | 'Liniendiagramm' | Barrierefreie Beschriftung |

Defaults: Teal-500 Linie, 10% Teal-Fuellung, 3px Punkte, 0.3 Kurvenspannung.


LuBarChart

<LuBarChart
  :data="{
    labels: ['Mo', 'Di', 'Mi', 'Do', 'Fr'],
    datasets: [{ label: 'Anwesenheit', data: [45, 42, 48, 43, 40] }],
  }"
  height="250px"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ----------- | --------------------- | ------------------ | ----------------------------------- | | data | ChartData<'bar'> | — | Chart.js Daten | | options | ChartOptions<'bar'> | {} | Optionen (deep-merged mit Defaults) | | height | String | '100%' | CSS-Hoehe | | ariaLabel | String | 'Balkendiagramm' | Barrierefreie Beschriftung |

Defaults: Teal-500 Hintergrund, 4px abgerundete Ecken.


LuDonutChart

<LuDonutChart
  :data="{
    labels: ['Anwesend', 'Urlaub', 'Krank', 'Abwesend'],
    datasets: [{ data: [35, 5, 3, 2] }],
  }"
  height="300px"
/>

Props:

| Prop | Typ | Default | Beschreibung | | ----------- | -------------------------- | ----------------- | ----------------------------------- | | data | ChartData<'doughnut'> | — | Chart.js Daten | | options | ChartOptions<'doughnut'> | {} | Optionen (deep-merged mit Defaults) | | height | String | '300px' | CSS-Hoehe | | ariaLabel | String | 'Kreisdiagramm' | Barrierefreie Beschriftung |

Defaults: 65% Cutout (Donut), 10-Farben-Palette im "Professional Calm"-Stil, Legende unten. Tooltip zeigt Label, Wert und berechneten Prozentsatz.


Tabs (Compound Pattern)

Barrierefreies Tab-System nach WAI-ARIA Tabs Pattern mit Tastaturnavigation (Pfeiltasten, Home, End).

<LuTabs v-model="activeTab">
  <LuTabList>
    <LuTab value="overview">Uebersicht</LuTab>
    <LuTab value="details">Details</LuTab>
    <LuTab value="history">Verlauf</LuTab>
  </LuTabList>

  <LuTabPanel value="overview">
    <p>Uebersichts-Inhalt...</p>
  </LuTabPanel>
  <LuTabPanel value="details">
    <p>Detail-Inhalt...</p>
  </LuTabPanel>
  <LuTabPanel value="history">
    <p>Verlaufs-Inhalt...</p>
  </LuTabPanel>
</LuTabs>

LuTabs

Root-Container. Stellt aktiven Tab-Status via provide/inject bereit.

| Prop | Typ | Default | Beschreibung | | ------------ | -------- | ------- | -------------------------- | | modelValue | String | '' | Aktiver Tab-Wert (v-model) |

LuTabList

Horizontaler Container fuer Tab-Buttons mit WAI-ARIA Tastaturnavigation.

LuTab

Einzelner Tab-Button. role="tab", aria-selected, aria-controls.

| Prop | Typ | Default | Beschreibung | | ------- | -------- | ------- | --------------------------------------------------- | | value | String | — | Eindeutige ID (muss mit LuTabPanel uebereinstimmen) |

Slots: default (Tab-Label)

LuTabPanel

Content-Panel. Wird nur gerendert wenn der zugehoerige Tab aktiv ist (v-if).

| Prop | Typ | Default | Beschreibung | | ------- | -------- | ------- | ----------------------------------------------------------- | | value | String | — | Muss mit dem value des zugehoerigen LuTab uebereinstimmen |

Slots: default (Panel-Inhalt)


Composables

useToast()

Globaler Toast-Zustand. Wird von <LuToast> fuer die Darstellung genutzt.

import { useToast } from '@vss-software/ui'

const { toasts, addToast, removeToast } = useToast()

// Toast hinzufuegen
const id = addToast({
  variant: 'success', // 'success' | 'warning' | 'error' | 'info'
  title: 'Gespeichert', // Optional
  message: 'Aenderungen uebernommen.',
  duration: 5000, // ms, 0 = kein Auto-Dismiss
})

// Toast manuell entfernen
removeToast(id)

| Return | Typ | Beschreibung | | ------------- | --------------------- | ---------------------------------- | | toasts | Toast[] (reactive) | Alle aktiven Toasts | | addToast | (options) => number | Toast hinzufuegen, gibt ID zurueck | | removeToast | (id) => void | Toast entfernen |


useModal()

Einfache reaktive Zustandsverwaltung fuer Modals.

import { useModal } from '@vss-software/ui'

const { isOpen, open, close, toggle } = useModal()
<LuButton @click="open">Dialog oeffnen</LuButton>
<LuModal v-model="isOpen" title="Mein Dialog">
  Inhalt...
</LuModal>

| Return | Typ | Beschreibung | | -------- | -------------- | ----------------------------- | | isOpen | Ref<boolean> | Reaktiver Sichtbarkeitsstatus | | open | () => void | Oeffnen | | close | () => void | Schliessen | | toggle | () => void | Umschalten |


useClickOutside()

Erkennt Klicks ausserhalb eines Elements.

import { useClickOutside } from '@vss-software/ui'
import { ref } from 'vue'

const dropdownRef = ref(null)
useClickOutside(dropdownRef, () => {
  isOpen.value = false
})

| Parameter | Typ | Beschreibung | | ----------- | -------------------------- | ------------------------------------ | | targetRef | Ref<HTMLElement \| null> | Ref zum Begrenzungselement | | callback | () => void | Wird bei Klick ausserhalb aufgerufen |

Registriert mousedown und touchstart Listener. Bereinigt automatisch bei Unmount.


Utilities

Chart Defaults

import {
  baseChartOptions,
  lineDatasetDefaults,
  barDatasetDefaults,
  deepMerge,
} from '@vss-software/ui'

| Export | Beschreibung | | --------------------------- | ---------------------------------------------------------------------------------- | | baseChartOptions | Gemeinsame Chart.js Optionen (responsive, Tooltip-Stil, Achsen-Formatierung) | | lineDatasetDefaults | Linien-Defaults: Teal-500, 10% Fuellung, 3px Punkte, 0.3 Spannung | | barDatasetDefaults | Balken-Defaults: Teal-500, 4px Border-Radius | | deepMerge(target, source) | Deep-Merge zweier Objekte. Arrays werden ersetzt. Schutz gegen Prototype-Pollution |


Barrierefreiheit (a11y)

Alle Komponenten folgen WCAG 2.1 AA Richtlinien:

  • ARIA-Rollen und -Attribute auf allen interaktiven Elementen
  • Tastaturnavigation (Tab, Enter, Space, Escape, Pfeiltasten)
  • Focus-Management mit sichtbarem Fokusring (Teal-500, 2px)
  • Focus-Trap in Modals
  • Screen-Reader-Unterstuetzung via aria-label, aria-live, role
  • Reduzierte Bewegung (prefers-reduced-motion) wird respektiert
  • Farbkontraste eingehalten (mindestens 4.5:1)

Lizenz

Proprietaer. Alle Rechte vorbehalten.