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

yukit-web

v0.3.2

Published

Angular UI component library for Yurest web applications.

Readme

yukit-web

Angular UI component library for Yurest web applications. Built on Spartan (Tailwind CSS v4 + @spartan-ng/brain) and themed with the Yurest design tokens.

Table of contents

Requirements

| Dependency | Version | Notes | | :---------------------------------------------------------------- | :------ | :----------------------------- | | Angular | 21+ | Standalone APIs, signal inputs | | Tailwind CSS | v4 | Required — see Setup | | @spartan-ng/brain | ^1.3.0 | Headless primitives | | @angular/cdk | ^21.0.0 | Overlays, a11y | | @ng-icons/core + lucide | ^32.2.0 | Icons | | @tanstack/angular-table | ^9.0.0 | <yuw-datatable> | | embla-carousel-angular | ^21.0.0 | Carousel primitives | | ngx-scrollbar | ^19.1.5 | Scroll areas |

Installation

npm install yukit-web \
  @spartan-ng/brain @angular/cdk @tanstack/angular-table \
  @ng-icons/core @ng-icons/lucide embla-carousel-angular ngx-scrollbar

Plus the Tailwind toolchain, as dev dependencies:

npm install -D tailwindcss @tailwindcss/postcss tw-animate-css

tw-animate-css is required because @spartan-ng/brain/hlm-tailwind-preset.css imports it.

Setup

yukit-web does not ship compiled component CSS. Its templates carry Tailwind utility classes, and your Tailwind build is what turns them into real styles. Two things have to be true in your app, or the components render unstyled.

1. Set up styles.css

@layer theme, base, components, utilities;

@import 'tailwindcss';
@import '@spartan-ng/brain/hlm-tailwind-preset.css';
@import 'yukit-web/theme.css';

@source "../node_modules/yukit-web";

Order matters:

| Line | Why | | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | | @layer … | Fixes layer precedence before anything registers into a layer. | | @import "tailwindcss" | Must come first — everything below extends it. | | Spartan preset | Maps token names (--primary, --radius, …) onto Tailwind utilities and registers Spartan's variants (data-open, data-checked, …). | | yukit-web/theme.css | Supplies the Yurest token values. Without the preset above it, the custom properties exist but no utilities are generated. | | @source | Required. Tailwind only generates the classes it can see. yukit-web's classes live inside the published package, which Tailwind does not scan by default. |

Adjust the @source path so it resolves from the file it is written in — the example assumes src/styles.css with node_modules one level up.

If you skip @source, the components render with no styling at all. That is the expected failure mode, not a bug: nothing is missing from the package, Tailwind simply never saw it. node_modules being gitignored does not prevent the scan — an explicit @source overrides that.

2. Enable the Tailwind PostCSS plugin

.postcssrc.json at the project root:

{
  "plugins": {
    "@tailwindcss/postcss": {}
  }
}

3. Icons

Components register the icons they need themselves — nothing to configure for <yuw-badge>, <yuw-dialog>, <yuw-pagination> and friends to render their own glyphs.

You only need provideIcons for icons you project into a component ([yuwAlertIcon], [yuwItemMedia], a button's content, <yuw-spinner icon="…">) — or name in data you hand to a component, such as the icon field of a <yuw-sidebar [content]> entry:

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideMail, lucidePlus } from '@ng-icons/lucide';

@Component({
  imports: [NgIcon, YuwButtonComponent],
  providers: [provideIcons({ lucideMail, lucidePlus })],
  template: `
    <yuw-button>
      <ng-icon name="lucidePlus" />
      New
    </yuw-button>
  `,
})
export class ExampleComponent {}

Quick start

Every export is standalone — import the component classes you use directly.

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { YuwButtonComponent, YuwInputComponent, yuwToast, YuwToasterComponent } from 'yukit-web';

