Try to find by number
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiBlockStatus} from '@taiga-ui/layout';
@Component({
imports: [TuiBlockStatus],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### With actions
**Template:**
```html
@if (size(); as size) {
Something has been achieved
You can do something with it, or you can not do it. And the description text can be quite long. }
```
**TypeScript:**
```ts
import {Component, computed, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_BREAKPOINT, TuiButton, type TuiSizeL} from '@taiga-ui/core';
import {TuiBlockStatus} from '@taiga-ui/layout';
@Component({
imports: [TuiBlockStatus, TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly breakpoint = inject(TUI_BREAKPOINT);
protected readonly size = computed(() =>
this.breakpoint() === 'mobile' ? 'm' : 'l',
);
}
```
#### Cards
**Template:**
```html
@if (size(); as size) {
We hide the unwanted block
Something happened in this block
We hide the unwanted block
Something happened in this block
Something happened in this block
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiPlatform} from '@taiga-ui/cdk';
import {TuiButton} from '@taiga-ui/core';
import {TuiBlockStatus} from '@taiga-ui/layout';
@Component({
imports: [TuiBlockStatus, TuiButton, TuiPlatform],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.container {
display: flex;
gap: 1rem;
flex-direction: column;
}
.card {
background-color: var(--tui-background-base-alt);
block-size: auto;
}
```
#### Desktop medium
**Template:**
```html
There is nothing here yet
To get started, create a server
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiBlockStatus} from '@taiga-ui/layout';
@Component({
imports: [TuiBlockStatus, TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Empty image block
**Template:**
```html
Something happened in this block
Try again later
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiBlockStatus} from '@taiga-ui/layout';
@Component({
imports: [TuiBlockStatus, TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Empty description block
**Template:**
```html
No operations
No operations
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiBlockStatus} from '@taiga-ui/layout';
@Component({
imports: [TuiBlockStatus, TuiButton],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.container {
display: flex;
gap: 1rem;
flex-direction: column;
}
.card {
background-color: var(--tui-background-base-alt);
block-size: auto;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiButton, type TuiSizeL} from '@taiga-ui/core';
import {TuiBlockStatus} from '@taiga-ui/layout';
@Component({
imports: [TuiBlockStatus, TuiButton, TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected card = false;
protected readonly sizes: TuiSizeL[] = ['l', 'm'];
protected size = this.sizes[0] || 'l';
protected readonly examples = [
'Basic',
'With actions',
'Cards',
'Customization',
'Mobile',
'Desktop medium',
'Empty image block',
'Empty description block',
];
}
```
---
# components/BottomSheet
- **Package**: `ADDON-MOBILE`
- **Type**: components
A non-modal draggable sheet
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [stops] | `readonly string[]` | Scroll snap stops |
### Usage Examples
#### Basic
**Template:**
```html
London is the capital and largest city of both England and the United Kingdom, with a population of 8,866,180 in 2022. Its wider metropolitan area is the largest in Western Europe, with a population of 14.9 million. London stands on the River Thames in southeast England, at the head of a 50-mile (80 km) tidal estuary down to the North Sea, and has been a major settlement for nearly 2,000 years. Its ancient core and financial centre, the City of London, was founded by the Romans and has retained its medieval boundaries. The City of Westminster, to the west of the City of London, has been the centuries-long host of the national government and parliament. London grew rapidly in the 19th century, becoming the world's largest city at the time. Since the 19th century, the name "London" has referred to the metropolis around the City of London.
```
**TypeScript:**
```ts
import {Component, type ElementRef, viewChild} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiBottomSheet} from '@taiga-ui/addon-mobile';
import {TuiButton, TuiTitle} from '@taiga-ui/core';
import {TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [TuiBottomSheet, TuiButton, TuiHeader, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly button = viewChild>('buttons');
protected readonly stops = ['112px'] as const;
protected onScroll({clientHeight, scrollTop}: HTMLElement): void {
const offset = Number.parseInt(this.stops[0], 10);
const top = Math.min(scrollTop, clientHeight - offset);
const transform = `translate3d(0, ${-top}px, 0)`;
this.button()?.nativeElement.style.setProperty('transform', transform);
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
position: relative;
display: block;
overflow: hidden;
}
.map {
inline-size: 18rem;
block-size: 30rem;
border: none;
}
.buttons {
.fullsize();
display: flex;
gap: 1rem;
padding: 1rem 1rem 8rem;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
box-sizing: border-box;
pointer-events: none;
& > * {
position: relative;
pointer-events: auto;
}
}
.sheet {
block-size: 25.75rem;
}
.content {
display: flex;
flex-direction: column;
gap: 1rem;
margin: 2rem 0;
}
```
#### Stops
**Template:**
```html
Taiga UI Give us a Star @if (show) { @for (_ of '-'.repeat(20); track $index) {
All work and no play makes Jack a dull boy
} All work and no play makes Jack a dull boy }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiBottomSheet} from '@taiga-ui/addon-mobile';
import {TuiButton, TuiCheckbox} from '@taiga-ui/core';
import {TuiAccordion, TuiBlock} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiAccordion,
TuiBlock,
TuiBottomSheet,
TuiButton,
TuiCheckbox,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected show = true;
}
```
**LESS:**
```less
:host {
position: relative;
display: flex;
align-items: flex-start;
justify-content: center;
inline-size: 14rem;
block-size: 26rem;
padding: 2rem;
background: linear-gradient(45deg, #4158d0 0%, #c850c0 50%, #ffcc70 100%);
overflow: hidden;
}
.header {
position: sticky;
z-index: 1;
display: flex;
inset-block-start: 2.25rem;
flex-direction: column;
gap: 1rem;
font: var(--tui-typography-heading-h5);
font-size: 1.25rem;
background: var(--tui-background-elevation-1);
box-shadow: 0 -1rem var(--tui-background-elevation-1);
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Reacting to scroll', 'Stops'];
}
```
---
# components/Breadcrumbs
- **Package**: `KIT`
- **Type**: components
Navigation element that shows a path from root page to the current
### Example
```html
@for (item of items; track item) { }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [itemsLimit] | `number` | Limit on visible items |
| [size] | `TuiSizeL` | Text size |
### Usage Examples
#### Basic
**Template:**
```html
@for (item of items; track item) { {{ item.caption }} } @for (item of items; track item) { {{ item.caption }} }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {RouterLink} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiItem} from '@taiga-ui/cdk';
import {TuiLink} from '@taiga-ui/core';
import {TuiBreadcrumbs} from '@taiga-ui/kit';
@Component({
imports: [RouterLink, TuiBreadcrumbs, TuiItem, TuiLink],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected items = [
{
caption: 'Selects',
routerLink: '/components/select',
},
{
caption: 'Multi',
routerLink: '/components/multi-select',
},
{
caption: 'With tags',
routerLink: '/components/multi-select',
},
{
caption: 'Current',
routerLink: '/navigation/breadcrumbs',
routerLinkActiveOptions: {exact: true},
},
];
}
```
#### Overflow
**Template:**
```html
Truncate Using .text-truncate() mixin
@for (item of items; track item) { }
Fade Combining .text-truncate() mixin with Fade directive
I'm just another card
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTable} from '@taiga-ui/addon-table';
import {TuiButton, TuiExpand, TuiIcon, TuiLink, TuiTitle} from '@taiga-ui/core';
import {TuiBadge, TuiChevron} from '@taiga-ui/kit';
import {TuiCard, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [
TuiBadge,
TuiButton,
TuiCard,
TuiChevron,
TuiExpand,
TuiHeader,
TuiIcon,
TuiLink,
TuiTable,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
public readonly collapsed = signal(true);
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
inline-size: 32rem;
background: var(--tui-background-base-alt);
box-shadow: 0 0 0 100rem var(--tui-background-base-alt);
}
tui-icon {
color: var(--tui-status-warning);
}
td:last-child {
color: var(--tui-text-secondary);
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly examples = ['Basic'];
}
```
---
# components/CardLarge
- **Package**: `LAYOUT`
- **Type**: components
### Usage Examples
#### Basic
**Template:**
```html
Desktop
Header
Replace me
Header
Replace me
iOS/Android
Header
Replace me
Header
Replace me
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiPlatform} from '@taiga-ui/cdk';
import {TuiButton, TuiLink, TuiTitle} from '@taiga-ui/core';
import {TuiBadge} from '@taiga-ui/kit';
import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [
TuiBadge,
TuiButton,
TuiCardLarge,
TuiHeader,
TuiLink,
TuiPlatform,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
section {
display: flex;
block-size: 3.125rem;
border-radius: 0.75rem;
justify-content: center;
align-items: center;
color: var(--tui-text-secondary);
border: 1px dashed;
}
[tuiLink].label {
font: var(--tui-typography-body-l);
}
```
#### Image
**Template:**
```html
Title Subtitle
Title Subtitle
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButtonX, TuiTitle} from '@taiga-ui/core';
import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [TuiButtonX, TuiCardLarge, TuiHeader, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
.image-1 {
background: url('/assets/images/illustration.jpg') no-repeat top right / 250%;
}
.image-2 {
background: url('/assets/images/road-illustration.jpg') no-repeat center / cover;
}
.image-2,
:host-context([tuiTheme='dark']) .image-1 {
&::before {
content: '';
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
inline-size: 100%;
block-size: 100%;
background: rgba(0, 0, 0, 0.5);
}
}
[tuiButtonX] {
backdrop-filter: blur(1rem) brightness(1.25);
}
[tuiButtonX][tuiTheme='dark'] {
backdrop-filter: blur(1rem) brightness(0.5);
}
```
#### Cell with close
**Template:**
```html
iOS/Android
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiPlatform} from '@taiga-ui/cdk';
import {TuiButtonX, TuiCell, TuiTitle} from '@taiga-ui/core';
import {TuiAvatar, TuiFade} from '@taiga-ui/kit';
import {TuiCardLarge} from '@taiga-ui/layout';
@Component({
imports: [
TuiAvatar,
TuiButtonX,
TuiCardLarge,
TuiCell,
TuiFade,
TuiPlatform,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
[tuiCardLarge] {
max-inline-size: 21.4375rem;
}
```
#### Paddings and radii
**Template:**
```html
Desktop
Normal Radius: 24, padding: 24
Compact Radius: 16, padding: 20
iOS/Android
Normal Radius: 24, padding: 20
Compact Radius: 16, padding: 16
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiPlatform} from '@taiga-ui/cdk';
import {TuiButton, TuiTitle} from '@taiga-ui/core';
import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [TuiButton, TuiCardLarge, TuiHeader, TuiPlatform, TuiTitle],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Map
**Template:**
```html
Title Subtitle
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiTitle} from '@taiga-ui/core';
import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [TuiButton, TuiCardLarge, TuiHeader, TuiTitle],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### In portal
**Template:**
```html
Show popover
Lorem Ipsum is simply dummy text of the printing and typesetting industry. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text.
Text without fade out when overflow content
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {TuiAvatar, TuiChip, TuiFade} from '@taiga-ui/kit';
@Component({
imports: [TuiAmountPipe, TuiAvatar, TuiChip, TuiFade],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
[tuiChip] {
margin: 0.5rem;
max-inline-size: 9rem;
}
.custom {
[tuiFade] {
flex: 1 1 auto;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {TuiDocAppearance} from '@demo/components/appearance';
import {TuiDocIcons} from '@demo/components/icons';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {type TuiSizeXXS} from '@taiga-ui/core';
import {TuiChip} from '@taiga-ui/kit';
@Component({
selector: 'example-chip',
imports: [TuiChip, TuiDemo, TuiDocAppearance, TuiDocIcons],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly examples = [
'Basic',
'Sizes and content',
'Interactive',
'Use cases',
'Auto color',
'Fade in complex designs',
];
protected readonly sizes: readonly TuiSizeXXS[] = ['xxs', 'xs', 's', 'm'];
protected size = this.sizes[2]!;
}
```
---
# components/ComboBox
- **Package**: `KIT`
- **Type**: components
ComboBox is a form control for selecting a single value from a set of options. It is similar to Select but with a major difference – possibility to enter value manually .
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [strict] | `boolean` | |
| [matcher] | `TuiStringMatcher | null` | function that compares search text and datalist's items to define a match between them. Lowercase string
comparison function by default. |
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiFilterByInputPipe} from '@taiga-ui/core';
import {TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiFilterByInputPipe,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items = [
'Darth Vader',
'Luke Skywalker',
'Princess Leia',
'Han Solo',
'Obi-Wan Kenobi',
'Yoda',
] as const;
protected value: string | null = this.items[0];
}
```
#### Virtual scroll
**Template:**
```html
@let items = countries | tuiFilterByInput; {{ item }}
```
**TypeScript:**
```ts
import {ScrollingModule} from '@angular/cdk/scrolling';
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDataList, TuiFilterByInputPipe, TuiScrollable} from '@taiga-ui/core';
import {TUI_COUNTRIES, TuiChevron, TuiComboBox} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
ScrollingModule,
TuiChevron,
TuiComboBox,
TuiDataList,
TuiFilterByInputPipe,
TuiScrollable,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly countries = Object.values(inject(TUI_COUNTRIES)());
protected value = null;
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils.less';
.scroll {
.scrollbar-hidden();
max-block-size: 100%;
}
```
#### With DropdownMobile
**Template:**
```html
```
**TypeScript:**
```ts
import {Component, computed, inject, type Signal, ViewEncapsulation} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDropdownMobile} from '@taiga-ui/addon-mobile';
import {TuiFilterByInputPipe} from '@taiga-ui/core';
import {TUI_COUNTRIES, TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiDropdownMobile,
TuiFilterByInputPipe,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation: ViewEncapsulation.None,
changeDetection,
})
export default class Example {
protected readonly countries: Signal = computed(() =>
Object.values(inject(TUI_COUNTRIES)),
);
protected value: string | null = null;
}
```
**LESS:**
```less
@sticky-header-height: 4.1875rem;
:root {
--tui-dropdown-mobile-offset: @sticky-header-height + 1rem;
}
header[tuiDocHeader] {
visibility: visible;
}
```
#### Override option component
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiAsOptionContent, TuiFilterByInputPipe} from '@taiga-ui/core';
import {TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
import {Option} from './option';
@Component({
imports: [
ReactiveFormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiFilterByInputPipe,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiAsOptionContent(Option)],
})
export default class Example {
protected readonly items = Array.from(
{length: 5},
(_, index) => `Option ${index + 1}`,
);
protected readonly control = new FormControl(this.items[2]!);
}
```
#### Non-strict mode
**Template:**
```html
Form control:{{ control.value | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
@Component({
imports: [JsonPipe, ReactiveFormsModule, TuiChevron, TuiComboBox, TuiDataListWrapper],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items = [
'Medical Expenses',
'Education',
'Travel',
'Home Repair',
'Car',
];
protected control = new FormControl(null);
}
```
#### Client-side filtering
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_DEFAULT_MATCHER, TUI_STRICT_MATCHER} from '@taiga-ui/cdk';
import {type TuiFilterByInputOptions, TuiFilterByInputPipe} from '@taiga-ui/core';
import {TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
const ROMAN_TO_LATIN: Record = {
I: '1',
II: '2',
III: '3',
IV: '4',
V: '5',
VI: '6',
VII: '7',
VIII: '8',
};
@Component({
imports: [
FormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiFilterByInputPipe,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items = [
'Charles III',
'Elizabeth II',
'George VI',
'Edward VIII',
'George V',
'William IV',
'George IV',
'George III',
];
protected value: string | null = null;
protected readonly filter: TuiFilterByInputOptions['filter'] = (
items,
query,
) => {
const filtered = items.filter((item) => {
const romanNumeral = item.split(' ').at(-1)!;
return (
query === ROMAN_TO_LATIN[romanNumeral] || TUI_DEFAULT_MATCHER(item, query)
);
});
/**
* Query "George V" EXACTLY matches one option,
* but it also PARTIALLY matches another option "George VI"
* We should continue filtering for such ambiguous cases
*/
return filtered.length === 1 && TUI_STRICT_MATCHER(filtered[0], query)
? items // Reset filtering
: filtered;
};
}
```
#### Textfield customization
**Template:**
```html
@if (value) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiFilterByInputPipe, TuiIcon} from '@taiga-ui/core';
import {TuiChevron, TuiComboBox, TuiDataListWrapper, TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiFilterByInputPipe,
TuiIcon,
TuiTooltip,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly cities = [
'Night City',
'Raccoon City',
'City 17',
'Springfield',
'Bikini Bottom',
'Gotham',
];
protected value: string | null = null;
}
```
#### Server-side filtering
**Template:**
```html
@let items = items$ | async; @if (items && showLoader()) { }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component, inject, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLoader} from '@taiga-ui/core';
import {TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
import {debounceTime, filter, of, Subject, switchMap, tap} from 'rxjs';
import {DatabaseServer} from './database';
@Component({
imports: [
AsyncPipe,
FormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiLoader,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly api = inject(DatabaseServer);
protected readonly showLoader = signal(false);
// Click on cleaner / datalist item triggers (input) events too
protected readonly search$ = new Subject();
protected readonly items$ = this.search$.pipe(
debounceTime(0), // ensure form control is updated after last input
filter(() => !this.value), // click on datalist item should not trigger new api request
tap(() => this.showLoader.set(true)),
debounceTime(300),
switchMap((query) => (query.length >= 2 ? this.api.request$(query) : of(null))),
tap(() => this.showLoader.set(false)),
);
protected value: string | null = null;
}
```
#### Items handlers
**Template:**
```html
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiFilterByInputPipe, tuiItemsHandlersProvider} from '@taiga-ui/core';
import {TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
interface Character {
readonly id: number;
readonly name: string;
}
@Component({
selector: 'example-6',
imports: [
FormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiFilterByInputPipe,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
/**
* You can also use input props of `Textfield`
* (they will have more priority):
* ```html
*
* ```
*/
tuiItemsHandlersProvider({
stringify: signal((x: Character) => x.name),
identityMatcher: signal((a: Character, b: Character) => a.id === b.id),
disabledItemHandler: signal((x: Character) => x.name.includes('Trevor')),
}),
],
})
export default class Example {
protected readonly users: Character[] = [
{id: 42, name: 'Tommy Vercetti'},
{id: 237, name: 'Carl Johnson'},
{id: 666, name: 'Niko Bellic'},
{id: 999, name: 'Trevor Philips'},
{id: 123, name: 'Michael De Santa'},
{id: 777, name: 'Franklin Clinton'},
];
protected value: Character | null = {id: 42, name: 'Tommy Vercetti'}; // !== this.users[0]
}
```
#### Customize content
**Template:**
```html
{{ card.number.slice(-4) }} {{ card.name }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiPaymentSystem, TuiThumbnailCard} from '@taiga-ui/addon-commerce';
import {type TuiStringHandler} from '@taiga-ui/cdk';
import {type TuiFilterByInputOptions, TuiFilterByInputPipe} from '@taiga-ui/core';
import {TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
interface Card {
name: string;
number: string;
paymentSystem: TuiPaymentSystem;
}
@Component({
imports: [
FormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiFilterByInputPipe,
TuiThumbnailCard,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected cards: Card[] = [
{name: 'Bitcoin', number: '5555555555554444', paymentSystem: 'mastercard'},
{name: 'Salary', number: '4242424242424242', paymentSystem: 'visa'},
{name: 'Charity', number: '2201382000000013', paymentSystem: 'mir'},
{name: 'Subscriptions', number: '6200000000000005', paymentSystem: 'unionpay'},
];
protected value: Card | null = null;
protected readonly filter: TuiFilterByInputOptions['filter'] = (items, value) =>
items.filter(
(item) =>
item.name.toLowerCase().includes(value.toLowerCase()) ||
item.number.includes(value),
);
protected readonly stringify: TuiStringHandler = (x) => x.number;
}
```
**LESS:**
```less
.card {
display: flex;
align-items: center;
gap: 0.5rem;
}
```
#### With DataList
**Template:**
```html
@for (category of categories; track category) { } @for (group of filmDatabase | keyvalue; track group) { @if (filters[group.key]) { @for (film of group.value | tuiFilterByInput; track film) { {{ film }} } } }
```
**TypeScript:**
```ts
import {KeyValuePipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDataList, TuiFilterByInputPipe} from '@taiga-ui/core';
import {TuiChevron, TuiChip, TuiComboBox} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
KeyValuePipe,
TuiChevron,
TuiChip,
TuiComboBox,
TuiDataList,
TuiFilterByInputPipe,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected filmDatabase = {
Action: [
'The Dark Knight',
'Inception',
'The Matrix',
'The Dark Knight Rises',
'Gladiator',
],
Comedy: [
'The Wolf of Wall Street',
'Back to the Future',
'Guardians of the Galaxy',
'The Truman Show',
'Deadpool',
],
Drama: [
'The Shawshank Redemption',
'The Godfather',
"Schindler's List",
'12 Angry Men',
],
Horror: ['The Silence of the Lambs', 'Alien', 'Psycho', 'The Shining'],
Romance: [
'Forrest Gump',
'Titanic',
'Good Will Hunting',
'Eternal Sunshine of the Spotless Mind',
'Slumdog Millionaire',
],
};
protected categories = Object.keys(this.filmDatabase);
protected filters: Record = this.categories.reduce(
(acc, category, i) => ({...acc, [category]: i % 2 === 0}),
{},
);
protected selectedCategory = true;
protected value: string | null = null;
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.filters {
.scrollbar-hidden();
position: sticky;
z-index: 1;
display: flex;
inset-block-start: 0;
overflow: scroll;
gap: 0.5rem;
padding: 0.5rem;
margin-block-end: 1rem;
border-block-end: 1px solid var(--tui-border-normal);
background: var(--tui-background-elevation-3);
}
```
#### Choose form control output
**Template:**
```html
@for (item of items | tuiFilterByInput; track item) { {{ item.name }} }
Form control:{{ control.value | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiStringHandler, type TuiStringMatcher} from '@taiga-ui/cdk';
import {TuiDataList, TuiFilterByInputPipe} from '@taiga-ui/core';
import {TuiChevron, TuiComboBox} from '@taiga-ui/kit';
interface Python {
readonly id: number;
readonly name: string;
}
@Component({
imports: [
JsonPipe,
ReactiveFormsModule,
TuiChevron,
TuiComboBox,
TuiDataList,
TuiFilterByInputPipe,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(777);
protected readonly items: readonly Python[] = [
{id: 42, name: 'John Cleese'},
{id: 0, name: 'Eric Idle'},
{id: 666, name: 'Michael Palin'},
{id: 123, name: 'Terry Gilliam'},
{id: 777, name: 'Terry Jones'},
{id: 999, name: 'Graham Chapman'},
];
protected readonly stringify: TuiStringHandler = (item) =>
typeof item === 'number'
? // Number-type form control value => human-readable text inside textfield
(this.items.find(({id}) => id === item)?.name ?? '')
: // for `tuiFilterByInput` pipe
item.name;
protected readonly matcher: TuiStringMatcher = (id, query) => {
const {name} = this.items.find((item) => item.id === id)!;
return String(id) === query || name.toLowerCase() === query.toLowerCase();
};
}
```
### TypeScript
```ts
import {Component, computed, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocItemsHandlers} from '@demo/components/items-handlers';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {WA_IS_MOBILE} from '@ng-web-apis/platform';
import {TUI_STRICT_MATCHER, type TuiContext, type TuiStringMatcher} from '@taiga-ui/cdk';
import {TuiDropdown, TuiFilterByInputPipe} from '@taiga-ui/core';
import {TUI_COUNTRIES, TuiChevron, TuiComboBox, TuiDataListWrapper} from '@taiga-ui/kit';
interface Country {
id: string;
name: string;
}
@Component({
imports: [
ReactiveFormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiDemo,
TuiDocControl,
TuiDocDropdown,
TuiDocIcons,
TuiDocInput,
TuiDocItemsHandlers,
TuiDocTextfield,
TuiDropdown,
TuiFilterByInputPipe,
],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent extends Array {
private readonly countriesI18n = inject(TUI_COUNTRIES);
protected readonly routes = DemoRoute;
protected readonly isMobile = inject(WA_IS_MOBILE);
protected readonly examples = [
'Basic',
'Non-strict mode',
'Client-side filtering',
'Textfield customization',
'Server-side filtering',
'Items handlers',
'Customize content',
'With DataList',
'Choose form control output',
'Virtual scroll',
'With DropdownMobile',
'Override option component',
];
protected readonly [4] = {
'database.ts': import('./examples/5/database.ts?raw', {with: {loader: 'text'}}),
};
protected readonly [11] = {
'option.ts': import('./examples/12/option.ts?raw', {with: {loader: 'text'}}),
};
protected readonly control = new FormControl(null);
protected readonly countries = computed(() =>
Object.entries(this.countriesI18n()).map(([id, name]) => ({id, name})),
);
protected readonly matcherVariants: ReadonlyArray> = [
TUI_STRICT_MATCHER as TuiStringMatcher,
(item: Country, search: string) => item.id === search,
];
protected matcher = this.matcherVariants[0]!;
protected readonly textfieldContentVariants = [
'',
'TOP SECRET',
({$implicit: x}: TuiContext) =>
x.name
.split(' ')
.map((x: string) => '*'.repeat(x.length))
.join(' '),
({$implicit: x}: TuiContext) =>
x?.name.includes('i') ? `->${x.name}<-` : x?.name,
];
protected strict = true;
protected readonly handler = (item: Country): boolean =>
item.id.charCodeAt(1) % 3 === 0;
}
```
---
# components/Comment
- **Package**: `KIT`
- **Type**: components
### Example
```html
Birthday gift
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiComment] | `TuiHorizontalDirection | TuiVerticalDirection | ''` | Direction of the comment mark |
### Usage Examples
#### Basic
**Template:**
```html
Birthday gift
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiComment} from '@taiga-ui/kit';
@Component({
imports: [TuiComment],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Customization
**Template:**
```html
Good job
Cashback
Extra payment
Check it out
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiComment} from '@taiga-ui/kit';
@Component({
imports: [TuiComment],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1.25rem;
flex-wrap: wrap;
}
.success {
background: var(--tui-status-positive);
}
.primary {
background: var(--tui-background-accent-1);
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {type TuiHorizontalDirection, type TuiVerticalDirection} from '@taiga-ui/core';
import {TuiComment} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiComment, TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Customization'];
protected readonly directions: ReadonlyArray<
TuiHorizontalDirection | TuiVerticalDirection | ''
> = ['', 'top', 'bottom', 'start', 'end'];
protected direction: TuiHorizontalDirection | TuiVerticalDirection | '' = 'top';
}
```
---
# components/Compass
- **Package**: `KIT`
- **Type**: components
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [degrees] | `number` | Pointer direction in degrees |
| [style.color] | `string` | Custom color |
### Usage Examples
#### Example 1
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCompass} from '@taiga-ui/kit';
@Component({
imports: [TuiCompass],
template: '',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Example 2
**TypeScript:**
```ts
import {Component, ElementRef, viewChild} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {EMPTY_CLIENT_RECT} from '@taiga-ui/cdk';
import {TuiCompass} from '@taiga-ui/kit';
@Component({
imports: [TuiCompass],
template: '',
encapsulation,
changeDetection,
host: {'(document:mousemove)': 'calculate($event)'},
})
export default class Example {
private readonly compass = viewChild(TuiCompass, {read: ElementRef});
protected degrees = 0;
protected calculate(event: MouseEvent): void {
const rect =
this.compass()?.nativeElement.getBoundingClientRect() ?? EMPTY_CLIENT_RECT;
const x = Math.ceil(event.clientX - (rect.left + rect.width / 2));
const y = Math.ceil(event.clientY - (rect.top + rect.height / 2));
this.degrees = Math.atan2(y, x) * (180 / Math.PI) + 90;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiCompass} from '@taiga-ui/kit';
@Component({
imports: [TuiCompass, TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected colorVariants = [
'#428bf9',
'rgb(234, 56, 24)',
'var(--tui-status-positive)',
'',
];
protected color = this.colorVariants[0]!;
protected degrees = 90;
public readonly examples = ['Basic', 'Direction'];
}
```
---
# components/Confirm
- **Package**: `KIT`
- **Type**: components
Confirm is a ready to use Dialog to ask user to confirm an action See this example to learn how to use confirm to prevent data loss on forms inside other modals
### Example
```html
Show
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| content | `PolymorpheusContent` | Content of the confirm |
| [appearance] | `string` | Appearance of the confirming button |
| [no] | `string` | button |
| [yes] | `string` | button |
### Usage Examples
#### Basic
**Template:**
```html
Show
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiResponsiveDialogService} from '@taiga-ui/addon-mobile';
import {TuiButton, TuiNotificationService} from '@taiga-ui/core';
import {TUI_CONFIRM, type TuiConfirmData} from '@taiga-ui/kit';
import {switchMap} from 'rxjs';
@Component({
imports: [TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiResponsiveDialogService);
private readonly alerts = inject(TuiNotificationService);
protected onClick(): void {
const data: TuiConfirmData = {
content:
'This is PolymorpheusContent so it can be template too!',
yes: 'That is great!',
no: 'Who cares?',
};
this.dialogs
.open(TUI_CONFIRM, {
label: 'Do you like Taiga UI?',
size: 's',
data,
})
.pipe(switchMap((response) => this.alerts.open(String(response))))
.subscribe();
}
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiButton, TuiDialogService, TuiNotificationService} from '@taiga-ui/core';
import {TUI_CONFIRM, type TuiConfirmData} from '@taiga-ui/kit';
import {switchMap} from 'rxjs';
@Component({
imports: [TuiButton, TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Example implements TuiConfirmData {
private readonly dialogs = inject(TuiDialogService);
private readonly alerts = inject(TuiNotificationService);
protected readonly routes = DemoRoute;
protected readonly examples = ['Basic'];
protected readonly exampleService = import('./examples/import/service.md');
public readonly appearances = ['primary', 'accent', 'secondary'];
public appearance = this.appearances[0]!;
public no = 'No';
public yes = 'Yes';
public readonly content =
'This is PolymorpheusContent, so it can be anything you like!';
protected onClick(): void {
this.dialogs
.open(TUI_CONFIRM, {
label: 'Are you sure?',
size: 's',
data: this,
})
.pipe(switchMap((response) => this.alerts.open(String(response))))
.subscribe();
}
}
```
---
# components/Copy
- **Package**: `KIT`
- **Type**: components
This component provides an easy way to copy text content to the clipboard. It displays the content normally and shows a copy button on hover, with visual feedback when content is copied.
### Usage Examples
#### Basic
**Template:**
```html
Bank account1234 42069237 88884321
Very very long text that is so long it will wrap to the next line
Very very long text that is so long it will overflow and get truncated
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTitle} from '@taiga-ui/core';
import {TuiCopy} from '@taiga-ui/kit';
@Component({
imports: [TuiCopy, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: block;
inline-size: 17rem;
}
[tuiSubtitle] {
color: var(--tui-text-secondary);
}
span {
display: inline-block;
max-inline-size: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: text-bottom;
}
```
#### Sizes
**Template:**
```html
@for (font of fonts | keyvalue: orderBy; track font) {
}
```
**TypeScript:**
```ts
import {KeyValuePipe} from '@angular/common';
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCopy} from '@taiga-ui/kit';
@Component({
imports: [KeyValuePipe, TuiCopy],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly fonts = {
'heading-h1': '--tui-typography-heading-h1',
'heading-h2': '--tui-typography-heading-h2',
'heading-h3': '--tui-typography-heading-h3',
'heading-h4': '--tui-typography-heading-h4',
'heading-h5': '--tui-typography-heading-h5',
'heading-h6': '--tui-typography-heading-h6',
'body-l': '--tui-typography-body-l',
'body-m': '--tui-typography-body-m',
'body-s': '--tui-typography-body-s',
'body-xs': '--tui-typography-body-xs',
'ui-l': '--tui-typography-ui-l',
'ui-m': '--tui-typography-ui-m',
'ui-s': '--tui-typography-ui-s',
'ui-xs': '--tui-typography-ui-xs',
} as const;
protected orderBy(): number {
return 0;
}
}
```
#### InputCopy
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiInput} from '@taiga-ui/core';
import {TuiCopy, TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiCopy, TuiIcon, TuiInput, TuiInputChip],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = '';
protected multiValue = ['I', 'love', 'Taiga UI'];
}
```
#### With CopyProcessor
**Template:**
```html
Taiga UI
When you copy, the result will have a space prepended and appended: " Taiga UI " .
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCopyProcessor} from '@taiga-ui/cdk';
import {TuiCopy} from '@taiga-ui/kit';
@Component({
imports: [TuiCopy, TuiCopyProcessor],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly processor = (value: string): string => ` ${value} `;
}
```
#### ButtonCopy
**Template:**
```html
Alexander Inkin Name Login
Roman Sedov NameLogin
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiHint} from '@taiga-ui/core';
import {TuiCopy} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiCopy, TuiHint],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly examples = [
'Basic',
'Sizes',
'InputCopy',
'With CopyProcessor',
'ButtonCopy',
];
}
```
---
# components/Counter
- **Package**: `KIT`
- **Type**: components
### Example
```html
{{ content }}
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [appearance] | `string` | Appearance |
| [size] | `TuiSizeS | TuiSizeL` | Size |
| [step] | `number` | Step |
| [min] | `number` | Minimum value |
| [max] | `number` | Maximum value |
| ng-content | `string` | Text inside counter |
### Usage Examples
#### Sizes
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCounter} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiCounter],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 0;
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### Appearances
**Template:**
```html
{{ value | i18nPlural: pluralize }} {{ value | i18nPlural: pluralize }} {{ value | i18nPlural: pluralize }}
```
**TypeScript:**
```ts
import {I18nPluralPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCounter} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, I18nPluralPipe, TuiCounter],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 0;
protected readonly pluralize = {
'=0': '',
'=1': ' pc',
other: ' pcs',
};
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### Adjusts font size
**Template:**
```html
$
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCounter} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiCounter],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly step = 1_000;
protected value = 9_000;
}
```
#### Animated
**Template:**
```html
Steak $25
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTitle} from '@taiga-ui/core';
import {TuiCounter} from '@taiga-ui/kit';
import {TuiCard} from '@taiga-ui/layout';
@Component({
imports: [FormsModule, TuiCard, TuiCounter, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 0;
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.footer {
position: relative;
display: flex;
inline-size: 100%;
block-size: var(--tui-height-m);
align-items: center;
color: var(--tui-text-secondary);
}
.counter {
.transition(inline-size);
position: absolute;
inline-size: 100%;
min-inline-size: var(--tui-height-m);
inset-inline-end: 0;
overflow: hidden;
&_minimized {
inline-size: var(--tui-height-m);
color: transparent;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiCounter} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiCounter, TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = [
'Sizes',
'Appearances',
'Adjusts font size',
'Animated',
];
protected readonly value = 0;
protected readonly step = 1;
protected readonly content = '';
protected readonly sizeVariants = ['l', 'm', 's'] as const;
protected readonly size = this.sizeVariants[0];
protected readonly appearanceVariants = ['primary', 'flat', 'secondary'] as const;
protected readonly appearance = this.appearanceVariants[0];
protected readonly minVariants = [-Infinity, -5, 0] as const;
protected readonly maxVariants = [0, 5, Infinity] as const;
protected readonly min = this.minVariants[0];
protected readonly max = this.maxVariants[2];
}
```
---
# components/DataList
- **Package**: `CORE`
- **Type**: components
DataList allows to make lists or menus
### Example
```html
@for (item of items$ | async; track item) { {{ item }} }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [emptyContent] | `PolymorpheusContent` | Content to display when there are no options inside |
| [size] | `TuiSizeS | TuiSizeL` | Size of items |
### Usage Examples
#### Links
**Template:**
```html
Menu @for (group of groups; track group) { @if (!group.label) { } @for (item of group.items; track item) { {{ item.label }} } }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {RouterLink, RouterLinkActive} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDataList, TuiDropdown} from '@taiga-ui/core';
import {TuiChevron} from '@taiga-ui/kit';
@Component({
imports: [
RouterLink,
RouterLinkActive,
TuiButton,
TuiChevron,
TuiDataList,
TuiDropdown,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly groups = [
{
label: 'Components',
items: [
{
label: 'Input',
routerLink: '/components/input',
},
{
label: 'Select',
routerLink: '/components/select',
},
{
label: 'DataList',
routerLink: '/components/data-list',
},
],
},
{
label: 'Styles',
items: [
{
label: 'Icons',
routerLink: '/icons',
},
{
label: 'Typography',
routerLink: '/typography',
},
],
},
{
label: '',
items: [
{
label: 'Changelog',
routerLink: '/changelog',
},
],
},
];
}
```
#### Submenu
**Template:**
```html
Open @let frenchFries = 'French Fries'; {{ frenchFries }} Burgers Drinks @let item = 'Ice Cream'; {{ item }} @for (burger of burgers; track burger) { {{ burger }} } Nested menu @for (drink of drinks; track drink) { {{ drink }} }
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TuiButton,
TuiDataList,
TuiDialogService,
TuiDropdown,
type TuiSizeL,
type TuiSizeS,
} from '@taiga-ui/core';
import {TuiDataListDropdownManager} from '@taiga-ui/kit';
@Component({
imports: [TuiButton, TuiDataList, TuiDataListDropdownManager, TuiDropdown],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiDialogService);
protected dropdownOpen = false;
protected size: TuiSizeL | TuiSizeS = 's';
protected readonly burgers = [
'Classic',
'Cheeseburger',
'Royal Cheeseburger Quarterpounder',
];
protected readonly drinks = ['Cola', 'Tea', 'Coffee', 'Slurm'];
protected open = false;
protected selectOption(item: string): void {
this.dropdownOpen = false;
this.dialogs.open(`You selected ${item}`).subscribe();
}
}
```
#### Form control
**Template:**
```html
Multi select control
Open @for (burger of burgers; track burger) { {{ burger }} } @for (drink of drinks; track drink) { {{ drink }} }
{{ value }}
Separate toggles
Open
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiCheckbox, TuiDataList, TuiDropdown} from '@taiga-ui/core';
import {TuiButtonSelect, TuiChevron, TuiMultiSelect, TuiSwitch} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiButton,
TuiButtonSelect,
TuiCheckbox,
TuiChevron,
TuiDataList,
TuiDropdown,
TuiMultiSelect,
TuiSwitch,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected first = false;
protected second = true;
protected open = false;
protected label = false;
protected value = [];
protected readonly burgers = ['Hamburger', 'Cheeseburger'];
protected readonly drinks = ['Cola', 'Tea', 'Coffee', 'Slurm'];
}
```
#### Custom list
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiContext, type TuiStringHandler} from '@taiga-ui/cdk';
import {TuiSelectLike, TuiTextfield} from '@taiga-ui/core';
import {TuiChevron, TuiInputChip} from '@taiga-ui/kit';
import {CustomListComponent} from './custom-list';
const INCOME = {
name: 'Income',
items: [
'Donations',
'Product placement',
'Sponsorship',
'Found on the street',
'Unexpected inheritance',
'Investments',
'Color copier',
],
};
const EXPENSES = {
name: 'Expenses',
items: [
'Energy drinks',
'Coffee',
'Ramen',
'Bills',
'Back medicine',
'Warhammer 40000 figurines',
],
};
@Component({
imports: [
CustomListComponent,
FormsModule,
TuiChevron,
TuiInputChip,
TuiSelectLike,
TuiTextfield,
],
templateUrl: './index.html',
styles: `
.control {
width: 320px;
}
`,
encapsulation,
changeDetection,
})
export default class Example {
protected value: string[] = [];
protected readonly items = [INCOME, EXPENSES];
protected readonly valueContent: TuiStringHandler> = ({
$implicit,
}) => {
if (!$implicit.length) {
return 'All';
}
const selected = this.items.find(
({items}) =>
this.value.length === items.length &&
items.every((item) => this.value.includes(item)),
);
return selected ? `${selected.name} only` : `Selected: ${$implicit.length}`;
};
}
```
#### Complex
**Template:**
```html
List of components 💰 Money: 📅 Calendar: {{ dateValue }} 📧 Email: {{ emailValue }} ⌛ Range: {{ rangeValue }} Exchange Rates:
{{ moneyValue / dollar | tuiAmount: 'USD' }}
{{ moneyValue / euro | tuiAmount: 'EUR' }}
Email: {{ emailValue }}
Chosen date: {{ dateValue }}
Range date: {{ rangeValue }}
Dol - {{ dollar }}, Eur - {{ euro }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {TuiDay, TuiDayRange} from '@taiga-ui/cdk';
import {
TuiButton,
TuiCalendar,
TuiDataList,
TuiDropdown,
TuiGroup,
TuiInput,
} from '@taiga-ui/core';
import {TuiDataListDropdownManager, TuiInputDateRange} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiAmountPipe,
TuiButton,
TuiCalendar,
TuiDataList,
TuiDataListDropdownManager,
TuiDropdown,
TuiGroup,
TuiInput,
TuiInputDateRange,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected dropdownOpen = false;
protected dateValue = new TuiDay(2020, 0, 1);
protected euro = 87; // 1 euro = 87 rub
protected dollar = 75; // 1 dollar = 75 rub
protected emailValue = 'mail@mail.ru';
protected moneyValue = 1000;
protected rangeValue = new TuiDayRange(
TuiDay.currentLocal(),
TuiDay.currentLocal().append({year: 1}),
);
protected onDayClick(day: TuiDay): void {
this.dateValue = day;
}
}
```
**LESS:**
```less
.example {
margin-block-end: 0.5rem;
min-inline-size: 20.25rem;
}
.form {
min-inline-size: 18.75rem;
overflow: hidden;
}
.exchange {
margin: 1.5625rem;
}
.group {
max-inline-size: 30.25rem;
}
```
#### Options with long text
**Template:**
```html
Why Taiga UI? @for (group of groups; track group) { @for (item of group.items; track item) { {{ item }} } }
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_MOBILE} from '@ng-web-apis/platform';
import {TuiButton, TuiDataList, TuiDropdown} from '@taiga-ui/core';
import {TuiChevron} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiButton, TuiChevron, TuiDataList, TuiDropdown],
templateUrl: './index.html',
styles: `
.option {
white-space: normal;
}
`,
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
protected readonly groups = [
{
label: 'Advantages of Taiga UI',
items: [
'🧩 Modular and fully-treeshakable. We harnessed the power of Secondary Entry Points mechanism. You can import even just one entity from our library and be sure that there is no redundant code in your bundle.',
'🧙 Agnostic. Our components are very flexible and are ready for any use case. But we take care of basic UX aspects to let you focus on your project features.',
'🦋 Customizable. We use CSS custom properties for all our styling and provide easy methods to customize all UI components.',
'🛠 Well engineered. We are not afraid to use DI to the max. All our components use OnPush, and the whole project is developed with strict TypeScript mode.',
'📦 It is big! We have 130+ components, 100+ directives, dozens of tokens, utils and tools. And it is not over yet.',
'🏗 Maintained! The library started as an internal product in our company. It is used by 50+ projects in production now and it is here to stay.',
],
},
{
label: 'Well-engineered Taiga UI components',
items: ['Calendar', 'Dialog', 'ComboBox', 'Select'],
},
];
protected readonly isMobile = inject(WA_IS_MOBILE);
public control = new FormControl('');
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiDataList, type TuiSizeL, type TuiSizeS} from '@taiga-ui/core';
import {delay, of} from 'rxjs';
@Component({
imports: [TuiDataList, TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
private readonly items = ['Foo', 'Bar', 'Baz'];
protected readonly items$ = inject(WA_IS_E2E)
? of(this.items)
: of(this.items).pipe(delay(1e3));
protected readonly emptyContentVariants = ['Loading...', ''];
protected emptyContent = this.emptyContentVariants[0];
protected readonly sizeVariants: ReadonlyArray = ['s', 'm', 'l'];
protected size = this.sizeVariants[0]!;
protected readonly customList = {
'custom-list/index.ts': import('./examples/4/custom-list/index.ts?raw', {
with: {loader: 'text'},
}),
'custom-list/index.html': import('./examples/4/custom-list/index.html'),
};
protected readonly examples = [
'Links',
'Submenu',
'Form control',
'Custom list',
'Complex',
'Options with long text',
];
}
```
---
# components/DataListWrapper
- **Package**: `KIT`
- **Type**: components
DataListWrapper is an abstraction over DataList to simplify usage in common cases where precise control is not necessary.
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [items] | `readonly T[] | ReadonlyArray | null` | Items to select |
| [itemContent] | `PolymorpheusContent>` | Content of an item |
| [emptyContent] | `PolymorpheusContent` | Content to display when there are no options inside |
| [disabledItemHandler] | `TuiBooleanHandler` | |
| [size] | `TuiSizeL | TuiSizeXS` | Size of items |
| [labels] | `readonly string[]` | Group label |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (itemClick) | `T` | Emits on click on item from datalist |
### Usage Examples
#### Disables items that start with T
**Template:**
```html
@if (items | tuiFilterByInput; as filtered) { @if (input.value) { } }
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiBooleanHandler} from '@taiga-ui/cdk';
import {TuiDropdown, TuiFilterByInputPipe, TuiInput} from '@taiga-ui/core';
import {TuiDataListWrapper} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDataListWrapper,
TuiDropdown,
TuiFilterByInputPipe,
TuiInput,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl('');
protected readonly items = inject('Pythons' as any);
protected readonly disabledItemHandler: TuiBooleanHandler = (v) =>
v.startsWith('T');
}
```
#### Custom item content
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiFilterByInputPipe} from '@taiga-ui/core';
import {
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiStringifyContentPipe,
} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiFilterByInputPipe,
TuiStringifyContentPipe,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl<{name: string; surname: string} | null>(
null,
);
protected readonly items = [
{name: 'John', surname: 'Cleese'},
{name: 'Eric', surname: 'Idle'},
{name: 'Graham', surname: 'Chapman'},
{name: 'Michael', surname: 'Palin'},
{name: 'Terry', surname: 'Gilliam'},
{name: 'Terry', surname: 'Jones'},
];
protected readonly stringify = (item: {name: string; surname: string}): string =>
`${item.name} ${item.surname}`;
}
```
#### Group by labels
**Template:**
```html
@if (input.value) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInput} from '@taiga-ui/core';
import {TuiDataListWrapper} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiDataListWrapper, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl('');
protected readonly items = [
['Caesar', 'Greek', 'Apple and Chicken'],
['Broccoli Cheddar', 'Chicken and Rice', 'Chicken Noodle'],
] as const;
protected labels = ['Salad', 'Soup'] as const;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = [
'Disables items that start with T',
'Custom item content',
'Group by labels',
];
}
```
---
# components/Dialog
- **Package**: `CORE`
- **Type**: components
Customizable modal dialogs
### Example
```html
Show
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [appearance] | `string` | Appearance of dialog |
| [closable] | `boolean` | |
| [dismissible] | `boolean` | if you want prevent closing, for example, with a confirmation prompt. |
| [data] | `string` | |
| [label] | `string` | Heading of dialog |
| [required] | `boolean` | (you can catch it with "catch" operator or onError handler) |
| [size] | `TuiSizeS | TuiSizeL` | Size |
### Usage Examples
#### String
**Template:**
```html
Default button
Custom button
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDialogService} from '@taiga-ui/core';
@Component({
imports: [TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiDialogService);
protected default(): void {
this.dialogs
.open(
'This is a plain string dialog. It supports basic HTML',
{label: 'Heading', size: 's'},
)
.subscribe();
}
protected custom(): void {
this.dialogs
.open('Good, Anakin, Good!', {
label: 'Star wars. Episode III',
size: 's',
data: 'Do it!',
})
.subscribe();
}
}
```
#### Directive
**Template:**
```html
Show dialog
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAutoFocus} from '@taiga-ui/cdk';
import {TuiButton, TuiDialog, TuiInput} from '@taiga-ui/core';
import {TuiForm} from '@taiga-ui/layout';
@Component({
imports: [TuiAutoFocus, TuiButton, TuiDialog, TuiForm, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
}
```
#### Component
**Template:**
```html
Show dialog
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDialogService, TuiNotificationService} from '@taiga-ui/core';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import {switchMap} from 'rxjs';
import {DialogComponent} from './component';
@Component({
imports: [TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly alerts = inject(TuiNotificationService);
private readonly dialogs = inject(TuiDialogService);
protected click(): void {
this.dialogs
.open(new PolymorpheusComponent(DialogComponent), {
label: 'Edit info',
size: 's',
data: 'Alex Inkin',
})
.pipe(switchMap((name) => this.alerts.open(name)))
.subscribe();
}
}
```
#### Confirmation
**Template:**
```html
Show dialog
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiResponsiveDialogService} from '@taiga-ui/addon-mobile';
import {TuiAutoFocus} from '@taiga-ui/cdk';
import {TuiButton, TuiDialogService, TuiInput} from '@taiga-ui/core';
import {TuiConfirmService} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
import {type PolymorpheusContent} from '@taiga-ui/polymorpheus';
@Component({
imports: [FormsModule, TuiAutoFocus, TuiButton, TuiForm, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
// Provide TUI_CONFIRM_DIALOG if you want to override default Confirm dialog
TuiConfirmService,
{
provide: TuiDialogService,
useExisting: TuiResponsiveDialogService,
},
],
})
export default class Example {
private readonly confirm = inject(TuiConfirmService);
private readonly dialogs = inject(TuiDialogService);
protected value = '';
protected onModelChange(value: string): void {
this.value = value;
this.confirm.markAsDirty();
}
protected onClick(content: PolymorpheusContent): void {
const closable = this.confirm.withConfirm({
label: 'Are you sure?',
data: {content: 'Your data will be lost'},
});
this.dialogs
.open(content, {label: 'Application form', closable, dismissible: closable})
.subscribe({
complete: () => {
this.value = '';
this.confirm.markAsPristine();
},
});
}
}
```
#### Closing
**Template:**
```html
Show dialog
Hello!
Don't forget to set ID for accessible label
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {Router} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_DIALOGS_CLOSE, TuiButton, TuiDialog, TuiTitle} from '@taiga-ui/core';
import {TuiHeader} from '@taiga-ui/layout';
import {merge} from 'rxjs';
import {AuthService} from './service';
@Component({
imports: [TuiButton, TuiDialog, TuiHeader, TuiTitle],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
// This has to be added to global providers, shown here for demonstration purposes only
{
provide: TUI_DIALOGS_CLOSE,
useFactory: () => merge(inject(AuthService), inject(Router).events),
},
],
})
export default class Example {
protected readonly auth = inject(AuthService);
protected open = false;
}
```
#### Fullscreen
**Template:**
```html
Show fullscreen
Show scrollable
Fullscreen heading
This is shown fullscreen regardless of content height, you can use margin-top: auto to make sure footer is at the bottom of the page.
Back Action @for (_ of '-'.repeat(50); track $index) {
Title
Description
Secondary title
Another description
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiCell, TuiDialog, TuiTitle} from '@taiga-ui/core';
import {TuiAvatar, TuiProgress} from '@taiga-ui/kit';
import {TuiAppBar, TuiFloatingContainer, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [
TuiAppBar,
TuiAvatar,
TuiButton,
TuiCell,
TuiDialog,
TuiFloatingContainer,
TuiHeader,
TuiProgress,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected fullscreen = false;
protected scrollable = false;
}
```
#### Customization
**Template:**
```html
Show augmented
Show custom
Augmented design
Using both built-in "taiga" appearance and custom "compact" appearance to alter built-in styles
Custom design
Overriding default appearance completely and taking the styles of dialog fully upon oneself, leaving only behavior like focus trap and closable/dismissible interactions to Taiga UI
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate perspiciatis exercitationem nemo velit aliquam voluptates non porro, vel, nihil laudantium sapiente ex omnis corrupti assumenda voluptatibus, architecto sequi saepe consectetur ratione qui. Beatae, sapiente explicabo velit facere repudiandae veniam et soluta quia qui expedita voluptate accusamus dolor adipisci. Illo quia sint consequatur unde nulla fuga eum officiis, impedit dolorem? Vel itaque temporibus nihil quia? Provident earum aperiam autem veritatis hic doloremque unde nesciunt accusantium nisi corrupti.
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [direction] | `TuiHorizontalDirection` | . |
| [overlay] | `boolean` | Show overlay under the drawer. |
| header | `string` | tag inside the drawer. |
| footer | `string` | tag inside the drawer. |
### Usage Examples
#### Full
**Template:**
```html
Toggle
Caption・caption Drawer title
Label
In publishing and graphic design, Lorem ipsum is a placeholder text commonly used.
}
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiLink, TuiPopup, TuiTitle} from '@taiga-ui/core';
import {TuiBadge, TuiDrawer, TuiTabs} from '@taiga-ui/kit';
import {TuiHeader, TuiNavigation} from '@taiga-ui/layout';
@Component({
imports: [
TuiBadge,
TuiButton,
TuiDrawer,
TuiHeader,
TuiLink,
TuiNavigation,
TuiPopup,
TuiTabs,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly open = signal(false);
}
```
#### Modal
**Template:**
```html
Open
Sticky header
Close
@for (_ of '-'.repeat(30); track $index) {
Content
}
```
**TypeScript:**
```ts
import {Component, inject, signal} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDialogService, TuiInput, TuiPopup, TuiTitle} from '@taiga-ui/core';
import {TUI_CONFIRM, TuiDrawer} from '@taiga-ui/kit';
import {TuiHeader} from '@taiga-ui/layout';
import {filter} from 'rxjs';
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiDrawer,
TuiHeader,
TuiInput,
TuiPopup,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly dialogs = inject(TuiDialogService);
protected readonly control = new FormControl('Some value');
protected readonly open = signal(false);
public onClose(): void {
if (this.control.pristine) {
this.open.set(false);
return;
}
this.dialogs
.open(TUI_CONFIRM, {
label: 'Cancel editing form?',
size: 's',
data: {content: 'You have unsaved changes that will be lost'},
})
.pipe(filter(Boolean))
.subscribe(() => {
this.open.set(false);
this.control.reset('Some value');
});
}
}
```
**LESS:**
```less
.drawer {
inline-size: 20rem;
}
.header {
position: sticky;
}
```
### TypeScript
```ts
import {Component, signal} from '@angular/core';
import {ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiButton, type TuiHorizontalDirection, TuiPopup, TuiTitle} from '@taiga-ui/core';
import {TuiDrawer} from '@taiga-ui/kit';
import {TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiDemo,
TuiDrawer,
TuiHeader,
TuiPopup,
TuiTitle,
],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Full', 'Modal'];
protected readonly directionVariants: readonly TuiHorizontalDirection[] = [
'start',
'end',
];
protected readonly open = signal(false);
protected overlay = false;
protected direction: TuiHorizontalDirection = 'end';
public onClose(): void {
this.open.set(false);
}
}
```
---
# components/ElasticContainer
- **Package**: `LAYOUT`
- **Type**: components
A wrapper component that changes its height with transition, depending on the content
### Usage Examples
#### Example 1
**Template:**
```html
{{ current }} Show {{ current === more ? 'less' : 'more' }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLink} from '@taiga-ui/core';
import {TuiElasticContainer} from '@taiga-ui/layout';
@Component({
imports: [TuiElasticContainer, TuiLink],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly more =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin iaculis ipsum in elit mattis consectetur. Maecenas venenatis ligula libero, lobortis rhoncus eros aliquam a. Vivamus blandit scelerisque urna, eu euismod ipsum ultricies non. Aenean fringilla tincidunt luctus. Phasellus eleifend a enim vel aliquet. Donec accumsan orci ac nunc suscipit posuere in a turpis. Fusce hendrerit in lectus eu egestas. Donec nisl ipsum, faucibus sit amet elit eu, vehicula hendrerit purus. Duis tempus pulvinar pharetra. In volutpat, odio dictum ornare iaculis, arcu turpis blandit quam, sit amet malesuada nisl enim nec tortor. In eleifend arcu diam, ut dignissim risus elementum nec. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque pellentesque elit ac feugiat posuere. Aliquam diam ante, condimentum eget nisi nec, suscipit efficitur velit. Cras sed dolor eu tortor dapibus condimentum.';
protected readonly less =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin iaculis ipsum in elit mattis consectetur. Maecenas venenatis ligula libero, lobortis rhoncus eros aliquam a. Vivamus blandit scelerisque urna, eu euismod ipsum ultricies non. Aenean fringilla tincidunt luctus. Phasellus eleifend a enim vel aliquet. Donec accumsan orci ac nunc suscipit posuere in a turpis. Fusce hendrerit in lectus eu egestas.';
protected current = this.less;
protected toggle(): void {
this.current = this.current === this.less ? this.more : this.less;
}
}
```
#### Example 2
**Template:**
```html
Editable content
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiElasticContainer} from '@taiga-ui/layout';
@Component({
imports: [TuiElasticContainer],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.container {
border: 2px solid var(--tui-text-tertiary);
overflow: visible;
&:focus-within {
border-color: var(--tui-background-accent-1);
}
}
.editable {
outline: none;
padding: 1rem;
}
```
#### Example 3
**Template:**
```html
@for (_ of '-'.repeat(content); track $index) {
I'm content
} Add content Remove content
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiElasticContainer} from '@taiga-ui/layout';
@Component({
imports: [TuiButton, TuiElasticContainer],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected content = 1;
protected add(): void {
this.content++;
}
protected remove(): void {
this.content--;
}
}
```
**LESS:**
```less
.visible {
overflow: visible;
}
```
#### Example 4
**Template:**
```html
Add item @for (item of items; track item) {
Expand Nested form Remove
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiArrayRemove} from '@taiga-ui/cdk';
import {TuiButton, TuiExpand, TuiInput} from '@taiga-ui/core';
import {TuiChevron} from '@taiga-ui/kit';
import {TuiElasticContainer} from '@taiga-ui/layout';
@Component({
imports: [
FormsModule,
TuiButton,
TuiChevron,
TuiElasticContainer,
TuiExpand,
TuiInput,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected items = [
{
expanded: false,
value: 'Test 1',
},
{
expanded: false,
value: 'Test 2',
},
];
protected add(): void {
this.items = this.items.concat({expanded: false, value: 'New value'});
}
protected remove(index: number): void {
this.items = tuiArrayRemove(this.items, index);
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.wrapper:not(:last-child) {
margin-block-end: 1rem;
}
.title {
display: flex;
align-items: center;
margin: 0;
}
.remove {
margin-inline-start: auto;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
public readonly examples = [
'Show more',
'contenteditable',
'Add and remove content',
'With animations inside',
];
}
```
---
# components/Error
- **Package**: `CORE`
- **Type**: components
Component for showing arbitrary messages styled as errors, with height and fade transition, as well as displaying form validation errors from controls
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [error] | `TuiValidationError | string | null` | Error value (string or TuiValidationError) |
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {
type AbstractControl,
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiError, TuiInput} from '@taiga-ui/core';
import {TuiSwitch} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
export function passwordValidator(field: AbstractControl): Validators | null {
return field.value && /^[a-z]+$/i.test(field.value)
? null
: {other: 'Only latin letters are allowed'};
}
export function superComputerValidator(field: AbstractControl): Validators | null {
return field.value === '42' ? null : {other: 'Wrong'};
}
@Component({
imports: [FormsModule, ReactiveFormsModule, TuiError, TuiForm, TuiInput, TuiSwitch],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected enabled = false;
protected readonly form = new FormGroup(
{
answer: new FormControl('', [Validators.required, superComputerValidator]),
password: new FormControl('', [Validators.required, passwordValidator]),
},
(control) => (control.invalid ? {other: 'Form is invalid'} : null),
);
constructor() {
this.form.controls.password.valueChanges?.subscribe(() => {
this.form.controls.password.markAsTouched();
});
}
protected get error(): string | null {
return this.enabled ? 'An error' : null;
}
}
```
#### DI
**Template:**
```html
```
**TypeScript:**
```ts
import {isPlatformBrowser} from '@angular/common';
import {Component, inject, PLATFORM_ID, signal} from '@angular/core';
import {toSignal} from '@angular/core/rxjs-interop';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_VALIDATION_ERRORS, TuiError, TuiInput} from '@taiga-ui/core';
import {TuiInputNumber} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
import {map, timer} from 'rxjs';
@Component({
imports: [ReactiveFormsModule, TuiError, TuiForm, TuiInput, TuiInputNumber],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
{
provide: TUI_VALIDATION_ERRORS,
useFactory: () => ({
required: 'Enter this!',
email: 'Enter a valid email',
maxlength: ({requiredLength}: {requiredLength: string}) =>
`Maximum length — ${requiredLength}`,
minlength: ({requiredLength}: {requiredLength: string}) =>
signal(`Minimum length — ${requiredLength}`),
min: isPlatformBrowser(inject(PLATFORM_ID))
? toSignal(
timer(0, 2000).pipe(
map((index) => (index % 2 ? 'Fix please' : 'Min number 3')),
),
)
: 'Min number 3',
}),
},
],
})
export default class Example {
protected readonly form = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
value: new FormControl('', [Validators.minLength(4), Validators.maxLength(4)]),
number: new FormControl(2, [Validators.min(3)]),
});
}
```
#### Template
**Template:**
```html
```
**TypeScript:**
```ts
import {Component, viewChild} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiMarkControlAsTouchedAndValidate} from '@taiga-ui/cdk';
import {TuiButton, TuiCheckbox, TuiError, TuiInput} from '@taiga-ui/core';
import {TuiForm} from '@taiga-ui/layout';
import {type PolymorpheusContent} from '@taiga-ui/polymorpheus';
@Component({
imports: [ReactiveFormsModule, TuiButton, TuiCheckbox, TuiError, TuiForm, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly secret = viewChild('secretError');
protected readonly company = viewChild('companyError');
protected readonly form = new FormGroup({
secret: new FormControl('', [
({value}) => (/^\d{10}$/.test(value || '') ? null : {secret: this.secret}),
Validators.required,
]),
company: new FormControl('', [({value}) => (value ? {inn: this.company} : null)]),
checkbox: new FormControl(false, [Validators.requiredTrue]),
});
protected onSubmit(): void {
tuiMarkControlAsTouchedAndValidate(this.form);
}
}
```
#### Array
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {
type AbstractControl,
FormArray,
FormControl,
FormGroup,
ReactiveFormsModule,
type ValidationErrors,
Validators,
} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiError, TuiTitle} from '@taiga-ui/core';
import {TuiInputPhone} from '@taiga-ui/kit';
import {TuiForm, TuiHeader} from '@taiga-ui/layout';
function phoneValidator({value}: AbstractControl): ValidationErrors | null {
return value.length === 12 ? null : {length: 'Invalid phone number length'};
}
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiError,
TuiForm,
TuiHeader,
TuiInputPhone,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({
phones: new FormArray(
[new FormControl('', [Validators.required, phoneValidator])],
[
(control) =>
(control as FormArray).controls.filter(({valid}) => valid).length < 2
? {length: 'You should add at least 2 phone number'}
: null,
],
),
});
protected addPhone(): void {
this.form.controls.phones.push(
new FormControl('', [Validators.required, phoneValidator]),
);
}
protected removePhone(index: number): void {
this.form.controls.phones.removeAt(index);
}
}
```
#### Asynchronous
**Template:**
```html
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {
type AsyncValidatorFn,
FormControl,
FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiError, TuiInput} from '@taiga-ui/core';
import {TuiForm} from '@taiga-ui/layout';
import {delay, of} from 'rxjs';
function asyncValidatorFn(isE2E: boolean): AsyncValidatorFn {
return ({value}) =>
value && /^[a-z]+$/i.test(value)
? of(null)
: of({error: 'Only latin letters allowed'}).pipe(delay(isE2E ? 0 : 3000));
}
@Component({
imports: [ReactiveFormsModule, TuiError, TuiForm, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({
text: new FormControl(
'русский текст',
[Validators.required],
[asyncValidatorFn(inject(WA_IS_E2E))],
),
});
}
```
#### Pipe
**Template:**
```html
Name
Price
@for (control of controls; track $index) {
{{ data[$index]?.name }}
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCurrencyPipe} from '@taiga-ui/addon-commerce';
import {TuiTable} from '@taiga-ui/addon-table';
import {
TuiError,
TuiHint,
TuiTextfield,
tuiValidationErrorsProvider,
} from '@taiga-ui/core';
import {TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiCurrencyPipe,
TuiError,
TuiHint,
TuiInputNumber,
TuiTable,
TuiTextfield,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiValidationErrorsProvider({
required: 'Enter this!',
max: (context: {max: number}): string => `Too expensive, max ${context.max}`,
}),
],
})
export default class Example {
protected readonly data = [{name: 'Latte'}, {name: 'Cappuccino'}] as const;
protected readonly controls = [
new FormControl(null, [Validators.required, Validators.max(6)]),
new FormControl(null, [Validators.required, Validators.max(5)]),
] as const;
}
```
#### Component
**Template:**
```html
```
**TypeScript:**
```ts
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiContext} from '@taiga-ui/cdk';
import {TuiError, TuiInput, tuiValidationErrorsProvider} from '@taiga-ui/core';
import {injectContext, PolymorpheusComponent} from '@taiga-ui/polymorpheus';
@Component({
template: 'Required: {{ context.$implicit }}',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Error {
protected readonly context = injectContext>();
}
@Component({
imports: [ReactiveFormsModule, TuiError, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiValidationErrorsProvider({required: new PolymorpheusComponent(Error)}),
],
})
export default class Example {
protected readonly test = new FormControl('', [Validators.required]);
}
```
#### Initially touched
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiError, TuiInput} from '@taiga-ui/core';
@Component({
imports: [ReactiveFormsModule, TuiError, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl('', Validators.required);
constructor() {
this.control.markAsTouched();
}
}
```
### TypeScript
```ts
import {Component, type TemplateRef, viewChild} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiValidationError} from '@taiga-ui/cdk';
import {TuiError} from '@taiga-ui/core';
@Component({
imports: [TuiDemo, TuiError],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly errorContent = viewChild>('errorContent');
protected readonly examples = [
'Basic',
'DI',
'Template',
'Array',
'Asynchronous',
'Pipe',
'Component',
'Initially touched',
];
protected readonly errorVariants: readonly string[] = [
'Error as string',
'Error as HTML content',
];
protected selectedError = this.errorVariants[0]!;
protected get error(): TuiValidationError | string | null {
return this.selectedError === this.errorVariants[1]
? new TuiValidationError(this.errorContent())
: this.selectedError;
}
}
```
---
# components/Expand
- **Package**: `Core`
- **Type**: components
### Usage Examples
#### Lazy
**Template:**
```html
Chapman: Mr Wentworth just told me to come in here and say that there was trouble at the mill, that's all - I didn't expect a kind of Spanish Inquisition.
Show/Hide
NOBODY expects the Spanish Inquisition!
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiItem} from '@taiga-ui/cdk';
import {TuiButton, TuiExpand} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiExpand, TuiItem],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected expanded = false;
}
```
#### Eager
**Template:**
```html
Show/Hide @for (_ of '-'.repeat(3); track $index) {
I am eagerly loaded but hidden
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiExpand} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiExpand],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected expanded = false;
}
```
#### Async
**Template:**
```html
Show/Hide @if (loading$ | async) { } @else {
You can use ElasticContainer to animate height changes
Just some more content
Making this section bigger than loader }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_FALSE_HANDLER, TuiItem} from '@taiga-ui/cdk';
import {TuiButton, TuiExpand, TuiLoader} from '@taiga-ui/core';
import {TuiElasticContainer} from '@taiga-ui/layout';
import {map, startWith, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiButton, TuiElasticContainer, TuiExpand, TuiItem, TuiLoader],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly loading$ = timer(2000).pipe(
map(TUI_FALSE_HANDLER),
startWith(true),
);
protected expanded = false;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly examples = ['Lazy', 'Eager', 'Async'];
}
```
### LESS
```less
@import '@taiga-ui/styles/utils';
.tooltip {
position: relative;
&:hover .bubble {
opacity: 1;
}
}
.bubble {
.transition(opacity);
position: absolute;
inset-inline-start: 3.125rem;
inset-block-end: 1.875rem;
inline-size: 15.625rem;
background: var(--tui-text-primary);
color: var(--tui-background-base);
border-radius: 0.25rem;
padding: 0.625rem;
opacity: 0;
&::after {
content: '';
position: absolute;
inset-inline-start: 50%;
inset-block-end: -0.9375rem;
border-block-start: 0.9375rem solid var(--tui-text-primary);
border-inline-start: 0.625rem solid transparent;
border-inline-end: 0.625rem solid transparent;
transform: translate(-0.625rem, 0);
}
}
```
---
# components/Filter
- **Package**: `KIT`
- **Type**: components
Components shows separated items that can be used to filter content on the page. There are also an option with badges.
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [badgeHandler] | `TuiHandler` | to get a number to show by default |
| [content] | `PolymorpheusContent` | Template for custom content in filter |
| [disabledItemHandler] | `TuiBooleanHandler` | |
| [identityMatcher] | `TuiIdentityMatcher` | |
| [items] | `T[]` | for view |
| [size] | `TuiSizeS | TuiSizeL` | Size |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (toggledItem) | `T` | Toggled event of item |
### Usage Examples
#### Basic
**Template:**
```html
Form value: {{ form.value | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiBooleanHandler} from '@taiga-ui/cdk';
import {TuiFilter} from '@taiga-ui/kit';
@Component({
imports: [JsonPipe, ReactiveFormsModule, TuiFilter],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({filters: new FormControl(['Food'])});
protected readonly items = [
'News',
'Food',
'Clothes',
'Popular',
'Goods',
'Furniture',
'Tech',
'Building materials',
];
protected disabledItemHandler: TuiBooleanHandler = (item) => item.length < 7;
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
max-inline-size: 34.375rem;
}
.title {
font: var(--tui-typography-heading-h5);
margin: 0 0 0.75rem;
}
.filters {
display: inline;
}
.tag {
margin: 0 0.25rem 0.25rem 0;
}
```
#### With badges
**Template:**
```html
{{ item.title }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiCell, TuiExpand, TuiLabel, TuiTitle} from '@taiga-ui/core';
import {TuiAvatar, TuiSwitch} from '@taiga-ui/kit';
import {TuiFloatingContainer, TuiSlides} from '@taiga-ui/layout';
@Component({
imports: [
FormsModule,
TuiAvatar,
TuiButton,
TuiCell,
TuiExpand,
TuiFloatingContainer,
TuiLabel,
TuiSlides,
TuiSwitch,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected floating = true;
protected action = false;
protected secondAction = false;
}
```
**LESS:**
```less
.content {
position: relative;
display: block;
inline-size: 18rem;
block-size: 30rem;
overflow: auto;
box-shadow: 0 0.25rem 1.25rem rgba(0, 0, 0, 0.1);
background: var(--tui-background-elevation-1);
}
footer {
margin-inline: 1rem;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiButton, TuiCell, TuiExpand, TuiLabel, TuiTitle} from '@taiga-ui/core';
import {TuiAvatar, TuiSwitch} from '@taiga-ui/kit';
import {TuiFloatingContainer} from '@taiga-ui/layout';
@Component({
imports: [
FormsModule,
TuiAvatar,
TuiButton,
TuiCell,
TuiDemo,
TuiExpand,
TuiFloatingContainer,
TuiLabel,
TuiSwitch,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Example {
protected floating = true;
protected secondAction = false;
protected readonly routes = DemoRoute;
protected readonly examples = [
'Basic',
'Sheet',
'Text',
'Content',
'Overlay',
'Crossfade',
];
protected readonly colors = [
'',
'transparent',
'var(--tui-background-elevation-1)',
'var(--tui-background-base-alt)',
'red',
'#8a8db5',
'rgba(255, 221, 45, 0.8)',
];
protected color = this.colors[0]!;
}
```
### LESS
```less
.content {
position: relative;
display: block;
inline-size: 18rem;
block-size: 22rem;
overflow: auto;
box-shadow: 0 0.25rem 1.25rem rgba(0, 0, 0, 0.1);
background: var(--tui-background-elevation-1);
}
footer {
margin-inline-start: 1rem;
margin-inline-end: 1rem;
}
```
---
# components/Form
- **Package**: `LAYOUT`
- **Type**: components
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAnimated} from '@taiga-ui/cdk';
import {
TuiButton,
TuiError,
TuiIcon,
TuiInput,
TuiNotification,
TuiTitle,
} from '@taiga-ui/core';
import {TuiSegmented, TuiSwitch, TuiTooltip} from '@taiga-ui/kit';
import {TuiCardLarge, TuiElasticContainer, TuiForm, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [
ReactiveFormsModule,
TuiAnimated,
TuiButton,
TuiCardLarge,
TuiElasticContainer,
TuiError,
TuiForm,
TuiHeader,
TuiIcon,
TuiInput,
TuiNotification,
TuiSegmented,
TuiSwitch,
TuiTitle,
TuiTooltip,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({
name: new FormControl('', Validators.required),
email: new FormControl(''),
subscribe: new FormControl(false),
basic: new FormControl(true),
});
}
```
#### Expansive
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiCurrency, TuiCurrencyPipe} from '@taiga-ui/addon-commerce';
import {TuiDay, TuiTime} from '@taiga-ui/cdk';
import {
TuiButton,
TuiCheckbox,
TuiError,
TuiGroup,
TuiIcon,
TuiInput,
TuiLabel,
TuiRadio,
TuiTitle,
} from '@taiga-ui/core';
import {
TuiBlock,
TuiChevron,
TuiDataListWrapper,
TuiInputDate,
TuiInputNumber,
TuiInputPhone,
TuiInputSlider,
TuiPassword,
TuiSelect,
TuiTooltip,
} from '@taiga-ui/kit';
import {TuiForm, TuiHeader} from '@taiga-ui/layout';
class User {
constructor(
protected readonly firstName: string,
protected readonly lastName: string,
) {}
protected toString(): string {
return `${this.firstName} ${this.lastName}`;
}
}
class Account {
constructor(
protected readonly id: string,
protected readonly name: string,
protected readonly amount: number,
protected readonly currency: TuiCurrency,
protected readonly cardSvg: string,
) {}
}
@Component({
imports: [
ReactiveFormsModule,
TuiBlock,
TuiButton,
TuiCheckbox,
TuiChevron,
TuiCurrencyPipe,
TuiDataListWrapper,
TuiError,
TuiForm,
TuiGroup,
TuiHeader,
TuiIcon,
TuiInput,
TuiInputDate,
TuiInputNumber,
TuiInputPhone,
TuiInputSlider,
TuiLabel,
TuiPassword,
TuiRadio,
TuiSelect,
TuiTitle,
TuiTooltip,
],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Example {
protected readonly svgIcons = {
common: 'https://ng-web-apis.github.io/dist/assets/images/common.svg',
universal: 'https://ng-web-apis.github.io/dist/assets/images/universal.svg',
intersection:
'https://ng-web-apis.github.io/dist/assets/images/intersection-observer.svg',
mutation:
'https://ng-web-apis.github.io/dist/assets/images/mutation-observer.svg',
};
protected persons = [new User('Roman', 'Sedov'), new User('Alex', 'Inkin')];
protected accounts = [
new Account('1', 'Common', 24876.55, TuiCurrency.Ruble, this.svgIcons.common),
new Account('2', 'Universal', 335, TuiCurrency.Dollar, this.svgIcons.universal),
new Account(
'3',
'Intersection',
10000,
TuiCurrency.Euro,
this.svgIcons.intersection,
),
new Account('4', 'Mutation', 100, TuiCurrency.Pound, this.svgIcons.mutation),
];
protected form = new FormGroup({
nameValue: new FormControl('', Validators.required),
textValue: new FormControl('', Validators.required),
passwordValue: new FormControl('', Validators.required),
phoneValue: new FormControl('', Validators.required),
moneyValue: new FormControl('100', Validators.required),
periodValue: new FormControl(new TuiDay(2017, 2, 15), Validators.required),
timeValue: new FormControl(new TuiTime(12, 30), Validators.required),
personValue: new FormControl(this.persons[0]),
quantityValue: new FormControl(50_000),
radioValue: new FormControl('with-commission'),
accountWherefrom: new FormControl(null),
accountWhere: new FormControl(null),
checkboxValue: new FormControl(false),
osnoValue: new FormControl(true),
usnValue: new FormControl(false),
eshnValue: new FormControl(false),
envdValue: new FormControl(false),
});
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.stack {
display: flex;
gap: inherit;
flex-direction: column;
}
.ticks-labels {
.tui-slider-ticks-labels();
margin-block-start: -1rem;
}
```
#### Grouped
**Template:**
```html
StepperTabs @switch (segmentedIndex) { @case (0) { Common Network access } @case (1) { CommonNetwork access } } @switch (index()) { @case (0) {
} @case (1) {
} }
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiButton, TuiCheckbox, TuiInput, TuiTitle} from '@taiga-ui/core';
import {TuiSegmented, TuiStepper, TuiTabs} from '@taiga-ui/kit';
import {TuiCardLarge, TuiForm, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiCardLarge,
TuiCheckbox,
TuiForm,
TuiHeader,
TuiInput,
TuiSegmented,
TuiStepper,
TuiTabs,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Example {
protected readonly index = signal(0);
protected segmentedIndex = 0;
protected readonly form = new FormGroup({
name: new FormControl('', Validators.required),
ip: new FormControl('', Validators.required),
});
protected previous(): void {
this.index.update((index) => index - 1);
}
protected next(): void {
this.index.update((index) => index + 1);
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
display: block;
max-inline-size: 32rem;
}
form {
margin: 1.25rem 0;
}
footer {
display: flex;
justify-content: space-between;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly routes = DemoRoute;
protected readonly examples = ['Basic', 'Expansive', 'Grouped'];
}
```
### LESS
```less
.bar {
block-size: 6.25rem;
}
```
---
# components/Group
- **Package**: `CORE`
- **Type**: components
A directive for grouping other components. For example, Input or Button .
### Example
```html
Button 1 Button 2 Button 3
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [collapsed] | `boolean` | by default) |
| [rounded] | `boolean` | The first and the last items are rounded |
| [orientation] | `TuiOrientation` | Horizontal or vertical direction of group |
| [size] | `TuiSizeL` | Size of rounding |
### Usage Examples
#### Inputs
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiError, TuiGroup, TuiIcon, TuiInput} from '@taiga-ui/core';
import {
TuiChevron,
TuiDataListWrapper,
TuiInputChip,
TuiMultiSelect,
TuiTooltip,
} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiChevron,
TuiDataListWrapper,
TuiError,
TuiGroup,
TuiIcon,
TuiInput,
TuiInputChip,
TuiMultiSelect,
TuiTooltip,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items = ['Option 1', 'Option 2', 'Option 3'];
protected readonly form = new FormGroup({
value: new FormControl('', Validators.required),
multi: new FormControl([], Validators.required),
number: new FormControl('', Validators.required),
});
}
```
**LESS:**
```less
.group {
max-inline-size: 30.25rem;
}
```
#### ButtonGroup
**Template:**
```html
Button 1 Button 2
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiGroup} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiGroup],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.group {
max-inline-size: 30rem;
white-space: nowrap;
}
```
#### Vertical group
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiGroup, TuiRadio} from '@taiga-ui/core';
import {TuiBlock} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiBlock, TuiGroup, TuiRadio],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected form = new FormGroup({value: new FormControl('orange')});
}
```
#### Directive
**Template:**
```html
Directive helps to avoid extra layers of HTML
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiGroup, TuiRadio, TuiTitle} from '@taiga-ui/core';
import {TuiBlock} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiBlock, TuiGroup, TuiRadio, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({value: new FormControl('')});
}
```
**LESS:**
```less
.content {
padding: 0.5rem 0;
white-space: normal;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiButton, TuiGroup, type TuiOrientation, type TuiSizeL} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiDemo, TuiGroup],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected rounded = true;
protected collapsed = false;
protected readonly examples = [
'Inputs',
'ButtonGroup',
'Vertical group',
'Directive',
];
protected readonly orientationVariants: readonly TuiOrientation[] = [
'horizontal',
'vertical',
];
protected orientation = this.orientationVariants[0]!;
protected readonly sizeVariants: readonly TuiSizeL[] = ['m', 'l'];
protected size = this.sizeVariants[1]!;
protected readonly routes = DemoRoute;
}
```
### LESS
```less
.group {
max-inline-size: 30.25rem;
}
```
---
# components/Header
- **Package**: `LAYOUT`
- **Type**: components
### Usage Examples
#### Sizes
**Template:**
```html
@for (size of sizes; track $index) {
Title
Subtitle
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s.
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TuiButton,
type TuiSizeL,
type TuiSizeS,
type TuiSizeXS,
TuiTitle,
} from '@taiga-ui/core';
import {TuiBadgeNotification} from '@taiga-ui/kit';
import {TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [TuiBadgeNotification, TuiButton, TuiHeader, TuiTitle],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly sizes = [
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'body-l',
'body-m',
'body-s',
] as const;
protected toBadgeSize(size: string): TuiSizeS {
switch (size) {
case 'body-l':
case 'body-m':
case 'h6':
return 's';
default:
return 'm';
}
}
protected toButtonSize(size: string): TuiSizeL | TuiSizeXS {
switch (size) {
case 'h1':
return 'l';
case 'h2':
case 'h3':
return 'm';
case 'h4':
case 'h5':
return 's';
default:
return 'xs';
}
}
}
```
#### Accessories
**Template:**
```html
Opensource
Taiga UI
Component library
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium ad aliquam asperiores atque autem consequatur cumque cupiditate delectus doloremque doloribus ea earum eius eos error esse est eum eveniet
Maskito
Awesome one
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium ad aliquam asperiores atque autem consequatur cumque cupiditate delectus doloremque doloribus ea earum eius eos error esse est eum eveniet
Polymorpheus
The power of dreams
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiIcon, TuiLink, TuiTitle} from '@taiga-ui/core';
import {
TuiAvatar,
TuiBadge,
TuiBadgeNotification,
TuiSensitive,
TuiTooltip,
} from '@taiga-ui/kit';
import {TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [
TuiAvatar,
TuiBadge,
TuiBadgeNotification,
TuiButton,
TuiHeader,
TuiIcon,
TuiLink,
TuiSensitive,
TuiTitle,
TuiTooltip,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 3rem;
}
```
#### Interactive
**Template:**
```html
Title
Subtitle
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiTitle} from '@taiga-ui/core';
import {TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [TuiHeader, TuiIcon, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.interactive-title {
.transition(color);
cursor: pointer;
& > tui-icon {
.transition(transform);
}
&:hover {
color: var(--tui-text-action);
& > tui-icon {
transform: translate(0.25rem);
}
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {tuiDocExampleOptionsProvider} from '@taiga-ui/addon-doc';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
providers: [tuiDocExampleOptionsProvider({fullsize: true})],
})
export default class Page {
protected readonly examples = ['Sizes', 'Accessories', 'Interactive'];
}
```
---
# components/Icon
- **Package**: `CORE`
- **Type**: components
A component to show icons and color them with CSS. Taiga UI ships with Lucide icons . Same mechanism is used in all iconStart / iconEnd inputs across the library.
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [icon] | `string` | Icon name |
| [background] | `string` | Icon used as a mask to produce 2-color icons |
| [badge] | `string` | Second icon used as a smaller badge in bottom right corner |
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
@Component({
imports: [TuiIcon],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
color: var(--tui-text-action);
--tui-font-icon: 'Material Symbols Outlined';
}
```
#### Parameters
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiNumberFormat, TuiTextfield} from '@taiga-ui/core';
import {TuiInputSlider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInputSlider, TuiNumberFormat, TuiTextfield],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected container = 24;
protected icon = 24;
protected thickness = 2;
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
tui-icon {
margin: 1rem;
align-self: center;
box-shadow: 0 0 0 0.125rem var(--tui-border-normal);
}
```
#### Features
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiIconPipe} from '@taiga-ui/core';
@Component({
imports: [TuiIcon, TuiIconPipe],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
}
tui-icon {
color: var(--tui-text-action);
&:first-child {
color: var(--tui-status-negative);
background-color: var(--tui-status-negative-pale-hover);
}
&::after {
color: var(--tui-status-warning);
}
}
img {
inline-size: 1.5rem;
block-size: 1.5rem;
}
```
#### Bundled
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, tuiIconsProvider} from '@taiga-ui/core';
import heart from '@taiga-ui/icons/src/heart.svg';
import search from '@taiga-ui/icons/src/search.svg';
@Component({
imports: [TuiIcon],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [
tuiIconsProvider({
'@tui.heart': heart,
'@tui.search': search,
}),
],
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
color: var(--tui-text-action);
}
```
#### Resolver
**Template:**
```html
```
**TypeScript:**
```ts
import {Component, SkipSelf} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiStringHandler} from '@taiga-ui/cdk';
import {TUI_ICON_RESOLVER, TuiIcon} from '@taiga-ui/core';
@Component({
imports: [TuiIcon],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [
{
provide: TUI_ICON_RESOLVER,
deps: [[new SkipSelf(), TUI_ICON_RESOLVER]],
useFactory(defaultResolver: TuiStringHandler) {
return (name: string) =>
name.startsWith('@tui.')
? defaultResolver(name)
: `/assets/icons/${name}.svg`;
},
},
],
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
color: var(--tui-text-action);
}
```
#### External
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
@Component({
imports: [TuiIcon],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly icon = `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(
'',
)}`;
}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
color: var(--tui-text-action);
--tui-stroke-width: ~'0px';
}
```
#### Background
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
@Component({
imports: [TuiIcon],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
}
tui-icon {
background: var(--tui-border-normal);
color: var(--tui-status-warning);
&:first-child {
color: var(--tui-status-positive);
}
&:last-child {
color: var(--tui-status-negative);
}
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiIcon} from '@taiga-ui/core';
import {TUI_PREVIEW_ICONS} from '@taiga-ui/kit';
@Component({
imports: [TuiDemo, TuiIcon],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly used = Object.values(inject(TUI_PREVIEW_ICONS)); // compatibility with proprietary icons
protected readonly iconVariants = [
'@tui.info',
'@tui.heart',
...this.used,
'https://raw.githubusercontent.com/MarsiBarsi/readme-icons/main/github.svg',
"\"data:image/svg+xml,\"",
'',
];
protected readonly backgroundVariants = [
'',
'@tui.info-filled',
'@tui.heart-filled',
...new Set(
this.used.map((icon) => (icon.includes('filled') ? icon : `${icon}-filled`)),
),
];
protected readonly examples = [
'Basic',
'Parameters',
'Features',
'Bundled',
'Resolver',
'External',
'Background',
];
protected icon = '@tui.heart';
protected background = '';
protected badge = '';
}
```
### LESS
```less
tui-icon {
color: var(--tui-status-info);
&::after {
color: var(--tui-status-warning);
}
}
```
---
# components/Input
- **Package**: `CORE`
- **Type**: components
Input is a basic string textfield. All other input components are built on its basis.
### Example
```html
@if (textfield.size !== 's') { }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [(ngModel)] | `string` | Value (or reactive control directives) |
| [filler] | `string` | Filler |
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiInput} from '@taiga-ui/core';
import {TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInput, TuiTooltip],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = '';
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### States
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiInput} from '@taiga-ui/core';
import {TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInput, TuiTooltip],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 'Test';
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### Dropdown
**Template:**
```html
@if (items | tuiFilterByInput; as filtered) { @if (input.value && filtered.length) { } }
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TuiDropdown,
TuiFilterByInputPipe,
TuiIcon,
TuiInput,
TuiSelectLike,
} from '@taiga-ui/core';
import {TuiChevron, TuiDataListWrapper, TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiChevron,
TuiDataListWrapper,
TuiDropdown,
TuiFilterByInputPipe,
TuiIcon,
TuiInput,
TuiSelectLike,
TuiTooltip,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = '';
protected readonly items = inject('Pythons' as any);
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### InputPassword
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiInput} from '@taiga-ui/core';
import {TuiPassword} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInput, TuiPassword],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = '';
}
```
#### Custom cleaner
**Template:**
```html
@if (value) { Clear }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButtonX, TuiInput} from '@taiga-ui/core';
@Component({
imports: [FormsModule, TuiButtonX, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = '';
protected clear(): void {
console.info('The custom clear handler has been invoked');
this.value = '';
}
}
```
#### Mask
**Template:**
```html
@if (value) { Clear }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {MaskitoDirective} from '@maskito/angular';
import {type MaskitoOptions} from '@maskito/core';
import {
maskitoAddOnFocusPlugin,
maskitoCaretGuard,
maskitoNumberOptionsGenerator,
maskitoRemoveOnBlurPlugin,
} from '@maskito/kit';
import {TuiButtonX, TuiInput} from '@taiga-ui/core';
const postfix = ' rad';
const numberOptions = maskitoNumberOptionsGenerator({
postfix,
decimalSeparator: ',',
maximumFractionDigits: 8,
min: 0,
});
@Component({
imports: [FormsModule, MaskitoDirective, TuiButtonX, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = Math.PI.toFixed(8);
protected readonly options: MaskitoOptions = {
...numberOptions,
plugins: [
...numberOptions.plugins,
maskitoCaretGuard((value) => [0, value.length - postfix.length]),
maskitoAddOnFocusPlugin(postfix),
maskitoRemoveOnBlurPlugin(postfix),
],
};
protected clear(): void {
this.value = postfix;
}
}
```
#### Long label
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInput} from '@taiga-ui/core';
import {TuiFade, TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiFade, TuiInput, TuiInputChip],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 'Single string';
protected chips = ['Single string'];
}
```
**LESS:**
```less
:host {
display: flex;
inline-size: 17rem;
flex-direction: column;
gap: 1rem;
tui-textfield[data-focus='true'] {
align-items: center;
[tuiLabel] {
transition: none;
white-space: normal;
}
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiIcon, TuiInput} from '@taiga-ui/core';
import {TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiDemo,
TuiDocControl,
TuiDocIcons,
TuiDocInput,
TuiDocTextfield,
TuiIcon,
TuiInput,
TuiTooltip,
],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected routes = DemoRoute;
protected readonly examples = [
'Basic',
'States',
'Dropdown',
'InputPassword',
'Custom cleaner',
'Mask',
'Long label',
];
protected value = '';
protected filler = '';
}
```
---
# components/InputCard
- **Package**: `ADDON-COMMERCE`
- **Type**: components
InputCard can be used with InputExpire and InputCVC to input a card. Use tuiCreateLuhnValidator(message) to create a Validator that uses Luhn algorithm
### Example
```html
@if (textfield.size !== 's') { }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [(ngModel)] | `string` | Card number (also works with a reactive control) |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (binChange) | `string | null` | BIN value (card first 6 symbols) |
### Usage Examples
#### Form
**Template:**
```html
{{ form.value | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component, inject, signal} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiCreateLuhnValidator, TuiInputCard} from '@taiga-ui/addon-commerce';
import {
TuiError,
TuiNotificationService,
tuiTextfieldOptionsProvider,
} from '@taiga-ui/core';
@Component({
imports: [JsonPipe, ReactiveFormsModule, TuiError, TuiInputCard],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiTextfieldOptionsProvider({cleaner: signal(true)})],
})
export default class Example {
private readonly alerts = inject(TuiNotificationService);
protected readonly form = new FormGroup({
card: new FormControl('', tuiCreateLuhnValidator('Card number is invalid')),
expire: new FormControl(''),
cvc: new FormControl(''),
});
protected onBinChange(bin: string | null): void {
this.alerts.open(String(bin), {label: '(binChange)'}).subscribe();
}
}
```
**LESS:**
```less
form {
display: flex;
flex-direction: column;
gap: 1rem;
}
section {
display: flex;
gap: inherit;
tui-textfield {
flex: 1;
}
}
```
#### Card
**Template:**
```html
@if (card === '1234123412341234') { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputCard, TuiThumbnailCard} from '@taiga-ui/addon-commerce';
@Component({
imports: [FormsModule, TuiInputCard, TuiThumbnailCard],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected card = '1234123412341234';
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiInputCard} from '@taiga-ui/addon-commerce';
@Component({
imports: [
FormsModule,
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocIcons,
TuiDocInput,
TuiDocTextfield,
TuiInputCard,
],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected card = '';
protected readonly examples = ['Form', 'Card'];
}
```
---
# components/InputCardGroup
- **Package**: `ADDON-COMMERCE`
- **Type**: components
InputCardGroup is used to input a card as a separated control
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [cardValidator] | `TuiBooleanHandler` | Custom card validator for moving focus to the next field |
| [codeLength] | `3 | 4` | Code length |
| [compact] | `boolean` | Manually set compact mode (forced on mobile resolution) |
| [icon] | `PolymorpheusContent` | Custom card icon |
| [id] | `string` | accordingly). Auto-generated when not provided. |
| [inputs] | `TuiCardInputs` | Toggle availability of inputs |
| [placeholder] | `string` | Placeholder |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (binChange) | `string | null` | BIN value (card first 6 symbols) |
### Usage Examples
#### With validation
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {
type TuiCard,
tuiCardExpireValidator,
tuiCardNumberValidator,
TuiInputCardGroup,
} from '@taiga-ui/addon-commerce';
import {TuiError} from '@taiga-ui/core';
@Component({
imports: [ReactiveFormsModule, TuiError, TuiInputCardGroup],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(null, [
tuiCardNumberValidator,
tuiCardExpireValidator,
]);
protected get card(): string | null {
const value = this.control.value?.card || '';
if (value.length < 7) {
return null;
}
switch (value.charAt(0)) {
case '0':
case '1':
case '2':
return 'https://ng-web-apis.github.io/dist/assets/images/common.svg';
case '3':
case '4':
case '5':
return 'https://ng-web-apis.github.io/dist/assets/images/geolocation.svg';
case '6':
case '7':
return 'https://ng-web-apis.github.io/dist/assets/images/intersection-observer.svg';
case '8':
case '9':
default:
return 'https://ng-web-apis.github.io/dist/assets/images/payment-request.svg';
}
}
}
```
#### With saved cards
**Template:**
```html
Toggle dropdown
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiInputCardGroup, TuiThumbnailCard} from '@taiga-ui/addon-commerce';
import {TuiButton, TuiDataList, TuiIcon, TuiTextfield, TuiTitle} from '@taiga-ui/core';
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiDataList,
TuiIcon,
TuiInputCardGroup,
TuiTextfield,
TuiThumbnailCard,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Example {
protected readonly items = [
{card: '4321***1234', expire: '12/21', name: 'Salary', bank: 'Wachovia Bank'},
{
card: '8765***5678',
expire: '03/42',
cvc: '***',
name: 'Tips',
bank: 'Bank of America',
},
{card: '4200***9000', name: 'Dogecoins', bank: 'Crypto'},
];
protected readonly card = new FormGroup({meta: new FormControl(this.items[0])});
protected open = false;
}
```
**LESS:**
```less
.new {
inline-size: 2.5rem;
block-size: 1.625rem;
border-radius: 0.25rem;
background: var(--tui-background-neutral-1);
color: var(--tui-text-action);
}
.card {
background: var(--tui-chart-categorical-01);
button:nth-child(4) & {
background: var(--tui-chart-categorical-05);
}
}
.label {
margin: 0 auto 0 0.75rem;
}
```
#### With custom card template
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {
type TuiCard,
tuiCardExpireValidator,
tuiCardNumberValidator,
TuiInputCardGroup,
} from '@taiga-ui/addon-commerce';
import {TuiError} from '@taiga-ui/core';
import {PolymorpheusTemplate} from '@taiga-ui/polymorpheus';
@Component({
imports: [PolymorpheusTemplate, ReactiveFormsModule, TuiError, TuiInputCardGroup],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(null, [
tuiCardNumberValidator,
tuiCardExpireValidator,
]);
}
```
#### Custom form state
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiCard, TuiInputCardGroup} from '@taiga-ui/addon-commerce';
@Component({
imports: [ReactiveFormsModule, TuiInputCardGroup],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected control = new FormControl({
card: '',
expire: '',
cvc: '***',
});
}
```
#### Custom labels
**Template:**
```html
@if (!control.value) { Enter card number }
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {
TUI_INPUT_CARD_GROUP_TEXTS,
type TuiCard,
TuiInputCardGroup,
} from '@taiga-ui/addon-commerce';
@Component({
imports: [ReactiveFormsModule, TuiInputCardGroup],
templateUrl: './index.html',
changeDetection,
providers: [
{
provide: TUI_INPUT_CARD_GROUP_TEXTS,
useValue: signal({
cardNumberText: 'Number',
expiryText: 'MM/YY',
cvcText: 'Code',
}),
},
],
})
export default class Example {
protected readonly control = new FormControl>({
card: '558620******2158',
expire: '12/25',
});
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {
TUI_INPUT_CARD_GROUP_OPTIONS,
type TuiCard,
type TuiCardInputs,
TuiInputCardGroup,
TuiThumbnailCard,
} from '@taiga-ui/addon-commerce';
import {tuiIsString} from '@taiga-ui/cdk';
import {type PolymorpheusContent} from '@taiga-ui/polymorpheus';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiInputCardGroup,
TuiThumbnailCard,
],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class PageComponent {
private readonly options = inject(TUI_INPUT_CARD_GROUP_OPTIONS);
protected readonly examples = [
'With validation',
'With saved cards',
'With custom card template',
'Custom form state',
'Custom labels',
];
protected readonly cards: Record = {
common: 'https://ng-web-apis.github.io/dist/assets/images/common.svg',
universal: 'https://ng-web-apis.github.io/dist/assets/images/universal.svg',
mutation:
'https://ng-web-apis.github.io/dist/assets/images/mutation-observer.svg',
};
protected iconVariants: readonly string[] = Object.keys(this.cards);
protected iconSelected: PolymorpheusContent = null;
protected id = '';
protected placeholder = this.options.placeholder;
protected readonly codeLengthVariants = [3, 4] as const;
protected codeLength: 3 | 4 = this.codeLengthVariants[0];
protected readonly inputsVariants: readonly TuiCardInputs[] = [
{cvc: true, expire: true},
{cvc: false, expire: true},
{cvc: false, expire: false},
{cvc: true, expire: false},
];
protected inputs = this.options.inputs;
protected compact = false;
protected formControl = new FormControl(null);
protected get icon(): PolymorpheusContent {
return tuiIsString(this.iconSelected)
? this.cards[this.iconSelected]
: this.iconSelected;
}
protected getContentVariants(
template: PolymorpheusContent,
): readonly PolymorpheusContent[] {
return [...this.iconVariants, template];
}
}
```
### LESS
```less
@import '@taiga-ui/styles/utils';
.form {
display: flex;
flex-wrap: wrap;
}
.control {
flex: 1;
margin-block-end: 0.25rem;
&:not(:last-child) {
margin-inline-end: 1.25rem;
}
}
.error {
min-inline-size: 100%;
}
.title {
font: var(--tui-typography-heading-h5);
}
.card {
background: #87ceeb;
}
```
---
# components/InputChip
- **Package**: `KIT`
- **Type**: components
InputChip uses specifically modified Input to represent array of selectable items.
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [unique] | `boolean` | Ability to enter unique or non-unique tags |
| [separator] | `string` | String or RegExp to separate tags |
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputChip],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value: string[] = inject('Pythons' as any);
protected readonly separator = /[\s,;]/;
}
```
**LESS:**
```less
:host {
display: flex;
inline-size: 19rem;
flex-direction: column;
gap: 1rem;
}
```
#### Virtual scroll
**Template:**
```html
{{ item }}
Select bazillion
```
**TypeScript:**
```ts
import {
CdkFixedSizeVirtualScroll,
CdkVirtualForOf,
CdkVirtualScrollViewport,
} from '@angular/cdk/scrolling';
import {Component, computed, viewChild} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_DEFAULT_MATCHER} from '@taiga-ui/cdk';
import {
TuiButton,
TuiDataList,
TuiInput,
TuiScrollable,
TuiSelectLike,
type TuiTextfieldComponent,
} from '@taiga-ui/core';
import {TuiChevron, TuiInputChip, TuiMultiSelect} from '@taiga-ui/kit';
@Component({
imports: [
CdkFixedSizeVirtualScroll,
CdkVirtualForOf,
CdkVirtualScrollViewport,
FormsModule,
ReactiveFormsModule,
TuiButton,
TuiChevron,
TuiDataList,
TuiInput,
TuiInputChip,
TuiMultiSelect,
TuiScrollable,
TuiSelectLike,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly filter = viewChild>('filter');
protected readonly items: string[] = Array.from({length: 3000}).map(
(_, i) => `Item #${i}`,
);
protected value: string[] = [];
protected readonly filtered = computed((value = this.filter()?.value()) =>
value
? this.items.filter((item) => TUI_DEFAULT_MATCHER(item, value))
: this.items,
);
protected get content(): string {
return this.value.length
? `Selected ${this.value.length} out of ${this.items.length}`
: '';
}
protected onClick(): void {
this.value = this.items.filter((_, i) => i < 2000);
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils.less';
[tuiScrollable] {
.scrollbar-hidden();
}
```
#### Stringify
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDataList, TuiTextfield} from '@taiga-ui/core';
import {TuiChevron, TuiInputChip, TuiMultiSelect} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiChevron,
TuiDataList,
TuiInputChip,
TuiMultiSelect,
TuiTextfield,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: string[] = [];
protected items = [
{nickname: 'a.inkin', name: 'Alex Inkin'},
{nickname: 'r.sedov', name: 'Roman Sedov'},
];
protected readonly stringify = (value: string): string =>
this.items.find((item) => item.nickname === value)?.name ?? '';
}
```
#### Chips
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputChip],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = ['I', 'love', 'Angular'];
}
```
**LESS:**
```less
:host {
display: flex;
inline-size: 19rem;
flex-direction: column;
gap: 1rem;
}
```
#### Disabled items
**Template:**
```html
@if (control.value.length > 2) { Clear }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiBooleanHandler} from '@taiga-ui/cdk';
import {TuiButtonX} from '@taiga-ui/core';
import {TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiButtonX, TuiInputChip],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly required = ['Required', 'Obligatory'];
protected readonly control = new FormControl(this.required.concat('Removable'), {
nonNullable: true,
});
protected readonly handler: TuiBooleanHandler = (item) =>
this.required.includes(item);
}
```
#### MultiSelect
**Template:**
```html
Using checkboxes in the dropdown and making the textfield non-writable Multi Select Conditional input in textfield Multi Select with if/else @if (filter) { } @else { }
Toggle filter Working with objects @for (user of users | tuiFilterByInput; track user) { {{ user.name }} } @for (user of more | tuiFilterByInput; track user) { {{ user.name }} }
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiIsString} from '@taiga-ui/cdk';
import {
TuiCheckbox,
TuiDataList,
TuiFilterByInputPipe,
TuiSelectLike,
TuiTextfield,
} from '@taiga-ui/core';
import {
TuiChevron,
TuiDataListWrapper,
TuiHideSelectedPipe,
TuiInputChip,
TuiMultiSelect,
} from '@taiga-ui/kit';
interface User {
readonly name: string;
readonly index: number;
}
@Component({
imports: [
FormsModule,
TuiCheckbox,
TuiChevron,
TuiDataList,
TuiDataListWrapper,
TuiFilterByInputPipe,
TuiHideSelectedPipe,
TuiInputChip,
TuiMultiSelect,
TuiSelectLike,
TuiTextfield,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected arbitrary: string[] = [];
protected pythons: string[] = [];
protected multi: string[] = [];
protected conditionalMulti: string[] = [];
protected objects: User[] = [];
protected filter = false;
protected readonly items: string[] = inject('Pythons' as any);
protected readonly users = this.items.map((name, index) => ({name, index}));
protected readonly more = [
{name: 'Carol Cleveland', index: -1},
{name: 'Neil Innes', index: -2},
];
protected readonly strings = tuiIsString;
protected readonly stringify = ({name}: User): string => name;
protected readonly disabled = (item: string): boolean => !this.items.includes(item);
}
```
**LESS:**
```less
:host {
display: flex;
inline-size: 19rem;
flex-direction: column;
gap: 1rem;
}
```
#### Customization
**Template:**
```html
5 ? 'negative' : 'positive'" [editable]="false" [iconStart]="context.item.length > 5 ? '@tui.info' : ''" [tuiHint]="context.item.length > 5 ? 'Please keep it under 6 chars' : ''" />
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiHint, TuiIcon} from '@taiga-ui/core';
import {TuiInputChip, TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiHint, TuiIcon, TuiInputChip, TuiTooltip],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(['Keep', 'it', 'simple']);
}
```
#### Mask
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {MaskitoDirective} from '@maskito/angular';
import {type MaskitoOptions} from '@maskito/core';
import {TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [MaskitoDirective, ReactiveFormsModule, TuiInputChip],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl();
protected readonly mask: MaskitoOptions = {mask: [/\d/, /\d/, /\d/]};
}
```
#### Direction
**Template:**
```html
كلمات عربية
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputChip],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
host: {dir: 'rtl'},
})
export default class Example {
protected value = [
'حبيبي',
'صباح الخير',
'من فضلك',
'شكرا',
'أنا آسف',
'تصبح على خير',
];
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
inline-size: 19rem;
}
```
#### Mobile
**Template:**
```html
Mobile dropdown with writable input
@if (items | tuiFilterByInput; as items) { Done } Mobile sheet with options Native MultiSelect
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdownMobile, TuiDropdownSheet} from '@taiga-ui/addon-mobile';
import {type TuiIdentityMatcher} from '@taiga-ui/cdk';
import {TuiButton, TuiFilterByInputPipe, TuiSelectLike} from '@taiga-ui/core';
import {
TuiChevron,
TuiDataListWrapper,
TuiInputChip,
TuiMultiSelect,
} from '@taiga-ui/kit';
interface User {
readonly name: string;
readonly index: number;
}
@Component({
imports: [
FormsModule,
TuiButton,
TuiChevron,
TuiDataListWrapper,
TuiDropdownMobile,
TuiDropdownSheet,
TuiFilterByInputPipe,
TuiInputChip,
TuiMultiSelect,
TuiSelectLike,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items: string[] = inject('Pythons' as any);
protected readonly users = this.items.map((name, index) => ({name, index}));
protected writable: string[] = [];
protected sheet: string[] = [];
protected native: User[] = [{name: this.items[0] || '', index: 0}];
protected readonly disabled = (item: string): boolean => !this.items.includes(item);
protected readonly identity: TuiIdentityMatcher = (a, b) => a.index === b.index;
protected readonly stringify = ({name}: User): string => name;
}
```
**LESS:**
```less
:host {
display: flex;
inline-size: 19rem;
flex-direction: column;
gap: 1rem;
}
```
#### Table
**Template:**
```html
Options
Multi Select with dropdown
Multi Select
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormControl, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTable} from '@taiga-ui/addon-table';
import {TuiSelectLike} from '@taiga-ui/core';
import {
TuiChevron,
TuiDataListWrapper,
TuiInputChip,
TuiMultiSelect,
} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
ReactiveFormsModule,
TuiChevron,
TuiDataListWrapper,
TuiInputChip,
TuiMultiSelect,
TuiSelectLike,
TuiTable,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items: string[] = inject('Pythons' as any);
protected readonly multiControl = new FormControl(null, {
validators: Validators.required,
});
protected readonly multiControl2 = new FormControl(null, {
validators: Validators.required,
});
}
```
**LESS:**
```less
:host {
display: flex;
inline-size: 19rem;
flex-direction: column;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocIcons,
TuiDocInput,
TuiDocTextfield,
TuiInputChip,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class PageComponent {
protected readonly routes = DemoRoute;
protected readonly control = new FormControl();
protected unique = true;
protected separator = ',';
protected readonly examples = [
'Basic',
'Chips',
'Disabled items',
'MultiSelect',
'Customization',
'Mask',
'Direction',
'Mobile',
'Table',
'Virtual scroll',
'Stringify',
];
}
```
---
# components/InputColor
- **Package**: `KIT`
- **Type**: components
InputColor = Textfield + type="color" + Maskito + ❤️
### Example
```html
@if (textfieldDoc.size !== 's') { Choose color }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [align] | `TuiHorizontalDirection` | Alignment of the color picker |
| [format] | `'hex' | 'hexa'` | Color format |
### Usage Examples
#### Basic
**Template:**
```html
Choose color
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputColor} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiInputColor],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl();
}
```
#### Opacity
**Template:**
```html
Choose color0%Opacity100%
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputColor, tuiInputColorOptionsProvider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputColor],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiInputColorOptionsProvider({format: 'hexa', align: 'end'})],
})
export default class Example {
protected value = '#ff7f50cc';
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils.less';
.label {
.tui-slider-ticks-labels();
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TUI_INPUT_COLOR_OPTIONS, TuiInputColor} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocInput,
TuiDocTextfield,
TuiInputColor,
],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent {
protected readonly control = new FormControl();
protected readonly options = inject(TUI_INPUT_COLOR_OPTIONS);
protected readonly examples = ['Basic', 'Opacity'];
protected readonly aligns = ['start', 'end'] as const;
protected align = this.options.align;
protected readonly formats = ['hex', 'hexa'] as const;
protected format = this.options.format;
}
```
---
# components/InputDate
- **Package**: `KIT`
- **Type**: components
InputDate = Textfield + Calendar + ❤️
### Example
```html
@if (textfieldDoc.size !== 's') { Choose date }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [min] | `TuiDay` | Min date |
| [max] | `TuiDay` | Max date |
### Usage Examples
#### Basic
**Template:**
```html
Choose a date
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiInputDate} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDate],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = new TuiDay(2017, 0, 15);
}
```
#### Calendar customization
**Template:**
```html
Choose a date
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiDay, TuiMonth} from '@taiga-ui/cdk';
import {type TuiMarkerHandler} from '@taiga-ui/core';
import {TuiInputDate} from '@taiga-ui/kit';
const CHEAPEST_TICKET: [string] = ['var(--tui-status-positive)'];
@Component({
imports: [FormsModule, TuiInputDate],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: TuiDay | null = null;
protected readonly defaultActiveMonth = signal(new TuiMonth(2000, 0));
protected readonly markerHandler: TuiMarkerHandler = (day: TuiDay) =>
day.day % 5 === 0 ? CHEAPEST_TICKET : [];
}
```
#### Custom dropdown
**Template:**
```html
Choose a date Today
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiButton} from '@taiga-ui/core';
import {TuiInputDate} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiButton, TuiInputDate],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(null);
protected readonly today = TuiDay.currentLocal();
}
```
**LESS:**
```less
.button {
inline-size: 100%;
border-radius: 0;
box-shadow: 0 -1px var(--tui-border-normal);
}
```
#### Validation
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiError, tuiValidationErrorsProvider} from '@taiga-ui/core';
import {TuiInputDate, TuiUnfinishedValidator} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiError,
TuiForm,
TuiInputDate,
TuiUnfinishedValidator,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiValidationErrorsProvider({
tuiUnfinished: 'Either fill this or leave blank',
required: 'This field is required',
}),
],
})
export default class Example {
protected readonly form = new FormGroup({
required: new FormControl(null, Validators.required),
optional: new FormControl(),
});
}
```
#### Value transformer
**Template:**
```html
Choose a date
{{ value | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiInputDate, tuiInputDateOptionsProvider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, JsonPipe, TuiInputDate],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputDateOptionsProvider({
valueTransformer: {
fromControlValue: (value: Date | null): TuiDay | null =>
value && TuiDay.fromUtcNativeDate(value),
toControlValue: (value: TuiDay | null): Date | null =>
value?.toUtcNativeDate() || null,
},
}),
],
})
export default class Example {
protected value: Date | null = null;
}
```
#### Format
**Template:**
```html
Choose a date
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {tuiDateFormatProvider} from '@taiga-ui/core';
import {TuiInputDate} from '@taiga-ui/kit';
@Component({
selector: 'example-5',
imports: [FormsModule, ReactiveFormsModule, TuiInputDate],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiDateFormatProvider({mode: 'mm/dd/yyyy', separator: '/'})],
})
export default class Example {
protected value = new TuiDay(2017, 0, 15);
}
```
#### Mobile
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdownSheet, TuiMobileCalendarDropdown} from '@taiga-ui/addon-mobile';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiInputDate} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
@Component({
imports: [
ReactiveFormsModule,
TuiDropdownSheet,
TuiForm,
TuiInputDate,
TuiMobileCalendarDropdown,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({
native: new FormControl(TuiDay.currentLocal()),
mobile: new FormControl(TuiDay.currentLocal().append({day: 1})),
fullscreen: new FormControl(TuiDay.currentLocal().append({day: 2})),
});
}
```
#### Limits
**Template:**
```html
Choose a date
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiInputDate} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDate],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: TuiDay | null = null;
protected readonly today = TuiDay.currentLocal();
protected readonly min = new TuiDay(this.today.year, this.today.month, 1);
protected readonly max = this.min.append({month: 1, day: -1});
protected readonly handler = (day: TuiDay): boolean => day.daySame(this.today);
}
```
#### Datalist
**Template:**
```html
Select date @for (date of dates | keyvalue: asIs; track date) { {{ date.key }} }
```
**TypeScript:**
```ts
import {KeyValuePipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiDataList} from '@taiga-ui/core';
import {TuiInputDate} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, KeyValuePipe, TuiDataList, TuiInputDate],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly today = TuiDay.currentLocal();
protected value: TuiDay | null = this.today;
protected dates = {
Today: this.today,
Tomorrow: this.today.append({day: 1}),
'End of week': this.today.append({day: 6 - this.today.dayOfWeek()}),
'End of month': new TuiDay(this.today.year, this.today.month, 1).append({
month: 1,
day: -1,
}),
'End of Year': new TuiDay(this.today.year + 1, 0, 1).append({day: -1}),
};
protected asIs(): number {
return 0;
}
}
```
**LESS:**
```less
section {
display: flex;
}
tui-data-list {
inline-size: 11rem;
box-shadow: -1px 0 var(--tui-border-normal);
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocItemsHandlers} from '@demo/components/items-handlers';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TUI_FIRST_DAY, TUI_LAST_DAY, TuiDay} from '@taiga-ui/cdk';
import {TuiDropdown} from '@taiga-ui/core';
import {TuiInputDate} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocDropdown,
TuiDocIcons,
TuiDocInput,
TuiDocItemsHandlers,
TuiDocTextfield,
TuiDropdown,
TuiInputDate,
],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly examples = [
'Basic',
'Calendar customization',
'Custom dropdown',
'Validation',
'Value transformer',
'Format',
'Mobile',
'Limits',
'Datalist',
];
protected readonly control = new FormControl();
protected readonly routes = DemoRoute;
protected readonly dates = [
TUI_FIRST_DAY,
TuiDay.currentLocal(),
TuiDay.currentLocal().append({year: 1, month: 1}),
TuiDay.currentLocal().append({year: -1, month: -1}),
TUI_LAST_DAY,
] as const;
protected min = this.dates[0];
protected max = this.dates[4];
protected readonly handler = (item: TuiDay): boolean => item.dayOfWeek() > 4;
}
```
---
# components/InputDateMulti
- **Package**: `KIT`
- **Type**: components
InputDateMulti uses specifically modified InputChip to represent array of dates.
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [min] | `TuiDay` | Min date |
| [max] | `TuiDay` | Max date |
### Usage Examples
#### Example 1
**Template:**
```html
Plain strings
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiInputDateMulti} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDateMulti],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [new TuiDay(2025, 6, 6)];
}
```
#### Example 2
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiInputDateMulti} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDateMulti],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [new TuiDay(2025, 6, 6)];
}
```
**LESS:**
```less
:host {
display: block;
inline-size: 20rem;
}
```
#### Example 3
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiInputDateMulti} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDateMulti],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [new TuiDay(2025, 6, 6)];
protected readonly handler = (item: TuiDay): boolean => item.dayOfWeek() > 4;
}
```
**LESS:**
```less
:host {
display: block;
inline-size: 20rem;
}
```
#### Example 4
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {tuiDateFormatProvider} from '@taiga-ui/core';
import {TuiInputDateMulti} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDateMulti],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiDateFormatProvider({mode: 'mm/dd/yyyy', separator: '/'})],
})
export default class Example {
protected value = [new TuiDay(2025, 6, 6)];
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 2rem;
inline-size: 20rem;
}
```
#### Example 5
**Template:**
```html
4 ? 'negative' : 'positive'" [iconStart]="context.item.dayOfWeek() > 4 ? '@tui.heart' : ''" />
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {type TuiMarkerHandler} from '@taiga-ui/core';
import {TuiInputDateMulti} from '@taiga-ui/kit';
const DOT: [string] = ['var(--tui-status-positive)'];
@Component({
imports: [FormsModule, TuiInputDateMulti],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [new TuiDay(2025, 6, 4), new TuiDay(2025, 6, 6)];
protected readonly markerHandler: TuiMarkerHandler = (day: TuiDay) =>
day.isWeekend ? [] : DOT;
}
```
**LESS:**
```less
:host {
display: block;
inline-size: 22rem;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocItemsHandlers} from '@demo/components/items-handlers';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TUI_FIRST_DAY, TUI_LAST_DAY, TuiDay} from '@taiga-ui/cdk';
import {TuiDropdown} from '@taiga-ui/core';
import {TuiInputChip, TuiInputDate, TuiInputDateMulti} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocDropdown,
TuiDocIcons,
TuiDocInput,
TuiDocItemsHandlers,
TuiDocTextfield,
TuiDropdown,
TuiInputChip,
TuiInputDate,
TuiInputDateMulti,
],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly control = new FormControl();
protected readonly routes = DemoRoute;
public readonly examples = [
'Basic',
'Chip',
'Disabled items',
'Format',
'customization',
];
protected readonly dates = [
TUI_FIRST_DAY,
TuiDay.currentLocal(),
TuiDay.currentLocal().append({year: 1, month: 1}),
TuiDay.currentLocal().append({year: -1, month: -1}),
TUI_LAST_DAY,
] as const;
protected min = this.dates[0];
protected max = this.dates[4];
protected readonly handler = (item: TuiDay): boolean => item.dayOfWeek() > 4;
}
```
---
# components/InputDateRange
- **Package**: `KIT`
- **Type**: components
InputDateRange = Textfield + CalendarRange + ❤️
### Example
```html
@if (textfieldDoc.size !== 's') { Choose range }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [min] | `TuiDay` | Min date |
| [max] | `TuiDay` | Max date |
| [minLength] | `TuiDayLike | null` | Min length of the range |
| [maxLength] | `TuiDayLike | null` | Max length of the range |
| [items] | `TuiDayRangePeriod[]` | Period list items |
| [listSize] | `TuiSizeL` | Period list size |
### Usage Examples
#### Example 1
**Template:**
```html
Choose range
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay, TuiDayRange} from '@taiga-ui/cdk';
import {TuiInputDateRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDateRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = new TuiDayRange(new TuiDay(2017, 0, 15), new TuiDay(2017, 0, 20));
}
```
#### Example 2
**Template:**
```html
Choose range
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiDayRange} from '@taiga-ui/cdk';
import {tuiCreateDefaultDayRangePeriods, TuiInputDateRange} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiInputDateRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(null);
protected readonly items = tuiCreateDefaultDayRangePeriods();
public get content(): string {
const {value} = this.control;
return value
? String(this.items.find((period) => period.range.daySame(value)) || '')
: '';
}
}
```
#### Example 3
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiError, tuiValidationErrorsProvider} from '@taiga-ui/core';
import {TuiInputDateRange, TuiUnfinishedValidator} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiError,
TuiForm,
TuiInputDateRange,
TuiUnfinishedValidator,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiValidationErrorsProvider({
tuiUnfinished: 'Either fill this or leave blank',
required: 'This field is required',
}),
],
})
export default class Example {
protected readonly form = new FormGroup({
required: new FormControl(null, Validators.required),
optional: new FormControl(),
});
}
```
#### Example 4
**Template:**
```html
Choose range
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDayRange} from '@taiga-ui/cdk';
import {TuiInputDateRange, tuiInputDateRangeOptionsProvider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDateRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputDateRangeOptionsProvider({
valueTransformer: {
fromControlValue: (value: string): TuiDayRange | null =>
value ? TuiDayRange.normalizeParse(value) : null,
toControlValue: (value: TuiDayRange | null): string =>
value?.toString() || '',
},
}),
],
})
export default class Example {
protected value = '';
}
```
#### Example 5
**Template:**
```html
Choose range
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiDayRange} from '@taiga-ui/cdk';
import {tuiDateFormatProvider} from '@taiga-ui/core';
import {TuiInputDateRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, ReactiveFormsModule, TuiInputDateRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiDateFormatProvider({mode: 'mm/dd/yyyy', separator: '/'})],
})
export default class Example {
protected value: TuiDayRange | null = null;
}
```
#### Example 6
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdownSheet, TuiMobileCalendarDropdown} from '@taiga-ui/addon-mobile';
import {TuiInputDateRange} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
@Component({
imports: [
ReactiveFormsModule,
TuiDropdownSheet,
TuiForm,
TuiInputDateRange,
TuiMobileCalendarDropdown,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({
mobile: new FormControl(),
fullscreen: new FormControl(),
});
}
```
#### Example 7
**Template:**
```html
Take days off
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {TuiInputDateRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDateRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: TuiDay | null = null;
protected readonly today = TuiDay.currentLocal();
protected readonly min = new TuiDay(this.today.year, this.today.month, 1);
protected readonly max = this.min.append({month: 2, day: -1});
protected readonly handler = (day: TuiDay): boolean => day.dayOfWeek() > 4;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocItemsHandlers} from '@demo/components/items-handlers';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TUI_FIRST_DAY, TUI_LAST_DAY, TuiDay, type TuiDayLike} from '@taiga-ui/cdk';
import {TuiDropdown, TuiInput, type TuiSizeL, type TuiSizeS} from '@taiga-ui/core';
import {
tuiCreateDefaultDayRangePeriods,
type TuiDayRangePeriod,
TuiInputDateRange,
} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocDropdown,
TuiDocIcons,
TuiDocInput,
TuiDocItemsHandlers,
TuiDocTextfield,
TuiDropdown,
TuiInput,
TuiInputDateRange,
],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly control = new FormControl();
public readonly examples = [
'Basic',
'DataList',
'Validation',
'Value transformer',
'Format',
'Mobile',
'Limits',
];
protected readonly dates = [
TUI_FIRST_DAY,
TuiDay.currentLocal(),
TuiDay.currentLocal().append({year: 1, month: 1}),
TuiDay.currentLocal().append({year: -1, month: -1}),
TUI_LAST_DAY,
] as const;
protected readonly sizeVariants: ReadonlyArray = ['s', 'm', 'l'];
protected listSize = this.sizeVariants[2]!;
protected readonly items = tuiCreateDefaultDayRangePeriods();
protected min = this.dates[0];
protected max = this.dates[4];
protected readonly limits = [{day: 3}, {day: 5}] as const;
protected readonly periodListItems = [null, this.items];
protected selectedPeriodList: readonly TuiDayRangePeriod[] | null = null;
protected minLength: TuiDayLike | null = null;
protected maxLength: TuiDayLike | null = null;
protected readonly handler = (item: TuiDay): boolean => item.dayOfWeek() > 4;
}
```
---
# components/InputDateTime
- **Package**: `KIT`
- **Type**: components
InputDateTime = Textfield + Calendar + ❤️
### Example
```html
@if (textfieldDoc.size !== 's') { Choose date }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [timeMode] | `MaskitoTimeMode` | Time format mode for SS and MS support |
| [min] | `TuiDay | [TuiDay, TuiTime] | null` | Min date |
| [max] | `TuiDay | [TuiDay, TuiTime] | null` | Max date |
### Usage Examples
#### Example 1
**Template:**
```html
Choose a date
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay, TuiTime} from '@taiga-ui/cdk';
import {TuiInputDateTime} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputDateTime],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [new TuiDay(2020, 8, 20), new TuiTime(19, 19)];
}
```
#### Example 2
**Template:**
```html
Choose a date
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiDay, TuiMonth, type TuiTime} from '@taiga-ui/cdk';
import {type TuiMarkerHandler} from '@taiga-ui/core';
import {TuiInputDateTime} from '@taiga-ui/kit';
const CHEAPEST_TICKET: [string] = ['var(--tui-status-positive)'];
@Component({
imports: [FormsModule, TuiInputDateTime],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: [TuiDay, TuiTime] | null = null;
protected readonly defaultActiveMonth = signal(new TuiMonth(2000, 0));
protected readonly markerHandler: TuiMarkerHandler = (day: TuiDay) =>
day.day % 5 === 0 ? CHEAPEST_TICKET : [];
}
```
#### Example 3
**Template:**
```html
Tomorrow morning
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay, tuiSum, TuiTime} from '@taiga-ui/cdk';
import {TuiButton} from '@taiga-ui/core';
import {TuiInputDateTime} from '@taiga-ui/kit';
type ControlValue = [TuiDay, TuiTime | null] | null;
@Component({
imports: [ReactiveFormsModule, TuiButton, TuiInputDateTime],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly open = signal(false);
protected readonly control = new FormControl(null);
protected readonly tomorrow: ControlValue = [
TuiDay.currentLocal().append({day: 1}),
new TuiTime(9, 0),
];
protected isSame(a: ControlValue, b: ControlValue): boolean {
return Boolean(a && b && tuiSum(...a.map(Number)) === tuiSum(...b.map(Number)));
}
}
```
**LESS:**
```less
button {
inline-size: 100%;
border-radius: 0;
box-shadow: 0 -1px var(--tui-border-normal);
}
```
#### Example 4
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {
FormControl,
FormGroup,
ReactiveFormsModule,
type ValidationErrors,
type ValidatorFn,
} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiError, tuiValidationErrorsProvider} from '@taiga-ui/core';
import {TuiInputDateTime, TuiUnfinishedValidator} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
export function minLengthValidator(minLength: number): ValidatorFn {
return ({value}): ValidationErrors | null =>
value?.filter(Boolean).length >= minLength ? null : {required: {value}};
}
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiError,
TuiForm,
TuiInputDateTime,
TuiUnfinishedValidator,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiValidationErrorsProvider({
tuiUnfinished: 'Either fill this or leave blank',
required: 'This field is required',
}),
],
})
export default class Example {
protected readonly form = new FormGroup({
timeRequired: new FormControl(null, minLengthValidator(2)),
dayRequired: new FormControl(
null,
minLengthValidator(1), // The same as `Validators.required` (from @angular/forms)
),
optional: new FormControl(),
});
}
```
#### Example 5
**Template:**
```html
{{ value | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay, TuiTime} from '@taiga-ui/cdk';
import {TuiInputDateTime, tuiInputDateTimeOptionsProvider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, JsonPipe, TuiInputDateTime],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputDateTimeOptionsProvider({
valueTransformer: {
fromControlValue: (value: Date | null): [TuiDay, TuiTime | null] | null =>
value && [
TuiDay.fromUtcNativeDate(value),
new TuiTime(value.getUTCHours(), value.getUTCMinutes()),
],
toControlValue: (value: [TuiDay, TuiTime | null] | null): Date | null => {
const {hours = 0, minutes = 0} = value?.[1] ?? {};
return (
value &&
new Date(value[0].toUtcNativeDate().setUTCHours(hours, minutes))
);
},
},
}),
],
})
export default class Example {
protected value: Date | null = new Date(Date.UTC(2024, 7, 9, 12, 17));
}
```
#### Example 6
**Template:**
```html
ISO 8601 date format
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay} from '@taiga-ui/cdk';
import {tuiDateFormatProvider} from '@taiga-ui/core';
import {TuiInputDateTime} from '@taiga-ui/kit';
@Component({
selector: 'example-6',
imports: [FormsModule, ReactiveFormsModule, TuiInputDateTime],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiDateFormatProvider({mode: 'yyyy/mm/dd', separator: '-'})],
})
export default class Example {
protected value = [new TuiDay(2017, 0, 15)];
}
```
#### Example 7
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdownSheet, TuiMobileCalendarDropdown} from '@taiga-ui/addon-mobile';
import {TuiDay, TuiTime} from '@taiga-ui/cdk';
import {TuiInputDateTime} from '@taiga-ui/kit';
import {TuiForm} from '@taiga-ui/layout';
@Component({
imports: [
ReactiveFormsModule,
TuiDropdownSheet,
TuiForm,
TuiInputDateTime,
TuiMobileCalendarDropdown,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({
native: new FormControl([TuiDay.currentLocal(), new TuiTime(12, 34, 56, 789)]),
mobile: new FormControl([TuiDay.currentLocal().append({day: 1})]),
fullscreen: new FormControl([
TuiDay.currentLocal().append({day: 2}),
new TuiTime(23, 59),
]),
});
}
```
#### Example 8
**Template:**
```html
Choose a date
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay, TuiTime} from '@taiga-ui/cdk';
import {TuiInputDateTime} from '@taiga-ui/kit';
const TODAY = TuiDay.currentLocal();
@Component({
imports: [FormsModule, TuiInputDateTime],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: [TuiDay, TuiTime | null] | null = null;
protected readonly min = [
new TuiDay(TODAY.year, TODAY.month, 1),
new TuiTime(0, 0),
] as const;
protected readonly max = [
this.min[0].append({month: 1, day: -1}),
new TuiTime(23, 59),
] as const;
protected readonly handler = ([day]: [TuiDay, TuiTime | null]): boolean =>
day.daySame(TODAY);
}
```
#### Example 9
**Template:**
```html
@for (date of dates | keyvalue: asIs; track date) { {{ date.key }} } v5
```
**TypeScript:**
```ts
import {KeyValuePipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDay, TuiTime} from '@taiga-ui/cdk';
import {TuiDataList} from '@taiga-ui/core';
import {TuiInputDateTime} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, KeyValuePipe, TuiDataList, TuiInputDateTime],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly dates: Record = {
'Taiga UI Birthday': [new TuiDay(2020, 8, 20), new TuiTime(19, 19)],
'2.0.0 release': [new TuiDay(2020, 11, 29), new TuiTime(19, 5)],
'3.0.0 release': [new TuiDay(2022, 7, 30), new TuiTime(17, 18)],
'4.0.0 release': [new TuiDay(2024, 7, 9), new TuiTime(12, 17)],
};
protected value: [TuiDay, TuiTime | null] | null =
Object.values(this.dates).at(-1) ?? null;
protected toISOString([day, time]: readonly [TuiDay, TuiTime]): string {
return `${day.toString('yyyy/mm/dd', '-')}T${time.toString()}`;
}
protected asIs(): number {
return 0;
}
}
```
**LESS:**
```less
section {
display: flex;
}
tui-data-list {
inline-size: 12rem;
box-shadow: -1px 0 var(--tui-border-normal);
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {type MaskitoTimeMode} from '@maskito/kit';
import {WA_IS_MOBILE} from '@ng-web-apis/platform';
import {TUI_FIRST_DAY, TUI_LAST_DAY, TuiDay, TuiTime} from '@taiga-ui/cdk';
import {TuiDropdown} from '@taiga-ui/core';
import {TuiInputDateTime} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocDropdown,
TuiDocIcons,
TuiDocInput,
TuiDocTextfield,
TuiDropdown,
TuiInputDateTime,
],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly routes = DemoRoute;
protected readonly isMobile = inject(WA_IS_MOBILE);
protected readonly control = new FormControl();
public readonly examples = [
'Basic',
'Calendar customization',
'Custom dropdown',
'Validation',
'Value transformer',
'Date format',
'Mobile',
'Limits',
'Datalist',
];
protected readonly dates = [
TUI_FIRST_DAY,
TuiDay.currentLocal(),
TuiDay.currentLocal().append({year: 1, month: 1}),
TuiDay.currentLocal().append({year: -1, month: -1}),
[TuiDay.currentLocal().append({day: 1}), new TuiTime(12, 34)],
TUI_LAST_DAY,
] as const satisfies ReadonlyArray;
protected readonly timeModeVariants = [
'HH:MM',
'HH:MM AA',
'HH:MM:SS',
'HH:MM:SS AA',
'HH:MM:SS.MSS',
'HH:MM:SS.MSS AA',
] as const satisfies readonly MaskitoTimeMode[];
protected min: TuiDay | readonly [TuiDay, TuiTime] | null = this.dates[0];
protected max: TuiDay | readonly [TuiDay, TuiTime] | null = this.dates.at(-1) ?? null;
protected timeMode: MaskitoTimeMode = this.timeModeVariants[0];
}
```
---
# components/InputFiles
- **Package**: `KIT`
- **Type**: components
An input for uploading one or several files using native input file capabilities
### Example
```html
@for (file of files$ | async; track file) { } @for (file of rejected; track file) { }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [accept] | `string` | Allowed formats |
| [maxFileSize] | `number` | Max file size in bytes (30 MB by default — 30 * 1000 * 1000) |
| [multiple] | `boolean` | Allows to upload several files |
| [tuiInputFiles] | `TuiSizeL` | Drop zone size |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (reject) | `TuiFileLike[]` | Emits files that were rejected. |
### Usage Examples
#### Single
**Template:**
```html
@if (!control.value) { } @if (control.value | tuiFileRejected: {accept: 'image/*'} | async; as file) { } @if (loadedFiles$ | async; as file) { } @if (failedFiles$ | async; as file) { } @if (loadingFiles$ | async; as file) { }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiFileLike, TuiFiles} from '@taiga-ui/kit';
import {finalize, map, type Observable, of, Subject, switchMap, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, ReactiveFormsModule, TuiFiles],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(
null,
Validators.required,
);
protected readonly failedFiles$ = new Subject();
protected readonly loadingFiles$ = new Subject();
protected readonly loadedFiles$ = this.control.valueChanges.pipe(
switchMap((file) => this.processFile(file)),
);
protected removeFile(): void {
this.control.setValue(null);
}
protected processFile(file: TuiFileLike | null): Observable {
this.failedFiles$.next(null);
if (this.control.invalid || !file) {
return of(null);
}
this.loadingFiles$.next(file);
return timer(1000).pipe(
map(() => {
if (Math.random() > 0.5) {
return file;
}
this.failedFiles$.next(file);
return null;
}),
finalize(() => this.loadingFiles$.next(null)),
);
}
}
```
#### Multiple
**Template:**
```html
@for (file of accepted$ | async; track file) { } @for (file of rejected; track file) { }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {
type AbstractControl,
FormControl,
ReactiveFormsModule,
type ValidatorFn,
} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiValidationError} from '@taiga-ui/cdk';
import {TuiError} from '@taiga-ui/core';
import {TuiFiles, tuiFilesAccepted} from '@taiga-ui/kit';
import {map} from 'rxjs';
@Component({
imports: [AsyncPipe, ReactiveFormsModule, TuiError, TuiFiles],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl([], [maxFilesLength(5)]);
protected readonly accepted$ = this.control.valueChanges.pipe(
map(() => tuiFilesAccepted(this.control)),
);
protected rejected: readonly File[] = [];
protected onReject(files: readonly File[]): void {
this.rejected = Array.from(new Set(this.rejected.concat(files)));
}
protected onRemove(file: File): void {
this.rejected = this.rejected.filter((rejected) => rejected !== file);
this.control.setValue(
this.control.value?.filter((current) => current !== file) ?? [],
);
}
}
export function maxFilesLength(maxLength: number): ValidatorFn {
return ({value}: AbstractControl) =>
value.length > maxLength
? {
maxLength: new TuiValidationError(
'Error: maximum limit - 5 files for upload',
),
}
: null;
}
```
#### Standalone
**Template:**
```html
@for (file of files; track file) { } @for (file of rejectedFiles; track file) { } @if (loadingFile && !isE2E) { }
With link
With deleted state
@for (file of removedFiles; track file) { Restore } @for (file of restoredFiles; track file) { }
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormControl} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiItem} from '@taiga-ui/cdk';
import {TuiIcon, TuiLink} from '@taiga-ui/core';
import {type TuiFileLike, TuiFiles} from '@taiga-ui/kit';
@Component({
imports: [TuiFiles, TuiIcon, TuiItem, TuiLink],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly isE2E = inject(WA_IS_E2E);
protected readonly control = new FormControl(null);
protected readonly files: readonly TuiFileLike[] = [
{name: 'Loaded.txt'},
{
name: 'A file with a very very long title to check that it can be cut correctly.txt',
},
];
protected loadingFile: TuiFileLike | null = {name: 'Loading file.txt'};
protected readonly rejectedFiles: readonly TuiFileLike[] = [
{
name: 'File with an error.txt',
content: 'Something went wrong this time',
},
];
protected readonly fileWithLink: TuiFileLike = {
name: 'with link.txt',
src: 'https://tools.ietf.org/html/rfc675',
};
protected removedFiles = [this.loadingFile as unknown as TuiFileLike];
protected restoredFiles: TuiFileLike[] = [];
protected removeLoading(): void {
this.loadingFile = null;
}
protected restore(file: TuiFileLike | null): void {
if (!file) {
return;
}
this.restoredFiles = [...this.restoredFiles, file];
this.removedFiles = this.removedFiles.filter(
(removed) => file.name !== removed?.name,
);
}
protected remove(file: TuiFileLike): void {
this.removedFiles = [...this.removedFiles, file];
this.restoredFiles = this.restoredFiles.filter(
(restored) => file.name !== restored?.name,
);
}
}
```
#### With button
**Template:**
```html
@for (file of files; track file) { } @for (file of rejectedFiles; track file) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiItem} from '@taiga-ui/cdk';
import {type TuiFileLike, TuiFiles} from '@taiga-ui/kit';
@Component({
imports: [TuiFiles, TuiItem],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected height = 3;
protected readonly files: readonly TuiFileLike[] = [
{name: 'Loaded.txt'},
{name: 'one_more_file.txt'},
{name: 'one_more_file.txt'},
{name: 'one_more_file.txt'},
{name: 'one_more_file.txt'},
{name: 'one_more_file.txt'},
{name: 'last_file.txt'},
];
protected readonly rejectedFiles: readonly TuiFileLike[] = [
{name: 'File with an error.txt'},
];
}
```
#### Custom content
**Template:**
```html
@if (dragged) {
} file is on checking
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiLink} from '@taiga-ui/core';
import {TuiAvatar, type TuiFileLike, TuiFiles} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiAvatar, TuiFiles, TuiIcon, TuiLink],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(null);
protected readonly file: TuiFileLike = {name: 'custom.txt'};
}
```
**LESS:**
```less
:host {
display: block;
min-inline-size: 25rem;
}
.types {
color: var(--tui-text-secondary);
font-size: 0.6875rem;
}
```
#### Camera capture
**Template:**
```html
@if (!control.value) { } @if (control.valueChanges | async; as file) { }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiFileLike, TuiFiles} from '@taiga-ui/kit';
@Component({
imports: [AsyncPipe, ReactiveFormsModule, TuiFiles],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(null);
protected removeFile(): void {
this.control.setValue(null);
}
}
```
#### Model
**Template:**
```html
@for (file of rejected; track file) { } @for (file of files; track file) { }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiFiles} from '@taiga-ui/kit';
@Component({
imports: [AsyncPipe, FormsModule, TuiFiles],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected files: File[] = [];
protected rejected: File[] = [];
protected onRemove(remove: File): void {
this.files = this.files.filter((file) => file !== remove);
this.rejected = this.rejected.filter((file) => file !== remove);
}
protected onChange(files: File[]): void {
this.files = files.filter((file) => !this.rejected.includes(file));
}
protected onReject(rejected: File[]): void {
this.rejected = rejected;
}
}
```
#### Accept
**Template:**
```html
@for (file of accepted$ | async; track file) { } @for (file of rejected; track file) { }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiFiles, tuiFilesAccepted, tuiInputFilesOptionsProvider} from '@taiga-ui/kit';
import {map, startWith} from 'rxjs';
@Component({
standalone: true,
imports: [AsyncPipe, ReactiveFormsModule, TuiFiles],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputFilesOptionsProvider({
accept: '.jpeg, .jpg, .png, .pdf, .doc, .docx, .zip, .tif',
}),
],
})
export default class Example {
protected readonly control = new FormControl([
new File(['mock zip content'], 'valid.zip', {type: 'application/zip'}),
new File(['Lorem ipsum'], 'wrong.txt', {type: 'application/txt'}),
]);
protected readonly accepted$ = this.control.valueChanges.pipe(
startWith(this.control.value),
map(() => tuiFilesAccepted(this.control)),
);
protected rejected: readonly File[] = [];
protected onReject(files: readonly File[]): void {
this.rejected = Array.from(new Set(this.rejected.concat(files)));
}
protected onRemove(file: File): void {
this.rejected = this.rejected.filter((current) => current !== file);
this.control.setValue(
this.control.value?.filter((current) => current !== file) ?? [],
);
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {type TuiSizeL} from '@taiga-ui/core';
import {TuiFiles, tuiFilesAccepted} from '@taiga-ui/kit';
import {map} from 'rxjs';
@Component({
imports: [ReactiveFormsModule, TuiDemo, TuiDocControl, TuiFiles],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent {
protected readonly examples = [
'Single',
'Multiple',
'Standalone',
'With button',
'Custom content',
'Camera capture',
'Model',
'Accept',
];
protected multiple = true;
protected showSize = true;
protected showDelete: boolean | 'always' = true;
protected expanded = false;
protected maxFilesCount = 3;
protected accept = '';
protected acceptVariants = ['image/*', 'application/pdf', 'image/*,application/pdf'];
protected readonly showDeleteVariants = [true, false, 'always'];
protected readonly maxFileSizeVariants = [
100,
512000,
30 * 1000 * 1000,
2.2 * 1000 * 1000,
];
protected rejected: readonly File[] = [];
protected maxFileSize = this.maxFileSizeVariants[2]!;
protected readonly sizeVariants: readonly TuiSizeL[] = ['m', 'l'];
protected size = this.sizeVariants[0]!;
protected readonly control = new FormControl(null);
protected readonly files$ = this.control.valueChanges.pipe(
map(() => tuiFilesAccepted(this.control)),
);
protected removeFile(file: File): void {
this.rejected = this.rejected.filter((current) => current !== file);
this.control.setValue(
this.control.value?.filter((current) => current !== file) || null,
);
}
protected updateRejected(rejected: readonly File[]): void {
this.rejected = rejected;
}
protected multipleChange(multiple: boolean): void {
this.rejected = [];
this.control.setValue(null);
this.multiple = multiple;
}
}
```
---
# components/InputInline
- **Package**: `KIT`
- **Type**: components
Inline input field
### Usage Examples
#### Basic
**Template:**
```html
{{ toggleContent }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiInputInline} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiButton, TuiInputInline],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected form = new FormGroup({
testValue1: new FormControl('Hello 1'),
testValue2: new FormControl('Hello 2'),
testValue3: new FormControl('Hello 3'),
testValue4: new FormControl(''),
});
protected get toggleContent(): string {
return this.form.disabled ? 'enable (allow editing)' : 'disable';
}
protected get input4Empty(): boolean {
return this.form.get('testValue4')!.value === '';
}
protected onToggleClick(): void {
if (this.form.disabled) {
this.form.enable();
} else {
this.form.disable();
}
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.input1,
.input2,
.input3 {
margin-inline-end: 0.625rem;
}
.input1 {
border: 2px solid var(--tui-status-negative);
}
.input2 {
background: var(--tui-background-accent-opposite);
padding: 0.625rem;
color: var(--tui-background-base);
letter-spacing: 0.625rem;
font-size: 1.25rem;
}
.input3 {
font-family: monospace;
font-weight: bold;
background: var(--tui-border-normal);
}
.input4 {
&_empty {
opacity: 0.3;
}
}
```
#### Heading
**Template:**
```html
@if (editing) { Type a heading
} @else { {{ heading }} Edit heading }
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Culpa exercitationem, sed? Deserunt dignissimos dolorem doloribus officiis quae repellat rerum? Accusantium fuga hic nam necessitatibus non officiis perferendis repellendus tempore voluptates!
Accusantium adipisci blanditiis esse est et eum fugit id illum, in iste itaque iusto laborum nostrum officia quam quasi quos repellat temporibus tenetur, ullam? Blanditiis fuga iusto maiores omnis quidem!
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAutoFocus} from '@taiga-ui/cdk';
import {TuiButton, TuiNotificationService} from '@taiga-ui/core';
import {TuiInputInline} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiAutoFocus, TuiButton, TuiInputInline],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly alerts = inject(TuiNotificationService);
protected heading = 'Page heading';
protected editing = false;
protected toggle(): void {
this.editing = !this.editing;
}
protected onBlur(): void {
this.editing = false;
this.saveHeading(this.heading);
}
private saveHeading(newHeading: string): void {
this.alerts.open(newHeading, {label: 'New heading'}).subscribe();
}
}
```
**LESS:**
```less
.header {
display: flex;
block-size: 1.5rem;
align-items: center;
white-space: nowrap;
line-height: 1.5rem;
&_empty {
opacity: 0.3;
}
}
```
#### External update
**Template:**
```html
```
**TypeScript:**
```ts
import {
ChangeDetectorRef,
Component,
DestroyRef,
inject,
NgZone,
type OnInit,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {tuiWatch, tuiZoneOptimized} from '@taiga-ui/cdk';
import {TuiInputInline} from '@taiga-ui/kit';
import {timer} from 'rxjs';
@Component({
imports: [FormsModule, TuiInputInline],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example implements OnInit {
private readonly cd = inject(ChangeDetectorRef);
private readonly destroyRef = inject(DestroyRef);
private readonly zone = inject(NgZone);
protected readonly isE2E = inject(WA_IS_E2E);
protected count = '0';
public ngOnInit(): void {
if (this.isE2E) {
return;
}
timer(0, 3000)
.pipe(
tuiZoneOptimized(this.zone),
tuiWatch(this.cd),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((value) => {
this.count = String(value);
});
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.input1 {
border: 2px solid var(--tui-status-negative);
padding: 0.625rem;
font-size: 1.25rem;
text-align: center;
}
```
#### Inside text
**Template:**
```html
I funny.
He
___
funny.
You funny.
Writing practice Learning to write type underscore and hyphen
___------
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputInline} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputInline],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected answer = '';
}
```
**LESS:**
```less
.task:first-child input:not(:placeholder-shown) {
text-decoration: underline;
text-align: center;
}
tui-input-inline._empty,
tui-input-inline:has(input:placeholder-shown) {
color: var(--tui-text-tertiary);
}
tui-input-inline:has(input[placeholder]) {
min-inline-size: 3ch;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Heading', 'External update', 'Inside text'];
}
```
---
# components/InputMonth
- **Package**: `KIT`
- **Type**: components
InputMonth = Textfield + CalendarMonth + ❤️
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [min] | `TuiMonth | null` | The lowest value in the range of permitted dates |
| [max] | `TuiMonth | null` | The greatest value in the range of permitted dates |
| [disabledItemHandler] | `TuiBooleanHandler` | |
| [(year)] | `TuiYear` | Current year |
### Usage Examples
#### Example 1
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiBooleanHandler, TuiMonth, TuiYear} from '@taiga-ui/cdk';
import {TuiInputMonth} from '@taiga-ui/kit';
const NEXT_YEAR = TuiMonth.currentLocal().year + 1;
@Component({
imports: [FormsModule, TuiInputMonth],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly min = TuiMonth.currentLocal().append({month: 1});
protected readonly max = new TuiMonth(NEXT_YEAR, 11);
protected value: TuiMonth | null = null;
protected activeYear = new TuiYear(NEXT_YEAR);
protected readonly isSummerHandler: TuiBooleanHandler = ({month}) =>
[5, 6, 7].includes(month);
}
```
#### Example 2
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiMonth} from '@taiga-ui/cdk';
import {TuiError, tuiValidationErrorsProvider} from '@taiga-ui/core';
import {TuiInputMonth} from '@taiga-ui/kit';
import {interval, map, startWith} from 'rxjs';
@Component({
imports: [ReactiveFormsModule, TuiError, TuiInputMonth],
templateUrl: './index.html',
styles: ':host {display: block; min-height: 4rem}',
encapsulation,
changeDetection,
providers: [
tuiValidationErrorsProvider({
required: interval(1000).pipe(
map((i) => (i % 2 ? 'NOW!!!' : 'Enter this!')),
startWith('Required field!'),
),
}),
],
})
export default class Example {
protected readonly control = new FormControl(
null,
Validators.required,
);
}
```
#### Example 3
**Template:**
```html
I am a label
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiMonth} from '@taiga-ui/cdk';
import {TuiIcon} from '@taiga-ui/core';
import {TuiInputMonth, tuiInputMonthOptionsProvider, TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInputMonth, TuiTooltip],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiInputMonthOptionsProvider({icon: ''})],
})
export default class Example {
protected value: TuiMonth | null = null;
}
```
#### Example 4
**Template:**
```html
My wife's birthday
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiMonth} from '@taiga-ui/cdk';
import {TuiLink} from '@taiga-ui/core';
import {TuiInputMonth} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputMonth, TuiLink],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value: TuiMonth | null = null;
protected readonly open = signal(false);
protected chooseTheOnlyCorrectOption(): void {
this.value = new TuiMonth(1998, 2);
this.open.set(false);
}
}
```
**LESS:**
```less
.option {
border-block-start: 1px solid var(--tui-border-normal);
inline-size: 100%;
block-size: 3rem;
}
```
#### Example 5
**Template:**
```html
```
**TypeScript:**
```ts
import {Component, inject, LOCALE_ID} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiContext, TuiMonth, type TuiStringHandler} from '@taiga-ui/cdk';
import {TuiInputMonth} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputMonth],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly formatter = Intl.DateTimeFormat(inject(LOCALE_ID), {
year: '2-digit',
month: 'short',
});
protected value: TuiMonth | null = TuiMonth.currentLocal().append({month: -1});
protected readonly stringify: TuiStringHandler> = ({
$implicit,
}) => this.formatter.format($implicit.toLocalNativeDate());
}
```
#### Example 6
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiMonth} from '@taiga-ui/cdk';
import {TuiInputMonth} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputMonth],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: TuiMonth | null = null;
}
```
#### Example 7
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiMonthRange} from '@taiga-ui/cdk';
import {TuiIcon} from '@taiga-ui/core';
import {TuiInputMonthRange, TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInputMonthRange, TuiTooltip],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: TuiMonthRange | null = null;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {
TUI_FALSE_HANDLER,
type TuiBooleanHandler,
TuiDay,
TuiMonth,
TuiYear,
} from '@taiga-ui/cdk';
import {TuiTitle} from '@taiga-ui/core';
import {TuiInputMonth} from '@taiga-ui/kit';
const TAIGA_BIRTHDAY = new TuiDay(2020, 8, 20);
const TAIGA_V3 = new TuiDay(2022, 7, 30);
const TAIGA_V4 = new TuiDay(2024, 7, 9);
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocInput,
TuiDocTextfield,
TuiInputMonth,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class PageComponent {
protected readonly routes = DemoRoute;
protected readonly control = new FormControl(null);
public readonly examples = [
'Basic',
'Form control value',
'Textfield customization',
'Dropdown customization',
'Selected value customization',
'Native picker',
'Range mode',
];
protected readonly monthVariants = [
TuiDay.currentLocal().append({year: -100, month: -1}),
TuiDay.currentLocal().append({year: -10, month: -1}),
new TuiMonth(2007, 0),
TAIGA_BIRTHDAY,
TAIGA_V3,
TAIGA_V4,
TuiDay.currentLocal().append({year: 10, month: 1}),
TuiDay.currentLocal().append({year: 100, month: 1}),
] as const satisfies readonly TuiMonth[];
protected readonly yearVariants = [
new TuiYear(TuiDay.currentLocal().year),
new TuiYear(2077),
new TuiYear(2007),
] as const satisfies readonly TuiYear[];
protected readonly disabledItemHandlerVariants = [
TUI_FALSE_HANDLER,
({month}) => month % 3 === 0,
] as const satisfies ReadonlyArray>;
protected min: TuiMonth | null = null;
protected max: TuiMonth | null = null;
protected year = this.yearVariants[0];
protected disabledItemHandler: TuiBooleanHandler =
this.disabledItemHandlerVariants[0];
}
```
---
# components/InputNumber
- **Package**: `KIT`
- **Type**: components
InputNumber is a form field to provide numerical input. example of [ 'Number as form control value', 'BigInt as form control value', 'Textfield-based', 'Localization', 'Affixes', 'Step', 'Custom step buttons', 'Fluid typography', 'Value transformer', 'Quantum', ]; track example ) {
### Example
```html
@if (textfieldDoc.size !== 's') { Enter a number }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [min] | `number | bigint | null` | value in the range of permitted values |
| [max] | `number | bigint | null` | value in the range of permitted values |
| [step] | `number | bigint` | Step to increase/decrease value with keyboard and buttons on the side |
| [prefix] | `string` | number |
| [postfix] | `string` | number |
| [quantum] | `number | bigint` | |
### Usage Examples
#### Example 1
**Template:**
```html
Enter a number
Control value:{{ control.value | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiError, tuiValidationErrorsProvider} from '@taiga-ui/core';
import {TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [JsonPipe, ReactiveFormsModule, TuiError, TuiInputNumber],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiValidationErrorsProvider({required: 'Required field'})],
})
export default class Example {
protected readonly control = new FormControl(
null,
Validators.required,
);
}
```
#### Example 10
**Template:**
```html
Control value:
{{ value | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, JsonPipe, TuiInputNumber],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 0.5;
}
```
#### Large integer and large decimal parts
JavaScript’s built‑in number type loses precision when a value contains too many digits in either the integer part, the decimal part, or both. The built‑in bigint type avoids this issue but supports only integers. If you need to preserve all extremely large digits of both parts, you can override the built‑in TuiValueTransformer and store the number as an {significand: bigint; exp: number} instead.
**Template:**
```html
Control value:
{{ stringified() }}
```
**TypeScript:**
```ts
import {Component, computed, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiNumberFormat} from '@taiga-ui/core';
import {TuiInputNumber} from '@taiga-ui/kit';
import {BigIntWithDecimal, type ControlValue} from './transformer';
@Component({
imports: [BigIntWithDecimal, FormsModule, TuiInputNumber, TuiNumberFormat],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly infinity = Infinity;
protected readonly value = signal({
significand: 123456700042n,
exp: -5,
});
protected readonly stringified = computed(() =>
JSON.stringify(
this.value(),
(_, x) => (typeof x === 'bigint' ? `${String(x)}n` : x),
2,
),
);
}
```
#### Example 2
**Template:**
```html
Enter a really huge integer
Control value:{{ stringified() }}
```
**TypeScript:**
```ts
import {Component, computed, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiNumberFormatProvider} from '@taiga-ui/core';
import {TuiInputNumber, tuiInputNumberOptionsProvider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputNumber],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputNumberOptionsProvider({
min: -Infinity,
max: Infinity,
}),
tuiNumberFormatProvider({precision: 0}),
],
})
export default class Example {
protected readonly value = signal(
BigInt(`777${Number.MAX_SAFE_INTEGER}00`),
);
protected readonly stringified = computed((x = this.value()) =>
typeof x === 'bigint' ? `${x}n` : 'null',
);
}
```
#### Example 3
**Template:**
```html
I am a label
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
import {TuiInputNumber, TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInputNumber, TuiTooltip],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: number | null = null;
}
```
#### Example 4
**Template:**
```html
Type number like a German
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon, TuiNumberFormat, type TuiNumberFormatSettings} from '@taiga-ui/core';
import {TuiInputNumber, TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInputNumber, TuiNumberFormat, TuiTooltip],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: number | null = 1_234_567.89;
protected numberFormat: Partial = {
decimalSeparator: ',',
thousandSeparator: '.',
};
}
```
#### Example 5
**Template:**
```html
Price
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCurrencyPipe} from '@taiga-ui/addon-commerce';
import {TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiCurrencyPipe, TuiInputNumber],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: number | null = null;
}
```
#### Example 6
**Template:**
```html
Percentage
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputNumber],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value: number | null = null;
}
```
#### Example 7
**Template:**
```html
+ 100 + 1000
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiButton, TuiInputNumber],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value: number | null = 1_000;
protected onStep(step: number): void {
this.value = Math.max(0, (this.value ?? 0) + step);
}
}
```
**LESS:**
```less
[tuiButton] {
border-radius: 10rem;
margin-inline-start: 0.125rem;
}
```
#### Example 8
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiFluidTypography, TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiFluidTypography, TuiInputNumber],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 10000;
}
```
#### Example 9
**Template:**
```html
Control value: {{ value.toString() }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiValueTransformer} from '@taiga-ui/cdk';
import {TuiInputNumber, tuiInputNumberOptionsProvider} from '@taiga-ui/kit';
class NaNTransformer extends TuiValueTransformer {
public override fromControlValue(value: number): number | null {
return Number.isNaN(value) ? null : value;
}
public override toControlValue(value: number | null): number {
return value ?? Number.NaN;
}
}
@Component({
imports: [FormsModule, TuiInputNumber],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiInputNumberOptionsProvider({valueTransformer: new NaNTransformer()})],
})
export default class Example {
protected value = Number.NaN;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocNumberFormat} from '@demo/components/number-format';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiNumberFormat} from '@taiga-ui/core';
import {TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocIcons,
TuiDocInput,
TuiDocNumberFormat,
TuiDocTextfield,
TuiInputNumber,
TuiNumberFormat,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class PageComponent {
protected readonly routes = DemoRoute;
protected readonly control = new FormControl(null, Validators.required);
protected readonly maxVariants: readonly number[] = [
Number.MAX_SAFE_INTEGER,
Infinity,
10,
500,
];
protected readonly minVariants: readonly number[] = [
Number.MIN_SAFE_INTEGER,
-Infinity,
-500,
5,
25,
];
protected min = this.minVariants[0]!;
protected max = this.maxVariants[0]!;
protected step = 0;
protected prefix = '';
protected postfix = '';
protected quantum = 0;
protected readonly bigIntWithDecimalTransformer = import(
'./examples/11/transformer.ts?raw',
{with: {loader: 'text'}}
);
}
```
---
# components/InputPhone
- **Package**: `KIT`
- **Type**: components
InputPhone allows to input a phone number
### Example
```html
Type a phone number
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [mask] | `string` | Text mask. You can use # , - , brackets and spaces as a template symbol |
| [filler] | `string` | Filler |
### Usage Examples
#### Example 1
**Template:**
```html
Type a phone number
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_IOS} from '@ng-web-apis/platform';
import {TuiInputPhone} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputPhone],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly isIos = inject(WA_IS_IOS);
public value = '+71234567890';
protected get pattern(): string | null {
return this.isIos ? '+[0-9-]{1,20}' : null;
}
}
```
#### Example 2
**Template:**
```html
{{ user() || 'Phone number or name' }}
@if (items | tuiFilterByInput: filter; as filtered) { @if (!user() && input.value && filtered.length) { } }
{{ user }} {{ user.phone }}
value: {{ value() }}
```
**TypeScript:**
```ts
import {Component, computed, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_DEFAULT_MATCHER} from '@taiga-ui/cdk';
import {
TuiCell,
type TuiFilterByInputOptions,
TuiFilterByInputPipe,
TuiTitle,
} from '@taiga-ui/core';
import {TuiAvatar, TuiDataListWrapper, TuiInputPhone} from '@taiga-ui/kit';
class User {
constructor(
public readonly firstName: string,
public readonly lastName: string,
public readonly phone: string,
public readonly avatarUrl: string | null = null,
public readonly disabled = false,
) {}
public toString(): string {
return `${this.firstName} ${this.lastName}`;
}
}
const DATA: readonly User[] = [
new User(
'Alex',
'Inkin',
'+11234567890',
'https://avatars.githubusercontent.com/u/11832552',
),
new User(
'Vladimir',
'Potekhin',
'+13213213213',
'https://avatars.githubusercontent.com/u/46284632',
),
new User(
'Nikita',
'Barsukov',
'+18005553535',
'https://avatars.githubusercontent.com/u/35179038',
),
new User(
'Roman',
'Sedov',
'+18003000600',
'https://avatars.githubusercontent.com/u/10106368',
),
new User(
'Yulia',
'Tsareva',
'+13332221110',
'https://avatars.githubusercontent.com/u/8158578',
),
];
@Component({
imports: [
FormsModule,
TuiAvatar,
TuiCell,
TuiDataListWrapper,
TuiFilterByInputPipe,
TuiInputPhone,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
public readonly value = signal('');
public readonly items = DATA;
public readonly user = computed(() =>
this.items.find(({phone}) => phone === this.value()),
);
protected selectUser(user: User): void {
this.value.set(user.phone);
}
protected onInput(value: string): void {
const [user] = this.filter(this.items, value);
if (value === user?.toString() || value === user?.phone) {
this.value.set(user.phone);
}
}
protected readonly filter: TuiFilterByInputOptions['filter'] = (
items,
search,
) =>
items.filter(
(item) =>
(search.startsWith('+') &&
TUI_DEFAULT_MATCHER(item.phone, search.replaceAll(/\D/g, ''))) ||
TUI_DEFAULT_MATCHER(item.toString(), search),
);
}
```
#### Example 3
**Template:**
```html
Type a phone number
{{ value }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiValueTransformer} from '@taiga-ui/cdk';
import {TuiInputPhone, tuiInputPhoneOptionsProvider} from '@taiga-ui/kit';
const VALUE_TRANSFORMER: TuiValueTransformer = {
fromControlValue: (value) => `+${value}`,
toControlValue: (value) => value.slice(1),
};
@Component({
imports: [FormsModule, TuiInputPhone],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiInputPhoneOptionsProvider({valueTransformer: VALUE_TRANSFORMER})],
})
export default class Example {
public value = '';
}
```
#### Example 4
**Template:**
```html
Type a phone number
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiValueTransformer} from '@taiga-ui/cdk';
import {TuiFlagPipe, TuiInputPhone, tuiInputPhoneOptionsProvider} from '@taiga-ui/kit';
const VALUE_TRANSFORMER: TuiValueTransformer = {
fromControlValue: (value) => `+${value}`,
toControlValue: (value) => value.slice(1),
};
@Component({
imports: [FormsModule, TuiFlagPipe, TuiInputPhone],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [tuiInputPhoneOptionsProvider({valueTransformer: VALUE_TRANSFORMER})],
})
export default class Example {
public value = '';
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiInputPhone} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocIcons,
TuiDocInput,
TuiDocTextfield,
TuiInputPhone,
],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class PageComponent {
protected phoneMasks = [
'+7 (###) ###-##-##',
'+850 (####)-#############',
'+1 ### ###-####',
];
protected filler = '';
protected phoneMask = this.phoneMasks[0]!;
public control = new FormControl('', [Validators.required, Validators.minLength(12)]);
public readonly examples = [
'Basic',
'With autocomplete',
'Value transformer',
'With flag',
];
}
```
### LESS
```less
@import '@taiga-ui/styles/utils';
.item {
display: flex;
align-items: center;
justify-content: space-between;
inline-size: 100%;
margin: -0.625rem 0;
}
.avatar {
margin-inline-start: 0.75rem;
}
.name,
.phone {
margin: 0;
}
.phone {
font: var(--tui-typography-body-s);
color: var(--tui-text-tertiary);
}
```
---
# components/InputPhoneInternational
- **Package**: `KIT`
- **Type**: components
Allows to input phone number in international format InputPhoneInternational is based on @maskito/phone and libphonenumber-js libraries. libphonenumber is an ultimate phone number formatting and parsing library developed by Google . This library collects the latest phone number rules from ITU documents, user bug reports, telecom company home pages and government telecommunication authorities. It is always up-to-date (for more than 10 years), and releases are published almost every month. It means that InputPhoneInternational has the robust source of truth!
### Example
```html
@if (textfield.size !== 's') { Type a phone number }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [countries] | `ReadonlyArray` | Array of ISO-codes of countries to choose |
| [countrySearch] | `boolean` | Enable filter input for countries |
| [(countryIsoCode)] | `boolean` | ISO-code of selected country |
### Usage Examples
#### Choose metadata
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiCountryIsoCode} from '@taiga-ui/i18n';
import {
TuiInputPhoneInternational,
tuiInputPhoneInternationalOptionsProvider,
} from '@taiga-ui/kit';
import {defer} from 'rxjs';
@Component({
imports: [FormsModule, TuiInputPhoneInternational],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
/**
* You can choose: lazily load metadata or include it in your bundle.
* Lazy loading:
*/
tuiInputPhoneInternationalOptionsProvider({
metadata: defer(async () =>
import('libphonenumber-js/max/metadata').then((m) => m.default),
),
}),
/**
* Eager loading:
* ```ts
* import metadata from 'libphonenumber-js/mobile/metadata';
* import {of} from 'rxjs';
* // [...]
* tuiInputPhoneInternationalOptionsProvider({
* metadata: of(metadata),
* }),
* ```
*/
],
})
export default class Example {
protected readonly countries: readonly TuiCountryIsoCode[] = [
'IN',
'CN',
'US',
'ID',
'PK',
];
protected countryIsoCode: TuiCountryIsoCode = 'US';
protected value = '+12125552368';
}
```
#### Choose any countries
**Template:**
```html
Phone number
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiCountryIsoCode} from '@taiga-ui/i18n';
import {
TuiInputPhoneInternational,
tuiInputPhoneInternationalOptionsProvider,
TuiSortCountriesPipe,
} from '@taiga-ui/kit';
import {getCountries} from 'libphonenumber-js';
import {defer} from 'rxjs';
@Component({
imports: [FormsModule, TuiInputPhoneInternational, TuiSortCountriesPipe],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputPhoneInternationalOptionsProvider({
metadata: defer(async () =>
import('libphonenumber-js/max/metadata').then((m) => m.default),
),
}),
],
})
export default class Example {
protected readonly countries = getCountries();
protected countryIsoCode: TuiCountryIsoCode = 'CN';
protected value = '';
}
```
#### Mobile dropdown
**Template:**
```html
Phone number
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdownSheet} from '@taiga-ui/addon-mobile';
import {type TuiCountryIsoCode} from '@taiga-ui/i18n';
import {
TuiInputPhoneInternational,
tuiInputPhoneInternationalOptionsProvider,
TuiSortCountriesPipe,
} from '@taiga-ui/kit';
import {getCountries} from 'libphonenumber-js';
import {defer} from 'rxjs';
@Component({
imports: [
FormsModule,
TuiDropdownSheet,
TuiInputPhoneInternational,
TuiSortCountriesPipe,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputPhoneInternationalOptionsProvider({
metadata: defer(async () =>
import('libphonenumber-js/max/metadata').then((m) => m.default),
),
}),
],
})
export default class Example {
protected readonly countries = getCountries();
protected countryIsoCode: TuiCountryIsoCode = 'CN';
protected value = '';
}
```
#### Customize with icons
**Template:**
```html
Phone number
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
import {type TuiCountryIsoCode} from '@taiga-ui/i18n';
import {
TuiInputPhoneInternational,
tuiInputPhoneInternationalOptionsProvider,
TuiTooltip,
} from '@taiga-ui/kit';
import {defer} from 'rxjs';
@Component({
imports: [FormsModule, TuiIcon, TuiInputPhoneInternational, TuiTooltip],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputPhoneInternationalOptionsProvider({
metadata: defer(async () =>
import('libphonenumber-js/max/metadata').then((m) => m.default),
),
}),
],
})
export default class Example {
protected readonly countries: readonly TuiCountryIsoCode[] = [
'TR',
'IR',
'IQ',
'SA',
'YE',
];
protected countryIsoCode: TuiCountryIsoCode = 'TR';
protected value = '';
}
```
#### Customize separator
**Template:**
```html
Type your number
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiCountryIsoCode} from '@taiga-ui/i18n';
import {
TuiInputPhoneInternational,
tuiInputPhoneInternationalOptionsProvider,
} from '@taiga-ui/kit';
import {getCountries} from 'libphonenumber-js';
import {defer} from 'rxjs';
@Component({
imports: [FormsModule, TuiInputPhoneInternational],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputPhoneInternationalOptionsProvider({
metadata: defer(async () =>
import('libphonenumber-js/max/metadata').then((m) => m.default),
),
separator: ' ',
}),
],
})
export default class Example {
protected readonly countries = getCountries();
protected countryIsoCode: TuiCountryIsoCode = 'FR';
protected value = '';
}
```
#### Use phone format helpers
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {MaskitoPipe} from '@maskito/angular';
import {maskitoTransform} from '@maskito/core';
import {maskitoPhoneOptionsGenerator} from '@maskito/phone';
import metadata from 'libphonenumber-js/max/metadata';
@Component({
imports: [MaskitoPipe],
template: 'Phone: {{ rawValue | maskito: mask }}',
encapsulation,
changeDetection,
host: {'(click)': 'showUtilityPower()'},
})
export default class Example {
protected rawValue = '12125552368';
protected readonly mask = maskitoPhoneOptionsGenerator({
metadata,
countryIsoCode: 'US',
});
protected showUtilityPower(): void {
console.info(maskitoTransform(this.rawValue, this.mask));
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiDropdown, TuiIcon} from '@taiga-ui/core';
import {type TuiCountryIsoCode} from '@taiga-ui/i18n';
import {
TuiInputPhoneInternational,
tuiInputPhoneInternationalOptionsProvider,
TuiTooltip,
} from '@taiga-ui/kit';
import {getCountries} from 'libphonenumber-js';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocDropdown,
TuiDocInput,
TuiDocTextfield,
TuiDropdown,
TuiIcon,
TuiInputPhoneInternational,
TuiTooltip,
],
templateUrl: './index.html',
changeDetection,
providers: [
tuiInputPhoneInternationalOptionsProvider({
metadata: import('libphonenumber-js/max/metadata').then((m) => m.default),
}),
],
})
export default class PageComponent {
protected readonly routes = DemoRoute;
protected readonly countriesVariants: ReadonlyArray = [
['RU', 'KZ', 'UA', 'BY'],
getCountries(),
];
protected countries = this.countriesVariants[0]!;
protected countrySearch = false;
protected readonly countryIsoCodeVariants: readonly TuiCountryIsoCode[] = [
'RU',
'KZ',
'UA',
'BY',
];
protected countryIsoCode = this.countryIsoCodeVariants[0]!;
protected formControl = new FormControl('', [
Validators.required,
Validators.minLength(9),
]);
protected readonly examples = [
'Choose metadata',
'Choose any countries',
'Mobile dropdown',
'Customize with icons',
'Customize separator',
'Use phone format helpers',
];
}
```
---
# components/InputPin
- **Package**: `KIT`
- **Type**: components
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputPin} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiInputPin],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl('', Validators.minLength(4));
}
```
#### Alphanumeric
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputPin} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputPin],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = '';
}
```
#### Hidden
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputPin} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiInputPin],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(null);
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent {
protected readonly examples = ['Basic', 'Alphanumeric', 'Hidden'];
}
```
---
# components/InputRange
- **Package**: `KIT`
- **Type**: components
InputRange = Textfield + InputNumber × 2 + Range + ❤️ 2
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [min] | `number` | value in the range of permitted values |
| [max] | `number` | value in the range of permitted values |
| [prefix] | `readonly [string, string] | null` | number |
| [postfix] | `readonly [string, string] | null` | number |
| [content] | `[PolymorpheusContent, PolymorpheusContent]` | A template for custom view of the selected value. |
| [quantum] | `number` | |
| [segments] | `number` | for no ticks) |
| [step] | `number` | |
| [keySteps] | `TuiKeySteps` | Anchor points of non-uniform format between value and position |
| [style.--tui-thumb-size.px] | `number` | Size of thumb |
### Usage Examples
#### Example 1
**Template:**
```html
Type number like a German
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiNumberFormat, type TuiNumberFormatSettings} from '@taiga-ui/core';
import {TuiInputRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputRange, TuiNumberFormat],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly max = 1_000_000;
protected readonly min = 0;
protected value = [0.42, 123_456.78];
protected readonly numberFormat: Partial = {
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
};
}
```
#### Example 2
**Template:**
```html
@let currency = 'USD' | tuiCurrency;
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCurrencyPipe} from '@taiga-ui/addon-commerce';
import {TuiInputRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiCurrencyPipe, TuiInputRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [42, 777];
}
```
#### Example 3
**Template:**
```html
Select volume range
20%40%60%80%
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
import {TuiInputRange} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiIcon, TuiInputRange],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl([20, 40]);
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.ticks-labels {
.tui-slider-ticks-labels();
}
tui-icon {
block-size: 1.25rem;
font-size: 1rem;
}
```
#### Example 4
**Template:**
```html
Desired departure day @switch (value) { @case (0) { Today } @case (1) { Tomorrow } @case (7) { In a week } @default { {{ value }}{{ value | i18nPlural: pluralize }} } }
```
**TypeScript:**
```ts
import {I18nPluralPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, I18nPluralPipe, TuiInputRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [0, 7];
// See https://angular.dev/api/common/I18nPluralPipe#example
protected readonly pluralize = {
'=1': ' day later',
other: ' days later',
};
}
```
#### Example 5
**Template:**
```html
Not linear growing sliders
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiKeySteps} from '@taiga-ui/core';
import {TuiInputRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, JsonPipe, TuiInputRange],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [100_000, 500_000];
protected readonly min = 0;
protected readonly max = 1_000_000;
protected readonly step = 5;
protected readonly ticksLabels = ['0', '10K', '100K', '500k', '1000K'];
protected readonly segments = this.ticksLabels.length - 1;
protected readonly keySteps: TuiKeySteps = [
// [percent, value]
[0, this.min],
[25, 10_000],
[50, 100_000],
[75, 500_000],
[100, this.max],
];
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.ticks-labels {
.tui-slider-ticks-labels();
}
```
#### Example 6
**Template:**
```html
Control value:
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, JsonPipe, TuiInputRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = [0.25, 0.75];
// Form control can only contain decimal number which is multiple of this constant
protected quantum = 0.05;
// But granularity of each discrete slider step is equal to this constant
protected readonly step = 0.25;
}
```
#### Example 7
**Template:**
```html
How far back to look
Control value:
```
**TypeScript:**
```ts
import {I18nPluralPipe, JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {CHAR_ZERO_WIDTH_SPACE} from '@taiga-ui/cdk';
import {tuiInputNumberOptionsProvider, TuiInputRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, I18nPluralPipe, JsonPipe, TuiInputRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiInputNumberOptionsProvider({
minusSign: CHAR_ZERO_WIDTH_SPACE,
prefix: CHAR_ZERO_WIDTH_SPACE, // Make minus non-erasable
}),
],
})
export default class Example {
protected value = [-30, 0];
// See https://angular.dev/api/common/I18nPluralPipe#example
protected readonly pluralize = {
'=-1': ' day ago',
other: ' days ago',
};
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocNumberFormat} from '@demo/components/number-format';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {type TuiContext} from '@taiga-ui/cdk';
import {
type TuiKeySteps,
TuiLoader,
TuiNumberFormat,
TuiTextfield,
TuiTitle,
} from '@taiga-ui/core';
import {TuiInputRange} from '@taiga-ui/kit';
import {PolymorpheusComponent, type PolymorpheusContent} from '@taiga-ui/polymorpheus';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocNumberFormat,
TuiDocTextfield,
TuiInputRange,
TuiNumberFormat,
TuiTextfield,
TuiTitle,
],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent {
protected readonly routes = DemoRoute;
protected readonly control = new FormControl([0, 10]);
public readonly examples = [
'Override number format',
'Affixes',
'Visual segments + labels for ticks',
'Custom value content',
'KeySteps',
'Quantum',
'Using negative values with hidden minus sign',
];
protected readonly suffixVariants = [
['$', '$'],
['€', '€'],
[' ₽', ' ₽'],
['%', '%'],
[' kg', ' kg'],
];
protected keyStepsVariants: readonly TuiKeySteps[] = [
[
[0, 0],
[50, 1_000],
[100, 10_000],
],
];
protected readonly contentVariants: Array<
readonly [
PolymorpheusContent>,
PolymorpheusContent>,
]
> = [
['', ''],
['START', 'END'],
[
({$implicit: val}: TuiContext) =>
val === this.max ? 'MAX' : `${val}`,
({$implicit: val}: TuiContext) =>
val === this.max ? 'MAX' : `${val}`,
],
[
({$implicit: val}: TuiContext) =>
val === this.min ? 'MIN' : `${val}`,
({$implicit: val}: TuiContext) =>
val === this.min ? 'MIN' : `${val}`,
],
[
({$implicit: val}: TuiContext) => (val === 5 ? 'FIVE' : `${val}`),
({$implicit: val}: TuiContext) => (val === 5 ? 'FIVE' : `${val}`),
],
['', new PolymorpheusComponent(TuiLoader)],
];
protected min = 0;
protected max = 100;
protected step = 1;
protected segments = 1;
protected prefix: [string, string] | null = null;
protected postfix: [string, string] | null = null;
protected quantum = 0;
protected keySteps: TuiKeySteps | null = null;
protected content = this.contentVariants[0]!;
protected thumbSize = 12;
}
```
---
# components/InputSlider
- **Package**: `KIT`
- **Type**: components
InputSlider = Textfield + InputNumber + Slider + ❤️
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [min] | `number` | value in the range of permitted values |
| [max] | `number` | value in the range of permitted values |
| [prefix] | `string` | number |
| [postfix] | `string` | number |
| [quantum] | `number` | |
| [step] | `number` | |
| [segments] | `number[] | number` | for no ticks) |
| [keySteps] | `TuiKeySteps | null` | Anchor points of non-uniform format between value and position |
| [style.--tui-thumb-size.px] | `number` | Size of thumb |
### Usage Examples
#### Textfield customization
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
import {TuiInputSlider, TuiTooltip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiIcon, TuiInputSlider, TuiTooltip],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 42;
}
```
#### InputNumber customization
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiNumberFormat} from '@taiga-ui/core';
import {TuiInputSlider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputSlider, TuiNumberFormat],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 9_999.9;
}
```
#### Slider customization
**Template:**
```html
Rate your mind
Decrease 20%40%60%80% Increase
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiInputSlider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiButton, TuiInputSlider],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly max = 100;
protected readonly min = 0;
protected readonly step = 20;
protected value = 20;
protected increase(): void {
this.value = Math.min(this.value + this.step, this.max);
}
protected decrease(): void {
this.value = Math.max(this.value - this.step, this.min);
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.slider-ticks-labels {
.tui-slider-ticks-labels();
}
button {
margin: -0.125rem;
}
```
#### KeySteps
**Template:**
```html
Not linear growing slider
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLegendItem, TuiRingChart} from '@taiga-ui/addon-charts';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {tuiSum} from '@taiga-ui/cdk';
import {
TuiCheckbox,
tuiFormatNumber,
TuiIcon,
TuiNotification,
TuiNotificationService,
} from '@taiga-ui/core';
@Component({
imports: [
TuiAmountPipe,
TuiCheckbox,
TuiIcon,
TuiLegendItem,
TuiNotification,
TuiRingChart,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly alerts = inject(TuiNotificationService);
private enabled = Array.from({length: 5}, () => true);
protected readonly data = [13769, 12367, 10172, 3018, 2592];
protected readonly sum = tuiSum(...this.data);
protected readonly labels = ['Axes', 'Faxes', 'Taxes', 'Saxes', 'Other'];
protected get value(): readonly number[] {
return this.data.map((value, index) => (this.enabled[index] ? value : 0));
}
protected isEnabled(index: number): boolean {
return this.enabled[index] ?? false;
}
protected toggle(index: number): void {
this.enabled = this.enabled.map((value, i) => (i === index ? !value : value));
}
protected onClick(index: number): void {
if (this.isEnabled(index)) {
this.alerts
.open(`Category spending: ${tuiFormatNumber(this.data[index] ?? 0)} ₽`, {
label: this.labels[index],
})
.subscribe();
} else {
this.toggle(index);
}
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
--tui-chart-categorical-00: #c779d0;
--tui-chart-categorical-01: #feac5e;
--tui-chart-categorical-02: #ff5f6d;
--tui-chart-categorical-03: #4bc0c8;
--tui-chart-categorical-04: #9795cd;
}
.chart {
pointer-events: none;
}
.wrapper {
display: flex;
align-items: center;
margin-block-start: 1rem;
@media @tui-mobile {
flex-direction: column;
}
}
.disable {
.transition(~'transform, color');
margin-inline-start: 0.5rem;
will-change: transform;
color: var(--tui-text-secondary);
pointer-events: auto;
&::before {
font-size: 1rem;
}
&:hover {
color: var(--tui-text-primary);
}
&_rotated {
transform: rotate(45deg);
}
}
.legend {
margin: 0 0 0 2rem;
justify-content: center;
@media @tui-mobile {
margin: 2rem 0 0;
}
}
.item {
margin: 0 0.5rem 0.75rem 0;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiLegendItem} from '@taiga-ui/addon-charts';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {type TuiSizeS} from '@taiga-ui/core';
@Component({
imports: [TuiAmountPipe, TuiDemo, TuiLegendItem],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected text = 'Text inside';
protected active = false;
protected readonly examples = ['With a ring chart', 'Toggling'];
protected readonly sizeVariants: readonly TuiSizeS[] = ['s', 'm'];
protected size = this.sizeVariants[0]!;
protected disabled = false;
protected color = '';
protected readonly colorVariants: readonly string[] = [
'var(--tui-chart-categorical-04)',
'var(--tui-background-accent-1)',
'var(--tui-background-neutral-1)',
];
}
```
---
# components/Like
- **Package**: `KIT`
- **Type**: components
A like component based on native checkbox with icons and custom color for icon when :checked state.
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLike} from '@taiga-ui/kit';
@Component({
imports: [TuiLike],
templateUrl: './index.html',
styles: ':host { display: flex; gap: 1rem; align-items: center; }',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Icons from DI
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLike, tuiLikeOptionsProvider} from '@taiga-ui/kit';
@Component({
imports: [TuiLike],
templateUrl: './index.html',
styles: ':host { display: flex; gap: 1rem; align-items: center; }',
encapsulation,
changeDetection,
providers: [
tuiLikeOptionsProvider({
icons: {unchecked: '@tui.star', checked: '@tui.star-filled'},
}),
],
})
export default class Example {}
```
#### External icons
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLike, tuiLikeOptionsProvider} from '@taiga-ui/kit';
@Component({
imports: [TuiLike],
templateUrl: './index.html',
styles: ':host { display: flex; gap: 1rem; align-items: center; }',
encapsulation,
changeDetection,
providers: [
tuiLikeOptionsProvider({
icons: {
unchecked:
'https://raw.githubusercontent.com/MarsiBarsi/readme-icons/main/github.svg',
checked: '/assets/icons/github.svg',
},
}),
],
})
export default class Example {}
```
#### Other appearances
**Template:**
```html
@for (appearance of appearances; track appearance) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLike} from '@taiga-ui/kit';
@Component({
imports: [TuiLike],
templateUrl: './index.html',
styles: ':host { display: flex; gap: 1rem; align-items: center; }',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly appearances = ['negative', 'positive', 'warning', 'flat'] as const;
}
```
#### With forms
**Template:**
```html
NgModel
Liked: {{ liked }}
Reactive form
Liked: {{ likeForm.value.liked }}
Toggle
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiLike} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, ReactiveFormsModule, TuiButton, TuiLike],
templateUrl: './index.html',
styles: ':host { display: flex; column-gap: 3rem; justify-content: space-between; flex-wrap: wrap; }',
encapsulation,
changeDetection,
})
export default class Example {
protected liked = false;
protected likeForm = new FormGroup({liked: new FormControl(false)});
protected changeValue(): void {
this.liked = !this.liked;
this.likeForm.setValue({liked: !this.likeForm.value.liked});
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = [
'Basic',
'Icons from DI',
'External icons',
'Other appearances',
'With forms',
];
}
```
---
# components/LineChart
- **Package**: `ADDON-CHARTS`
- **Type**: components
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [dots] | `boolean` | Show dots on chart |
| [filled] | `boolean` | Filled with gradient |
| [height] | `number` | Axis Y range, pixel scale is 1:1 |
| [y] | `number` | Start of Y axis |
| [width] | `number` | Axis X range, pixel scale is 1:1 |
| [x] | `number` | Start of X axis |
| [smoothingFactor] | `number` | Smoothing factor from 0 to 99 |
| [value] | `TuiPoint[]` | Array of data |
| [xStringify] | `TuiStringHandler | null` | Function to stringify a value number to a string in axis X hint |
| [yStringify] | `TuiStringHandler | null` | Function to stringify a value number to a string in axis Y hint |
### Usage Examples
#### Line
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAxes, TuiLineChart, TuiLineChartHint} from '@taiga-ui/addon-charts';
import {type TuiContext} from '@taiga-ui/cdk';
import {type TuiPoint} from '@taiga-ui/core';
@Component({
imports: [TuiAxes, TuiLineChart, TuiLineChartHint],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly value: readonly TuiPoint[] = [
[50, 50],
[100, 75],
[150, 50],
[200, 150],
[250, 155],
[300, 190],
[350, 90],
];
protected readonly stringify = String;
protected readonly hintContent = ({
$implicit,
}: TuiContext): number => $implicit[0]?.[1] ?? 0;
}
```
**LESS:**
```less
.axes {
block-size: 12.5rem;
inline-size: 25rem;
color: #bc71c9;
}
```
#### Smooth
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAxes, TuiLineChart} from '@taiga-ui/addon-charts';
import {type TuiPoint} from '@taiga-ui/core';
@Component({
imports: [TuiAxes, TuiLineChart],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly value: readonly TuiPoint[] = [
[50, 50],
[100, 75],
[150, 50],
[200, 150],
[250, 155],
[300, 190],
[350, 90],
];
}
```
**LESS:**
```less
.axes {
block-size: 12.5rem;
inline-size: 25rem;
color: #bc71c9;
}
```
#### Dotted
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAxes, TuiLineChart} from '@taiga-ui/addon-charts';
import {type TuiPoint} from '@taiga-ui/core';
@Component({
imports: [TuiAxes, TuiLineChart],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly dotted: readonly TuiPoint[] = [
[50, 50],
[100, 75],
[150, 50],
];
protected readonly solid: readonly TuiPoint[] = [
[150, 50],
[200, 150],
[250, 155],
];
protected readonly dashed: readonly TuiPoint[] = [
[250, 155],
[300, 190],
[350, 90],
];
}
```
**LESS:**
```less
.axes {
block-size: 12.5rem;
inline-size: 25rem;
color: #bc71c9;
}
.chart {
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
&_dotted {
stroke-dasharray: 2;
}
&_dashed {
stroke-dasharray: 4;
}
}
```
#### Hint
**Template:**
```html
Vertical: {{ value[0] }}
Horizontal: {{ value[1] }}
index: {{ index }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAxes, TuiChartHint, TuiLineChart} from '@taiga-ui/addon-charts';
import {type TuiContext, type TuiStringHandler} from '@taiga-ui/cdk';
import {TuiHint, type TuiPoint} from '@taiga-ui/core';
@Component({
imports: [TuiAxes, TuiChartHint, TuiHint, TuiLineChart],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly value: TuiPoint[] = [
[50, 50],
[100, 75],
[150, 50],
[200, 150],
[250, 155],
[300, 190],
[350, 90],
];
protected readonly singleValue: TuiPoint[] = [[200, 150]];
protected readonly hint: TuiStringHandler> = ({$implicit}) =>
`Vertical: ${$implicit[1]}\nHorizontal: ${$implicit[0]}`;
}
```
**LESS:**
```less
.axes {
block-size: 12.5rem;
inline-size: 25rem;
color: #bc71c9;
}
```
#### Several lines with hints
**Template:**
```html
@for (value of values; track value) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAxes, TuiLineChart, TuiLineChartHint} from '@taiga-ui/addon-charts';
import {type TuiContext, type TuiStringHandler} from '@taiga-ui/cdk';
import {type TuiPoint} from '@taiga-ui/core';
@Component({
imports: [TuiAxes, TuiLineChart, TuiLineChartHint],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly values: TuiPoint[][] = [
[
[50, 50],
[100, 75],
[150, 50],
[200, 150],
[250, 155],
[300, 190],
[350, 90],
],
[
[50, 40],
[100, 60],
[150, 90],
[200, 120],
[250, 150],
[300, 110],
[350, 130],
],
[
[50, 0],
[100, 0],
[150, 80],
[200, 50],
[250, 130],
[300, 200],
[350, 200],
],
];
protected readonly hint: TuiStringHandler> = ({
$implicit,
}) => `${$implicit[0]?.[0]} items:\n\n${$implicit.map(([_, y]) => y).join('$\n')}$`;
}
```
**LESS:**
```less
.axes {
block-size: 12.5rem;
inline-size: 25rem;
}
.chart {
position: absolute;
color: #ffb74c;
&:first-child {
color: #bc71c9;
}
&:last-child {
color: #4dc3f7;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiAxes, TuiLineChart} from '@taiga-ui/addon-charts';
import {type TuiStringHandler} from '@taiga-ui/cdk';
import {type TuiPoint} from '@taiga-ui/core';
@Component({
imports: [TuiAxes, TuiDemo, TuiLineChart],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly examples = [
'Line',
'Smooth',
'Dotted',
'Hint',
'Several lines with hints',
];
protected readonly value: readonly TuiPoint[] = [
[50, 50],
[100, 75],
[150, 50],
[200, 150],
[250, 155],
[300, 190],
[350, 90],
];
protected readonly yStringifyVariants: ReadonlyArray> = [
(y) => `${(10 * y).toLocaleString('ru-RU', {maximumFractionDigits: 0})} $`,
];
protected readonly xStringifyVariants: ReadonlyArray> = [
(x) => `${100 * x}`,
];
protected yStringify: TuiStringHandler | null = null;
protected xStringify: TuiStringHandler | null = null;
protected x = 0;
protected y = 0;
protected width = 400;
protected height = 200;
protected smoothingFactor = 0;
protected filled = false;
protected dots = false;
}
```
### LESS
```less
.axes {
block-size: 12.5rem;
inline-size: 25rem;
color: #bc71c9;
}
```
---
# components/LineClamp
- **Package**: `KIT`
- **Type**: components
Component cuts overflown text with "..." and shows it by hover
### Example
```html
Lorem ipsum Gaudeamus igitur Carpe diem Veni, vidi, vici
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [content] | `PolymorpheusContent` | Content |
| [lineHeight] | `number` | Height of single line. It used to limit component's height. |
| [linesLimit] | `number` | Number of visible lines |
| [style.max-width.px] | `number` | Value of max-width |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (overflownChange) | `boolean` | when all content is visible. |
### Usage Examples
#### Styles change
**Template:**
```html
Use white-space: nowrap to expand to the right
Daenerys of the House Targaryen, the First of Her Name, The Unburnt, Queen of the Andals, the Rhoynar and the First Men, Queen of Meereen, Khaleesi of the Great Grass Sea, Protector of the Realm, Lady Regent of the Seven Kingdoms, Breaker of Chains and Mother of Dragons
Jorah Mormont of House Mormont, Lord of Bear Island
DRAFT Davis, Julia
@if (!value()) { Load dynamic value }
@if (value$ | async; as value) {
} @else { Loading... }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component, inject, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {tuiWatch} from '@taiga-ui/cdk';
import {TuiButton, TuiNotification} from '@taiga-ui/core';
import {TuiChip, TuiLineClamp} from '@taiga-ui/kit';
import {map, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiButton, TuiChip, TuiLineClamp, TuiNotification],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly isE2E = inject(WA_IS_E2E);
protected readonly value = signal('');
protected value$ = timer(this.isE2E ? 0 : 4000).pipe(
map(() => `${'async fake value, '.repeat(10)}end!`),
tuiWatch(),
);
public update(): void {
this.value.set(
'Daenerys of the House Targaryen, the First of Her Name, The Unburnt, Queen of the Andals, the Rhoynar and the First Men, Queen of Meereen, Khaleesi of the Great Grass Sea, Protector of the Realm, Lady Regent of the Seven Kingdoms, Breaker of Chains and Mother of Dragons',
);
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.island {
inline-size: 20rem;
margin-block-end: 1rem;
padding: 1rem;
border: 1px solid var(--tui-border-normal);
border-radius: 1rem;
box-sizing: border-box;
}
.hint {
font: var(--tui-typography-body-s);
line-height: inherit;
}
.no-wrap {
white-space: nowrap;
}
.wrapper {
position: relative;
inset-block-start: 0;
inset-inline-start: 0;
inline-size: 100%;
margin: 0 auto;
border: 1px solid var(--tui-border-normal);
background-color: var(--tui-background-base);
}
.result {
display: flex;
inline-size: 100%;
block-size: 3.5rem;
align-items: center;
.content {
padding: 0 1rem;
}
}
```
#### Expanding
**Template:**
```html
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
@for (text of texts; track text) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDataList, TuiDropdown, TuiLink} from '@taiga-ui/core';
import {TuiChevron, TuiLineClamp} from '@taiga-ui/kit';
function randomString(len: number): string {
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let randomString = '';
for (let i = 0; i < len; i++) {
const randomPoz = Math.floor(Math.random() * charSet.length);
// eslint-disable-next-line unicorn/prefer-string-slice
randomString += charSet.substring(randomPoz, randomPoz + 1);
}
return randomString;
}
@Component({
imports: [TuiChevron, TuiDataList, TuiDropdown, TuiLineClamp, TuiLink],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
protected texts = [
randomString(100),
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
randomString(500),
];
}
```
**LESS:**
```less
.dropdown-button {
inline-size: 16rem;
}
```
#### Custom content workaround
**Template:**
```html
1. Display only the first line, in a popup display remaining lines.
2. Do not use `tui-line-clamp`, use `text-overflow: ellipsis` instead.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLink} from '@taiga-ui/core';
@Component({
imports: [TuiLink],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Appearances
**Template:**
```html
Links by default have action appearance which you can override with action-grayscale or
If you use no appearance, links would inherit text color in tandem with an underline decoration to make them visible.
It would also work well together with dark theme and other text colors
If you want, you can enable dashed underline for any appearance using text-decoration-line: underline or switch inherited link underline to dashed style by text-decoration-style: dashed
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLink} from '@taiga-ui/core';
@Component({
imports: [TuiLink],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
[tuiTheme='dark'] {
background: var(--tui-background-base);
color: var(--tui-text-secondary);
padding: 1rem;
margin: 1rem -1rem;
}
```
#### Long text
**Template:**
```html
Link can be written inline in text.
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the standard dummy text block and if no extra whitespace is introduced, the icon would wrap together with the last word inside the tag
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLink} from '@taiga-ui/core';
@Component({
imports: [TuiLink],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Icons', 'Appearances', 'Long text'];
}
```
---
# components/List
- **Package**: `LAYOUT`
- **Type**: components
### Usage Examples
#### Bulleted
**Template:**
```html
Body L
List of Continents
Eurasia
Africa
Australia
Antarctica
North America
South America
Body M
List of Continents
Eurasia
Africa
Australia
Antarctica
North America
South America
UI S
List of Continents
Eurasia
Africa
Australia
Antarctica
North America
South America
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiList} from '@taiga-ui/layout';
@Component({
imports: [TuiList],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
h2 {
padding-block-start: 0.3125rem;
padding-block-end: 0.3125rem;
}
h6 {
margin: 0;
}
```
#### Numbered
**Template:**
```html
Body L
List of Continents
Eurasia
Africa
Australia
Antarctica
North America
South America
Body M
List of Continents
Eurasia
Africa
Australia
Antarctica
North America
South America
UI S
List of Continents
Eurasia
Africa
Australia
Antarctica
North America
South America
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiList} from '@taiga-ui/layout';
@Component({
imports: [TuiList],
templateUrl: './index.html',
styleUrl: '../1/index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Nested
**Template:**
```html
Header of first list
First level
Second level
Second level
Third level
Third level
Second level
Third level
Third level
Fourth level
Fourth level
First level
Header of second list
First level
Second level
Second level
Third level
Third level
First level
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTitle} from '@taiga-ui/core';
import {TuiHeader, TuiList} from '@taiga-ui/layout';
@Component({
imports: [TuiHeader, TuiList, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### Long text
**Template:**
```html
If a list item is long, the bullet stays aligned to the top.
There are no restrictions on the text length at all. You can write as much text as you want.
This item uses nested tags inside which should not break
If a list item is long, the bullet stays aligned to the top.
There are no restrictions on the text length at all. You can write as much text as you want.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiList} from '@taiga-ui/layout';
@Component({
imports: [TuiList],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
max-inline-size: 20rem;
gap: 1rem;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Bulleted', 'Numbered', 'Nested', 'Long text'];
}
```
---
# components/Loader
- **Package**: `CORE`
- **Type**: components
### Example
```html
Colonel Trautman: It's over Johnny. It's over!
Rambo: Nothing is over! Nothing! You just don't turn it off! It wasn't my war! You asked me I didn't ask you! And I did what I had to do to win, for somebody who wouldn't let us win! Then I come back to the world, and I see all those maggots at the airport, protestin' me, spittin', callin' me a baby killer and all kinds of vile crap! Who are they to protest me?! Huh?! Who are they?! Unless they been me and been there and know what the hell they yellin' about!
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [loading] | `boolean` | Show/hide loader |
| [inheritColor] | `boolean` | Inherit parent color |
| [overlay] | `boolean` | Content overlay when loader is showed |
| [size] | `TuiSizeXS | TuiSizeXL` | Size |
| [textContent] | `PolymorpheusContent` | Custom content under loader |
### Usage Examples
#### With inherited background color
**Template:**
```html
I don't know who you are. I don't know what you want. If you are looking for ransom, I can tell you I don't have money. But what I do have are a very particular set of skills; skills I have acquired over a very long career. Skills that make me a nightmare for people like you. If you let my daughter go now, that'll be the end of it. I will not look for you, I will not pursue you. But if you don't, I will look for you, I will find you, and I will kill you.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLoader} from '@taiga-ui/core';
@Component({
imports: [TuiLoader],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: block;
background: var(--tui-background-accent-1);
box-shadow: 0 0 0 100rem var(--tui-background-accent-1);
color: var(--tui-text-primary-on-accent-1);
}
```
#### With content overlay
**Template:**
```html
I don't know who you are. I don't know what you want. If you are looking for ransom, I can tell you I don't have money. But what I do have are a very particular set of skills; skills I have acquired over a very long career. Skills that make me a nightmare for people like you. If you let my daughter go now, that'll be the end of it. I will not look for you, I will not pursue you. But if you don't, I will look for you, I will find you, and I will kill you.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLoader} from '@taiga-ui/core';
@Component({
imports: [TuiLoader],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: block;
background: #3e4757;
box-shadow: 0 0 0 100rem #3e4757;
color: var(--tui-background-base);
}
```
#### Options
**Template:**
```html
This example demonstrates how to configure loader options using tuiLoaderOptionsProvider. The loader is configured with size 'l', overlay enabled, and inheritColor disabled.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLoader, tuiLoaderOptionsProvider} from '@taiga-ui/core';
@Component({
imports: [TuiLoader],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [
tuiLoaderOptionsProvider({
size: 'l',
inheritColor: false,
overlay: true,
}),
],
})
export default class Example {}
```
**LESS:**
```less
.inline-flex {
display: inline-flex;
}
```
#### Custom stroke width
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLoader, tuiLoaderOptionsProvider} from '@taiga-ui/core';
@Component({
imports: [TuiLoader],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiLoaderOptionsProvider({size: 'xl'})],
})
export default class Example {}
```
**LESS:**
```less
tui-loader {
min-inline-size: 3.5rem;
&:nth-child(1) {
/**
Don't use `rem` units if you support Safari.
Safari doesn't support `rem` units for `stroke-*` properties of ``.
Use `em` units => they will be interpreted as `rem` ones
(`` has `font-size: 1rem`).
Or just use simple `px` units.
*/
--tui-thickness: 0.125em;
}
&:nth-child(2) {
--tui-thickness: 0.25rem;
}
&:nth-child(3) {
--tui-thickness: 0.5rem;
}
}
:host {
display: flex;
gap: 2rem;
}
```
### TypeScript
```ts
import {Component, viewChild} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiLoader, type TuiSizeXS, type TuiSizeXXL} from '@taiga-ui/core';
import {type PolymorpheusContent} from '@taiga-ui/polymorpheus';
@Component({
imports: [TuiDemo, TuiLoader],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly textTemplate = viewChild('textTemplate');
protected readonly routes = DemoRoute;
protected readonly examples = [
'With inherited background color',
'With content overlay',
'Options',
'Custom stroke width',
];
protected loading = true;
protected inheritColor = false;
protected overlay = false;
protected readonly sizeVariants: ReadonlyArray = [
'xs',
's',
'm',
'l',
'xl',
'xxl',
];
protected size = this.sizeVariants[2]!;
protected selectedTemplate = '';
protected readonly textVariants = ['', 'template', 'string'];
protected get template(): PolymorpheusContent {
switch (this.selectedTemplate) {
case 'string': {
return 'string';
}
case 'template': {
return this.textTemplate() || '';
}
default: {
return '';
}
}
}
}
```
### LESS
```less
.example {
padding: 1.25rem;
background: var(--tui-background-neutral-1);
}
```
---
# components/Message
- **Package**: `KIT`
- **Type**: components
Message component is used to display message block.
### Example
```html
Message
```
### Usage Examples
#### Basic
**Template:**
```html
Incoming message Outgoing message
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiMessage} from '@taiga-ui/kit';
@Component({
imports: [TuiMessage],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
}
```
#### Custom color
**Template:**
```html
I'm default 🤘And I'm colored 🤘🏿
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiMessage} from '@taiga-ui/kit';
@Component({
imports: [TuiMessage],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
[tuiAppearance][data-appearance='custom'] {
background: rgba(175, 242, 24, 1);
}
.container {
display: flex;
inline-size: 100%;
}
.incoming {
justify-content: flex-start;
}
.outgoing {
justify-content: flex-end;
}
```
#### With link
**Template:**
```html
All relevant guides in this project with a lot of examples
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLink} from '@taiga-ui/core';
import {TuiMessage} from '@taiga-ui/kit';
@Component({
imports: [TuiLink, TuiMessage],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Chat messages
**Template:**
```html
Fade
Left Comment with very long contentRight Comment with very long content
Multiline
Very long content that goes on the second lineVery long content that goes on the second line
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiFade, TuiMessage} from '@taiga-ui/kit';
@Component({
imports: [TuiFade, TuiMessage],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.fade {
max-inline-size: 11rem;
}
.multiline {
white-space: initial;
}
.container {
display: flex;
inline-size: 100%;
}
.incoming {
justify-content: flex-start;
> [tuiMessage] {
margin-inline-start: 1rem;
margin-inline-end: 2rem;
}
}
.outgoing {
justify-content: flex-end;
> [tuiMessage] {
margin-inline-start: 2rem;
margin-inline-end: 1rem;
}
}
```
#### Inside cells
**Template:**
```html
Alexander I.
USD
Don't deny yourself anything
+ $123 456
June 2
German P.
USD
– $10 000
September 6
For game
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCell, TuiTitle} from '@taiga-ui/core';
import {TuiAvatar, TuiMessage} from '@taiga-ui/kit';
@Component({
imports: [TuiAvatar, TuiCell, TuiMessage, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
[tuiCell] {
max-inline-size: 25rem;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {TuiDocAppearance} from '@demo/components/appearance';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiMessage} from '@taiga-ui/kit';
@Component({
imports: [TuiDemo, TuiDocAppearance, TuiMessage],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly examples = [
'Basic',
'Custom color',
'With link',
'Chat messages',
'Inside cells',
];
}
```
---
# components/MobileCalendar
- **Package**: `ADDON-MOBILE`
- **Type**: components
A calendar for mobile devices. It is used in date picker controls on mobile devices if tuiMobileCalendar directive is applied. You can use TUI_CALENDAR_DATE_STREAM token to set value from outside (see samples)
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [(value)] | `TuiDay | TuiDayRange | readonly TuiDay[] | null` | Value |
| [disabledItemHandler] | `TuiBooleanHandler` | |
| [max] | `TuiDay` | Max date |
| [min] | `TuiDay` | Min date |
| [multi] | `boolean` | Array of single dates |
| [single] | `boolean` | |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (cancel) | `void` | Output when user clicks Cancel |
| (confirm) | `TuiDayRange | TuiDay` | Output when user clicks Confirm (range or single day) |
### Usage Examples
#### Custom dropdown
**Template:**
```html
Choose a date {{ date$ | async }}
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component, inject, INJECTOR, Injector} from '@angular/core';
import {toObservable} from '@angular/core/rxjs-interop';
import {FormControl} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TUI_CALENDAR_DATE_STREAM,
TuiMobileCalendarDropdownComponent,
} from '@taiga-ui/addon-mobile';
import {tuiControlValue, TuiDay} from '@taiga-ui/cdk';
import {TUI_MONTHS, TuiButton, TuiDialogService} from '@taiga-ui/core';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import {combineLatest, map, type Observable} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiButton],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiDialogService);
private readonly injector = inject(INJECTOR);
private readonly months$ = toObservable(inject(TUI_MONTHS));
private readonly control = new FormControl(null);
private readonly dialog$: Observable = this.dialogs.open(
new PolymorpheusComponent(
TuiMobileCalendarDropdownComponent,
Injector.create({
providers: [
{
provide: TUI_CALENDAR_DATE_STREAM,
useValue: tuiControlValue(this.control),
},
],
parent: this.injector,
}),
),
{
appearance: 'fullscreen',
closable: false,
data: {
single: true,
min: TuiDay.currentLocal(),
},
},
);
protected readonly date$ = combineLatest([
tuiControlValue(this.control),
this.months$,
]).pipe(
map(([value, months]) =>
value
? `${months[value.month]} ${value.day}, ${value.year}`
: 'Choose a date',
),
);
protected get empty(): boolean {
return !this.control.value;
}
protected onClick(): void {
this.dialog$.subscribe((value) => this.control.setValue(value));
}
}
```
**LESS:**
```less
.wrapper {
display: flex;
align-items: center;
}
.date {
margin-inline-start: 1rem;
&_empty {
color: var(--tui-text-tertiary);
}
}
```
#### Range
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiMobileCalendar} from '@taiga-ui/addon-mobile';
import {TuiDay} from '@taiga-ui/cdk';
import {tuiCalendarSheetOptionsProvider} from '@taiga-ui/core';
@Component({
imports: [TuiMobileCalendar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiCalendarSheetOptionsProvider({rangeMode: true})],
})
export default class Example {
protected min = new TuiDay(new Date().getFullYear(), new Date().getMonth(), 1);
protected max = new TuiDay(new Date().getFullYear(), new Date().getMonth(), 10);
}
```
**LESS:**
```less
.example {
block-size: 35rem;
}
```
#### Localization
**Template:**
```html
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiMobileCalendar} from '@taiga-ui/addon-mobile';
import {TuiDay, TuiDayOfWeek} from '@taiga-ui/cdk';
import {tuiCalendarOptionsProvider} from '@taiga-ui/core';
@Component({
imports: [TuiMobileCalendar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiCalendarOptionsProvider({weekStart: signal(TuiDayOfWeek.Sunday)})],
})
export default class Example {
protected min = TuiDay.currentLocal();
}
```
**LESS:**
```less
tui-mobile-calendar {
max-inline-size: 20rem;
block-size: 30rem;
}
```
#### Custom dropdown (range)
**Template:**
```html
Calendar {{ date$ | async }}
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component, inject, INJECTOR, Injector} from '@angular/core';
import {toObservable} from '@angular/core/rxjs-interop';
import {FormControl} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TUI_CALENDAR_DATE_STREAM,
TuiMobileCalendarDropdownComponent,
} from '@taiga-ui/addon-mobile';
import {tuiControlValue, TuiDay, type TuiDayRange} from '@taiga-ui/cdk';
import {TUI_MONTHS, TuiButton, TuiDialogService} from '@taiga-ui/core';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import {combineLatest, map, type Observable} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiButton],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiDialogService);
private readonly injector = inject(INJECTOR);
private readonly months$ = toObservable(inject(TUI_MONTHS));
private readonly control = new FormControl(null);
private readonly dialog$: Observable = this.dialogs.open(
new PolymorpheusComponent(
TuiMobileCalendarDropdownComponent,
Injector.create({
providers: [
{
provide: TUI_CALENDAR_DATE_STREAM,
useValue: tuiControlValue(this.control),
},
],
parent: this.injector,
}),
),
{
appearance: 'fullscreen',
closable: false,
data: {min: new TuiDay(2018, 2, 10)},
},
);
protected readonly date$ = combineLatest([
tuiControlValue(this.control),
this.months$,
]).pipe(
map(([value, months]) => {
if (!value) {
return 'Choose a date range';
}
return value.isSingleDay
? `${months[value.from.month]} ${value.from.day}, ${value.from.year}`
: `${months[value.from.month]} ${value.from.day}, ${value.from.year} - ${
months[value.to.month]
} ${value.to.day}, ${value.to.year}`;
}),
);
protected get empty(): boolean {
return !this.control.value;
}
protected onClick(): void {
this.dialog$.subscribe((value) => this.control.setValue(value));
}
}
```
**LESS:**
```less
.wrapper {
display: flex;
align-items: center;
}
.date {
margin-inline-start: 1rem;
&_empty {
color: var(--tui-text-tertiary);
}
}
```
#### Custom dropdown (multi)
**Template:**
```html
Calendar {{ date$ | async }}
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component, inject, INJECTOR, Injector} from '@angular/core';
import {toObservable} from '@angular/core/rxjs-interop';
import {FormControl} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TUI_CALENDAR_DATE_STREAM,
TuiMobileCalendarDropdownComponent,
} from '@taiga-ui/addon-mobile';
import {tuiControlValue, TuiDay} from '@taiga-ui/cdk';
import {TUI_MONTHS, TuiButton, TuiDialogService} from '@taiga-ui/core';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import {combineLatest, map, type Observable} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiButton],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiDialogService);
private readonly injector = inject(INJECTOR);
private readonly months$ = toObservable(inject(TUI_MONTHS));
private readonly control = new FormControl(null);
private readonly dialog$: Observable = this.dialogs.open(
new PolymorpheusComponent(
TuiMobileCalendarDropdownComponent,
Injector.create({
providers: [
{
provide: TUI_CALENDAR_DATE_STREAM,
useValue: tuiControlValue(this.control),
},
],
parent: this.injector,
}),
),
{
appearance: 'fullscreen',
closable: false,
data: {
multi: true,
min: new TuiDay(2018, 2, 10),
},
},
);
protected readonly date$ = combineLatest([
tuiControlValue(this.control),
this.months$,
]).pipe(
map(([value, months]) => {
if (!value?.length) {
return 'Choose dates';
}
return value
.map((day) => `${months[day.month]} ${day.day}, ${day.year}`)
.join('; ');
}),
);
protected get empty(): boolean {
return !this.control.value?.length;
}
protected onClick(): void {
this.dialog$.subscribe((value) => this.control.setValue(value));
}
}
```
**LESS:**
```less
.wrapper {
display: flex;
align-items: center;
}
.date {
margin-inline-start: 1rem;
&_empty {
color: var(--tui-text-tertiary);
}
}
```
#### Without header
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiMobileCalendar} from '@taiga-ui/addon-mobile';
import {TuiDay} from '@taiga-ui/cdk';
import {TUI_CHOOSE_DAY_OR_RANGE_TEXTS} from '@taiga-ui/kit';
@Component({
imports: [TuiMobileCalendar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [
{
provide: TUI_CHOOSE_DAY_OR_RANGE_TEXTS,
useValue: null,
},
],
})
export default class Example {
protected min = new TuiDay(new Date().getFullYear(), new Date().getMonth(), 1);
protected max = new TuiDay(new Date().getFullYear(), new Date().getMonth(), 10);
}
```
**LESS:**
```less
:host {
display: block;
block-size: 35rem;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TUI_CALENDAR_DATE_STREAM, TuiMobileCalendar} from '@taiga-ui/addon-mobile';
import {
TUI_FALSE_HANDLER,
TUI_FIRST_DAY,
TUI_LAST_DAY,
type TuiBooleanHandler,
tuiControlValue,
TuiDay,
type TuiDayRange,
} from '@taiga-ui/cdk';
import {type Observable} from 'rxjs';
@Component({
imports: [TuiDemo, TuiMobileCalendar],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
providers: [
{
deps: [Page],
provide: TUI_CALENDAR_DATE_STREAM,
useFactory: (component: Page): Observable => component.stream,
},
],
})
export default class Page {
protected minVariants = [
TUI_FIRST_DAY,
new TuiDay(2017, 2, 5),
new TuiDay(1900, 0, 1),
];
protected min = this.minVariants[0]!;
protected maxVariants = [
TUI_LAST_DAY,
new TuiDay(2020, 2, 5),
new TuiDay(2300, 0, 1),
];
protected max = this.maxVariants[0]!;
protected single = true;
protected multi = false;
protected readonly disabledItemHandlerVariants: ReadonlyArray<
TuiBooleanHandler
> = [TUI_FALSE_HANDLER, ({day}) => day % 3 === 0];
protected disabledItemHandler = this.disabledItemHandlerVariants[0]!;
protected control = new FormControl(null);
protected stream = tuiControlValue(this.control);
protected readonly routes = DemoRoute;
protected readonly examples = [
'Custom dropdown',
'Range',
'Localization',
'Custom dropdown (range)',
'Custom dropdown (multi)',
'Without header',
];
}
```
### LESS
```less
.calendar {
block-size: min(28.75rem, 60vh);
}
```
---
# components/Navigation
- **Package**: `LAYOUT`
- **Type**: components
Taiga UI is a low level component library with many atomic components that provide great flexibility when combined together. An exception to the general rule is the Navigation component – an opinionated global wrapping navigation that exposes less customization options developed specifically to target data-heavy dashboards with minimal space lost and compact controls.
### Usage Examples
#### Full
**Template:**
```html
@for (group of drawer | keyvalue; track group) { @for (item of group.value; track item) { {{ item.name }} } }
Dark mode A very very long product name
Test
Link 1 Link 2 A very very long project A very very long project Something else Create Notifications More
Reset navigation appearance by tuiDropdownAppearance=""
Set tuiTheme="dark" on the button so it fits the header
Get app theme: readonly dark = inject(TUI_DARK_MODE)
Use it to set theme on the wrapping tag:
[attr.tuiTheme]="dark() ? 'dark' : 'light'"
@for (_ of '-'.repeat(10); track $index) {
Some random content A subtitle
}
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiThemeColorService} from '@taiga-ui/addon-mobile';
import {
TUI_DARK_MODE,
TuiButton,
TuiDataList,
TuiDropdown,
TuiLink,
TuiTitle,
} from '@taiga-ui/core';
import {TuiAvatar, TuiChevron, TuiFade, TuiSwitch, TuiTabs} from '@taiga-ui/kit';
import {TuiCardLarge, TuiHeader, TuiList, TuiNavigation} from '@taiga-ui/layout';
@Component({
imports: [
FormsModule,
TuiAvatar,
TuiButton,
TuiCardLarge,
TuiChevron,
TuiDataList,
TuiDropdown,
TuiFade,
TuiHeader,
TuiLink,
TuiList,
TuiNavigation,
TuiSwitch,
TuiTabs,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly theme = inject(TuiThemeColorService);
protected readonly dark = inject(TUI_DARK_MODE);
protected color = false;
protected onColor(color: boolean): void {
this.theme.color = color ? 'purple' : 'black';
}
}
```
#### Subheader object
**Template:**
```html
Basic
Input
Card
@if (current === 'basic') {
Research and Development Platform
Here you can initiate and participate in the review of package objects. Each object have up to 3 groups of reviewers, with one response required from each type, and any other participant can change both positive.
Personal and Development Plan Here you can initiate and participate in the review of package objects. Each object have up to 3 groups of reviewers, with one response required from each type, and any other participant can change both positive
} First tab Second tab Third tab Button Button @for (_ of '-'.repeat(10); track $index) {
Some random content A subtitle
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiGroup, TuiIcon, TuiInput, TuiLink, TuiTitle} from '@taiga-ui/core';
import {TuiBadge, TuiBlock, TuiBreadcrumbs, TuiFade, TuiTabs} from '@taiga-ui/kit';
import {TuiCardLarge, TuiHeader, TuiNavigation} from '@taiga-ui/layout';
@Component({
imports: [
FormsModule,
TuiBadge,
TuiBlock,
TuiBreadcrumbs,
TuiButton,
TuiCardLarge,
TuiFade,
TuiGroup,
TuiHeader,
TuiIcon,
TuiInput,
TuiLink,
TuiNavigation,
TuiTabs,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected current = 'basic';
}
```
#### Customization
**Template:**
```html
Drawer content Arbitrary content
@for (_ of '-'.repeat(10); track $index) {
Some random content A subtitle
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiPortals, TuiPortalService, tuiProvide, TuiVCR} from '@taiga-ui/cdk';
import {TuiButton, TuiPopupService, TuiTitle} from '@taiga-ui/core';
import {TuiChevron, TuiFade} from '@taiga-ui/kit';
import {
TuiCardLarge,
TuiHeader,
tuiLayoutIconsProvider,
TuiNavigation,
} from '@taiga-ui/layout';
@Component({
imports: [
TuiButton,
TuiCardLarge,
TuiChevron,
TuiFade,
TuiHeader,
TuiNavigation,
TuiTitle,
TuiVCR,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [
tuiLayoutIconsProvider({grid: '@tui.align-justify'}),
// Ignore portal related code, it is only here to position drawer inside the example block
TuiPopupService,
tuiProvide(TuiPortalService, TuiPopupService),
],
})
export default class Example extends TuiPortals {
protected open = true;
}
```
**LESS:**
```less
:host {
// Use TuiThemeColorService instead, this is just for demo purposes
--tui-theme-color: #87ceeb;
}
.drawer {
// Unnecessary when TuiThemeColorService is instead
background: rgb(135, 206, 235);
color: var(--tui-text-primary);
}
aside::before {
display: none;
}
main::before {
content: '';
}
header::after {
display: none;
}
```
#### InputSearch
**Template:**
```html
Taiga UI {{ item.title }} {{ item.subtitle }} Alex Inkin
Toggle open: {{ open }}
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_DEFAULT_MATCHER} from '@taiga-ui/cdk';
import {TuiButton, TuiCell, TuiTextfield, TuiTitle} from '@taiga-ui/core';
import {TuiSearchResults} from '@taiga-ui/experimental';
import {TuiAvatar} from '@taiga-ui/kit';
import {TuiInputSearch, TuiNavigation} from '@taiga-ui/layout';
import {filter, map, startWith, switchMap, timer} from 'rxjs';
interface Result {
href: string;
title: string;
subtitle?: string;
icon?: string;
}
const DATA: Record = {
Documents: [
{
title: 'Monty Python',
href: 'https://en.wikipedia.org/wiki/Monty_Python',
},
],
Code: [
{
title: 'Taiga UI',
href: 'https://github.com/taiga-family/taiga-ui',
icon: '@tui.github',
},
{
title: 'Maskito',
href: 'https://github.com/taiga-family/maskito',
icon: '@tui.github',
},
{
title: 'Web APIs with Angular',
href: 'https://github.com/taiga-family/ng-web-apis',
icon: '@tui.github',
},
],
Links: [
{
title: 'Taiga UI',
subtitle: 'Super awesome library',
href: 'https://taiga-ui.dev',
icon: '/assets/images/taiga.svg',
},
{
title: 'Maskito',
href: 'https://maskito.dev',
icon: '@tui.external-link',
},
],
};
@Component({
imports: [
AsyncPipe,
ReactiveFormsModule,
TuiAvatar,
TuiButton,
TuiCell,
TuiInputSearch,
TuiNavigation,
TuiSearchResults,
TuiTextfield,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class ExampleComponent {
protected readonly popular = ['Taiga UI', 'Maskito', 'Web APIs for Angular'];
protected readonly control = new FormControl('');
protected readonly results$ = this.control.valueChanges.pipe(
filter(Boolean),
switchMap((value: string) =>
timer(2000).pipe(
map(() => this.filter(value)),
startWith(null),
),
),
);
protected open = false;
private filter(query: string): Record {
return Object.entries(DATA).reduce(
(result, [key, value]) => ({
...result,
[key]: value.filter(({title, href, subtitle = ''}) =>
TUI_DEFAULT_MATCHER(`${title}${href}${subtitle}`, query),
),
}),
{},
);
}
}
```
### TypeScript
```ts
import {Component, inject, type OnDestroy} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDemo} from '@demo/utils';
import {TuiThemeColorService} from '@taiga-ui/addon-mobile';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Page implements OnDestroy {
protected readonly theme = inject(TuiThemeColorService);
protected readonly examples = [
'Full',
'Subheader compact',
'Subheader object',
'Customization',
'InputSearch',
];
constructor() {
this.theme.color = 'black';
}
public ngOnDestroy(): void {
this.theme.color = '#ff7043';
}
}
```
### LESS
```less
:host ::ng-deep header[tuiNavigationHeader][tuiNavigationHeader] {
inline-size: 100%;
}
.sticky-example ::ng-deep .t-demo {
block-size: 30rem;
transform: translate3d(0, 0, 0);
padding: 0 !important;
.custom-portal {
position: sticky;
z-index: 10;
inset-block-start: 0;
block-size: 30rem;
margin-block-end: -30rem;
transform: translate3d(0, 0, 0);
pointer-events: none;
}
}
```
---
# components/Notification
- **Package**: `CORE`
- **Type**: components
A notification message that can be displayed inline or as an alert
### Example
```html
Title
I'm a subtitle secondary text
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [size] | `TuiSizeS | TuiSizeL` | Notification size |
| [icon] | `TuiStringHandler | string` | Icon |
| [label] | `string` | Heading |
| [data] | `I` | Arbitrary input data for the notification |
| [autoClose] | `TuiNumberHandler | number` | Auto close timeout, 0 for no auto close |
| [closable] | `boolean` | Has close button |
| [block] | `'start' | 'end'` | Block position |
| [inline] | `'start' | 'center' | 'end'` | Inline position |
### Usage Examples
#### Basic
**Template:**
```html
Hello world Close
I am title
I am content of the notification and I can even wrap to multiple lines.
So what? Whatever
Close
Most boring notification
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiLink, TuiNotification, TuiTitle} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiLink, TuiNotification, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### Options
**Template:**
```html
Works with token options
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiNotification, tuiNotificationOptionsProvider} from '@taiga-ui/core';
@Component({
imports: [TuiNotification],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiNotificationOptionsProvider({
icon: '@tui.alarm-clock',
appearance: 'neutral',
size: 's',
}),
],
})
export default class Example {}
```
#### Interactive
**Template:**
```html
Custom icon color Vertically centered Toasts A notification as a toast
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {RouterLink} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {DemoRoute} from '@demo/routes';
import {TuiNotification, TuiTitle} from '@taiga-ui/core';
@Component({
imports: [RouterLink, TuiNotification, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly routes = DemoRoute;
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
button[tuiNotification]::before {
.center-top();
color: var(--tui-text-negative);
}
```
#### Directive
**Template:**
```html
Show This is a declarative directive alert Close
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiNotification} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiNotification],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly show = signal(false);
}
```
#### Service
**Template:**
```html
Show string &ngsp; Show component
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {Router} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {type TuiPortalContext} from '@taiga-ui/cdk';
import {
TuiButton,
TuiLink,
type TuiNotificationOptions,
TuiNotificationService,
} from '@taiga-ui/core';
import {injectContext, PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import {switchMap, takeUntil} from 'rxjs';
@Component({
imports: [TuiAmountPipe, TuiButton, TuiLink],
template: `
Your balance:
{{ value | tuiAmount: 'RUB' }}
Submit
Increase
`,
changeDetection,
})
class Alert {
protected readonly context =
injectContext, number>>();
protected value = this.context.data;
protected increaseBalance(): void {
this.value += 10;
}
}
@Component({
selector: 'example-4',
imports: [TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly notifications = inject(TuiNotificationService);
protected readonly notification = this.notifications
.open(new PolymorpheusComponent(Alert), {
label: 'Heading is so long that it should be shown in two lines of text',
data: 237,
appearance: 'warning',
autoClose: 0,
})
.pipe(
switchMap((value) =>
this.notifications.open(`Got a value — ${value}`, {label: 'Response'}),
),
takeUntil(inject(Router).events),
);
protected onClick(): void {
this.notification.subscribe();
}
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {TuiDocAppearance} from '@demo/components/appearance';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {
TUI_NOTIFICATION_OPTIONS,
TuiButton,
TuiNotificationService,
TuiTitle,
} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiDemo, TuiDocAppearance, TuiTitle],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly notifications = inject(TuiNotificationService);
protected readonly options = inject(TUI_NOTIFICATION_OPTIONS);
protected readonly sizes = ['s', 'm', 'l'] as const;
protected readonly icons = [this.options.icon, '@tui.clock', ''];
protected readonly autoCloseVariants = [0, 3000, 5000, 1000, 500];
protected readonly inlineVariants = ['start', 'center', 'end'] as const;
protected readonly blockVariants = ['start', 'end'] as const;
protected readonly examples = [
'Basic',
'Options',
'Interactive',
'Directive',
'Service',
];
public size = this.options.size;
public icon = this.options.icon;
public closable = true;
public label = '';
public autoClose = 0;
public block = this.blockVariants[0];
public inline = this.inlineVariants[2];
}
```
---
# components/NotificationMiddle
- **Package**: `KIT`
- **Type**: components
A modal component to indicate an ongoing blocking action
### Example
```html
Show Notification example Close
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [closable] | `boolean` | Whether the notification can be closed by the user taping outside or pressing Escape |
### Usage Examples
#### Default
**Template:**
```html
Show
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiNotificationMiddle} from '@taiga-ui/kit';
@Component({
imports: [TuiButton, TuiNotificationMiddle],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
}
```
#### Content
**Template:**
```html
Loader with text Custom icon Text Icon
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiIconPipe} from '@taiga-ui/core';
import {TuiNotificationMiddle} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiButton, TuiIconPipe, TuiNotificationMiddle],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected text = false;
protected icon = false;
}
```
#### Transition
**Template:**
```html
@if (loading()) {
Please wait...
} @else {
Operation successful!
} Show
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {toObservable, toSignal} from '@angular/core/rxjs-interop';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TUI_FALSE_HANDLER, TuiAnimated} from '@taiga-ui/cdk';
import {TuiButton, TuiLoader} from '@taiga-ui/core';
import {TuiAvatar, TuiNotificationMiddle} from '@taiga-ui/kit';
import {filter, map, startWith, switchMap, take, tap, timer} from 'rxjs';
@Component({
imports: [TuiAnimated, TuiAvatar, TuiButton, TuiLoader, TuiNotificationMiddle],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly open = signal(false);
protected readonly loading = toSignal(
toObservable(this.open).pipe(
filter(Boolean),
switchMap(() =>
timer(3000, 2000).pipe(
take(2),
map(TUI_FALSE_HANDLER),
startWith(true),
tap({complete: () => this.open.set(false)}),
),
),
),
);
protected onClick(): void {
this.open.set(true);
}
}
```
#### Service
**Template:**
```html
Show
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiNotificationMiddleService} from '@taiga-ui/kit';
import {bufferTime, first, startWith, switchMap, timer} from 'rxjs';
@Component({
imports: [TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly notification = inject(TuiNotificationMiddleService);
protected onClick(): void {
this.notification
.open('Loading...')
.pipe(
startWith(null),
// Imitating a quick request
switchMap(() => timer(100)),
// Using minimal time to show a notification
bufferTime(600),
first(),
)
.subscribe();
}
}
```
### TypeScript
```ts
import {Component, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiButton} from '@taiga-ui/core';
import {TuiNotificationMiddle} from '@taiga-ui/kit';
@Component({
imports: [TuiButton, TuiDemo, TuiNotificationMiddle],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Page {
protected routes = DemoRoute;
protected readonly examples = ['Default', 'Content', 'Transition', 'Service'];
protected readonly open = signal(false);
protected closable = false;
}
```
---
# components/Pager
- **Package**: `KIT`
- **Type**: components
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [size] | `TuiSizeS` | Layout size |
| [index] | `number` | Current active dot |
| [max] | `number` | Max visible dots |
| [count] | `number` | Count of dots |
| [valueContent] | `TemplateRef` | A template for custom view |
### Usage Examples
#### Basic
**Template:**
```html
Try again later Retry }
```
**TypeScript:**
```ts
import {Component, inject, signal} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_MOBILE} from '@ng-web-apis/platform';
import {TuiButton, TuiDialog, TuiLoader, TuiTitle} from '@taiga-ui/core';
import {TuiBlockStatus, TuiPdfViewer} from '@taiga-ui/layout';
@Component({
imports: [TuiBlockStatus, TuiButton, TuiDialog, TuiLoader, TuiPdfViewer, TuiTitle],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly sanitizer = inject(DomSanitizer);
protected readonly isMobile = inject(WA_IS_MOBILE);
protected readonly pdf = '/assets/media/taiga.pdf';
protected open = false;
protected readonly url = this.sanitizer.bypassSecurityTrustResourceUrl(
'https://app.embedpdf.com/',
);
protected readonly loading = signal(true);
protected readonly error = signal(false);
protected openPdf(): void {
this.open = true;
this.load();
}
protected load(): void {
this.loading.set(true);
setTimeout(() => {
this.loading.set(false);
this.error.set(Math.random() <= 0.5);
}, 1000);
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly routes = DemoRoute;
protected examples = ['Basic', 'With responsive dialog', 'Loading and error states'];
}
```
---
# components/PieChart
- **Package**: `ADDON-CHARTS`
- **Type**: components
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [(activeItemIndex)] | `number` | Active fragment |
| [size] | `TuiSizeS | TuiSizeXL` | Size |
| [value] | `readonly number[]` | Value |
### Usage Examples
#### Sizes
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiPieChart} from '@taiga-ui/addon-charts';
@Component({
imports: [TuiPieChart],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly value = [40, 30, 20, 10];
protected index = 1;
}
```
**LESS:**
```less
.wrapper {
display: flex;
align-items: center;
}
```
#### With labels
**Template:**
```html
{{ value[index] || 0 | tuiAmount: 'RUB' }}
{{ labels[index] }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiChartHint, TuiPieChart} from '@taiga-ui/addon-charts';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {TuiHint} from '@taiga-ui/core';
@Component({
imports: [TuiAmountPipe, TuiChartHint, TuiHint, TuiPieChart],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly value = [13769, 12367, 10172, 3018, 2592];
protected readonly labels = ['Food', 'Cafe', 'Open Source', 'Taxi', 'Other'];
}
```
**LESS:**
```less
:host {
--tui-chart-categorical-00: #c779d0;
--tui-chart-categorical-01: #feac5e;
--tui-chart-categorical-02: #ff5f6d;
--tui-chart-categorical-03: #4bc0c8;
--tui-chart-categorical-04: #9795cd;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiPieChart} from '@taiga-ui/addon-charts';
import {TuiCurrency, tuiGetCurrencySymbol} from '@taiga-ui/addon-commerce';
import {type TuiContext, tuiRound, tuiSum} from '@taiga-ui/cdk';
import {tuiFormatNumber, type TuiSizeXL, type TuiSizeXS} from '@taiga-ui/core';
import {type PolymorpheusContent} from '@taiga-ui/polymorpheus';
@Component({
imports: [TuiDemo, TuiPieChart],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly examples = ['Sizes', 'With labels'];
protected readonly routes = DemoRoute;
protected readonly valueVariants = [
[0, 30, 20, 10],
[40, 30, 20, 10],
[13769, 12367, 10172, 3018, 2592],
];
protected value = this.valueVariants[0]!;
protected readonly activeItemIndexVariants = [Number.NaN, 0, 1, 2];
protected activeItemIndex = this.activeItemIndexVariants[0]!;
protected readonly sizeVariants: ReadonlyArray = [
'xs',
's',
'm',
'l',
'xl',
];
protected size = this.sizeVariants[2]!;
protected readonly contentVariants: ReadonlyArray<
PolymorpheusContent>
> = [
'',
({$implicit}) => this.getPercent($implicit),
({$implicit}) => this.format($implicit),
];
protected hintContent = this.contentVariants[0]!;
protected getPercent(index: number): string {
return `${tuiRound((100 * (this.value[index] ?? 0)) / tuiSum(...this.value), 2)} %`;
}
protected format(index: number): string {
return `${tuiFormatNumber(this.value[index] ?? 0)} ${tuiGetCurrencySymbol(
TuiCurrency.Ruble,
)}`;
}
}
```
### LESS
```less
.chart {
margin: 0 auto;
}
```
---
# components/Pin
- **Package**: `KIT`
- **Type**: components
Pins are used to show a location on a 2D plane. Use color , background , border and box-shadow to customize the pin Pins are designed to be absolutely positioned on map or similar medium, therefore specifically their center is placed where you put them.
### Usage Examples
#### Default
**Template:**
```html
4.5
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
import {TuiPin} from '@taiga-ui/kit';
@Component({
imports: [TuiIcon, TuiPin],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 2rem;
background: var(--tui-background-base-alt);
box-shadow: 0 0 0 2rem var(--tui-background-base-alt);
padding: 1rem 0 0 1rem;
margin-block-end: -1rem;
}
.white {
background: var(--tui-background-base);
color: var(--tui-background-accent-2);
border-width: 0.125rem;
}
```
#### Dot
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiPin} from '@taiga-ui/kit';
@Component({
imports: [TuiPin],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 2rem;
background: var(--tui-background-base-alt);
box-shadow: 0 0 0 2rem var(--tui-background-base-alt);
padding: 0.375rem 0 0 0.375rem;
margin-block-end: -0.375rem;
}
.blue {
background: #428bf9;
}
.yellow {
background: #ffdd2d;
}
.green {
background: #00b92d;
}
.red {
background: #f52222;
}
```
#### Openable
**Template:**
```html
4.5
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiIcon} from '@taiga-ui/core';
import {TuiPin} from '@taiga-ui/kit';
@Component({
imports: [TuiIcon, TuiPin],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected a = true;
protected b = false;
protected c = true;
protected d = false;
}
```
**LESS:**
```less
:host {
position: relative;
display: block;
block-size: 5rem;
border-inline-start: 2rem solid transparent;
background: var(--tui-background-base-alt);
box-shadow: 0 0 0 2rem var(--tui-background-base-alt);
}
[tuiPin] {
position: absolute;
inset-block-start: 4rem;
}
.link {
border: 0.125rem solid var(--tui-text-action);
color: var(--tui-text-action);
background: var(--tui-background-base);
}
.crazy {
background: #ff9bda;
color: #0075ff;
border-color: #7b439e;
box-shadow: 0 0 0 0.125rem #b286ff;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Default', 'Dot', 'Openable'];
}
```
---
# components/Popout
- **Package**: `EXPERIMENTAL`
- **Type**: components
Using service to show content in a new window.
### Usage Examples
#### Popout window
**Template:**
```html
Bidirectional communication
Toggle popout window
Bidirectional communication
```
**TypeScript:**
```ts
import {Component, inject, signal, type TemplateRef} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiAutoFocus} from '@taiga-ui/cdk';
import {TuiButton, TuiInput} from '@taiga-ui/core';
import {TuiPopoutService} from '@taiga-ui/experimental';
import {TuiCard} from '@taiga-ui/layout';
import {type Subscription} from 'rxjs';
@Component({
imports: [FormsModule, TuiAutoFocus, TuiButton, TuiCard, TuiInput],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
private readonly popout = inject(TuiPopoutService);
private sub: Subscription | null = null;
protected readonly value = signal('Value');
protected toggle(content: TemplateRef): void {
if (this.sub) {
this.sub.unsubscribe();
this.sub = null;
} else {
this.sub = this.popout
.open(content, {
title: 'Page',
features: {
width: 320,
height: 160,
left: 600,
top: 300,
},
})
.subscribe({
complete: () => {
this.sub = null;
},
});
}
}
}
```
#### Picture in Picture
**Template:**
```html
@if (!supported) {
Document Picture-in-Picture is not supported in your browser, fallback to regular popout window will be used
} Open Picture-in-Picture
```
**TypeScript:**
```ts
import {Component, inject, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {WA_DOCUMENT_PIP} from '@ng-web-apis/experimental';
import {TuiButton, TuiNotification} from '@taiga-ui/core';
import {TuiPopoutService} from '@taiga-ui/experimental';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import {type Subscription} from 'rxjs';
import {Popout} from './popout';
@Component({
imports: [TuiButton, TuiNotification],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly popout = inject(TuiPopoutService);
protected readonly supported = !!inject(WA_DOCUMENT_PIP)?.requestWindow;
protected readonly subscription = signal(null);
protected open(): void {
this.subscription.set(
this.popout
.open(new PolymorpheusComponent(Popout), {
pip: true,
features: {height: 480, width: 320},
})
.subscribe({complete: () => this.subscription.set(null)}),
);
}
}
```
#### Directive
**Template:**
```html
Call us
Taiga Headquarters
Calling...
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiButton, TuiTitle} from '@taiga-ui/core';
import {TuiPopout} from '@taiga-ui/experimental';
import {TuiAvatar} from '@taiga-ui/kit';
import {TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [TuiAvatar, TuiButton, TuiHeader, TuiPopout, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Example {
protected readonly open = signal(false);
}
```
**LESS:**
```less
main {
display: grid;
block-size: 100vh;
place-items: center;
place-content: center;
grid-auto-rows: min-content;
gap: 2rem;
padding: 2rem;
box-sizing: border-box;
background: var(--tui-background-base);
}
section {
display: flex;
gap: 1rem;
}
[tuiTitle] {
text-align: center;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page extends Array {
protected readonly examples = ['Popout window', 'Picture in Picture', 'Directive'];
protected readonly [1] = {
'popout.ts': import('./examples/2/popout.ts?raw', {with: {loader: 'text'}}),
};
}
```
---
# components/Preview
- **Package**: `KIT`
- **Type**: components
Preview component allows to open modal for viewing some document and to work with it (download, zoom, rotate etc) As a document you can provide images, embeds and other arbitrary content. The component automatically adjusts to the mobile device
### Usage Examples
#### Full preview
**Template:**
```html
With all features
Show preview {{ titles[index] }} Delete Download Close
Important document
Hello everyone! This is some important document in A4 format, although it is made using html
This shows that the component preview can work with absolutely any content: this way you can show any template, image, pdf or even iframe with your favorite site. We will put this content in the center of the portal and provide the user with control over it, and we will provide you with convenient levers to change it and process actions.
```
**TypeScript:**
```ts
import {Component, inject, type TemplateRef, viewChild} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiClamp, TuiSwipe, type TuiSwipeEvent} from '@taiga-ui/cdk';
import {TuiButton, type TuiDialogContext, TuiNotificationService} from '@taiga-ui/core';
import {TuiPreview, TuiPreviewDialogService} from '@taiga-ui/kit';
import {type PolymorpheusContent, PolymorpheusOutlet} from '@taiga-ui/polymorpheus';
@Component({
imports: [PolymorpheusOutlet, TuiButton, TuiPreview, TuiSwipe],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly previewService = inject(TuiPreviewDialogService);
private readonly alerts = inject(TuiNotificationService);
protected readonly preview = viewChild>('preview');
protected readonly contentSample =
viewChild>>('contentSample');
protected index = 0;
protected length = 2;
protected titles = ['Transaction cert.jpg', 'My face.jpg'];
protected get previewContent(): PolymorpheusContent {
const content = this.contentSample();
return this.index === 0 && content
? content
: 'https://avatars.githubusercontent.com/u/10106368';
}
protected show(): void {
this.previewService.open(this.preview() || '').subscribe({
complete: () => console.info('complete'),
});
}
protected download(): void {
this.alerts.open('Downloading...').subscribe();
}
protected delete(): void {
this.alerts.open('Deleting...').subscribe();
}
protected onSwipe(swipe: TuiSwipeEvent): void {
if (swipe.direction === 'left') {
this.index = tuiClamp(this.index + 1, 0, this.length - 1);
}
if (swipe.direction === 'right') {
this.index = tuiClamp(this.index - 1, 0, this.length - 1);
}
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.content {
font: var(--tui-typography-body-l);
background-color: var(--tui-background-base);
inline-size: 50rem;
block-size: 68.75rem;
padding: 3.75rem;
box-sizing: border-box;
border-radius: 0.75rem;
}
.polymorpheus {
padding: 2.5rem 10.375rem;
}
```
#### Preview with directive
**Template:**
```html
Show preview {{ titles[index] }} Close
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiPreview} from '@taiga-ui/kit';
import {PolymorpheusOutlet} from '@taiga-ui/polymorpheus';
@Component({
imports: [PolymorpheusOutlet, TuiButton, TuiPreview],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
protected index = 0;
protected length = 2;
protected titles = ['pic_1.jpg', 'pic_2.jpg'];
protected content = [
'https://picsum.photos/600/500',
'https://picsum.photos/500/600',
];
}
```
#### Simple mode
**Template:**
```html
Show simple preview Close
```
**TypeScript:**
```ts
import {Component, inject, type TemplateRef, viewChild} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, type TuiDialogContext} from '@taiga-ui/core';
import {TuiPreview, TuiPreviewDialogService} from '@taiga-ui/kit';
@Component({
imports: [TuiButton, TuiPreview],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly previewDialogService = inject(TuiPreviewDialogService);
protected readonly preview = viewChild>('preview');
protected show(): void {
this.previewDialogService.open(this.preview() || '').subscribe();
}
}
```
**LESS:**
```less
.content {
inline-size: 80%;
block-size: 80%;
}
```
#### With loading and unavailable image
**Template:**
```html
Show preview {{ title$ | async }} Download Close @if (contentUnavailable$ | async) {
Use '"> 's CSS-property color to set solid color of progress indicator.
With fancy color gradient
Set component's input property color to get more complex color combinations.
Use directive tuiProgressFixedGradient to make gradient fixed.
Multicolor segments
Use tuiProgressColorSegments directive to to get multicolor segments.
```
**TypeScript:**
```ts
import {AsyncPipe, isPlatformServer} from '@angular/common';
import {Component, inject, PLATFORM_ID} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiProgress} from '@taiga-ui/kit';
import {of, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly animationDisabled =
inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID));
protected readonly fastValue$ = this.animationDisabled ? of(80) : timer(500, 100);
protected readonly slowValue$ = this.animationDisabled ? of(4) : timer(500, 2000);
protected readonly colors = [
'var(--tui-chart-categorical-01)',
'var(--tui-chart-categorical-21)',
'lightskyblue',
'#3682db',
'var(--tui-background-accent-1)',
];
}
```
**LESS:**
```less
.progress {
margin-block-end: 1rem;
color: var(--tui-chart-categorical-09);
}
.description {
font: var(--tui-typography-heading-h6);
margin-block-end: 1rem;
&:first-child {
margin-block-start: 0;
}
}
```
#### Sizes
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### With label
**Template:**
```html
@if (value$ | async; as value) { {{ value }}%
}
```
**TypeScript:**
```ts
import {AsyncPipe, isPlatformServer} from '@angular/common';
import {Component, inject, PLATFORM_ID} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiProgress} from '@taiga-ui/kit';
import {map, of, startWith, takeWhile, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly max = 100;
protected readonly value$ =
inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID))
? of(30)
: timer(300, 300).pipe(
map((i) => i + 30),
startWith(30),
takeWhile((value) => value <= this.max),
);
}
```
**LESS:**
```less
.label-wrapper {
inline-size: 100%;
text-shadow: 0 0 0.25rem #000;
color: var(--tui-text-primary-on-accent-1);
}
```
#### Stacked progress bars
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.label-wrapper {
inline-size: 100%;
}
.progress {
&:nth-child(1) {
color: #a3ecb3;
}
&:nth-child(2) {
color: #39b54a;
}
}
```
#### Indeterminate
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiProgress],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Customizable corners
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
[tuiProgressBar] {
border-radius: 0;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiLink, type TuiSizeXS, type TuiSizeXXL} from '@taiga-ui/core';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiDemo, TuiLink, TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected value = 6;
protected max = 10;
protected readonly sizeVariants: ReadonlyArray = [
'xs',
's',
'm',
'l',
'xl',
'xxl',
];
protected size = this.sizeVariants[2]!;
protected readonly colorVariants: readonly string[] = [
'var(--tui-background-accent-1)',
'lightskyblue',
'#3682db',
'rgba(74, 201, 155, 1)',
'linear-gradient(to right, var(--tui-chart-categorical-02), var(--tui-chart-categorical-14), var(--tui-chart-categorical-12))',
];
protected color = this.colorVariants[0]!;
protected readonly examples = [
'Basic',
'Multicolor',
'Sizes',
'With label',
'Stacked progress bars',
'Indeterminate',
'Customizable corners',
];
}
```
### LESS
```less
@import '@taiga-ui/styles/utils';
.api-progress {
inline-size: 50%;
@media @tui-mobile {
inline-size: 100%;
}
}
dt,
dd {
display: inline;
margin: 0;
}
```
---
# components/ProgressCircle
- **Package**: `KIT`
- **Type**: components
'"> is a component to visually represent the completion of a process or operation (as a partially filled circle/ring). It shows how much has been completed and how much remains.
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [value] | `number` | is omitted. |
| [max] | `number` | |
| [size] | `TuiSizeXXL | TuiSizeXXS` | Size of the circle. |
| [color] | `string` | |
| [arc] | `boolean` | Use arc shape with small bottom open arc sector (instead of default circle). |
| [style.--tui-thickness.px] | `number` | Width of the circle's stroke |
### Usage Examples
#### Basic
**Template:**
```html
```
**TypeScript:**
```ts
import {AsyncPipe, isPlatformServer} from '@angular/common';
import {Component, inject, PLATFORM_ID} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiProgress} from '@taiga-ui/kit';
import {map, of, startWith, takeWhile, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiProgress],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly max = 100;
protected readonly value$ =
inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID))
? of(30)
: timer(300, 200).pipe(
map((i) => i + 30),
startWith(30),
takeWhile((value) => value <= this.max),
);
}
```
#### Sizes
**Template:**
```html
@for (size of sizes; track size) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly sizes = ['xxs', 'xs', 's', 'm', 'l', 'xl', 'xxl'] as const;
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
display: flex;
flex-direction: column;
gap: 1rem;
@media @tui-desktop-min {
flex-direction: row-reverse;
}
}
```
#### With label
**Template:**
```html
@if (value$ | async; as value) { COMPLETED{{ value }}% }
```
**TypeScript:**
```ts
import {AsyncPipe, isPlatformServer} from '@angular/common';
import {Component, inject, PLATFORM_ID} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiProgress} from '@taiga-ui/kit';
import {map, of, startWith, takeWhile, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly max = 100;
protected readonly value$ =
inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID))
? of(30)
: timer(300, 200).pipe(
map((i) => i + 30),
startWith(30),
takeWhile((value) => value <= this.max),
);
}
```
**LESS:**
```less
.text {
font: var(--tui-typography-body-s);
color: var(--tui-text-tertiary);
}
.percent {
font: var(--tui-typography-heading-h6);
}
```
#### Colors
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.progress {
&[data-size='l'] {
color: var(--tui-chart-categorical-01);
}
&[data-size='m'] {
color: var(--tui-chart-categorical-03);
}
&[data-size='s'] {
color: var(--tui-chart-categorical-09);
}
}
```
#### Dynamic color
**Template:**
```html
```
**TypeScript:**
```ts
import {AsyncPipe, isPlatformServer} from '@angular/common';
import {Component, inject, PLATFORM_ID} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiProgress} from '@taiga-ui/kit';
import {map, of, repeat, share, takeWhile, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly max = 100;
protected readonly value$ =
inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID))
? of(30)
: timer(300, 200).pipe(
takeWhile((value) => value <= this.max),
share(),
repeat(),
);
protected readonly color$ = this.value$.pipe(
map((value) => {
if (value < 33) {
return 'red';
}
if (value < 66) {
return 'yellow';
}
return 'green';
}),
);
}
```
**LESS:**
```less
tui-progress-circle {
transition: color 2s;
}
```
#### Anti-clockwise direction
**Template:**
```html
```
**TypeScript:**
```ts
import {AsyncPipe, isPlatformServer} from '@angular/common';
import {Component, inject, PLATFORM_ID} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_E2E} from '@ng-web-apis/platform';
import {TuiProgress} from '@taiga-ui/kit';
import {of, repeat, takeWhile, timer} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly max = 100;
protected readonly value$ =
inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID))
? of(30)
: timer(300, 200).pipe(
takeWhile((value) => value <= this.max),
repeat(),
);
}
```
**LESS:**
```less
tui-progress-circle {
transform: scaleX(-1) // reversed direction
rotate(-90deg); // top side starting point
}
```
#### Thickness
**Template:**
```html
@for (_ of '-'.repeat(4); track $index) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
tui-progress-circle {
&:nth-child(1) {
--tui-thickness: 0.125rem;
}
&:nth-child(2) {
--tui-thickness: 0.25rem;
}
&:nth-child(3) {
--tui-thickness: 0.375rem;
}
&:nth-child(4) {
--tui-thickness: 0.5rem;
}
}
:host {
display: flex;
flex-direction: column;
gap: 1rem;
@media @tui-desktop-min {
flex-direction: row;
}
}
```
#### Arc mode
**Template:**
```html
@for (size of sizes; track size) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiProgress],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly sizes = ['xxs', 'xs', 's', 'm', 'l', 'xl', 'xxl'] as const;
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
display: flex;
flex-direction: row-reverse;
gap: 1rem;
@media @tui-mobile {
flex-direction: column;
margin-block-end: 1rem;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiLink, type TuiSizeXXL, type TuiSizeXXS, TuiTitle} from '@taiga-ui/core';
import {tuiInputNumberOptionsProvider, TuiProgress} from '@taiga-ui/kit';
@Component({
imports: [TuiDemo, TuiLink, TuiProgress, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
providers: [tuiInputNumberOptionsProvider({min: 0})],
})
export default class Page {
protected value = 6;
protected max = 10;
protected arc = false;
protected thickness = 6;
protected readonly sizeVariants: ReadonlyArray = [
'xxs',
'xs',
's',
'm',
'l',
'xl',
'xxl',
];
protected size: TuiSizeXXL | TuiSizeXXS = 'm';
protected readonly colorVariants: readonly string[] = [
'var(--tui-background-accent-1)',
'lightskyblue',
'#3682db',
'rgba(74, 201, 155, 1)',
'url(#gradient)',
];
protected color = this.colorVariants[0]!;
protected readonly examples = [
'Basic',
'Sizes',
'With label',
'Colors',
'Dynamic color',
'Anti-clockwise direction',
'Thickness',
'Arc mode',
];
}
```
### LESS
```less
dt,
dd {
display: inline;
margin: 0;
}
```
---
# components/PullToRefresh
- **Package**: `ADDON-MOBILE`
- **Type**: components
Component to refresh content after pull top. It emulates appearance of native iOS and Android components It emits (pulled) event when the pull threshold is reached. You can set that threshold in pixels by TUI_PULL_TO_REFRESH_THRESHOLD DI token. You can finish loading with TUI_PULL_TO_REFRESH_LOADED stream token that can be provided in DI. Use overscroll-behavior: none; CSS on your scrolling container to stop elastic scrolling on iOS
### Usage Examples
#### Android
**Template:**
```html
Finish loading
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
}
```
**TypeScript:**
```ts
import {Component, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiHint, TuiTitle} from '@taiga-ui/core';
import {TuiAvatar, TuiPulse} from '@taiga-ui/kit';
@Component({
imports: [TuiAvatar, TuiButton, TuiHint, TuiPulse, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly step = signal(0);
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
tui-pulse {
color: var(--tui-background-accent-2);
}
.step-1 {
position: absolute;
inset-block-start: 20%;
inset-inline-end: 10%;
}
.step-2 {
position: absolute;
inset-block-start: 25%;
inset-inline-end: 25%;
}
.avatar {
inline-size: 100%;
block-size: 12rem;
object-fit: cover;
object-position: top;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Popover'];
}
```
---
# components/Push
- **Package**: `KIT`
- **Type**: components
Notifications in style of native browser push
### Example
```html
I've seen things you people wouldn't believe. Attack ships on fire off The Shoulder Of Orion. I watched C-Beams glitter in the dark near The Tannhauser Gate. All those moments will be lost in time, like tears in rain. I want more life Time to die
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [heading] | `string` | Heading of the push |
| [type] | `string` | Small text near icon, typically, category of the message |
| [lines] | `number` | A number of visible lines |
| [timestamp] | `number | string` | if the number is passed. |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (close) | `void` | Output for close button clicks. If you do not listen to this output, close button is hidden. |
### Usage Examples
#### Basic
**Template:**
```html
Do you like our owl? It's artificial? Nice hooters! I've had people walk out on me before, but not when I was being so charming.
I’ve seen things you people wouldn't believe. Attack ships on fire off The Shoulder Of Orion. I watched C-Beams glitter in the dark near The Tannhauser Gate. All those moments will be lost in time, like tears in rain. Time to die
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiIcon, TuiLink, TuiNotificationService} from '@taiga-ui/core';
import {TuiPush} from '@taiga-ui/kit';
@Component({
imports: [TuiButton, TuiIcon, TuiLink, TuiPush],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly alert = inject(TuiNotificationService);
protected onClose(): void {
this.alert
.open('Close button is visible when you subscribe to (close) output')
.subscribe();
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
display: block;
}
.wrapper {
box-shadow: var(--tui-shadow-small);
inline-size: 22.5rem;
max-inline-size: 100%;
border-radius: var(--tui-radius-l);
margin-block-end: 1rem;
background: var(--tui-background-elevation-2);
}
.push {
box-shadow: none;
}
.human {
color: var(--tui-text-positive);
}
```
#### Service
**Template:**
```html
Show push
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiNotificationService} from '@taiga-ui/core';
import {TuiPushService} from '@taiga-ui/kit';
import {switchMap, take} from 'rxjs';
@Component({
imports: [TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly push = inject(TuiPushService);
protected readonly alert = inject(TuiNotificationService);
protected onClick(): void {
this.push
.open('This is heavy!', {
heading: 'Great Scott!',
type: 'Quote',
icon: '@tui.video',
buttons: ['Roads?', '1.21 Gigawatts!?!'],
})
.pipe(
take(1),
switchMap((button) => this.alert.open(button)),
)
.subscribe();
}
}
```
#### Directive
**Template:**
```html
Show push I have a bad feeling about this... Fortune Glory
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiIcon, TuiLink} from '@taiga-ui/core';
import {TuiPush} from '@taiga-ui/kit';
@Component({
imports: [TuiButton, TuiIcon, TuiLink, TuiPush],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
protected toggle(open: boolean): void {
this.open = open;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiButton, TuiIcon} from '@taiga-ui/core';
import {TuiPush} from '@taiga-ui/kit';
@Component({
imports: [TuiButton, TuiDemo, TuiIcon, TuiPush],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Service', 'Directive'];
protected heading = '';
protected type = '';
protected lines = 2;
protected readonly timestampVars = ['', 'A moment ago', 123456789];
protected timestamp = this.timestampVars[0]!;
protected readonly routes = DemoRoute;
}
```
---
# components/Radio
- **Package**: `CORE`
- **Type**: components
A radio component that is able to imitate native control on mobile platforms. Use --tui-background-accent-2 CSS variable to customize color of native control emulation Due to internal Angular implementation of radio buttons, you are required to add name attribute to your input tag, unless you are using formControlName
### Usage Examples
#### Platforms
**Template:**
```html
@for (platform of platforms; track $index) {
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiKeySteps} from '@taiga-ui/core';
import {TuiRange} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, JsonPipe, TuiRange],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly ticksLabels = ['0', '10K', '100K', '500k', '1000K'];
protected readonly segments = this.ticksLabels.length - 1;
// 12.5% (of total distance) per step
protected readonly stepPercentage = 100 / (2 * this.segments);
protected value = [0, 100_000];
protected readonly keySteps: TuiKeySteps = [
// [percent, value]
[0, 0],
[25, 10_000],
[50, 100_000],
[75, 500_000],
[100, 1_000_000],
];
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.range {
z-index: 1;
/* (Optionally) expand clickable area as you wish */
&::after {
inset-block-start: -0.5rem;
inset-block-end: -1.5rem;
}
}
.ticks-labels {
.tui-slider-ticks-labels();
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {type TuiKeySteps, TuiTitle} from '@taiga-ui/core';
import {TuiRange} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiDemo, TuiRange, TuiTitle],
templateUrl: './index.html',
styles: `
:host tui-doc-example ::ng-deep tui-range {
max-inline-size: 19rem;
}
`,
changeDetection,
})
export default class Page {
protected readonly examples = ['Size', 'Segments', 'KeySteps'];
protected readonly control = new FormControl([0, 0]);
protected readonly limitVariants: readonly number[] = [Infinity, 100, 50, 10, 5, 1];
protected min = 0;
protected max = 100;
protected margin = 0;
protected limit = Infinity;
protected step = 1;
protected segments = 1;
protected thumbSize = 12;
protected readonly keyStepsVariants: readonly TuiKeySteps[] = [
[
[0, 0],
[50, 1_000],
[100, 10_000],
],
];
protected keySteps: TuiKeySteps | null = null;
protected get disabled(): boolean {
return this.control.disabled;
}
protected set disabled(value: boolean) {
if (value) {
this.control.disable();
return;
}
this.control.enable();
}
}
```
---
# components/Rating
- **Package**: `KIT`
- **Type**: components
### Usage Examples
#### Basic
**Template:**
```html
Rate Taiga UI
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiRating} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiRating],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 0;
}
```
**LESS:**
```less
.rating {
font-size: 1.5rem;
inline-size: 10rem;
}
```
#### Custom icons
**Template:**
```html
Are you satisfied with Taiga UI? @if (value) { Clear }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiContext} from '@taiga-ui/cdk';
import {TuiButton} from '@taiga-ui/core';
import {TuiRating} from '@taiga-ui/kit';
import {type PolymorpheusContent} from '@taiga-ui/polymorpheus';
@Component({
imports: [FormsModule, TuiButton, TuiRating],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = 0;
protected readonly icon: PolymorpheusContent> = ({$implicit}) => {
switch ($implicit) {
case 1:
return '@tui.frown';
case 2:
return '@tui.meh';
default:
return '@tui.smile';
}
};
}
```
**LESS:**
```less
.rating,
.button {
display: inline-block;
vertical-align: middle;
}
.rating {
color: var(--tui-status-info);
&[data-value='1'] {
color: var(--tui-status-negative);
background-color: var(--tui-status-negative-pale-hover);
}
&[data-value='2'] {
color: var(--tui-status-warning);
background-color: var(--tui-status-warning-pale-hover);
}
&[data-value='3'] {
color: var(--tui-status-positive);
background-color: var(--tui-status-positive-pale-hover);
}
&:hover {
color: var(--tui-status-info);
background-color: var(--tui-status-info-pale-hover);
}
}
```
#### Static
**Template:**
```html
Back to the Future
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiRating, type TuiRatingContext} from '@taiga-ui/kit';
import {type PolymorpheusContent} from '@taiga-ui/polymorpheus';
@Component({
imports: [FormsModule, TuiRating],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly icon: PolymorpheusContent = ({filled}) =>
filled ? '@tui.star-filled' : '@tui.star';
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Custom icons', 'Static'];
}
```
---
# components/Reorder
- **Package**: `ADDON-TABLE`
- **Type**: components
Component to change order of elements in an array
### Usage Examples
#### Usage
**Template:**
```html
{{ items | json }}
{{ enabled | json }}
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiReorder} from '@taiga-ui/addon-table';
@Component({
imports: [JsonPipe, TuiReorder],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected items = inject('Pythons' as any);
protected enabled = this.items;
}
```
**LESS:**
```less
.list {
inline-size: 12.5rem;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {}
```
---
# components/RingChart
- **Package**: `ADDON-CHARTS`
- **Type**: components
### Example
```html
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [(activeItemIndex)] | `number` | Active fragment |
| [size] | `TuiSizeXS | TuiSizeXL` | Size |
| [value] | `readonly number[]` | Value |
### Usage Examples
#### Sizes
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiRingChart} from '@taiga-ui/addon-charts';
@Component({
imports: [TuiRingChart],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly value = [40, 30, 20, 10];
}
```
**LESS:**
```less
.wrapper {
display: flex;
align-items: center;
}
```
#### With labels
**Template:**
```html
{{ sum | tuiAmount: 'RUB' }}
{{ label }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiRingChart} from '@taiga-ui/addon-charts';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {tuiSum} from '@taiga-ui/cdk';
@Component({
imports: [TuiAmountPipe, TuiRingChart],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly labels = ['Food', 'Cafe', 'Open Source', 'Taxi', 'other'];
protected readonly value = [13769, 12367, 10172, 3018, 2592];
protected readonly total = tuiSum(...this.value);
protected index = Number.NaN;
protected get sum(): number {
return (Number.isNaN(this.index) ? this.total : this.value[this.index]) ?? 0;
}
protected get label(): string {
return (Number.isNaN(this.index) ? 'Total' : this.labels[this.index]) ?? '';
}
}
```
**LESS:**
```less
:host {
--tui-chart-categorical-00: #c779d0;
--tui-chart-categorical-01: #feac5e;
--tui-chart-categorical-02: #ff5f6d;
--tui-chart-categorical-03: #4bc0c8;
--tui-chart-categorical-04: #9795cd;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiRingChart} from '@taiga-ui/addon-charts';
import {type TuiSizeXL, type TuiSizeXS} from '@taiga-ui/core';
@Component({
imports: [TuiDemo, TuiRingChart],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly examples = ['Sizes', 'With labels'];
protected readonly valueVariants = [
[40, 30, 20, 10],
[13769, 10172, 3018, 2592],
];
protected value = this.valueVariants[0]!;
protected readonly activeItemIndexVariants = [Number.NaN, 0, 1, 2];
protected activeItemIndex = this.activeItemIndexVariants[0]!;
protected readonly sizeVariants: ReadonlyArray = [
'xs',
's',
'm',
'l',
'xl',
];
protected size = this.sizeVariants[2]!;
}
```
### LESS
```less
.chart {
margin: 0 auto;
}
```
---
# components/Scrollbar
- **Package**: `CORE`
- **Type**: components
Scrollbar implements a custom scrollbar in Taiga UI style. Native scrollbar is hidden to keep native platform scroll experience Use TUI_SCROLL_REF token to get a scrollable container. For example, when working with virtual scroll.
### Usage Examples
#### Vertical
**Template:**
```html
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Ab aperiam beatae consequatur magnam mollitia necessitatibus quam qui quibusdam soluta, suscipit. Aut doloremque eum, hic quae ratione sunt suscipit! Eaque esse illo libero minima molestiae neque, nobis velit voluptates?
Animi est facere maxime porro quae quibusdam quos totam? Consectetur eligendi, explicabo magnam maxime sit voluptatibus. Assumenda beatae deserunt dolorem earum et eum harum in maxime quae, quam quos rem.
Adipisci commodi consectetur id iure praesentium quam quisquam unde veniam. Corporis cum dicta distinctio error excepturi, impedit quidem veritatis? Cupiditate eos illum ipsum labore, modi omnis repudiandae velit veniam voluptatem.
Asperiores dolorum, ex facilis hic maiores modi neque nisi nobis nostrum numquam placeat quod repellendus sequi velit voluptate! Adipisci atque deleniti eligendi ex tenetur. Beatae cumque dolore impedit perferendis repellat.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollbar} from '@taiga-ui/core';
@Component({
imports: [TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.box {
inline-size: 16rem;
block-size: 9.75rem;
border: 1px solid;
}
.content {
padding: 0 0.6875rem;
}
```
#### Horizontal
**Template:**
```html
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda delectus dolor eveniet maiores nobis quaerat quo velit vero voluptatem? Aliquam at deserunt excepturi id officiis porro quo quos voluptatum?
Ab aperiam beatae consequatur magnam mollitia necessitatibus quam qui quibusdam soluta, suscipit. Aut doloremque eum, hic quae ratione sunt suscipit! Eaque esse illo libero minima molestiae neque, nobis velit voluptates?
Animi est facere maxime porro quae quibusdam quos totam? Consectetur eligendi, explicabo magnam maxime sit voluptatibus. Assumenda beatae deserunt dolorem earum et eum harum in maxime quae, quam quos rem.
Adipisci commodi consectetur id iure praesentium quam quisquam unde veniam. Corporis cum dicta distinctio error excepturi, impedit quidem veritatis? Cupiditate eos illum ipsum labore, modi omnis repudiandae velit veniam voluptatem.
Asperiores dolorum, ex facilis hic maiores modi neque nisi nobis nostrum numquam placeat quod repellendus sequi velit voluptate! Adipisci atque deleniti eligendi ex tenetur. Beatae cumque dolore impedit perferendis repellat.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollbar} from '@taiga-ui/core';
@Component({
imports: [TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.box {
inline-size: 16rem;
block-size: 16rem;
border: 1px solid;
}
.line {
padding: 0 0.6875rem;
white-space: nowrap;
}
```
#### All
**Template:**
```html
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deleniti dignissimos, doloremque. Aperiam assumenda atque aut blanditiis corporis, eum, facilis harum laudantium magni necessitatibus nobis quas repudiandae sint ut voluptatem! Optio.
Accusamus aperiam assumenda aut consectetur, corporis delectus, dolor eaque eius est hic impedit labore possimus provident quas rem, rerum sequi sint tempora tempore ut? Debitis esse neque odio odit provident?
Cum eum illo, ipsa iure nostrum ut voluptates? Autem blanditiis corporis debitis deserunt ex expedita facilis fuga, illum iusto magnam praesentium provident recusandae repudiandae, totam, voluptatem. Minima numquam sapiente sunt.
Beatae consectetur cupiditate dignissimos ducimus eos excepturi labore pariatur placeat quia similique. Architecto aspernatur cumque debitis distinctio esse, facere fugit harum ipsum libero minus neque numquam omnis quidem, tempora, ut!
Mollitia, perspiciatis sunt! Architecto aspernatur assumenda beatae, blanditiis commodi consequuntur debitis et id, laboriosam maxime molestiae neque nihil officiis omnis, quam quos sint veritatis voluptate? Alias deserunt distinctio modi perferendis?
Ab aspernatur aut cumque cupiditate deleniti, dolorem ducimus eligendi eos facere harum hic ipsam ipsum iste itaque modi nam necessitatibus nostrum nulla omnis quae repellat, sapiente sit tempore. Ipsam, quidem!
Ab debitis deleniti distinctio est ex facere magni nemo numquam placeat quia, quibusdam sequi! Aliquid at consectetur culpa ea enim facilis, harum hic, inventore iste possimus praesentium quas tempora voluptates.
Aliquam eligendi ipsam modi nemo numquam obcaecati officia, quidem unde? Accusantium amet, animi deleniti dolorum ea earum eos, expedita ipsa minima modi, pariatur perspiciatis porro quibusdam quo repellat tempore voluptates!
Ab assumenda fugiat magni natus officiis perferendis ratione rem repellendus tenetur. At commodi laudantium modi, natus nobis nulla odio odit sed sint tempora tenetur voluptas? At odio praesentium quas ut!
Atque aut consectetur consequuntur debitis eius facere ipsa ipsam maiores minima minus mollitia qui quos repudiandae sapiente, soluta? Ad, amet dolore doloribus ducimus eos exercitationem molestiae quisquam soluta ullam voluptate.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deleniti dignissimos, doloremque. Aperiam assumenda atque aut blanditiis corporis, eum, facilis harum laudantium magni necessitatibus nobis quas repudiandae sint ut voluptatem! Optio.
Accusamus aperiam assumenda aut consectetur, corporis delectus, dolor eaque eius est hic impedit labore possimus provident quas rem, rerum sequi sint tempora tempore ut? Debitis esse neque odio odit provident?
Cum eum illo, ipsa iure nostrum ut voluptates? Autem blanditiis corporis debitis deserunt ex expedita facilis fuga, illum iusto magnam praesentium provident recusandae repudiandae, totam, voluptatem. Minima numquam sapiente sunt.
Beatae consectetur cupiditate dignissimos ducimus eos excepturi labore pariatur placeat quia similique. Architecto aspernatur cumque debitis distinctio esse, facere fugit harum ipsum libero minus neque numquam omnis quidem, tempora, ut!
Mollitia, perspiciatis sunt! Architecto aspernatur assumenda beatae, blanditiis commodi consequuntur debitis et id, laboriosam maxime molestiae neque nihil officiis omnis, quam quos sint veritatis voluptate? Alias deserunt distinctio modi perferendis?
Ab aspernatur aut cumque cupiditate deleniti, dolorem ducimus eligendi eos facere harum hic ipsam ipsum iste itaque modi nam necessitatibus nostrum nulla omnis quae repellat, sapiente sit tempore. Ipsam, quidem!
Ab debitis deleniti distinctio est ex facere magni nemo numquam placeat quia, quibusdam sequi! Aliquid at consectetur culpa ea enim facilis, harum hic, inventore iste possimus praesentium quas tempora voluptates.
Aliquam eligendi ipsam modi nemo numquam obcaecati officia, quidem unde? Accusantium amet, animi deleniti dolorum ea earum eos, expedita ipsa minima modi, pariatur perspiciatis porro quibusdam quo repellat tempore voluptates!
Ab assumenda fugiat magni natus officiis perferendis ratione rem repellendus tenetur. At commodi laudantium modi, natus nobis nulla odio odit sed sint tempora tenetur voluptas? At odio praesentium quas ut!
Atque aut consectetur consequuntur debitis eius facere ipsa ipsam maiores minima minus mollitia qui quos repudiandae sapiente, soluta? Ad, amet dolore doloribus ducimus eos exercitationem molestiae quisquam soluta ullam voluptate.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollbar, tuiScrollbarOptionsProvider} from '@taiga-ui/core';
@Component({
imports: [TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiScrollbarOptionsProvider({mode: 'hidden'})],
})
export default class Example {}
```
**LESS:**
```less
.box {
inline-size: 16rem;
block-size: 16rem;
border: 1px solid;
}
.content {
padding: 0 0.6875rem;
}
p {
white-space: nowrap;
}
```
#### Light scrollbar
**Template:**
```html
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deleniti dignissimos, doloremque. Aperiam assumenda atque aut blanditiis corporis, eum, facilis harum laudantium magni necessitatibus nobis quas repudiandae sint ut voluptatem! Optio.
Accusamus aperiam assumenda aut consectetur, corporis delectus, dolor eaque eius est hic impedit labore possimus provident quas rem, rerum sequi sint tempora tempore ut? Debitis esse neque odio odit provident?
Cum eum illo, ipsa iure nostrum ut voluptates? Autem blanditiis corporis debitis deserunt ex expedita facilis fuga, illum iusto magnam praesentium provident recusandae repudiandae, totam, voluptatem. Minima numquam sapiente sunt.
Beatae consectetur cupiditate dignissimos ducimus eos excepturi labore pariatur placeat quia similique. Architecto aspernatur cumque debitis distinctio esse, facere fugit harum ipsum libero minus neque numquam omnis quidem, tempora, ut!
Mollitia, perspiciatis sunt! Architecto aspernatur assumenda beatae, blanditiis commodi consequuntur debitis et id, laboriosam maxime molestiae neque nihil officiis omnis, quam quos sint veritatis voluptate? Alias deserunt distinctio modi perferendis?
Ab aspernatur aut cumque cupiditate deleniti, dolorem ducimus eligendi eos facere harum hic ipsam ipsum iste itaque modi nam necessitatibus nostrum nulla omnis quae repellat, sapiente sit tempore. Ipsam, quidem!
Ab debitis deleniti distinctio est ex facere magni nemo numquam placeat quia, quibusdam sequi! Aliquid at consectetur culpa ea enim facilis, harum hic, inventore iste possimus praesentium quas tempora voluptates.
Aliquam eligendi ipsam modi nemo numquam obcaecati officia, quidem unde? Accusantium amet, animi deleniti dolorum ea earum eos, expedita ipsa minima modi, pariatur perspiciatis porro quibusdam quo repellat tempore voluptates!
Ab assumenda fugiat magni natus officiis perferendis ratione rem repellendus tenetur. At commodi laudantium modi, natus nobis nulla odio odit sed sint tempora tenetur voluptas? At odio praesentium quas ut!
Atque aut consectetur consequuntur debitis eius facere ipsa ipsam maiores minima minus mollitia qui quos repudiandae sapiente, soluta? Ad, amet dolore doloribus ducimus eos exercitationem molestiae quisquam soluta ullam voluptate.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollbar} from '@taiga-ui/core';
@Component({
imports: [TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.box {
inline-size: 16rem;
block-size: 16rem;
background: var(--tui-background-base-alt);
}
.content {
padding: 0 0.6875rem;
}
p {
color: fade(#fff, 72%);
white-space: nowrap;
}
```
#### Virtual scroll
**Template:**
```html
Add
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deleniti dignissimos, doloremque. Aperiam assumenda atque aut blanditiis corporis, eum, facilis harum laudantium magni necessitatibus nobis quas repudiandae sint ut voluptatem! Optio.
Accusamus aperiam assumenda aut consectetur, corporis delectus, dolor eaque eius est hic impedit labore possimus provident quas rem, rerum sequi sint tempora tempore ut? Debitis esse neque odio odit provident?
Cum eum illo, ipsa iure nostrum ut voluptates? Autem blanditiis corporis debitis deserunt ex expedita facilis fuga, illum iusto magnam praesentium provident recusandae repudiandae, totam, voluptatem. Minima numquam sapiente sunt.
Beatae consectetur cupiditate dignissimos ducimus eos excepturi labore pariatur placeat quia similique. Architecto aspernatur cumque debitis distinctio esse, facere fugit harum ipsum libero minus neque numquam omnis quidem, tempora, ut!
Mollitia, perspiciatis sunt! Architecto aspernatur assumenda beatae, blanditiis commodi consequuntur debitis et id, laboriosam maxime molestiae neque nihil officiis omnis, quam quos sint veritatis voluptate? Alias deserunt distinctio modi perferendis?
Ab aspernatur aut cumque cupiditate deleniti, dolorem ducimus eligendi eos facere harum hic ipsam ipsum iste itaque modi nam necessitatibus nostrum nulla omnis quae repellat, sapiente sit tempore. Ipsam, quidem!
Ab debitis deleniti distinctio est ex facere magni nemo numquam placeat quia, quibusdam sequi! Aliquid at consectetur culpa ea enim facilis, harum hic, inventore iste possimus praesentium quas tempora voluptates.
Aliquam eligendi ipsam modi nemo numquam obcaecati officia, quidem unde? Accusantium amet, animi deleniti dolorum ea earum eos, expedita ipsa minima modi, pariatur perspiciatis porro quibusdam quo repellat tempore voluptates!
Ab assumenda fugiat magni natus officiis perferendis ratione rem repellendus tenetur. At commodi laudantium modi, natus nobis nulla odio odit sed sint tempora tenetur voluptas? At odio praesentium quas ut!
Atque aut consectetur consequuntur debitis eius facere ipsa ipsam maiores minima minus mollitia qui quos repudiandae sapiente, soluta? Ad, amet dolore doloribus ducimus eos exercitationem molestiae quisquam soluta ullam voluptate.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollbar, tuiScrollbarOptionsProvider} from '@taiga-ui/core';
@Component({
selector: 'example-7',
imports: [TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiScrollbarOptionsProvider({mode: 'hover'})],
})
export default class Example {}
```
**LESS:**
```less
.box {
inline-size: 16rem;
block-size: 16rem;
border: 1px solid;
}
.content {
padding: 0 0.6875rem;
}
p {
white-space: nowrap;
}
```
#### Native scrollbar
**Template:**
```html
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deleniti dignissimos, doloremque. Aperiam assumenda atque aut blanditiis corporis, eum, facilis harum laudantium magni necessitatibus nobis quas repudiandae sint ut voluptatem! Optio.
Accusamus aperiam assumenda aut consectetur, corporis delectus, dolor eaque eius est hic impedit labore possimus provident quas rem, rerum sequi sint tempora tempore ut? Debitis esse neque odio odit provident?
Cum eum illo, ipsa iure nostrum ut voluptates? Autem blanditiis corporis debitis deserunt ex expedita facilis fuga, illum iusto magnam praesentium provident recusandae repudiandae, totam, voluptatem. Minima numquam sapiente sunt.
Beatae consectetur cupiditate dignissimos ducimus eos excepturi labore pariatur placeat quia similique. Architecto aspernatur cumque debitis distinctio esse, facere fugit harum ipsum libero minus neque numquam omnis quidem, tempora, ut!
Mollitia, perspiciatis sunt! Architecto aspernatur assumenda beatae, blanditiis commodi consequuntur debitis et id, laboriosam maxime molestiae neque nihil officiis omnis, quam quos sint veritatis voluptate? Alias deserunt distinctio modi perferendis?
Ab aspernatur aut cumque cupiditate deleniti, dolorem ducimus eligendi eos facere harum hic ipsam ipsum iste itaque modi nam necessitatibus nostrum nulla omnis quae repellat, sapiente sit tempore. Ipsam, quidem!
Ab debitis deleniti distinctio est ex facere magni nemo numquam placeat quia, quibusdam sequi! Aliquid at consectetur culpa ea enim facilis, harum hic, inventore iste possimus praesentium quas tempora voluptates.
Aliquam eligendi ipsam modi nemo numquam obcaecati officia, quidem unde? Accusantium amet, animi deleniti dolorum ea earum eos, expedita ipsa minima modi, pariatur perspiciatis porro quibusdam quo repellat tempore voluptates!
Ab assumenda fugiat magni natus officiis perferendis ratione rem repellendus tenetur. At commodi laudantium modi, natus nobis nulla odio odit sed sint tempora tenetur voluptas? At odio praesentium quas ut!
Atque aut consectetur consequuntur debitis eius facere ipsa ipsam maiores minima minus mollitia qui quos repudiandae sapiente, soluta? Ad, amet dolore doloribus ducimus eos exercitationem molestiae quisquam soluta ullam voluptate.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollbar, tuiScrollbarOptionsProvider} from '@taiga-ui/core';
@Component({
imports: [TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiScrollbarOptionsProvider({mode: 'native'})],
})
export default class Example {}
```
**LESS:**
```less
.box {
inline-size: 16rem;
block-size: 16rem;
border: 1px solid;
}
.content {
padding: 0 0.6875rem;
}
p {
white-space: nowrap;
}
```
#### Nested scrollbar
**Template:**
```html
Parent
```
**TypeScript:**
```ts
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollbar, tuiScrollbarOptionsProvider} from '@taiga-ui/core';
import {TuiFade} from '@taiga-ui/kit';
@Component({
selector: 'child',
imports: [TuiFade, TuiScrollbar],
template: `
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Praesentium tempore sapiente nostrum, nulla autem, iste amet
ratione incidunt, quam obcaecati ipsa reiciendis modi quibusdam
at. Neque culpa excepturi repellat natus!
```
**TypeScript:**
```ts
import {JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {type TuiStringHandler} from '@taiga-ui/cdk';
import {TuiDataList} from '@taiga-ui/core';
import {TuiChevron, TuiSelect} from '@taiga-ui/kit';
interface Python {
readonly id: number;
readonly name: string;
}
@Component({
imports: [JsonPipe, ReactiveFormsModule, TuiChevron, TuiDataList, TuiSelect],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl(777);
protected readonly items: readonly Python[] = [
{id: 42, name: 'John Cleese'},
{id: 237, name: 'Eric Idle'},
{id: 666, name: 'Michael Palin'},
{id: 123, name: 'Terry Gilliam'},
{id: 777, name: 'Terry Jones'},
{id: 999, name: 'Graham Chapman'},
];
protected readonly stringify: TuiStringHandler = (id) =>
this.items.find((item) => item.id === id)?.name ?? '';
}
```
#### Example 6
**Template:**
```html
{{ item }}
```
**TypeScript:**
```ts
import {ScrollingModule} from '@angular/cdk/scrolling';
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDataList, TuiScrollable} from '@taiga-ui/core';
import {TUI_COUNTRIES, TuiChevron, TuiSelect} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
ScrollingModule,
TuiChevron,
TuiDataList,
TuiScrollable,
TuiSelect,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly countries = Object.values(inject(TUI_COUNTRIES)());
protected value = null;
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils.less';
.scroll {
.scrollbar-hidden();
block-size: 12.5rem;
}
```
#### Example 7
**Template:**
```html
Platform
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdownSheet} from '@taiga-ui/addon-mobile';
import {type TuiBooleanHandler, TuiPlatform} from '@taiga-ui/cdk';
import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiChevron,
TuiDataListWrapper,
TuiDropdownSheet,
TuiPlatform,
TuiSelect,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly platforms = ['web', 'ios', 'android'] as const;
protected value: 'android' | 'ios' | 'web' | null = 'ios';
protected readonly disabledItemHandler: TuiBooleanHandler = (x) =>
x === 'web';
}
```
#### Example 8
**Template:**
```html
@if (isMobile) { } @if (!isMobile) { } @if (!isMobile) { }
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_MOBILE} from '@ng-web-apis/platform';
import {type TuiBooleanHandler, type TuiStringHandler} from '@taiga-ui/cdk';
import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit';
interface Character {
id: number;
name: string;
}
@Component({
imports: [ReactiveFormsModule, TuiChevron, TuiDataListWrapper, TuiSelect],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly isMobile = inject(WA_IS_MOBILE);
protected readonly control = new FormControl(null, Validators.required);
protected items: readonly Character[] = [
{name: 'Luke Skywalker', id: 1},
{name: 'Leia Organa Solo', id: 2},
{name: 'Darth Vader', id: 3},
{name: 'Han Solo', id: 4},
{name: 'Obi-Wan Kenobi', id: 5},
{name: 'Yoda', id: 6},
];
protected stringify: TuiStringHandler = (item) => item.name;
protected readonly disabledItemHandler: TuiBooleanHandler = (item) =>
item.name === 'Darth Vader';
}
```
#### Example 9
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiChevron, TuiSelect} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiChevron, TuiSelect],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected groupItems: ReadonlyArray = [
['Caesar', 'Greek', 'Apple and Chicken'],
['Broccoli Cheddar', 'Chicken and Rice', 'Chicken Noodle'],
];
protected readonly labels = ['Salad', 'Soup'];
protected readonly control = new FormControl(null);
}
```
### TypeScript
```ts
import {Component, computed, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocItemsHandlers} from '@demo/components/items-handlers';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {WA_IS_MOBILE} from '@ng-web-apis/platform';
import {type TuiRawLoaderContent} from '@taiga-ui/addon-doc';
import {type TuiContext} from '@taiga-ui/cdk';
import {TuiDropdown, TuiInput} from '@taiga-ui/core';
import {TUI_COUNTRIES, TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit';
interface Country {
id: string;
name: string;
}
@Component({
imports: [
ReactiveFormsModule,
TuiChevron,
TuiDataListWrapper,
TuiDemo,
TuiDocControl,
TuiDocDropdown,
TuiDocInput,
TuiDocItemsHandlers,
TuiDocTextfield,
TuiDropdown,
TuiInput,
TuiSelect,
],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent {
private readonly countriesI18n = inject(TUI_COUNTRIES);
protected readonly routes = DemoRoute;
protected readonly isMobile = inject(WA_IS_MOBILE);
protected readonly control = new FormControl({
id: 'US',
name: 'USA',
});
protected readonly countries = computed(() =>
Object.entries(this.countriesI18n()).map(([id, name]) => ({id, name})),
);
protected readonly textfieldContentVariants = [
'',
'TOP SECRET',
({$implicit: x}: TuiContext) =>
x?.name.includes('i') ? `->${x.name}<-` : x?.name,
];
protected selectOptionExample: TuiRawLoaderContent = import(
'./examples/10/option.ts?raw',
{with: {loader: 'text'}}
);
protected readonly handler = (item: Country): boolean =>
item.id.charCodeAt(1) % 3 === 0;
}
```
---
# services/KeyboardService
- **Package**: `ADDON-MOBILE`
- **Type**: components/services
A service that allows hiding and showing virtual keyboard programmatically on both Android and iOS devices Does nothing on devices with no virtual keyboard or when input is not focused
### Usage Examples
#### Basic
**Template:**
```html
Type something
Toggle
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiKeyboardService} from '@taiga-ui/addon-mobile';
import {TuiButton, TuiInput} from '@taiga-ui/core';
@Component({
imports: [FormsModule, TuiButton, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly keyboard = inject(TuiKeyboardService);
protected value = '';
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly inject = import('./examples/inject.md');
}
```
---
# components/SheetDialog
- **Package**: `ADDON-MOBILE`
- **Type**: components
A mobile draggable sheet dialog
### Example
```html
Click Long tap Navigate to Examples
Karl Gambolputty de von Ausfern-schplenden-schlitter-crasscrenbon-fried-digger-dingle-dangle-dongle-dungle-burstein-von-knacker-thrasher-apple-banger-horowitz-ticolensic-grander-knotty-spelltinkle-grandlich-grumblemeyer-spelterwasser-kurstlich-himbleeisen-bahnwagen-gutenabend-bitte-ein-nürnburger-bratwustle-gerspurten-mitzweimache-luber von Hautkopft of Ulm was the last-surviving relative of Johann Gambolputty de von.
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [appearance] | `string` | Custom data-appearance attribute value |
| [closable] | `boolean` | Whether or not a sheet can be closed by user. |
| [bar] | `boolean` | Show bar |
| [data] | `I` | Optional data to be passed to the sheet. |
| [label] | `string` | Sheet label. |
| [stops] | `string[]` | An array of stop points in any units for the sheet. |
| [initial] | `number` | means to stop on top of the sheet's content. |
| [offset] | `number` | ) |
| [required] | `boolean` | (you can catch it with "catch" operator or onError handler) |
### Usage Examples
#### String
**Template:**
```html
Show
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiSheetDialogService} from '@taiga-ui/addon-mobile';
import {TuiButton} from '@taiga-ui/core';
@Component({
imports: [TuiButton],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly service = inject(TuiSheetDialogService);
protected onClick(): void {
this.service
.open('Supports basic HTML', {label: 'Simple sheet'})
.subscribe();
}
}
```
#### Basic
**Template:**
```html
Show
} }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiGroup, TuiTitle} from '@taiga-ui/core';
import {TuiElasticContainer, TuiSlides} from '@taiga-ui/layout';
@Component({
imports: [TuiButton, TuiElasticContainer, TuiGroup, TuiSlides, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items = ['First slide ', 'Second slide ', 'Third slide '];
protected index = 0;
}
```
**LESS:**
```less
[tuiGroup] {
inline-size: fit-content;
margin: 0 auto 1rem;
}
[tuiSubtitle] {
color: var(--tui-text-secondary);
}
[tuiTitle] {
--tui-duration: 0.6s;
animation-delay: 0.3s;
&.tui-leave {
animation-delay: 0s;
}
}
```
#### Stepper
**Template:**
```html
Personal detailsShipping addressPayment info @for (form of forms; track form; let i = $index) { @if (i === index) { } }
```
**TypeScript:**
```ts
import {KeyValuePipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAutoFocus, tuiMarkControlAsTouchedAndValidate} from '@taiga-ui/cdk';
import {TuiButton, TuiInput, TuiTitle} from '@taiga-ui/core';
import {TuiStepper} from '@taiga-ui/kit';
import {
TuiCard,
TuiElasticContainer,
TuiForm,
TuiHeader,
TuiSlides,
} from '@taiga-ui/layout';
@Component({
imports: [
KeyValuePipe,
ReactiveFormsModule,
TuiAutoFocus,
TuiButton,
TuiCard,
TuiElasticContainer,
TuiForm,
TuiHeader,
TuiInput,
TuiSlides,
TuiStepper,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected index = 0;
protected direction = 0;
protected readonly forms = [
new FormGroup({
Name: new FormControl('', Validators.required),
Surname: new FormControl('', Validators.required),
}),
new FormGroup({
Country: new FormControl('', Validators.required),
City: new FormControl('', Validators.required),
Address: new FormControl('', Validators.required),
}),
new FormGroup({
Card: new FormControl('', Validators.required),
Value: new FormControl('', Validators.required),
}),
];
protected onStep(step: number): void {
this.direction = step - this.index;
this.index = step;
}
protected onSubmit(): void {
tuiMarkControlAsTouchedAndValidate(this.forms[this.index]!);
if (this.forms[this.index]?.invalid) {
return;
}
this.direction = 1;
this.index = Math.min(this.index + 1, this.forms.length - 1);
}
}
```
**LESS:**
```less
tui-elastic-container {
padding: 2rem;
margin: 0 -2rem;
}
footer {
display: flex;
justify-content: space-between;
}
```
#### Routing
**Template:**
```html
Home Notifications Settings
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {toSignal} from '@angular/core/rxjs-interop';
import {
NavigationStart,
Router,
RouterLink,
RouterLinkActive,
RouterOutlet,
} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTabs} from '@taiga-ui/kit';
import {TuiSlides} from '@taiga-ui/layout';
import {filter, map, pairwise} from 'rxjs';
@Component({
imports: [RouterLink, RouterLinkActive, RouterOutlet, TuiSlides, TuiTabs],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly direction = toSignal(
inject(Router).events.pipe(
filter((event) => event instanceof NavigationStart),
map(({url}: any) => Number(url.split('/').at(-1))),
pairwise(),
map(([prev, next]) => next - prev),
),
{initialValue: 1},
);
}
```
#### Dialog
**Template:**
```html
Show dialog @if (step > 1) { Back }
Close @switch (step) { @case (1) {
Welcome to the slides demo
These wrapping components will be animated upon navigating this modal dialog. This works well on both desktop and mobile. In your own layouts watch out for unwanted scrollbars.
} @case (2) {
Header is optional @for (_ of '-'.repeat(5); track $index) {
Add one more activeItemIndex
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiIcon, TuiNumberFormat, TuiTextfield} from '@taiga-ui/core';
import {TuiInputNumber, TuiTabs} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiButton,
TuiIcon,
TuiInputNumber,
TuiNumberFormat,
TuiTabs,
TuiTextfield,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
protected activeItemIndex = 0;
protected items = Array.from({length: 5}, (_, i) => `Item #${i}`);
protected add(): void {
this.items = this.items.concat(`Item #${Date.now()}`);
}
protected remove(removed: string): void {
const index = this.items.indexOf(removed);
this.items = this.items.filter((item) => item !== removed);
if (index <= this.activeItemIndex) {
this.activeItemIndex = Math.max(this.activeItemIndex - 1, 0);
}
}
}
```
#### Vertical
**Template:**
```html
Item 1 Item 2 Item 3 with name so long it spans multiple lines
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec ultricies enim, vel molestie orci. In finibus diam ac nulla accumsan, vel condimentum lorem ultricies. In feugiat mauris sem, ac ultricies metus aliquet nec. Ut a iaculis metus, id vestibulum justo. Nulla id ante semper, aliquam augue vitae, sollicitudin massa. Sed congue nisi sed ullamcorper mollis. Vivamus volutpat non est a vestibulum. Sed in elementum odio. Proin a lectus ac quam vulputate ornare nec id mi. Maecenas pharetra ultricies efficitur. Etiam sit amet vulputate elit. Donec ut dapibus nunc. Nullam vestibulum diam eros, ac euismod velit porta ac. Ut ut auctor velit. Nulla ac lobortis erat, ut tempor neque.
Donec quis lacus leo. Mauris quis vestibulum mauris. Sed hendrerit odio id blandit iaculis. Nulla ac gravida ligula, tristique tempus eros. Mauris efficitur risus quis arcu pharetra, eu semper ex rutrum. Aenean justo felis, imperdiet non justo vel, fringilla maximus nibh. Vestibulum ut imperdiet ex, vel varius odio. Nunc nec lorem non odio mollis porta. In gravida accumsan lacus, vitae egestas lectus aliquet sed. Morbi justo orci, fringilla sit amet consectetur vel, consectetur a nibh. Sed eu porttitor ante. Morbi imperdiet ligula id velit dignissim malesuada. Vestibulum blandit posuere sem.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec ultricies enim, vel molestie orci. In finibus diam ac nulla accumsan, vel condimentum lorem ultricies. In feugiat mauris sem, ac ultricies metus aliquet nec. Ut a iaculis metus, id vestibulum justo. Nulla id ante semper, aliquam augue vitae, sollicitudin massa. Sed congue nisi sed ullamcorper mollis. Vivamus volutpat non est a vestibulum. Sed in elementum odio. Proin a lectus ac quam vulputate ornare nec id mi. Maecenas pharetra ultricies efficitur. Etiam sit amet vulputate elit. Donec ut dapibus nunc. Nullam vestibulum diam eros, ac euismod velit porta ac. Ut ut auctor velit. Nulla ac lobortis erat, ut tempor neque.
Donec quis lacus leo. Mauris quis vestibulum mauris. Sed hendrerit odio id blandit iaculis. Nulla ac gravida ligula, tristique tempus eros. Mauris efficitur risus quis arcu pharetra, eu semper ex rutrum. Aenean justo felis, imperdiet non justo vel, fringilla maximus nibh. Vestibulum ut imperdiet ex, vel varius odio. Nunc nec lorem non odio mollis porta. In gravida accumsan lacus, vitae egestas lectus aliquet sed. Morbi justo orci, fringilla sit amet consectetur vel, consectetur a nibh. Sed eu porttitor ante. Morbi imperdiet ligula id velit dignissim malesuada. Vestibulum blandit posuere sem.
Button Tabs Input
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {RouterLink, RouterLinkActive} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {DemoRoute} from '@demo/routes';
import {TuiTabs} from '@taiga-ui/kit';
@Component({
imports: [RouterLink, RouterLinkActive, TuiTabs],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly routes = DemoRoute;
}
```
**LESS:**
```less
.content {
display: flex;
margin: 2rem 0;
}
.left {
margin-inline-end: 2rem;
min-inline-size: 10rem;
inline-size: 10rem;
}
.right {
margin-inline-start: 2rem;
min-inline-size: 10rem;
inline-size: 10rem;
}
```
#### Styles
**Template:**
```html
Maps Calls Settings Favorite Trash
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTabs} from '@taiga-ui/kit';
@Component({
imports: [TuiTabs],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected activeItemIndex = 0;
}
```
**LESS:**
```less
.custom {
font: var(--tui-typography-heading-h5);
box-shadow: none;
}
```
#### Tabs with routing
**Template:**
```html
@for (url of urls; track url) { Example {{ $index + 1 }} }
```
**TypeScript:**
```ts
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {RouterLink, RouterLinkActive, RouterOutlet, type Routes} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTabs} from '@taiga-ui/kit';
@Component({
selector: 'example-1',
template: 'example-1',
changeDetection: ChangeDetectionStrategy.OnPush,
})
class Nav1 {}
@Component({
selector: 'example-2',
template: 'example-2',
changeDetection: ChangeDetectionStrategy.OnPush,
})
class Nav2 {}
@Component({
selector: 'example-3',
template: 'example-3',
changeDetection: ChangeDetectionStrategy.OnPush,
})
class Nav3 {}
@Component({
selector: 'example-4',
template: 'example-4',
changeDetection: ChangeDetectionStrategy.OnPush,
})
class Nav4 {}
@Component({
selector: 'example-5',
template: 'example-5',
changeDetection: ChangeDetectionStrategy.OnPush,
})
class Nav5 {}
export const routes: Routes = [
{
path: '',
component: Nav1,
},
{
path: 'nav-1',
component: Nav1,
},
{
path: 'nav-2',
component: Nav2,
},
{
path: 'nav-3',
component: Nav3,
},
{
path: 'nav-4',
component: Nav4,
},
{
path: 'nav-5',
component: Nav5,
},
];
@Component({
imports: [RouterLink, RouterLinkActive, RouterOutlet, TuiTabs],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly urls = ['nav-1', 'nav-2', 'nav-3', 'nav-4', 'nav-5'];
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {tuiDocExampleOptionsProvider} from '@taiga-ui/addon-doc';
import {type TuiSizeL} from '@taiga-ui/core';
import {TuiTabs} from '@taiga-ui/kit';
@Component({
imports: [TuiDemo, TuiTabs],
templateUrl: './index.html',
changeDetection,
providers: [tuiDocExampleOptionsProvider({fullsize: true})],
})
export default class Page {
protected readonly examples = [
'Basic',
'TabsWithMore',
'Complex',
'Stepper',
'Closing',
'Vertical',
'Styles',
];
protected buttons = ['Button 1', 'Button 2', 'Button 3', 'Button 4'];
protected readonly moreContentVariants = ['', 'And more'];
protected moreContent = this.moreContentVariants[0]!;
protected underline = true;
protected activeItemIndex = 0;
protected itemsLimit = 999;
protected sizes: readonly TuiSizeL[] = ['m', 'l'];
protected size = this.sizes[1]!;
}
```
---
# components/Textarea
- **Package**: `KIT`
- **Type**: components
Textarea uses Textfield to create a multi-line string input.
### Example
```html
@if (textfieldDoc.size !== 's') { Label }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [min] | `number` | Minimum number of rows in height |
| [max] | `number` | Maximum number of rows before scroll appears |
### Usage Examples
#### Basic
**Template:**
```html
Large with label insideMedium with label inside Large with label outside Medium with label outside Small with label outside
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTextarea} from '@taiga-ui/kit';
@Component({
imports: [TuiTextarea],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
#### Limit
**Template:**
```html
Limit Programmatically update
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiTextarea} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiButton, TuiTextarea],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected control = new FormControl(
'Adding [limit] directive allows you to display a counter of symbols inside the textarea, highlight excessive characters in red and also automatically add Validators.maxlength(x) validator',
);
constructor() {
this.control.markAsTouched();
}
}
```
#### Custom highlight
**Template:**
```html
Custom highlight
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTextarea, tuiTextareaOptionsProvider} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiTextarea],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
providers: [tuiTextareaOptionsProvider({min: 4, max: 4})],
})
export default class Example {
protected value =
'You can implement your own highlight, just make sure you do not alter font width or height';
protected process(text: string): string {
return text
.replaceAll('width', 'width')
.replaceAll('height', 'height');
}
}
```
**LESS:**
```less
:host {
::ng-deep .width {
background: var(--tui-status-info-pale);
}
::ng-deep .height {
background: var(--tui-status-positive-pale);
}
}
```
#### Icons
**Template:**
```html
Your best thought
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTextarea} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiTextarea],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = '';
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {TuiDocControl} from '@demo/components/control';
import {TuiDocIcons} from '@demo/components/icons';
import {TuiDocInput} from '@demo/components/input';
import {TuiDocTextfield} from '@demo/components/textfield';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDemo} from '@demo/utils';
import {TUI_TEXTAREA_OPTIONS, TuiTextarea} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiDemo,
TuiDocControl,
TuiDocIcons,
TuiDocInput,
TuiDocTextfield,
TuiTextarea,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class PageComponent {
protected readonly options = inject(TUI_TEXTAREA_OPTIONS);
protected readonly examples = ['Basic', 'Limit', 'Custom highlight', 'Icons'];
protected readonly control = new FormControl(null);
protected min = this.options.min;
protected max = this.options.max;
}
```
---
# components/ThumbnailCard
- **Package**: `ADDON-COMMERCE`
- **Type**: components
Customizable credit card thumbnail
### Example
```html
{{ contentProjection }}
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [paymentSystem] | `TuiPaymentSystem | null` | Payment system |
| [size] | `TuiSizeXS | TuiSizeL` | Size |
### Usage Examples
#### Sizes
**Template:**
```html
@for (size of sizes; track size) { 4572 }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiThumbnailCard} from '@taiga-ui/addon-commerce';
@Component({
imports: [TuiThumbnailCard],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly sizes = ['xs', 's', 'm', 'l'] as const;
}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
}
```
#### A cool one
**Template:**
```html
7777
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiThumbnailCard} from '@taiga-ui/addon-commerce';
@Component({
imports: [TuiThumbnailCard],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@keyframes spinCard {
0% {
transform: rotateY(0);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.3);
}
12% {
transform: rotateY(90deg) rotateZ(6deg) scale(1.7);
box-shadow: 0 0.25rem 0.5rem 0 rgba(0, 0, 0, 0.3);
}
25% {
transform: rotateY(180deg);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.3);
}
50% {
transform: rotateY(180deg);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.3);
}
62% {
transform: rotateY(270deg) rotateZ(-8deg) scale(1.7);
box-shadow: 0 0.25rem 0.5rem 0 rgba(0, 0, 0, 0.3);
}
80% {
transform: rotateY(720deg);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.3);
}
100% {
transform: rotateY(720deg);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.3);
}
}
:host {
perspective: 50rem;
}
.logo {
background-color: #7c48c3;
background-image: linear-gradient(45deg, #c86dd7 0%, #3023ae 100%);
overflow: visible;
animation: spinCard 5s infinite;
}
```
#### Backgrounds
**Template:**
```html
1234 5678 9000 7777
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiThumbnailCard} from '@taiga-ui/addon-commerce';
@Component({
imports: [TuiThumbnailCard],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
}
.star {
color: #000;
background:
linear-gradient(45deg, rgba(255, 170, 0, 0.82), #fa0), url('/assets/taiga-ui/icons/star.svg'),
url('/assets/taiga-ui/icons/star.svg');
background-size:
100%,
3rem 1.5rem,
3rem 1.5rem;
background-position:
0 0,
-0.75rem 0,
0.75rem;
background-color: #fff;
}
.gradient {
color: #fff;
background: #2b9aff linear-gradient(110deg, transparent 70%, #0780ff 71%, #db028b 100%);
}
.retrowave {
color: #ecb5ff;
background: url('/assets/images/avatar.jpg') no-repeat center/cover;
&::before {
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(0.25rem);
}
}
.radial {
color: #fff;
background: radial-gradient(#c900ff, #0079be);
}
```
#### External colored icon
**Template:**
```html
1234
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiThumbnailCard} from '@taiga-ui/addon-commerce';
@Component({
imports: [TuiThumbnailCard],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
gap: 1rem;
}
```
#### Textfield
**Template:**
```html
@for (state of statuses; track state) { {{ state }} } Card number
@if (card.startsWith('1234')) { }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInputCard, TuiThumbnailCard} from '@taiga-ui/addon-commerce';
import {TuiSegmented} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputCard, TuiSegmented, TuiThumbnailCard],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected statuses = ['normal', 'disabled', 'readOnly', 'invalid'] as const;
protected status: string = this.statuses[0];
protected card = '1234123412341234';
protected background =
'#2b9aff linear-gradient(110deg, transparent 70%, #0780ff 71%, #db028b 100%)';
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
}
```
### TypeScript
```ts
import {Component, inject} from '@angular/core';
import {TuiDocIcons} from '@demo/components/icons';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {
TUI_PAYMENT_SYSTEM_ICONS,
type TuiPaymentSystem,
TuiThumbnailCard,
} from '@taiga-ui/addon-commerce';
import {type TuiSizeL, type TuiSizeXS} from '@taiga-ui/core';
@Component({
imports: [TuiDemo, TuiDocIcons, TuiThumbnailCard],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly examples = [
'Sizes',
'A cool one',
'Backgrounds',
'External colored icon',
'Textfield',
];
protected readonly paymentSystemVariants = Object.keys(
inject(TUI_PAYMENT_SYSTEM_ICONS),
) as readonly TuiPaymentSystem[];
protected readonly sizeVariants: ReadonlyArray = [
'l',
'm',
's',
'xs',
];
protected size = this.sizeVariants[0]!;
protected paymentSystem = this.paymentSystemVariants[0]!;
protected contentProjection = '1234';
protected background =
'#2b9aff linear-gradient(110deg, transparent 70%, #0780ff 71%, #db028b 100%)';
}
```
---
# components/Tiles
- **Package**: `KIT`
- **Type**: components
Tiles is a light-weight touch-friendly tiles grid drag and drop component with no predefined styles.
### Example
```html
@for (item of items; track item) {
{{ item.name }}
}
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [debounce] | `number` | Debounce for the tile order change output. |
| [(order)] | `Map` | The order of the tiles. |
| [width] | `number` | Width of the tile. |
| [height] | `number` | Height of the tile. |
| [tuiTileHandle] | `Directive` | Directive to determine the handle of the tui-tile. |
### API - Outputs
| Event | Type | Description |
|-------|------|-------------|
| (orderChange) | `Map` | Output for tile order change. |
### Usage Examples
#### Basic
**Template:**
```html
@for (item of items; track item) {
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLink, TuiTitle} from '@taiga-ui/core';
import {TuiAvatar} from '@taiga-ui/kit';
@Component({
imports: [TuiAvatar, TuiLink, TuiTitle],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 2rem;
}
.flex {
display: flex;
gap: 0.5rem;
[tuiSubtitle] {
color: var(--tui-text-positive);
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Sizes', 'Custom'];
}
```
---
# components/Toast
- **Package**: `KIT`
- **Type**: components
### Example
```html
Show toast
I am a toast
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [appearance] | `string` | Appearance of a toast |
| [autoClose] | `number` | Automatic close timeout, 0 for a permanent toast |
| [closable] | `boolean` | Show close button on desktop and close on swipe on mobile |
| [data] | `I` | |
### Usage Examples
#### Basic
**Template:**
```html
@for (platform of platforms; track $index) {
{{ platform === 'web' ? 'Desktop' : 'Mobile' }}
Plain text interactive
With action Action
With icon
With badge Close
Avatar
Everything Action @if (platform === 'web') { Close }
The text of the notification telling what happened is in three lines because there is a lot of information Action
A token with a factory. It takes WA_IS_MOBILE and WA_IS_IOS , returns true if the device is mobile but not iOS (technically includes Windows Phone, Blackberry etc.)
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {RouterLink} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_ANDROID} from '@ng-web-apis/platform';
import {TuiLink} from '@taiga-ui/core';
@Component({
imports: [RouterLink, TuiLink],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly isAndroid = inject(WA_IS_ANDROID);
}
```
#### WA_IS_IOS
**Template:**
```html
A token with a factory. It takes WA_IS_MOBILE and if it is true detects iOS devices with a regex
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {RouterLink} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_IOS} from '@ng-web-apis/platform';
import {TuiLink} from '@taiga-ui/core';
@Component({
imports: [RouterLink, TuiLink],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly isIos = inject(WA_IS_IOS);
}
```
#### WA_IS_MOBILE
**Template:**
```html
A token with a factory. It takes WA_USER_AGENT token and parses it with a complex Regex to detect users with mobile devices
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {WA_IS_MOBILE} from '@ng-web-apis/platform';
@Component({
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly isMobile = inject(WA_IS_MOBILE);
}
```
#### TUI_NUMBER_FORMAT
**Template:**
```html
Using TUI_NUMBER_FORMAT injection token you can customize numbers formatting.
```
**TypeScript:**
```ts
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiActiveZone} from '@taiga-ui/cdk';
import {TuiButton, TuiDialogService, TuiInput} from '@taiga-ui/core';
@Component({
imports: [TuiActiveZone, TuiButton, TuiInput],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly dialog = inject(TuiDialogService);
protected active = false;
protected onZone(active: boolean): void {
console.info(active);
this.active = active;
}
protected onClick(): void {
this.dialog
.open(
'Dialogs automatically attach themselves to the currently active zone',
{label: "I'm inside", size: 's'},
)
.subscribe();
}
}
```
**LESS:**
```less
.active-zone {
padding: 1.25rem;
border: 2px solid;
&_active {
border-color: var(--tui-background-accent-1);
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly examples = ['Composite zone', 'Dialogs'];
}
```
### LESS
```less
.dropdown {
max-inline-size: 20rem;
padding: 0.5rem 1.25rem;
}
```
---
# directives/Animated
- **Package**: `CDK`
- **Type**: directives
TuiAnimated allows adding animation to DOM elements in the same way as Angular's animate attribute but supported in Angular 19. In case the user's operating system has reduced motion settings turned on, Taiga UI will honor this, disabling animations under its control, and TuiAnimated will not play animations. To override this behavior, use TUI_REDUCED_MOTION injection token. However, this is not the recommended approach, as the user's system preferences would be ignored.
### How to Use (Import)
```ts
import {TuiAnimated} from '@taiga-ui/cdk';
// ...
@Component({
imports: [
// ...
TuiAnimated,
],
// ...
})
export class Example {}
```
### How to Use (Template)
```html
Active
```
### Usage Examples
#### Usage
**Template:**
```html
{{ isOpen ? 'Hide me' : 'Show opening crawl' }} @if (isOpen) {
A long time ago in a galaxy far, far away....
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAnimated} from '@taiga-ui/cdk';
import {TuiAppearance, TuiButton} from '@taiga-ui/core';
@Component({
imports: [FormsModule, TuiAnimated, TuiAppearance, TuiButton],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected isOpen = false;
}
```
**LESS:**
```less
@width: 15rem;
.button {
inline-size: @width;
border-radius: 1rem 1rem 0 0;
}
.container {
block-size: 6rem;
inline-size: @width;
overflow: hidden;
background: #222;
color: var(--tui-status-warning);
&.tui-enter,
&.tui-leave {
animation-name: tuiFade, tuiScale;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly exampleImport = import('./examples/import/import.md');
protected readonly exampleTemplate = import('./examples/import/template.md');
protected readonly exampleStyle = import('./examples/import/style.md');
}
```
---
# directives/Appearance
- **Package**: `CORE`
- **Type**: directives
A directive for visual presets of interactive components
### Example
```html
Appearance
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiAppearance] | `string` | when host component already exposes it via hostDirectives) |
| [tuiAppearanceFocus] | `boolean | null` | Manual override of focused state |
| [tuiAppearanceState] | `TuiInteractiveState | null` | Manual override of interactive state |
### Usage Examples
#### Basic
**Template:**
```html
Non-interactive elements do not react to pointer
Hovered state is only triggered on devices with pointer Triggering state manually
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAppearance, TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [TuiAppearance, TuiDropdown],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
}
```
**LESS:**
```less
:host {
display: grid;
grid-auto-rows: 3rem;
gap: 1rem;
text-align: center;
}
div {
padding: 0.25rem 2rem;
}
button {
border: none;
inline-size: 100%;
block-size: 100%;
}
```
#### Custom
**Template:**
```html
SCSS mixins have the same names
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAppearance} from '@taiga-ui/core';
@Component({
imports: [TuiAppearance],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
button {
border: none;
padding: 1rem;
border-radius: 0.5rem;
cursor: pointer;
}
[tuiAppearance][data-appearance='acid'] {
color: var(--tui-chart-categorical-06);
background: var(--tui-chart-categorical-09);
--tui-border-focus: var(--tui-chart-categorical-06);
.appearance-hover({
color: var(--tui-chart-categorical-09);
background: var(--tui-chart-categorical-14);
});
.appearance-active({
color: var(--tui-chart-categorical-08);
background: var(--tui-chart-categorical-10);
});
}
```
#### Checkbox
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAppearance} from '@taiga-ui/core';
@Component({
imports: [TuiAppearance],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.like {
inline-size: var(--tui-height-m);
block-size: var(--tui-height-m);
border-radius: 100%;
cursor: pointer;
&::before {
.fullsize();
content: '';
background: currentColor;
mask: url('/assets/taiga-ui/icons/heart.svg') no-repeat center / 3em 1.5em;
}
&:checked::before {
color: var(--tui-text-negative);
mask: url('/assets/taiga-ui/icons/heart-filled.svg') no-repeat center;
}
}
```
#### Bundled
**Template:**
```html
@for (group of appearances | keyvalue: asIs; track group) {
{{ group.key }}
@for (appearance of group.value; track appearance) { {{ appearance }} } }
```
**TypeScript:**
```ts
import {ClipboardModule} from '@angular/cdk/clipboard';
import {KeyValuePipe} from '@angular/common';
import {Component, inject} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiNotificationService} from '@taiga-ui/core';
@Component({
imports: [ClipboardModule, KeyValuePipe, TuiButton],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
private readonly alerts = inject(TuiNotificationService);
protected readonly appearances = {
Primary: ['primary', 'primary-destructive', 'primary-grayscale'],
Secondary: ['secondary', 'secondary-destructive', 'secondary-grayscale'],
Flat: ['flat', 'flat-destructive', 'flat-grayscale'],
Outline: ['outline', 'outline-destructive', 'outline-grayscale'],
Action: ['action', 'action-destructive', 'action-grayscale'],
Status: ['neutral', 'negative', 'positive', 'warning', 'info'],
Others: ['icon', 'floating', 'textfield', 'accent'],
};
protected asIs(): number {
return 0;
}
protected onCopy(name: string): void {
this.alerts
.open(`Appearance ${name} copied`, {appearance: 'positive'})
.subscribe();
}
}
```
**LESS:**
```less
.section {
display: flex;
gap: 1rem;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiButton, type TuiInteractiveState} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected examples = ['Basic', 'Custom', 'Checkbox', 'Bundled'];
protected descriptions = [
'Interactive elements react to pointer natively but you can override state with inputs',
'Use LESS or SCSS mixins to create your own appearances in global styles',
'You can use it on input[type="checkbox"] to create a custom toggle component easily',
'You can create your own appearances or use one of the bundled options',
];
protected appearances = ['primary', 'secondary', 'flat'];
protected appearance = this.appearances[0]!;
protected states: readonly TuiInteractiveState[] = ['hover', 'active', 'disabled'];
protected state: TuiInteractiveState | null = null;
protected focus: boolean | null = null;
}
```
---
# directives/AutoFocus
- **Package**: `CDK`
- **Type**: directives
tuiAutoFocus allows to focus HTML-element right after its appearance. It works also with focusable Taiga UI components
### Usage Examples
#### Basic
**Template:**
```html
Show input @if (showInput) { Focusable input }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAutoFocus} from '@taiga-ui/cdk';
import {TuiButton, TuiInput} from '@taiga-ui/core';
@Component({
imports: [FormsModule, TuiAutoFocus, TuiButton, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected showInput = false;
protected model = 'Focused after its appearance';
protected onClick(): void {
this.showInput = true;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiLink} from '@taiga-ui/core';
@Component({
imports: [TuiDemo, TuiLink],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {}
```
### LESS
```less
.t-image {
inline-size: 100%;
}
```
---
# directives/CopyProcessor
- **Package**: `CDK`
- **Type**: directives
Directive is used to processed text when coping
### Usage Examples
#### Usage
**Template:**
```html
Copy this
Try copy this text
```
**TypeScript:**
```ts
import {Component, computed, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiCopyProcessor, type TuiStringHandler} from '@taiga-ui/cdk';
import {TUI_NUMBER_FORMAT, TuiNotificationService, TuiTextfield} from '@taiga-ui/core';
import {TuiInputNumber} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiCopyProcessor, TuiInputNumber, TuiTextfield],
templateUrl: './index.html',
encapsulation,
changeDetection,
host: {'(copy)': 'onCopy($event)'},
})
export default class Example {
private readonly alerts = inject(TuiNotificationService);
protected value = 12345.67;
protected format = inject(TUI_NUMBER_FORMAT);
protected readonly numberProcessor = computed(
({decimalSeparator, thousandSeparator} = this.format()) =>
(text: string) =>
text.replace(decimalSeparator, '.').replaceAll(thousandSeparator, ''),
);
protected onCopy(event: ClipboardEvent): void {
this.alerts.open(event.clipboardData?.getData('text/plain') ?? '').subscribe();
}
protected readonly textProcessor: TuiStringHandler = (text) =>
text.toUpperCase();
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {}
```
---
# directives/DateFormat
- **Package**: `CORE`
- **Type**: directives
Directive allows to customize TuiInputDate , TuiInputDateRange , TuiInputDateMulti and TuiInputDateTime date format.
### Usage Examples
#### Basic
Also available through tuiDateFormatProvider
**Template:**
```html
Cool
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDateFormat, TuiTextfield} from '@taiga-ui/core';
import {TuiInputDate} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiDateFormat, TuiInputDate, TuiTextfield],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly control = new FormControl();
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {RouterLink} from '@angular/router';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiLink} from '@taiga-ui/core';
@Component({
imports: [RouterLink, TuiDemo, TuiLink],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly routes = DemoRoute;
}
```
---
# directives/Dropdown
- **Package**: `CORE`
- **Type**: directives
tuiDropdown shows a dropdown with custom template. Use ActiveZone directive to hide dropdown. See also DropdownOpen
### Example
```html
PRESS! * There is also a pretty long text to check its width limitations
Here can be any content
You can even insert other components:
Do not touch!
Everything you want... *
* except cases of human rights violation
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiDropdownManual] | `boolean` | Show dropdown (basic manual implementation, see related pages for other options) |
| [tuiDropdown] | `PolymorpheusContent` | Content |
### Usage Examples
#### Basic
**Template:**
```html
{{ buttonLabel() }} @for (action of actions; track action.title) { {{ action.title }} {{ action.description }} }
```
**TypeScript:**
```ts
import {Component, computed, signal} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiActiveZone, TuiObscured} from '@taiga-ui/cdk';
import {TuiButton, TuiDataList, TuiDropdown, TuiTitle} from '@taiga-ui/core';
import {TuiChevron} from '@taiga-ui/kit';
interface ExampleAction {
readonly description: string;
readonly title: string;
}
@Component({
imports: [
TuiActiveZone,
TuiButton,
TuiChevron,
TuiDataList,
TuiDropdown,
TuiObscured,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly actions: readonly ExampleAction[] = [
{
title: 'Create task',
description: 'Draft a follow-up item for the team',
},
{
title: 'Schedule sync',
description: 'Find a 30-minute window for everyone',
},
{
title: 'Share update',
description: 'Post the latest progress to the channel',
},
];
protected readonly open = signal(false);
protected readonly selected = signal(null);
protected readonly buttonLabel = computed(() => this.selected()?.title ?? 'Choose');
protected onClick(): void {
this.open.update((open) => !open);
}
protected onObscured(obscured: boolean): void {
if (obscured) {
this.open.set(false);
}
}
protected onActiveZone(active: boolean): void {
if (!active) {
this.open.set(false);
}
}
protected onSelect(action: ExampleAction): void {
this.selected.set(action);
this.open.set(false);
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
tui-data-list {
gap: 0.25rem;
padding: 0.5rem;
}
```
#### Interesting
**Template:**
```html
Show help
You can ask any questions about and Alex will gladly answer!
Use polymorpheus directive on the template to make changes propagate both ways
@if (showBigText$ | async) {
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda at corporis ea hic illo ipsa laboriosam laudantium nemo neque officiis pariatur quidem quos rerum sunt, temporibus tenetur ullam vitae?
}
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdown, TuiInput} from '@taiga-ui/core';
import {TuiSwitch} from '@taiga-ui/kit';
import {PolymorpheusTemplate} from '@taiga-ui/polymorpheus';
import {interval, map} from 'rxjs';
@Component({
imports: [
AsyncPipe,
FormsModule,
PolymorpheusTemplate,
TuiDropdown,
TuiInput,
TuiSwitch,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
protected value = 'some data';
protected showBigText$ = interval(3000).pipe(map((i) => !(i % 2)));
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.flex {
display: flex;
align-items: center;
gap: 0.5rem;
margin-block-start: 1rem;
}
.dropdown {
max-inline-size: 20rem;
padding: 1rem;
}
```
#### Appearance
**Template:**
```html
Show dropdown
I'm a customized dropdown!
```
**TypeScript:**
```ts
import {Component, ViewEncapsulation} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDropdown, TuiLabel} from '@taiga-ui/core';
import {TuiSwitch} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiDropdown, TuiLabel, TuiSwitch],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation: ViewEncapsulation.None,
changeDetection,
})
export default class Example {
protected open = false;
}
```
**LESS:**
```less
tui-dropdown[data-appearance='round'] {
border-radius: 10rem;
}
```
#### Mobile
**Template:**
```html
Done Sum
@if (user.url) { }
{{ user.name }} {{ user.balance | tuiAmount: '$' : 'start' }}
```
**TypeScript:**
```ts
import {Component, inject, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {assets} from '@demo/utils';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {TuiDropdownMobile, TuiDropdownSheet} from '@taiga-ui/addon-mobile';
import {TuiButton, TuiDropdown, TuiFilterByInputPipe, TuiTitle} from '@taiga-ui/core';
import {
TUI_COUNTRIES,
TuiAvatar,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiFade,
TuiInitialsPipe,
TuiInputChip,
TuiInputNumber,
TuiMultiSelect,
TuiSelect,
} from '@taiga-ui/kit';
interface User {
readonly url?: string;
readonly name: string;
readonly balance: number;
}
@Component({
imports: [
FormsModule,
TuiAmountPipe,
TuiAvatar,
TuiButton,
TuiChevron,
TuiComboBox,
TuiDataListWrapper,
TuiDropdown,
TuiDropdownMobile,
TuiDropdownSheet,
TuiFade,
TuiFilterByInputPipe,
TuiInitialsPipe,
TuiInputChip,
TuiInputNumber,
TuiMultiSelect,
TuiSelect,
TuiTitle,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected country = null;
protected selected: readonly User[] = [];
protected sum = null;
protected user: User | null = null;
protected readonly open = signal(false);
protected readonly countries = Object.values(inject(TUI_COUNTRIES)());
protected readonly users: readonly User[] = [
{name: 'Alex Inkin', balance: 1323525, url: assets`/images/avatar.jpg`},
{name: 'Roman Sedov', balance: 523242},
{name: 'Vladimir Potekhin', balance: 645465},
{name: 'Nikita Barsukov', balance: 468468},
{name: 'Maxim Ivanov', balance: 498654},
];
protected readonly stringify = ({name}: User): string => name;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {changeDetection} from '@demo/emulate/change-detection';
import {DemoRoute} from '@demo/routes';
import {TuiDemo} from '@demo/utils';
import {TuiActiveZone, TuiObscured} from '@taiga-ui/cdk';
import {TuiButton, TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [
TuiActiveZone,
TuiButton,
TuiDemo,
TuiDocDropdown,
TuiDropdown,
TuiObscured,
],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent {
protected readonly routes = DemoRoute;
protected readonly examples = [
'Basic',
'Interesting',
'Change detection',
'Appearance',
'Mobile',
];
protected open = false;
protected onClick(): void {
this.open = !this.open;
}
protected onObscured(obscured: boolean): void {
if (obscured) {
this.open = false;
}
}
protected onActiveZone(active: boolean): void {
this.open = active && this.open;
}
}
```
---
# directives/DropdownContext
- **Package**: `CORE`
- **Type**: directives
DropdownContext allows to show custom right click context dropdown. To close dropdown: Use Esc Make mouse left/right click outside of dropdown content Manually toggle tuiDropdown to false using template reference variable (see first example)
### Example
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDropdown} from '@taiga-ui/core';
import {TuiTextarea} from '@taiga-ui/kit';
@Component({
imports: [ReactiveFormsModule, TuiButton, TuiDropdown, TuiTextarea],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected form = new FormGroup({reportText: new FormControl('Misspell HERE!')});
protected report(): void {
console.info(this.form.value);
}
}
```
**LESS:**
```less
.container {
display: flex;
inline-size: 20rem;
margin: 1rem;
flex-direction: column;
}
.button {
margin: 1rem auto 0;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [TuiDemo, TuiDocDropdown, TuiDropdown],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent {}
```
---
# directives/DropdownHover
- **Package**: `CORE`
- **Type**: directives
DropdownHover shows dropdown with custom template upon hover
### Example
```html
Hover pointer over to see a dropdown
Here you can have any content
You can select a text inside a dropdown and it will not close a dropdown
Button
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiDropdown] | `PolymorpheusContent` | Content |
| [tuiDropdownShowDelay] | `number` | Show delay for dropdown appearance after hover |
| [tuiDropdownHideDelay] | `number` | Show delay for dropdown appearance after hover |
### Usage Examples
#### Basic
**Template:**
```html
This is heavy!
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [TuiDropdown],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### With DropdownOpen
**Template:**
```html
Just a tab Hoverable/Clickable Option 1 Option 2 Option 3 Another tab
Turn option
Current state: {{ open ? 'open' : 'closed' }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDataList, TuiDropdown} from '@taiga-ui/core';
import {TuiChevron, TuiSwitch, TuiTabs} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiChevron,
TuiDataList,
TuiDropdown,
TuiSwitch,
TuiTabs,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({option: new FormControl(false)});
protected open = false;
protected openSettings = false;
protected index = 0;
protected onClick(): void {
this.open = false;
this.index = 1;
}
}
```
**LESS:**
```less
.settings {
margin: 1rem;
}
```
#### Nested
**Template:**
```html
Dropdown hover
Nested select
Nested dropdown hover
Nested content!
@for (item of items; track item) { {{ item }} }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDataList, TuiDropdown, TuiTextfield} from '@taiga-ui/core';
import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiChevron,
TuiDataList,
TuiDataListWrapper,
TuiDropdown,
TuiSelect,
TuiTextfield,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items = ['Edit', 'Download', 'Rename', 'Delete'];
protected readonly selectItems = ['Item 1', 'Item 2'];
protected open = false;
protected selected = new FormControl(null);
}
```
#### With custom host
**Template:**
```html
Won't open here Will open here
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDropdown, TuiGroup} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiDropdown, TuiGroup],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Mobile
**Template:**
```html
More GitHub Telegram Discord
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdownSheet} from '@taiga-ui/addon-mobile';
import {TuiButton, TuiDataList, TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiDataList, TuiDropdown, TuiDropdownSheet],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiButton, TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiDemo, TuiDocDropdown, TuiDropdown],
templateUrl: './index.html',
changeDetection,
})
export default class PageComponent {
protected readonly examples = [
'Basic',
'With DropdownOpen',
'Nested',
'With custom host',
'Mobile',
];
protected showDelay = 200;
protected hideDelay = 500;
}
```
---
# directives/DropdownOpen
- **Package**: `CORE`
- **Type**: directives
DropdownOpen is a composite dropdown directive, similar to manual dropdown, but it also takes care of opening and closing on its own. If an element is a textfield ( input or textarea ), arrow down press opens a dropdown. The next press focuses the first item from the list. If it is not a textfield, click opens and closes a dropdown. By default directive is applied to the first focusable element inside. If you want another element to be the host, use #tuiDropdownHost reference. Use tuiDropdownAuto selector with no binding if you do not want to track open state
### Example
```html
Start typing
@if (template) {
Do you like using Taiga UI?
Yes Yes
} @else { {{ content }} }
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiDropdown] | `PolymorpheusContent` | Content inside a dropdown |
| [(tuiDropdownOpen)] | `boolean` | Content inside a dropdown |
### Usage Examples
#### Menu
**Template:**
```html
Button @for (item of items; track item) { {{ item }} } Nevermind
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDataList, TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiDataList, TuiDropdown],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items = ['Edit', 'Download', 'Rename', 'Delete'];
protected open = false;
protected onClick(): void {
this.open = false;
}
}
```
**LESS:**
```less
[tuiButton]::after {
font-size: 1rem;
}
```
#### With custom host
**Template:**
```html
Button that does not open dropdown Menu
Nested Select @for (item of items; track item) { {{ item }} }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TuiButton,
TuiDataList,
TuiDropdown,
TuiGroup,
TuiTextfield,
} from '@taiga-ui/core';
import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit';
@Component({
imports: [
FormsModule,
TuiButton,
TuiChevron,
TuiDataList,
TuiDataListWrapper,
TuiDropdown,
TuiGroup,
TuiSelect,
TuiTextfield,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly items = ['Edit', 'Download', 'Rename', 'Delete'];
protected readonly selectItems = ['Item 1', 'Item 2'];
protected open = false;
protected selected = null;
protected onClick(): void {
this.open = false;
}
}
```
**LESS:**
```less
:host {
display: block;
inline-size: max-content;
}
.margin {
margin: 1rem;
}
```
#### With link
**Template:**
```html
@for (group of items; track group) { @for (item of group; track item) { {{ item }} @if (itemIsActive(item)) { } } }
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDataList, TuiDropdown, TuiIcon, TuiLink} from '@taiga-ui/core';
import {TuiChevron} from '@taiga-ui/kit';
@Component({
imports: [TuiChevron, TuiDataList, TuiDropdown, TuiIcon, TuiLink],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected open = false;
protected readonly items = [
['By interest', 'By genre', 'By release year', 'By subject'],
['Ascending', 'Descending'],
];
protected primary = 'By genre';
protected ascending = false;
protected onClick(item: string): void {
if (this.items[0]?.includes(item)) {
this.primary = item;
return;
}
this.ascending = item === this.items[1]?.[0];
}
protected itemIsActive(item: string): boolean {
return (
item === this.primary ||
(this.ascending && item === this.items[1]?.[0]) ||
(!this.ascending && item === this.items[1]?.[1])
);
}
}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
:host {
display: block;
text-align: end;
}
.link {
font-size: 1.0625rem;
}
.item {
min-inline-size: 12.5rem;
}
```
#### Complex example
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDataList, TuiDropdown} from '@taiga-ui/core';
import {TuiButtonSelect, TuiChevron, TuiMultiSelect} from '@taiga-ui/kit';
@Component({
imports: [
ReactiveFormsModule,
TuiButton,
TuiButtonSelect,
TuiChevron,
TuiDataList,
TuiDropdown,
TuiMultiSelect,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected readonly form = new FormGroup({
control: new FormControl([], {nonNullable: true}),
});
protected open = false;
protected readonly items = ['Drafts', 'In Progress', 'Completed'];
protected get length(): number {
return this.value.length || 0;
}
protected get text(): string {
switch (this.length) {
case 0:
return 'Select';
case 1:
return this.value[0] ?? '';
default:
return `${this.length} selected`;
}
}
private get value(): readonly string[] {
return this.form.get('control')?.value || [];
}
}
```
#### Custom positioning
**Template:**
```html
Show details
Custom positioning
You can achieve this with tuiAsPositionAccessor helper and a custom directive
```
**TypeScript:**
```ts
import {Component, Directive} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiInjectElement} from '@taiga-ui/cdk';
import {
tuiAsPositionAccessor,
TuiButton,
TuiDropdown,
TuiDropdownOpen,
type TuiPoint,
TuiPositionAccessor,
} from '@taiga-ui/core';
@Directive({
selector: '[topRight]',
providers: [tuiAsPositionAccessor(TopRightDirective)],
})
class TopRightDirective extends TuiPositionAccessor {
private readonly el = tuiInjectElement();
public readonly type = 'dropdown';
public getPosition({height}: DOMRect): TuiPoint {
const {right, top} = this.el.getBoundingClientRect();
return [right, top - height];
}
}
@Component({
imports: [TopRightDirective, TuiButton, TuiDropdown, TuiDropdownOpen],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.dropdown {
inline-size: 13rem;
padding: 0 1rem 1rem;
line-height: 2;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiButton, TuiDropdown, TuiInput} from '@taiga-ui/core';
@Component({
imports: [FormsModule, TuiButton, TuiDemo, TuiDocDropdown, TuiDropdown, TuiInput],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class PageComponent {
protected open = false;
protected input = '';
protected enabledVariants = [true, 'getter this.input.length > 2'];
protected enabledSelected = this.enabledVariants[0]!;
protected readonly contentVariants = ['Template', 'Custom string'];
protected content = this.contentVariants[0]!;
protected get template(): boolean {
return this.content === 'Template';
}
protected get enabled(): boolean {
return this.enabledSelected === true || this.input.length > 2;
}
protected onInput(input: string): void {
this.input = input;
this.open = this.enabled;
}
protected onClick(): void {
this.open = false;
}
}
```
### LESS
```less
.input {
inline-size: 15.625rem;
}
.dropdown {
padding: 1.25rem;
}
.buttons {
display: flex;
margin-block-end: 0;
}
.button {
flex: 1;
&:first-child {
margin-inline-end: 0.75rem;
}
}
```
---
# directives/DropdownSelection
- **Package**: `CORE`
- **Type**: directives
DropdownSelection shows dropdown with custom template on selected text
### Example
```html
Select a text to see a dropdown
Here you can have any content
You can select a text inside a dropdown and it will not close a dropdown
Button
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiDropdownSelection] | `TuiBooleanHandler` | and returns show/close dropdown |
| [tuiDropdownSelectionPosition] | `'selection' | 'word' | 'tag'` | Position of dropdown near text selection |
### Usage Examples
#### Sample
**Template:**
```html
Dropdown will be shown text selection:
Select a text to see dropdown
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [TuiDropdown],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### Textarea
**Template:**
```html
Type a message @for (item of items | tuiMapper: filter : search.replace('@', ''); track item) {
{{ item.name | tuiInitials }}
{{ item.name }} }
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {Component, ElementRef, viewChild, viewChildren} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {assets} from '@demo/utils';
import {type TuiBooleanHandler, type TuiMapper, TuiMapperPipe} from '@taiga-ui/cdk';
import {
TuiDataList,
TuiDriver,
TuiDropdown,
tuiGetWordRange,
TuiOption,
} from '@taiga-ui/core';
import {
TuiAvatar,
TuiInitialsPipe,
TuiTextarea,
TuiTextareaComponent,
} from '@taiga-ui/kit';
export interface User {
readonly avatar: string;
readonly login: string;
readonly name: string;
}
@Component({
imports: [
AsyncPipe,
FormsModule,
TuiAvatar,
TuiDataList,
TuiDropdown,
TuiInitialsPipe,
TuiMapperPipe,
TuiTextarea,
],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly textarea = viewChild.required<
TuiTextareaComponent,
ElementRef
>(TuiTextareaComponent, {read: ElementRef});
protected readonly options = viewChildren(TuiOption, {read: ElementRef});
protected readonly driver = viewChild(TuiDriver);
protected value = 'Type @ to see a dropdown';
protected readonly items = [
{
name: 'Alexander Inkin',
avatar: assets`images/avatar.jpg`,
login: 'a.inkin',
},
{
name: 'Roman Sedov',
avatar: '',
login: 'r.sedov',
},
];
protected get search(): string {
const el = this.textarea().nativeElement;
return el.value.slice(el.value.indexOf('@'), el.selectionStart) || '';
}
protected readonly filter: TuiMapper<[readonly User[], string], readonly User[]> = (
items,
search,
) =>
items.filter(
({name, login}) => login.startsWith(search) || name.startsWith(search),
);
protected predicate: TuiBooleanHandler = (range) =>
String(tuiGetWordRange(range)).startsWith('@');
protected onArrow(event: Event, index: number): void {
const item = this.options()[index];
if (!item) {
return;
}
event.preventDefault();
item.nativeElement.focus();
}
protected onClick(login: string): void {
const search = this.search;
const value = this.value.replace(search, login);
const caret = value.indexOf(login) + login.length;
this.value = value;
this.textarea().nativeElement.focus();
this.textarea().nativeElement.value = value;
this.textarea().nativeElement.setSelectionRange(caret, caret);
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {TuiDocDropdown} from '@demo/components/dropdown';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiButton, TuiDropdown} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiDemo, TuiDocDropdown, TuiDropdown],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class PageComponent {
protected positionVariants = ['selection', 'word', 'tag'] as const;
protected position: 'selection' | 'tag' | 'word' = this.positionVariants[0];
}
```
### LESS
```less
.dropdown {
max-inline-size: 20rem;
padding: 0.5rem 1.25rem;
}
```
---
# directives/ElasticSticky
- **Package**: `ADDON-MOBILE`
- **Type**: directives
Directive allows to scale "stuck" sticky heading. It can also be used as service TuiElasticStickyService
### Usage Examples
#### Basic
**Template:**
```html
I never wanted to do this in the first place!
{{ 237000 | tuiAmount: 'RUB' }}
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
```
**TypeScript:**
```ts
import {AsyncPipe} from '@angular/common';
import {type AfterViewInit, Component, viewChild} from '@angular/core';
import {outputToObservable} from '@angular/core/rxjs-interop';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {TuiElasticSticky} from '@taiga-ui/addon-mobile';
import {tuiClamp} from '@taiga-ui/cdk';
import {TuiScrollbar} from '@taiga-ui/core';
import {distinctUntilChanged, map, type Observable, startWith} from 'rxjs';
@Component({
imports: [AsyncPipe, TuiAmountPipe, TuiElasticSticky, TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example implements AfterViewInit {
protected readonly elasticSticky = viewChild(TuiElasticSticky);
protected scale$?: Observable;
public ngAfterViewInit(): void {
const sticky = this.elasticSticky();
if (!sticky) {
return;
}
// If we use it like that instead of (tuiElasticSticky)="onElasticSticky($event)"
// we will not trigger unnecessary change detection when scale is less than 0.5
this.scale$ = outputToObservable(sticky.tuiElasticSticky).pipe(
map((scale) => tuiClamp(scale, 0.5, 1)),
startWith(1),
distinctUntilChanged(),
);
}
}
```
**LESS:**
```less
:host {
display: block;
}
.scrollbar {
block-size: 12.5rem;
}
.header {
position: sticky;
z-index: 1;
inset-block-start: 0;
block-size: 5.5rem;
pointer-events: none;
}
.wrapper {
color: var(--tui-background-base);
background: #bc71c9;
font-size: 2rem;
pointer-events: auto;
}
.money {
display: block;
line-height: 1em;
padding: 1em 1.5rem;
}
```
#### Dynamic inner content
**Template:**
```html
I never wanted to do this in the first place!
1
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
show/hide content
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
@if (show) {
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
}
2
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
I always wanted to be... a lumberjack!
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiElasticSticky} from '@taiga-ui/addon-mobile';
import {tuiClamp} from '@taiga-ui/cdk';
import {TuiButton, TuiScrollbar} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiElasticSticky, TuiScrollbar],
templateUrl: './index.html',
styleUrl: '../1/index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected show = false;
protected scale1 = 1;
protected scale2 = 1;
protected onElastic1(scale: number): void {
this.scale1 = tuiClamp(scale, 0.2, 1);
}
protected onElastic2(scale: number): void {
this.scale2 = tuiClamp(scale, 0.2, 1);
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Basic', 'Dynamic inner content'];
}
```
---
# directives/Element
- **Package**: `CDK`
- **Type**: directives
Directive is used to get a link to a native element as template reference variable (analogue of @ViewChild('ref', {read: ElementRef} for template)
### Usage Examples
#### Usage
**Template:**
```html
C E
element instanceof ElementRef : {{ isElement(element) }}
Focus element
```
**TypeScript:**
```ts
import {Component, ElementRef} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiElement} from '@taiga-ui/cdk';
import {TuiButton} from '@taiga-ui/core';
import {TuiAvatar} from '@taiga-ui/kit';
@Component({
imports: [TuiAvatar, TuiButton, TuiElement],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected isLink(component: unknown): boolean {
return component instanceof TuiAvatar;
}
protected isElement(element: unknown): boolean {
return element instanceof ElementRef;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {}
```
---
# directives/Fade
- **Package**: `KIT`
- **Type**: directives
Directive that uses masking to fade out overflown content
### Example
```html
I am a very long text with white-space: nowrap that fades
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiFade] | `TuiOrientation` | Orientation of the fade |
| [tuiFadeHeight] | `string` | Line height (required for multiline text fade) |
| [tuiFadeOffset] | `string` | Offset from the edge for the fade to start |
| [tuiFadeSize] | `string` | Size of the fade |
### Usage Examples
#### Basic
**Template:**
```html
I am a very long text that overflows with a single line fade
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollable, TuiScrollbar} from '@taiga-ui/core';
import {TuiFade} from '@taiga-ui/kit';
@Component({
imports: [TuiFade, TuiScrollable, TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.scrollbar {
inline-size: 20rem;
}
.fade {
.scrollbar-hidden();
inline-size: 100%;
block-size: 2rem;
font: var(--tui-typography-heading-h6);
white-space: nowrap;
overflow: auto;
}
```
#### Multiline
**Template:**
```html
Daenerys of the House Targaryen, the First of Her Name, The Unburnt, Queen of the Andals, the Rhoynar and the First Men, Queen of Meereen, Khaleesi of the Great Grass Sea, Protector of the Realm, Lady Regent of the Seven Kingdoms, Breaker of Chains and Mother of Dragons. @if (expanded) { Show less }
Daenerys of the House Targaryen, the First of Her Name, The Unburnt, Queen of the Andals, the Rhoynar and the First Men, Queen of Meereen, Khaleesi of the Great Grass Sea, Protector of the Realm, Lady Regent of the Seven Kingdoms, Breaker of Chains and Mother of Dragons.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiScrollable, TuiScrollbar} from '@taiga-ui/core';
import {TuiFade} from '@taiga-ui/kit';
@Component({
imports: [TuiFade, TuiScrollable, TuiScrollbar],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
@import '@taiga-ui/styles/utils';
.fade {
.scrollbar-hidden();
inline-size: 17rem;
block-size: 10rem;
font: var(--tui-typography-body-l);
overflow: auto;
}
```
#### Hyphens
**Template:**
```html
Use CSS hyphens and declare lang on your html tag so that you don't get a situation where a whole long word is shifted to the new line and you observe no fade effect
Daenerys of the House Targaryen, the First of Her Name, The Unburnt, Queen of the pneumonoultramicroscopicsilicovolcanoconiosis Men, Queen of Meereen, Khaleesi of the Great Grass Sea, Protector of the Realm, Lady Regent of the Seven Kingdoms, Breaker of Chains and Mother of Dragons.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiNotification} from '@taiga-ui/core';
import {TuiFade} from '@taiga-ui/kit';
@Component({
imports: [TuiFade, TuiNotification],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
[tuiFade] {
inline-size: 25rem;
block-size: 2.5rem;
hyphens: auto;
}
```
#### InputChip
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTextfield} from '@taiga-ui/core';
import {TuiInputChip} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiInputChip, TuiTextfield],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected value = ['3', '4', '5', 'Compartmentalization'];
}
```
**LESS:**
```less
[tuiChip] {
max-inline-size: 6.25rem;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiFade} from '@taiga-ui/kit';
@Component({
imports: [TuiDemo, TuiFade],
templateUrl: './index.html',
styleUrl: './index.less',
changeDetection,
})
export default class Page {
protected readonly examples = [
'Basic',
'Multiline',
'Vertical',
'Hyphens',
'InputChip',
];
protected lineHeight = '100%';
protected size = '1.5em';
protected offset = '0em';
}
```
### LESS
```less
.fade {
font: var(--tui-typography-heading-h6);
white-space: nowrap;
}
```
---
# directives/FluidTypography
- **Package**: `KIT`
- **Type**: directives
A directive that adjusts font size for the text to fit in the container
### Usage Examples
#### Text
**Template:**
```html
Randomize
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton} from '@taiga-ui/core';
import {TuiFluidTypography} from '@taiga-ui/kit';
const WORDS = ['Rock', 'Paper', 'Scissor'];
@Component({
imports: [TuiButton, TuiFluidTypography],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected text = 'There is some text in here';
protected randomize(): void {
this.text = Array.from(
{length: Math.ceil(10 * Math.random())},
() => WORDS[Math.floor(Math.random() * WORDS.length)],
).join(', ');
}
}
```
#### Textfield
**Template:**
```html
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInput} from '@taiga-ui/core';
import {TuiFluidTypography} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiFluidTypography, TuiInput],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected value = '';
}
```
#### Options
**Template:**
```html
Min/Max (converted to pixels)
```
**TypeScript:**
```ts
import {Component, computed, inject, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInput} from '@taiga-ui/core';
import {
TUI_FLUID_TYPOGRAPHY_OPTIONS,
TuiFluidTypography,
TuiInputRange,
} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiFluidTypography, TuiInput, TuiInputRange],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
private readonly options = inject(TUI_FLUID_TYPOGRAPHY_OPTIONS);
protected value = 'I am a very long value';
protected readonly range = signal([this.options.min * 16, this.options.max * 16]);
protected readonly scale = computed<[number, number]>(() => [
(this.range()[0] ?? 0) / 16,
(this.range()[1] ?? 0) / 16,
]);
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected examples = ['Text', 'Textfield', 'Options'];
}
```
---
# directives/Highlight
- **Package**: `KIT`
- **Type**: directives
Directive is used to highlight text in element. You can configure the directive with TUI_HIGHLIGHT_OPTIONS token. Allowed options: highlightColor: The default color for the highlight. Use function tuiHighlightOptionsProvider to provide new value of this token. Does not work with inline elements
### Usage Examples
#### Usage
**Template:**
```html
Search
Member
Nickname
Fate
@for (row of rows; track row) {
@for (cell of row; track cell) {
{{ cell }}
}
}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiInput} from '@taiga-ui/core';
import {TuiHighlight} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiHighlight, TuiInput],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected search = '';
protected readonly rows = [
['King Arthur', '-', 'Arrested'],
['Sir Bedevere', 'The Wise', 'Arrested'],
['Sir Lancelot', 'The Brave', 'Arrested'],
['Sir Galahad', 'The Chaste', 'Killed'],
['Sir Robin', 'The Not-Quite-So-Brave-As-Sir-Lancelot', 'Killed'],
];
}
```
**LESS:**
```less
:host {
display: block;
}
table {
inline-size: 100%;
border-spacing: 0;
}
th,
td {
text-align: start;
border: 1px solid var(--tui-border-normal);
block-size: 3.375rem;
padding: 0 1rem;
vertical-align: middle;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {}
```
---
# directives/Hint
- **Package**: `CORE`
- **Type**: directives
Directive to show a hint by hover of an element
### Example
```html
To be accessible, hint should be set to a focusable element. See HintDescribe Hover it!
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiHint] | `PolymorpheusContent` | Content |
| [tuiHintShowDelay] | `number` | Show delay (ms) |
| [tuiHintHideDelay] | `number` | Hide delay (ms) |
### Usage Examples
#### Basic
**Template:**
```html
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time... like tears in rain... Time to die.
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time... like tears in rain... Time to die.
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time... like tears in rain... Time to die.
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time... like tears in rain... Time to die.
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time... like tears in rain... Time to die.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiRipple} from '@taiga-ui/addon-mobile';
@Component({
imports: [TuiRipple],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
[tuiRipple] {
position: relative;
padding: 1rem;
border: 1px solid var(--tui-border-normal);
border-radius: 1rem;
user-select: none;
margin-block-end: 1rem;
overflow: hidden;
}
```
#### Global Ripple
**Template:**
```html
Button
Content
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiRipple} from '@taiga-ui/addon-mobile';
import {TuiButton} from '@taiga-ui/core';
@Component({
imports: [TuiButton, TuiRipple],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {}
```
**LESS:**
```less
.block {
inline-size: 10rem;
block-size: 5rem;
margin-block-start: 2rem;
border: 1px solid var(--tui-background-accent-1);
}
.block,
button {
position: relative;
overflow: hidden;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Example {
protected readonly examples = ['Basic', 'Global Ripple'];
}
```
---
# directives/Sensitive
- **Package**: `KIT`
- **Type**: directives
A directive that allows you to hide sensitive data under a pixel mask. This can be account balances, write-off amounts and any other content
### Example
```html
Confidential information
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiSensitive] | `boolean` | Sensitive mode |
### Usage Examples
#### Basic
**Template:**
```html
Balance: 100 000$ Balance: 100 000$ Balance: 100 000$
hide
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLabel} from '@taiga-ui/core';
import {TuiSensitive, TuiSwitch} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiLabel, TuiSensitive, TuiSwitch],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected sensitive = true;
}
```
**LESS:**
```less
:host {
display: flex;
flex-direction: column;
gap: 1rem;
inline-size: 30rem;
}
.small {
font: var(--tui-typography-body-s);
}
.medium {
font: var(--tui-typography-heading-h6);
}
.big {
font: var(--tui-typography-heading-h3);
}
```
#### Components
**Template:**
```html
Pay 1000$ Pay 1000$
12 000$ 12 000$ 12 000$
Hide
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiLabel} from '@taiga-ui/core';
import {TuiBadge, TuiSensitive, TuiSwitch} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiBadge, TuiButton, TuiLabel, TuiSensitive, TuiSwitch],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected sensitive = true;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
import {TuiSensitive} from '@taiga-ui/kit';
@Component({
imports: [TuiDemo, TuiSensitive],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected sensitive = true;
protected readonly examples = ['Basic', 'Components'];
}
```
---
# directives/Shimmer
- **Package**: `KIT`
- **Type**: directives
tuiShimmer directive visually implements the "Shimmer" UI pattern — an animated loading indicator that simulates content appearance while data is being fetched. This pattern enhances the user experience by providing visual feedback during loading states, helping users understand that the interface is active and content is on its way. When to Use To indicate loading states in cards, headers, lists, avatars, and other UI elements. When you want to visually communicate that content is loading, rather than missing or frozen. Shimmer is used when you have cached data that is currently being refreshed and if you have no data at all — a better choice would be Skeleton
### Example
```html
You got $237 000,42 left
Where's the money, Lebowski?
@for (avatar of avatars; track $index) {
}
```
### API - Inputs
| Property | Type | Description |
|----------|-----|----------|
| [tuiShimmer] | `boolean` | Shimmer state |
### Usage Examples
#### Basic
**Template:**
```html
Loading
Shimmer
You got $237 000,42 left
Where's the money, Lebowski?
@for (avatar of avatars; track $index) {
}
Skeleton
{{ loading ? '' : 'You got $237 000,42 left' }}
{{ loading ? '' : "Where's the money, Lebowski?" }}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Awesome Cool
Chip Dale
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiLabel, TuiTitle} from '@taiga-ui/core';
import {
TuiAvatar,
TuiAvatarStack,
TuiBadge,
TuiChip,
TuiProgressCircle,
TuiSkeleton,
TuiSwitch,
} from '@taiga-ui/kit';
import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout';
@Component({
imports: [
FormsModule,
TuiAvatar,
TuiAvatarStack,
TuiBadge,
TuiButton,
TuiCardLarge,
TuiChip,
TuiHeader,
TuiLabel,
TuiProgressCircle,
TuiSkeleton,
TuiSwitch,
TuiTitle,
],
templateUrl: './index.html',
styleUrl: './index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected skeleton = false;
}
```
**LESS:**
```less
p {
display: flex;
gap: 1rem;
align-items: center;
justify-content: center;
}
```
#### Text
**Template:**
```html
Show skeleton
{{ skeleton ? '' : 'This text will be replaced by a placeholder.' }}
{{ skeleton ? '' : 'This text will be replaced by a skeleton made of 20 random length non-breaking spaces.' }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiLabel} from '@taiga-ui/core';
import {TuiSkeleton, TuiSwitch} from '@taiga-ui/kit';
@Component({
imports: [FormsModule, TuiLabel, TuiSkeleton, TuiSwitch],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {
protected skeleton = false;
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly examples = ['Components', 'Text'];
}
```
---
# directives/Swipe
- **Package**: `CDK`
- **Type**: directives
tuiSwipe directive allows detecting swipes on mobile devices. You can configure the directive with TUI_SWIPE_OPTIONS token. Allowed options: timeout: max time between touchstart and touchend threshold : min distance between touchstart and touchend.
### Usage Examples
#### Basic
**Template:**
```html
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time... like tears in rain... Time to die.
opacity
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time... like tears in rain... Time to die.
background
I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time... like tears in rain... Time to die.
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiTouchable} from '@taiga-ui/addon-mobile';
import {TuiAppearance} from '@taiga-ui/core';
import {TuiCardLarge} from '@taiga-ui/layout';
@Component({
imports: [TuiAppearance, TuiCardLarge, TuiTouchable],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {}
```
---
# directives/Truncate
- **Package**: `CDK`
- **Type**: directives
Truncates text with an ellipsis in the middle. Useful when both start and end of the string are relevant, such as long file names with extension.
### Example
```html
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
```
**TypeScript:**
```ts
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
tuiInjectElement,
TuiPortals,
TuiPortalService,
tuiProvide,
TuiVCR,
} from '@taiga-ui/cdk';
import {
tuiAsViewport,
TuiDropdown,
TuiPopupService,
type TuiRectAccessor,
} from '@taiga-ui/core';
@Component({
selector: 'portal-host',
imports: [TuiVCR],
template: `
`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [tuiProvide(TuiPortalService, TuiPopupService), tuiAsViewport(PortalHost)],
})
class PortalHost extends TuiPortals implements TuiRectAccessor {
private readonly el = tuiInjectElement();
public readonly type = 'viewport';
public getClientRect(): DOMRect {
return this.el.getBoundingClientRect();
}
}
@Component({
imports: [PortalHost, TuiDropdown],
templateUrl: './index.html',
styleUrl: '../1/index.less',
encapsulation,
changeDetection,
providers: [TuiPopupService],
})
export default class Example {}
```
#### Hint
**Template:**
```html
@for (direction of directions; track direction) {
{{ direction }} } Hint Use Hint
```
**TypeScript:**
```ts
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiInjectElement} from '@taiga-ui/cdk';
import {
tuiAsViewport,
TuiButton,
TuiHint,
type TuiHintDirection,
TuiLink,
type TuiRectAccessor,
} from '@taiga-ui/core';
import {TuiSegmented} from '@taiga-ui/kit';
@Component({
selector: 'portal-host',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [tuiAsViewport(PortalHost)],
})
class PortalHost implements TuiRectAccessor {
private readonly el = tuiInjectElement();
public readonly type = 'viewport';
public getClientRect(): DOMRect {
return this.el.getBoundingClientRect();
}
}
@Component({
imports: [FormsModule, PortalHost, TuiButton, TuiHint, TuiLink, TuiSegmented],
templateUrl: './index.html',
styleUrl: '../1/index.less',
encapsulation,
changeDetection,
})
export default class Example {
protected hintShown = false;
protected directions: TuiHintDirection[] = ['top', 'start', 'end', 'bottom'];
protected selected = this.directions[0]!;
protected toggleHint(): void {
this.hintShown = !this.hintShown;
}
}
```
### TypeScript
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {TuiDemo} from '@demo/utils';
@Component({
imports: [TuiDemo],
templateUrl: './index.html',
changeDetection,
})
export default class Page {
protected readonly providers = import('./examples/import/providers.md');
protected examples = ['Dropdown', 'Dropdown and custom portal', 'Hint'];
}
```
---
# pipes/Amount
- **Package**: `ADDON-COMMERCE`
- **Type**: pipes
Pipe to format number values to show money sums Number formatting can be customized by using TUI_NUMBER_FORMAT
### Usage Examples
#### base
**Template:**
```html
{{ 10728.9 | tuiAmount }}
{{ 10728.9 | tuiAmount: 'RUB' }}
{{ 10728.9 | tuiAmount: 'EUR' }}
{{ 10728.9 | tuiAmount: 'USD' }}
{{ 10728.9 | tuiAmount: 'GBP' }}
{{ -12345.1 | tuiAmount: 'USD' : 'start' }}
{{ 100 | tuiAmount: '£' : 'start' }}
{{ 200 | tuiAmount: 'AED' : 'start' }}
Remaining {{ 10728.9 | tuiAmount }} of {{ 11000 | tuiAmount }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
@Component({
imports: [TuiAmountPipe],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### format
**Template:**
```html
{{ -10000000.536 | tuiAmount: 'USD' : 'start' }}
{{ 200.536 | tuiAmount: 'EUR' }}
{{ 54000.643 | tuiAmount: 'USD' : 'start' }}
{{ 800 | tuiAmount: 'USD' : 'start' }}
{{ -0.83 | tuiAmount: 'RUB' : 'end' }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiAmountPipe} from '@taiga-ui/addon-commerce';
import {TuiNumberFormat} from '@taiga-ui/core';
@Component({
imports: [TuiAmountPipe, TuiNumberFormat],
templateUrl: './index.html',
encapsulation,
changeDetection,
})
export default class Example {}
```
#### options
**Template:**
```html
{{ -12.3 | tuiAmount }}
{{ 3000 | tuiAmount }}
```
**TypeScript:**
```ts
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {tuiAmountOptionsProvider, TuiAmountPipe} from '@taiga-ui/addon-commerce';
@Component({
imports: [TuiAmountPipe],
templateUrl: './index.html',
encapsulation,
changeDetection,
providers: [
tuiAmountOptionsProvider({
sign: 'always',
currency: 'USD',
currencyAlign: 'start',
}),
],
})
export default class Example {}
```
#### separate decimal part
**Template:**
```html