yukit-web
v0.3.2
Published
Angular UI component library for Yurest web applications.
Keywords
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
- Installation
- Setup
- Quick start
- Theming
- Conventions
- Components
- Utilities and services
- Development
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-scrollbarPlus the Tailwind toolchain, as dev dependencies:
npm install -D tailwindcss @tailwindcss/postcss tw-animate-csstw-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-hostgoes on the hover area,yuw-revealon 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 carryingdata-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, passheader(already translated) to give the column a visible title.
selectableenables the selection state; the checkbox column itself is a display column you provide (id: 'select', withflexRenderComponent(...)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
titleorariaLabel— 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