@Component({
  selector: 'app-login',
  imports: [YuwInputComponent, YuwButtonComponent, YuwToasterComponent],
  template: `
    <yuw-input [(value)]="email" type="email" placeholder="[email protected]" />
    <yuw-button color="primary" (clicked)="submit()">Sign in</yuw-button>

    <yuw-toaster />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LoginComponent {
  protected readonly email = signal('');

  protected submit(): void {
    yuwToast.success('Welcome back');
  }
}

Theming

yukit-web/theme.css is the only stylesheet the package exposes. It defines the Yurest palette in oklch under the token names Spartan expects, so bg-primary, text-muted-foreground, border-border and rounded-lg resolve to Yurest's design system instead of Spartan's default neutral gray.

Extra semantic tokens

It also adds Yurest-only semantic colors that Spartan has no equivalent for, registered so they behave like any other Tailwind color:

| Token | Utilities | | :----------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | | --success / --success-foreground | bg-success, text-success-foreground, … | | --warning / --warning-foreground | bg-warning, text-warning-foreground, … | | --info / --info-foreground | bg-info, text-info-foreground, … | | --destructive-foreground | text-destructive-foreground — Spartan defines only --destructive and hardcodes text-white; Yurest names a contrast color, so the pair is completed. |

To override a token, redefine it in your own :root after importing the theme.

The color input

Components that carry brand color (<yuw-button>, <yuw-badge>, <yuw-pagination>) accept a color input typed as YuwColorDto:

type YuwColorDto =
  | 'primary'
  | 'secondary'
  | 'neutral'
  | 'destructive'
  | 'success'
  | 'warning'
  | 'info'
  | 'muted'
  | 'violet'
  | 'orange'
  | 'pink'
  | 'teal';

It sets data-yuw-color on the host, and the theme remaps the Spartan semantic variables (--primary, --secondary, --border, …) for that subtree — so the whole component recolors consistently instead of one class being patched.

<yuw-button color="success" variant="outline">Approve</yuw-button>
<yuw-badge color="warning" dot />

Dark mode

Dark mode is supported via a .dark class on a root element, matching Tailwind v4's default dark variant. The consuming app toggles it:

document.documentElement.classList.toggle('dark');

Note that <yuw-toaster> defaults to theme="light" — pass theme="dark" or theme="system" explicitly in apps that render dark.

Reveal on hover

Repeated secondary controls (a row's edit button, an item's "…" menu) can stay hidden until the user points at their container. Two plain CSS classes, shipped in theme.css, do it with a fade:

<li class="yuw-reveal-host">
  Item name
  <yuw-button class="yuw-reveal" variant="ghost" size="icon" ariaLabel="Edit">…</yuw-button>
</li>
  • yuw-reveal-host goes on the hover area, yuw-reveal on the element that fades.
  • It shows on hover, on keyboard focus (:focus-visible, so a mouse click does not pin it open), and on an ancestor carrying data-highlighted.
  • Devices that cannot hover (phones, tablets) never hide the element, so the control stays reachable by touch.
  • Respects prefers-reduced-motion.

yuw-datatable rows already carry yuw-reveal-host, and the actions column applies yuw-reveal.

Conventions

A few rules hold across the whole library. Knowing them removes most of the per-component surprises.

Text is always yours. No component hardcodes a translatable string. Anything a user reads — labels, empty-state messages, accessible names for icon-only controls — is an input, often a required one (ariaLabel, previousLabel, emptyText, label). Pass it already translated.

Presence implies the feature. Instead of a removable flag plus a removeLabel string, there is one input: set removeLabel and the remove control appears, omit it and there is nothing to remove. The same shape applies to clearAriaLabel (search input, combobox), closeLabel (dialog, sheet) and revealAriaLabel (password input).

Two-way state uses model(). [(value)], [(open)], [(checked)], [(page)], [(activeTab)] are all two-way bindable, and each also emits a …Change output you can listen to one-way.

Class passthrough. Components whose host is display: contents accept your classes on a dedicated input so they actually reach the rendered element — class on most components, panelClass on overlays, tableClass on the datatable. They are merged last through tailwind-merge, so your class always beats the component default without !important.

Forms. <yuw-input>, <yuw-textarea>, <yuw-search-input>, <yuw-password-input>, <yuw-input-otp>, <yuw-checkbox>, <yuw-switch>, <yuw-radio-group>, <yuw-combobox> and <yuw-datepicker> implement ControlValueAccessor:

<form [formGroup]="form">
  <label yuwLabel for="email">Email</label>
  <yuw-input inputId="email" formControlName="email" type="email" [invalid]="emailInvalid()" />

  <yuw-checkbox formControlName="terms">Accept terms</yuw-checkbox>
</form>

disabled from the forms API (control.disable()) and the disabled input are OR'd together, so neither source silently overrides the other.

Components

| Component | Selector | Import | | :-------------------------------- | :------------------------ | :----------------------------------------- | | Actions | | | | Button | yuw-button | YuwButtonComponent | | Form controls | | | | Input | yuw-input | YuwInputComponent | | Input file | yuw-input-file | YuwInputFileComponent | | Textarea | yuw-textarea | YuwTextareaComponent | | Search input | yuw-search-input | YuwSearchInputComponent | | Password input | yuw-password-input | YuwPasswordInputComponent | | Input OTP | yuw-input-otp | YuwInputOtpComponent | | Label | label[yuwLabel] | YuwLabel | | Checkbox | yuw-checkbox | YuwCheckboxComponent | | Switch | yuw-switch | YuwSwitchComponent | | Radio group | yuw-radio-group | YuwRadioGroupComponent | | Combobox | yuw-combobox | YuwComboboxComponent | | Datepicker | yuw-datepicker | YuwDatepickerComponent | | Data display | | | | Avatar | yuw-avatar | YuwAvatarComponent | | Badge | yuw-badge | YuwBadgeComponent | | Card | [yuwCard] | YuwCardImports | | Item | [yuwItem] | YuwItemImports | | Attachment | [yuwAttachment] | YuwAttachmentDirective, … | | Table | [yuwTable] | YuwTableImports | | Datatable | yuw-datatable | YuwDatatableComponent | | Empty | [yuwEmpty] | YuwEmptyImports | | Skeleton | [yuwSkeleton] | YuwSkeletonDirective | | Spinner | yuw-spinner | YuwSpinnerComponent | | Navigation | | | | Accordion | yuw-accordion | YuwAccordionImports | | Breadcrumb | yuw-breadcrumb | YuwBreadcrumbComponent | | Pagination | yuw-pagination | YuwPaginationComponent | | Tabs | yuw-tabs | YuwTabsComponent, YuwTabPanelDirective | | Sidebar | yuw-sidebar | YuwSidebarComponent, … | | Overlays | | | | Dialog | yuw-dialog | YuwDialogImports | | Alert dialog | yuw-alert-dialog | YuwAlertDialogComponent | | Sheet | yuw-sheet | YuwSheetImports | | Popover | yuw-popover | YuwPopoverComponent | | Tooltip | [yuwTooltip] | YuwTooltipDirective | | Context menu | [yuwContextMenuTrigger] | YuwContextMenuImports | | Feedback | | | | Alert | yuw-alert | YuwAlertImports | | Toaster | yuw-toaster | YuwToasterComponent, yuwToast |


Button

Single action button, link, or a welded group of buttons.

<yuw-button variant="outline" color="primary" (clicked)="save()">Save</yuw-button>

<yuw-button href="/dashboard" target="_blank">Dashboard</yuw-button>

<yuw-button size="icon" ariaLabel="Add item">
  <ng-icon name="lucidePlus" />
</yuw-button>

<!-- Button group -->
<yuw-button [buttons]="actions" groupAriaLabel="Alignment" (buttonClicked)="onAction($event)" />

Inputs

| Input | Type | Default | | :----------------------- | :--------------------------------------------------------------------- | :------------- | | variant | 'default'|'secondary'|'outline'|'destructive'|'ghost'|'link' | 'default' | | size | 'default'|'xs'|'sm'|'lg'|'icon'|'icon-xs'|'icon-sm'|'icon-lg' | 'default' | | color | YuwColorDto | 'neutral' | | disabled | boolean | false | | type | 'button'|'submit'|'reset' | 'button' | | href | string|undefined | undefined | | target | string|undefined | undefined | | transparent | boolean — removes background and border | false | | ariaLabel | string|undefined — required for icon-only buttons | undefined | | class | string — merged onto the inner button/anchor, wins over defaults | '' | | buttons | YuwButtonGroupItemDto[]|undefined — switches to group mode | undefined | | orientation | 'horizontal'|'vertical' — group layout | 'horizontal' | | groupAriaLabel | string|undefined — accessible name of the group | undefined | | yuwDropdownMenuTrigger | TemplateRef<unknown>|undefined | undefined |

Outputs

| Output | Payload | Description | | :-------------- | :----------------------- | :---------------------------- | | clicked | MouseEvent | Single-button mode. | | buttonClicked | YuwButtonGroupClickDto | Group mode — { id, event }. |

Content slots

| Slot | Description | | :---------- | :------------ | | (default) | Button label. |

YuwButtonGroupItemDto entries are { id, label } plus optional variant, size, color, disabled, href, target, type, ariaLabel, transparent — each falling back to the component-level input of the same name.


Input

Single-line text field with addon slots and form integration.

<yuw-input [(value)]="username" placeholder="Enter username…" />

<yuw-input formControlName="email" type="email">
  <yuw-left-addon><ng-icon name="lucideMail" /></yuw-left-addon>
</yuw-input>

Inputs

| Input | Type | Default | | :------------- | :----------------------------------------------- | :---------- | | value | string — two-way | '' | | inputId | string|undefined — for external <label for> | undefined | | type | 'text'|'email'|'tel'|'url'|'number' | 'text' | | inputmode | YuwInputModeDto|undefined | undefined | | placeholder | string|undefined | undefined | | maxlength | number|undefined | undefined | | name | string|undefined | undefined | | autocomplete | string|undefined | undefined | | disabled | boolean | false | | readonly | boolean | false | | required | boolean | false | | invalid | boolean | false | | class | string | '' |

Outputs

| Output | Payload | Description | | :---------- | :------- | :------------------------------------------------- | | committed | string | Current value on the native change (blur/enter). |

Content slots

yuw-left-addon, yuw-right-addon — import YuwInputAddonImports.


Input file

File picker with two layouts. avatar (default) shows a yuw-avatar with the chosen image (or src) beside an upload button. inline is a full-width outline button with an upload icon; a chosen file replaces the button with one line showing its name, size and a remove icon, and several files collapse into one line (countLabel(n)) that opens a list with one removable row each — a popover from md up, a bottom sheet below. Removing never edits the value: the icon emits removed with the File and the consumer decides. The value is a File[]; writing [] or null clears the picker. All text comes from the consumer.

<yuw-input-file
  [(value)]="files"
  accept="image/*"
  [src]="imageUrl"
  [alt]="name"
  [fallback]="initials"
  uploadLabel="Upload photo"
/>
<yuw-input-file
  variant="inline"
  multiple
  [(value)]="files"
  uploadLabel="Upload files"
  removeLabel="Remove"
  [countLabel]="countLabel"
  (removed)="files.set(files().filter((f) => f !== $event))"
/>

Inputs

| Input | Type | Default | | :------------ | :--------------------------------------------- | :---------- | | value | File[] (two-way) | [] | | variant | 'avatar'|'inline' | 'avatar' | | uploadLabel | string|undefined | undefined | | removeLabel | string|undefined (inline) | undefined | | countLabel | (count: number) => string (inline, multiple) | undefined | | alt | string (avatar) | '' | | fallback | string|undefined (avatar) | undefined | | src | string|undefined (avatar) | undefined | | accept | string|undefined | undefined | | multiple | boolean | false | | size | 'default'|'xs'|'sm'|'lg' | 'default' | | inputId | string|undefined | undefined | | helperText | string|undefined | undefined | | errorText | string|undefined | undefined | | name | string|undefined | undefined | | disabled | boolean | false | | required | boolean | false | | invalid | boolean | false | | class | string | '' |

Outputs

| Output | Payload | Description | | :-------- | :------ | :----------------------------------------- | | removed | File | A remove icon was pressed (inline only). |


Textarea

Multi-line text field. Auto-grows where field-sizing is supported.

<yuw-textarea [(value)]="bio" placeholder="Write your biography…" [rows]="4" [maxlength]="500" />
<yuw-textarea formControlName="comments" invalid />

Inputs

Same contract as Input, minus type / inputmode, plus:

| Input | Type | Default | | :----- | :------------------ | :---------- | | rows | number|undefined | undefined |

Outputs

| Output | Payload | Description | | :---------- | :------- | :------------------------------------------ | | committed | string | Current value on the native change event. |


Search input

Text field with a leading search icon and an optional clear button.

<yuw-search-input
  [(value)]="query"
  placeholder="Search items…"
  clearAriaLabel="Clear search"
  (searched)="onSearch($event)"
/>

Inputs

Same contract as Input, minus type / inputmode / maxlength, plus:

| Input | Type | Default | | :--------------- | :------------------------------------------------------ | :---------- | | clearAriaLabel | string|undefined — set it to render the clear button | undefined |

Outputs

| Output | Payload | Description | | :---------- | :------- | :--------------------------------------------- | | searched | string | Emitted on every keystroke. | | committed | string | Emitted on Enter — for search-on-submit flows. |


Password input

Password field with an optional reveal toggle.

<yuw-password-input [(value)]="password" revealAriaLabel="Toggle password visibility" />
<yuw-password-input formControlName="password" autocomplete="current-password" />

Inputs

Same contract as Input, minus type / inputmode / maxlength, plus:

| Input | Type | Default | | :---------------- | :-------------------------------------------------- | :---------- | | revealAriaLabel | string|undefined — set it to render the toggle | undefined | | masked | boolean — two-way; a parent can re-mask the value | true |

Outputs

| Output | Payload | Description | | :---------- | :------- | :------------------------------------------ | | committed | string | Current value on the native change event. |


Input OTP

One-time password / verification code field, rendered as grouped slots.

<yuw-input-otp [(value)]="code" [groupSizes]="[3, 3]" (completed)="verify($event)" />
<yuw-input-otp formControlName="otpCode" mode="numeric" />

Inputs

| Input | Type | Default | | :--------------- | :----------------------------------------------- | :---------- | | value | string — two-way | '' | | groupSizes | readonly number[] — [3, 3] renders 000-000 | [6] | | mode | 'numeric'|'text' | 'numeric' | | inputId | string|undefined | undefined | | disabled | boolean | false | | invalid | boolean | false | | autofocus | boolean | false | | transformPaste | (pastedText: string, length: number) => string | identity | | class | string | '' |

Outputs

| Output | Payload | Description | | :---------- | :------- | :-------------------------------------------- | | completed | string | The full code, once the final slot is filled. |


Label

Styled form label, applied to a native <label>.

<label yuwLabel for="username">Username</label>
<yuw-input inputId="username" [(value)]="username" />

Accepts the native for / id attributes.


Checkbox

Projected content becomes the clickable label — the control is wrapped in a <label>, so clicking the text toggles it and the text is announced as the accessible name.

<yuw-checkbox [(checked)]="acceptedTerms">Accept terms</yuw-checkbox>
<yuw-checkbox [(indeterminate)]="isMixed" [(checked)]="allSelected">Select all</yuw-checkbox>
<yuw-checkbox formControlName="rememberMe" />

Inputs

| Input | Type | Default | | :---------------- | :------------------------------------- | :---------- | | checked | boolean — two-way (alias of value) | false | | indeterminate | boolean — two-way | false | | primary | boolean — primary fill | false | | disabled | boolean | false | | required | boolean | false | | invalid | boolean | false | | name | string|undefined | undefined | | inputId | string|undefined | undefined | | ariaLabel | string|undefined | undefined | | ariaLabelledby | string|undefined | undefined | | ariaDescribedby | string|undefined | undefined |


Switch

Toggle for binary settings. Projected content becomes the clickable label.

<yuw-switch [(checked)]="notificationsEnabled">Enable notifications</yuw-switch>
<yuw-switch formControlName="darkMode" size="sm" />

Inputs

| Input | Type | Default | | :---------------- | :------------------------------------- | :---------- | | checked | boolean — two-way (alias of value) | false | | size | 'default'|'sm' | 'default' | | disabled | boolean | false | | inputId | string|undefined | undefined | | ariaLabel | string|undefined | undefined | | ariaLabelledby | string|undefined | undefined | | ariaDescribedby | string|undefined | undefined |


Radio group

<yuw-radio-group [options]="planOptions" [(value)]="selectedPlan" ariaLabel="Plan" />
<yuw-radio-group [options]="genderOptions" formControlName="gender" ariaLabelledby="gender-heading" />
interface YuwRadioOptionDto {
  value: string;
  label: string;
  disabled?: boolean;
}

Inputs

| Input | Type | Default | | :--------------- | :-------------------------------------------- | :---------- | | options | readonly YuwRadioOptionDto[] — required | — | | value | string|undefined — two-way | undefined | | disabled | boolean — disables the whole group | false | | required | boolean | false | | name | string|undefined — generated when omitted | undefined | | ariaLabel | string|undefined | undefined | | ariaLabelledby | string|undefined | undefined | | class | string — layout tweaks (grid-cols-2, …) | '' |


Combobox

Searchable select. Single or multiple selection, in-place search or a read-only trigger, with optional custom option templates.

<!-- Type-to-filter field -->
<yuw-combobox
  [options]="userOptions"
  [(value)]="selectedUser"
  placeholder="Select user…"
  emptyText="No user found."
  triggerAriaLabel="Toggle options"
  clearAriaLabel="Clear selection"
/>

<!-- Classic select shape -->
<yuw-combobox
  readonlyTrigger
  [options]="colorOptions"
  [(value)]="color"
  searchPlaceholder="Search colors…"
  triggerAriaLabel="Toggle options"
/>

<!-- Multiple, with chips -->
<yuw-combobox multiple [options]="categoryOptions" [(value)]="categories" triggerAriaLabel="Toggle options" />

<!-- Custom option template -->
<yuw-combobox [options]="users" [(value)]="userId" triggerAriaLabel="Toggle options">
  <ng-template yuwComboboxItem let-option let-selected="selected">
    <img [src]="option.data.avatar" />
    {{ option.label }}
  </ng-template>
</yuw-combobox>
interface YuwComboboxOptionDto<TData = unknown> {
  value: string;
  label: string;
  disabled?: boolean;
  data?: TData; // available as `option.data` in a custom item template
}

Inputs

| Input | Type | Default | | :------------------ | :--------------------------------------------------------------- | :---------- | | options | YuwComboboxOptionDto<TData>[] — required | — | | value | string|string[]|undefined — two-way | undefined | | multiple | boolean — chips shape; takes precedence over readonlyTrigger | false | | readonlyTrigger | boolean — button trigger, search inside popover | false | | search | string — two-way filter query | '' | | placeholder | string|undefined | undefined | | searchPlaceholder | string|undefined | undefined | | emptyText | string|undefined | undefined | | triggerAriaLabel | string — required | — | | clearAriaLabel | string|undefined — set it to render a clear button | undefined | | inputId | string|undefined | undefined | | disabled | boolean | false | | invalid | boolean | false |


Datepicker

<yuw-datepicker [(value)]="selectedDate" placeholder="Select date…" />
<yuw-datepicker mode="range" [(value)]="dateRange" captionLayout="dropdown" />
<yuw-datepicker mode="monthYear" [(value)]="selectedMonth" />

Inputs

| Input | Type | Default | | :-------------- | :----------------------------------------------------------------- | :---------- | | mode | 'single'|'range'|'multiple'|'monthYear' | 'single' | | value | Date|[Date, Date]|Date[]|null — two-way, shape follows mode | null | | minDate | Date|undefined | undefined | | maxDate | Date|undefined | undefined | | captionLayout | 'label'|'dropdown'|'dropdown-months'|'dropdown-years' | 'label' | | align | 'start'|'center'|'end' | 'start' | | placeholder | string|undefined | undefined | | buttonId | string|undefined — generated when omitted | undefined | | formatDate | ((date: Date) => string)|undefined | undefined | | disabled | boolean | false | | invalid | boolean | false | | class | string — merged onto the trigger button | '' |

captionLayout is ignored in monthYear mode, which renders its own month/year grid.


Avatar

<yuw-avatar src="/user.png" alt="Jane Doe" fallback="JD" />
<yuw-avatar alt="Jane Doe" fallback="JD" badge />
<yuw-avatar [group]="teamMembers" alt="Team" groupCount="+5" />
interface YuwAvatarGroupItemDto {
  alt: string;
  src?: string;
  fallback: string;
}

Inputs

| Input | Type | Default | | :----------- | :-------------------------------------- | :---------- | | alt | string — required | — | | src | string|undefined | undefined | | fallback | string|undefined — initials | undefined | | size | 'default'|'sm'|'lg' | 'default' | | badge | boolean — status marker, bottom-right | false | | group | YuwAvatarGroupItemDto[]|undefined | undefined | | groupCount | string|undefined — overflow label | undefined | | class | string | '' |

Content slots

[yuwAvatarBadgeIcon] — projecting an icon turns the status dot into a role/verification marker.


Badge

<yuw-badge color="primary">New</yuw-badge>
<yuw-badge variant="outline" removeLabel="Remove tag" (removed)="onRemove()">Category</yuw-badge>
<yuw-badge dot color="success" />

Inputs

| Input | Type | Default | | :------------ | :------------------------------------------------------------------ | :---------- | | color | YuwColorDto | 'primary' | | variant | 'default'|'secondary'|'destructive'|'outline'|'ghost'|'link' | 'default' | | dot | boolean — compact dot, renders no content | false | | removeLabel | string|undefined — set it to render the dismiss button | undefined |

Outputs

| Output | Payload | Description | | :-------- | :------ | :------------------------ | | removed | void | Dismiss button activated. |

Content slots

[yuwBadgeIcon] — leading icon.


Card

Directives applied to your own elements — no wrapper element is inserted.

<div yuwCard size="sm">
  <div yuwCardHeader>
    <h3 yuwCardTitle>Monthly report</h3>
    <p yuwCardDescription>Summary of the last 30 days.</p>
    <div yuwCardAction><yuw-button size="sm" variant="ghost">Export</yuw-button></div>
  </div>
  <div yuwCardContent>…</div>
  <div yuwCardFooter>…</div>
</div>

| Directive | Notes | | :--------------------- | :------------------------ | | [yuwCard] | size: 'default'|'sm' | | [yuwCardHeader] | | | [yuwCardTitle] | | | [yuwCardDescription] | | | [yuwCardAction] | Top-right action area. | | [yuwCardContent] | | | [yuwCardFooter] | |

Import them all at once with YuwCardImports.


Item

Row/list layout primitive.

<div yuwItem variant="outline" size="sm">
  <div yuwItemMedia variant="icon"><ng-icon name="lucideMail" /></div>
  <div yuwItemContent>
    <div yuwItemTitle>New message</div>
    <div yuwItemDescription>You have a message from Ada Lovelace.</div>
  </div>
  <div yuwItemActions><yuw-button size="sm">Open</yuw-button></div>
</div>

| Directive | Notes | | :--------------------- | :-------------------------------------------------------------------------- | | [yuwItem] | variant: 'default'|'outline'|'muted', size: 'default'|'sm'|'xs' | | [yuwItemHeader] | | | [yuwItemMedia] | variant: 'default'|'icon'|'image' | | [yuwItemContent] | | | [yuwItemTitle] | | | [yuwItemDescription] | | | [yuwItemActions] | | | [yuwItemFooter] | | | [yuwItemGroup] | | | [yuwItemSeparator] | |

Import them all at once with YuwItemImports. Each directive also accepts the yuw-item-* element form (<yuw-item-title>).


Attachment

File attachment card, with upload states.

<div yuwAttachment state="uploading" size="sm">
  <div yuwAttachmentMedia variant="icon"><ng-icon name="lucideFileText" /></div>
  <div yuwAttachmentContent>
    <span yuwAttachmentTitle>report.pdf</span>
    <span yuwAttachmentDescription>2.4 MB</span>
  </div>
  <div yuwAttachmentActions>
    <button yuwAttachmentAction><ng-icon name="lucideX" /></button>
  </div>
</div>

| Directive | Notes | | :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | [yuwAttachment] | state: 'idle'|'uploading'|'processing'|'error'|'done', size: 'default'|'sm'|'xs', orientation: 'horizontal'|'vertical' | | [yuwAttachmentGroup] | | | [yuwAttachmentMedia] | variant: 'icon'|'image' | | [yuwAttachmentContent] | | | [yuwAttachmentTitle] | | | [yuwAttachmentDescription] | | | [yuwAttachmentActions] | | | button[yuwAttachmentAction] | Action button inside the card. | | [yuwAttachmentTrigger] | Makes a <button> / <a> the card's primary trigger. |


Table

Styling directives for a table you write yourself.

<div yuwTableContainer>
  <table yuwTable>
    <caption yuwCaption>Team members and their roles.</caption>
    <thead yuwTHead>
      <tr yuwTr>
        <th yuwTh>Name</th>
        <th yuwTh>Role</th>
      </tr>
    </thead>
    <tbody yuwTBody>
      <tr yuwTr>
        <td yuwTd>Ada Lovelace</td>
        <td yuwTd>Engineer</td>
      </tr>
    </tbody>
    <tfoot yuwTFoot>…</tfoot>
  </table>
</div>

Import them all at once with YuwTableImports.


Datatable

Sorting, pagination, row selection, column visibility and a loading skeleton, over TanStack Table.

<yuw-datatable
  [columns]="columns"
  [data]="users"
  sortable
  paginated
  selectable
  [loading]="loading()"
  [(columnVisibility)]="columnVisibility"
  emptyText="No users found."
  previousPageLabel="Previous page"
  nextPageLabel="Next page"
  (selectedRowsChange)="onSelect($event)"
/>

Inputs

| Input | Type | Default | | :------------------ | :------------------------------------------ | :------------------------------- | | columns | YuwDatatableColumnDef<TData>[] | [] | | data | TData[] | [] | | sortable | boolean | false | | paginated | boolean | false | | selectable | boolean | false | | loading | boolean — renders skeleton rows | false | | loadingRows | number | 5 | | emptyText | string|undefined | undefined | | previousPageLabel | string — required | — | | nextPageLabel | string — required | — | | caption | string|undefined | undefined | | tableClass | string|undefined — merged onto <table> | undefined | | sorting | SortingState — two-way | [] | | columnFilters | ColumnFiltersState — two-way | [] | | columnVisibility | ColumnVisibilityState — two-way | {} | | rowSelection | RowSelectionState — two-way | {} | | pagination | PaginationState — two-way | { pageIndex: 0, pageSize: 10 } |

Outputs

| Output | Payload | Description | | :------------------- | :-------- | :-------------------------------------------------- | | selectedRowsChange | TData[] | Selection changes. The initial state does not emit. |

buildYuwActionsColumn(label, onEdit, cellClass?, header?) builds an edit-button column whose button is revealed on row hover (see Reveal on hover). Because the button is hidden by default, pass header (already translated) to give the column a visible title.

selectable enables the selection state; the checkbox column itself is a display column you provide (id: 'select', with flexRenderComponent(...) header/cell calling TanStack's toggle handlers).


Empty

Empty-state panel ("no results", "inbox empty").

<div yuwEmpty>
  <div yuwEmptyHeader>
    <div yuwEmptyMedia variant="icon"><ng-icon name="lucideFolderOpen" /></div>
    <h3 yuwEmptyTitle>No files found</h3>
    <p yuwEmptyDescription>Try uploading a file or changing your query.</p>
  </div>
  <div yuwEmptyContent>
    <yuw-button>Upload file</yuw-button>
  </div>
</div>

[yuwEmptyMedia] accepts variant: 'default'|'icon'. Import them all at once with YuwEmptyImports.


Skeleton

<div yuwSkeleton class="h-4 w-32 rounded"></div>
<div yuwSkeleton class="size-10 rounded-full"></div>

Sized with standard CSS height/width classes on the host element.


Spinner

<yuw-spinner label="Loading" />
<yuw-spinner label="Refreshing" icon="lucideRefreshCw" />

Inputs

| Input | Type | Default | | :------ | :------------------------------------------- | :---------------- | | label | string — required, the accessible name | — | | icon | string — any registered icon name | 'lucideLoader2' |

The spinner inherits its size from the container's font size — wrap it in text-2xl to scale it.


Accordion

<yuw-accordion type="multiple">
  <yuw-accordion-item [disabled]="false" (openedChange)="onToggle($event)">
    <yuw-accordion-trigger>Shipping</yuw-accordion-trigger>
    <yuw-accordion-content>Delivered in 24–48 h.</yuw-accordion-content>
  </yuw-accordion-item>
</yuw-accordion>

| Element | Inputs / Outputs | | :---------------------- | :-------------------------------------------------------------------------------------------------------------------- | | yuw-accordion | type: 'single'|'multiple' (default 'single'), orientation: 'vertical'|'horizontal' (default 'vertical') | | yuw-accordion-item | isOpened: boolean, disabled: boolean; output openedChange | | yuw-accordion-trigger | triggerClass: string — merged after defaults, so it wins | | yuw-accordion-content | — |

Import them all at once with YuwAccordionImports.


Breadcrumb

<yuw-breadcrumb [items]="[{ label: 'Home', href: '/' }, { label: 'Settings' }]" ariaLabel="Breadcrumb" />
interface YuwBreadcrumbItemDto {
  label: string;
  href?: string;
}

Inputs

| Input | Type | Default | | :---------- | :-------------------------------------- | :------ | | items | YuwBreadcrumbItemDto[] — required | — | | ariaLabel | string — required | — | | separator | string | '/' |

The last item is announced as the current page (aria-current="page"); intermediate items render as links when they have an href.


Pagination

<yuw-pagination
  [(page)]="currentPage"
  [totalPages]="20"
  previousLabel="Previous page"
  nextLabel="Next page"
  ellipsisSrText="More pages"
  ariaLabel="Pagination"
/>

Inputs

| Input | Type | Default | | :--------------- | :--------------------------------------------------------------------- | :---------- | | page | number — two-way, required | — | | totalPages | number — required | — | | previousLabel | string — required | — | | nextLabel | string — required | — | | ellipsisSrText | string — required | — | | ariaLabel | string — required | — | | size | 'default'|'xs'|'sm'|'lg'|'icon'|'icon-xs'|'icon-sm'|'icon-lg' | 'icon' | | color | YuwColorDto | 'primary' |

getPaginationRange(page, totalPages) is exported if you need the same page/ellipsis sequence elsewhere.


Tabs

Triggers come from the tabs input; each panel is an <ng-template yuwTabPanel="id"> whose id matches a tab. Panels are lazy — instantiated the first time their tab becomes active.

<yuw-tabs [tabs]="settingsTabs" [(activeTab)]="selected" variant="line">
  <ng-template yuwTabPanel="general">Workspace name, timezone and locale.</ng-template>
  <ng-template yuwTabPanel="billing">Plan, invoices and payment method.</ng-template>
</yuw-tabs>
interface YuwTabItemDto {
  id: string;
  label: string;
  disabled?: boolean;
}

Inputs

| Input | Type | Default | | :--------------- | :------------------------------- | :------------- | | tabs | YuwTabItemDto[] — required | — | | activeTab | string — two-way, required | — | | variant | 'default'|'line' | 'default' | | orientation | 'horizontal'|'vertical' | 'horizontal' | | activationMode | 'automatic'|'manual' | 'automatic' |


Sidebar

Collapsible navigation sidebar — a fixed panel on desktop, a slide-in sheet on mobile. Open state lives in YuwSidebarService, so <yuw-sidebar-trigger> works from anywhere on the page.

<div yuwSidebarWrapper>
  <yuw-sidebar collapsible="icon">
    <div yuwSidebarHeader>Yurest</div>

    <div yuwSidebarContent>
      <div yuwSidebarGroup>
        <div yuwSidebarGroupLabel>Management</div>
        <div yuwSidebarGroupContent>
          <ul yuwSidebarMenu>
            <li yuwSidebarMenuItem>
              <a yuwSidebarMenuButton [isActive]="true" tooltip="Orders">
                <ng-icon name="lucideShoppingCart" />
                <span>Orders</span>
              </a>
              <span yuwSidebarMenuBadge>12</span>
            </li>
          </ul>
        </div>
      </div>
    </div>

    <div yuwSidebarFooter>…</div>
  </yuw-sidebar>

  <main yuwSidebarInset>
    <yuw-sidebar-trigger label="Toggle sidebar" />
  </main>
</div>

Object form — [content] and [footer]

Instead of hand-writing the group/menu/item tree, hand the sidebar a YuwSidebarContentDto. Header stays projected markup.

<yuw-sidebar collapsible="icon" [content]="nav" [footer]="account" (itemClicked)="onAction($event.id)">
  <div yuwSidebarHeader>Yurest</div>
</yuw-sidebar>
nav: YuwSidebarContentDto = {
  groups: [
    {
      id: 'management',
      label: 'Management',
      separatorAfter: true,
      items: [
        // route → routerLink anchor; active state resolves itself
        { id: 'orders', label: 'Orders', icon: 'lucideShoppingCart', route: '/orders', badge: '12' },
        // children → one nested submenu level
        { id: 'reports', label: 'Reports', children: [{ id: 'sales', label: 'Sales', route: '/reports/sales' }] },
        // href → plain anchor, full navigation
        { id: 'docs', label: 'Docs', href: 'https://example.com', target: '_blank' },
        // neither → <button>, reported through (itemClicked)
        { id: 'sync', label: 'Sync now' },
      ],
    },
  ],
};

An item's shape follows from its data — no kind field to keep in sync:

| Item has | Renders as | Active state | | :------- | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | route | [routerLink] anchor | Automatic via routerLinkActive → aria-current="page" and pointer-events: none, so an active entry takes no hover, click or tooltip. Set exact: true for a root path like /. | | href | plain <a href> | None — the browser leaves the page. | | neither | <button> | None — emits itemClicked with {id, event}. |

label and tooltip are consumer-supplied strings, already translated — the sidebar owns no user-facing copy. icon names an icon your app registers with provideIcons (see Icons).

Both forms compose: content/footer render their own regions, and anything projected still renders too. To place hand-written markup inside an object-rendered region — where it scrolls with the nav and shares the content area's flex sizing — mark it [yuwSidebarContentExtra] or [yuwSidebarFooterExtra].

<yuw-sidebar> inputs

| Input | Type | Default | | :---------------------- | :----------------------------------------------------- | :------------ | | side | 'left'|'right' | 'left' | | variant | 'sidebar'|'floating'|'inset' | 'sidebar' | | collapsible | 'offcanvas'|'icon'|'none' | 'offcanvas' | | sidebarWidthMobile | string — any CSS length | '18rem' | | sidebarContainerClass | string — merged onto the desktop container | '' | | content | YuwSidebarContentDto|undefined — data-driven nav | undefined | | footer | YuwSidebarContentDto|undefined — data-driven footer | undefined |

Output: itemClicked → YuwSidebarItemClickDto ({id, event}), for entries with no route/href.

Structure directives

| Directive | Notes | | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [yuwSidebarWrapper] | sidebarWidth, sidebarWidthIcon | | [yuwSidebarHeader] / [yuwSidebarContent] / [yuwSidebarFooter] | | | [yuwSidebarGroup] / [yuwSidebarGroupLabel] / [yuwSidebarGroupContent] | | | ul[yuwSidebarMenu] / li[yuwSidebarMenuItem] | | | a[yuwSidebarMenuButton], button[…] | variant: 'default'|'outline', size: 'default'|'sm'|'lg', isActive, closeMobileSidebarOnClick; add Spartan's tooltip to label items while collapsed to icons | | [yuwSidebarMenuBadge] | Trailing count/status. | | ul[yuwSidebarMenuSub] / li[yuwSidebarMenuSubItem] / a[yuwSidebarMenuSubButton] | Nested level. | | [yuwSidebarSeparator] | | | [yuwSidebarContentExtra] / [yuwSidebarFooterExtra] | Projection markers: place hand-written markup inside the region rendered from content/footer. Inert when that input is unset. | | button[yuwSidebarRail] | label — required, thin edge toggle. | | main[yuwSidebarInset] | Main content area. | | <yuw-sidebar-trigger> | label — required, screen-reader-only. |

Inject YuwSidebarService to read or drive the state (state(), isMobile(), openMobile(), toggle()); configure defaults app-wide with provideYuwSidebarConfig({ … }).


Dialog

Modal overlay for focused tasks and forms. The body scrolls internally when taller than the viewport.

Provide either title or ariaLabel — a modal without an accessible name is announced as an anonymous dialog by screen readers.

<yuw-dialog [(open)]="isOpen" title="Create project" description="Give it a name to get started." closeLabel="Close">
  <ng-container yuwDialogTrigger>Create project</ng-container>

  <yuw-input [(value)]="name" placeholder="Project name" />

  <div yuwDialogFooter>
    <yuw-button (clicked)="save()">Save</yuw-button>
  </div>
</yuw-dialog>

Inputs

| Input | Type | Default | | :--------------- | :------------------------------------------------------- | :---------- | | open | boolean — two-way | false | | showTrigger | boolean — set false when driving via [(open)] only | true | | title | string|undefined | undefined | | description | string|undefined | undefined | | ariaLabel | string|undefined | undefined | | closeLabel | string|undefined — set it to render the corner ✕ | undefined | | panelClass | string|undefined — e.g. sm:max-w-lg | undefined | | triggerVariant | button variant | 'outline' | | triggerSize | button size | 'default' |

Content slots

[yuwDialogTrigger], [yuwDialogFooter] (the footer renders only when filled), and the default slot for the body. Import with YuwDialogImports.


Alert dialog

Blocking confirmation for destructive or irreversible actions. Unlike <yuw-dialog> it cannot be dismissed by clicking the backdrop.

<yuw-alert-dialog
  [(open)]="isOpen"
  title="Delete invoice?"
  description="This action cannot be undone."
  confirmLabel="Delete"
  cancelLabel="Cancel"
  confirmVariant="destructive"
  (confirmed)="delete()"
/>

Inputs

| Input | Type | Default | | :--------------- | :---------------------- | :---------- | | open | boolean — two-way | false | | showTrigger | boolean | true | | title | string — required | — | | confirmLabel | string — required | — | | cancelLabel | string — required | — | | description | string|undefined | undefined | | confirmVariant | button variant | 'default' | | size | 'default'|'sm' | 'default' | | triggerVariant | button variant | 'outline' | | triggerSize | button size | 'default' |

Outputs

| Output | Payload | Description | | :---------- | :------ | :---------------------------------------------------------------- | | confirmed | void | Confirm action activated. | | cancelled | void | Cancel button activated — programma