# Import Map - Package Exports Reference > **Critical**: Always import from the correct package. This is the #1 cause of compilation errors. > **Auto-generated**: This file is automatically generated from Taiga UI source code. --- ## @taiga-ui/core **Components:** ### button ```text TuiButton, TuiButtonOptions ``` ### calendar ```text TuiCalendar, TuiCalendarOptions, TuiCalendarSheet, TuiCalendarSheetOptions, TuiCalendarSheetPipe, TuiCalendarSpin, TuiCalendarYear, TuiMarkerHandler, TuiOrderWeekDaysPipe ``` ### carousel ```text TuiCarousel, TuiCarouselComponent, TuiCarouselDirective ``` ### cell ```text TuiCell, TuiCellOptions ``` ### checkbox ```text TuiCheckbox, TuiCheckboxOptions ``` ### data-list ```text TuiDataList, TuiDataListAccessor, TuiDataListComponent, TuiDataListHost, TuiOptGroup, TuiOption, TuiOptionWithContent, TuiOptionWithValue, TuiWithOptionContent ``` ### error ```text TuiError, TuiErrorComponent, TuiErrorDirective, TuiErrorPipe ``` ### expand ```text TuiExpand ``` ### icon ```text TuiIcon, TuiIconPipe ``` ### input ```text TuiInput, TuiInputDirective, TuiWithInput ``` ### label ```text TuiLabel ``` ### link ```text TuiLink ``` ### loader ```text TuiLoader, TuiLoaderOptions ``` ### notification ```text TuiNotification, TuiNotificationComponent, TuiNotificationDirective, TuiNotificationOptions, TuiNotificationService, TuiNotificationTemplate ``` ### radio ```text TuiRadio, TuiRadioComponent, TuiRadioDirective, TuiRadioOptions ``` ### root ```text TuiRoot ``` ### scrollbar ```text TuiScrollControls, TuiScrollIntoView, TuiScrollRef, TuiScrollable, TuiScrollbar, TuiScrollbarDirective, TuiScrollbarOptions, TuiScrollbarService ``` ### slider ```text TuiKeySteps, TuiSlider, TuiSliderComponent, TuiSliderKeySteps, TuiSliderKeyStepsBase, TuiSliderReadonly, TuiSliderThumbLabel ``` ### spin-button ```text TuiSpinButton ``` ### textfield ```text TuiSelectLike, TuiTextfield, TuiTextfieldAccessor, TuiTextfieldComponent, TuiTextfieldContent, TuiTextfieldItem, TuiTextfieldItemComponent, TuiTextfieldMultiComponent, TuiTextfieldOptions, TuiTextfieldOptionsDirective TuiWithNativePicker ``` ### title ```text TuiTitle ``` **Directives:** ### appearance ```text TuiAppearance, TuiAppearanceOptions, TuiWithAppearance ``` ### button-x ```text TuiButtonX ``` ### date-format ```text TuiDateFormat ``` ### group ```text TuiGroup, TuiGroupOptions ``` ### icons ```text TuiIcons, TuiWithIcons ``` ### items-handlers ```text TuiItemsHandlers, TuiItemsHandlersDirective, TuiItemsHandlersValidator, TuiWithItemsHandlers ``` ### number-format ```text TuiNumberFormat ``` **Pipes:** ### filter-by-input ```text TuiFilterByInputOptions, TuiFilterByInputPipe ``` ### format-number ```text TuiFormatNumberPipe ``` **Services:** ### root ```text TuiPositionService, TuiVisualViewportService ``` **Types:** ### root ```text TuiHorizontalDirection, TuiInteractiveState, TuiOrientation, TuiPoint, TuiSizeL TuiSizeM, TuiSizeS, TuiSizeXL, TuiSizeXS, TuiSizeXXL TuiSizeXXS, TuiVerticalDirection ``` **Tokens:** ### root ```text TuiBreakpointMediaKey, TuiCommonIcons, TuiDateFormatSettings, TuiDecimalMode, TuiDecimalSymbol TuiMedia, TuiNumberFormatSettings ``` **Utilitys:** ### miscellaneous ```text TuiOptions ``` --- ## @taiga-ui/kit **Components:** ### accordion ```text TuiAccordion, TuiAccordionComponent, TuiAccordionDirective ``` ### action-bar ```text TuiActionBar ``` ### avatar ```text TuiAvatar, TuiAvatarLabeled, TuiAvatarOptions, TuiAvatarOutline, TuiAvatarStack ``` ### badge ```text TuiBadge, TuiBadgeOptions ``` ### badge-notification ```text TuiBadgeNotification, TuiBadgeNotificationOptions ``` ### badged-content ```text TuiBadgedContent, TuiBadgedContentComponent, TuiBadgedContentDirective ``` ### block ```text TuiBlock, TuiBlockOptions ``` ### breadcrumbs ```text TuiBreadcrumbs, TuiBreadcrumbsOptions ``` ### button-loading ```text TuiButtonLoading ``` ### calendar-month ```text TuiCalendarMonth, TuiCalendarMonthOptions ``` ### calendar-range ```text TuiCalendarRange, TuiDayRangePeriod ``` ### chip ```text TuiChip, TuiChipOptions ``` ### combo-box ```text TuiComboBox, TuiComboBoxDirective ``` ### comment ```text TuiComment ``` ### compass ```text TuiCompass ``` ### confirm ```text TuiConfirm, TuiConfirmData, TuiConfirmService ``` ### copy ```text TuiButtonCopy, TuiCopy, TuiCopyComponent, TuiCopyDirective, TuiCopyOptions ``` ### counter ```text TuiCounter, TuiCounterOptions ``` ### data-list-wrapper ```text TuiDataListGroupWrapperComponent, TuiDataListWrapper, TuiDataListWrapperComponent ``` ### drawer ```text TuiDrawer ``` ### files ```text TuiFile, TuiFileLike, TuiFileOptions, TuiFileRejectedPipe, TuiFileState, TuiFiles, TuiFilesComponent, TuiInputFiles, TuiInputFilesContent, TuiInputFilesDirective TuiInputFilesOptions, TuiInputFilesValidator ``` ### filter ```text TuiFilter ``` ### fullscreen ```text TuiFullscreen ``` ### input-chip ```text TuiInputChip, TuiInputChipComponent, TuiInputChipDirective, TuiInputChipOptions ``` ### input-color ```text TuiInputColor, TuiInputColorComponent, TuiInputColorOptions ``` ### input-date ```text TuiInputDate, TuiInputDateComponent, TuiInputDateDirective, TuiInputDateOptions ``` ### input-date-multi ```text TuiInputDateMulti, TuiInputDateMultiDirective ``` ### input-date-range ```text TuiInputDateRange, TuiInputDateRangeDirective, TuiInputDateRangeOptions ``` ### input-date-time ```text TuiInputDateTime, TuiInputDateTimeComponent, TuiInputDateTimeDirective, TuiInputDateTimeOptions ``` ### input-inline ```text TuiInputInline ``` ### input-month ```text TuiInputMonth, TuiInputMonthComponent, TuiInputMonthDirective, TuiInputMonthOptions ``` ### input-month-range ```text TuiInputMonthRange, TuiInputMonthRangeDirective, TuiInputMonthRangeOptions ``` ### input-number ```text TuiBigIntQuantumValueTransformer, TuiBigIntValueTransformer, TuiInputNumber, TuiInputNumberDirective, TuiInputNumberOptions, TuiInputNumberStep, TuiInputNumberStepButtons, TuiInputNumberStepService, TuiNumberMask, TuiNumberValueTransformer TuiQuantumValueTransformer, TuiQuantumValueTransformerBase, TuiWithNumberMask ``` ### input-phone ```text TuiInputPhone, TuiInputPhoneDirective, TuiInputPhoneOptions ``` ### input-phone-international ```text TuiInputPhoneInternational, TuiInputPhoneInternationalComponent, TuiInputPhoneInternationalOptions ``` ### input-pin ```text TuiInputPin, TuiInputPinComponent ``` ### input-range ```text TuiInputRange ``` ### input-slider ```text TuiInputSlider, TuiInputSliderDirective ``` ### input-time ```text TuiInputTime, TuiInputTimeComponent, TuiInputTimeDirective, TuiInputTimeOptions ``` ### input-year ```text TuiInputYear, TuiInputYearDirective, TuiInputYearOptions ``` ### items-with-more ```text TuiItemsWithMore, TuiItemsWithMoreComponent, TuiItemsWithMoreDirective, TuiItemsWithMoreService, TuiMore ``` ### like ```text TuiLike, TuiLikeOptions ``` ### line-clamp ```text TuiLineClamp, TuiLineClampBox, TuiLineClampOptions, TuiLineClampPositionDirective ``` ### message ```text TuiMessage ``` ### multi-select ```text TuiMultiSelect, TuiMultiSelectGroupComponent, TuiMultiSelectGroupDirective, TuiMultiSelectNative, TuiMultiSelectOption ``` ### notification-middle ```text TuiNotificationMiddle, TuiNotificationMiddleComponent, TuiNotificationMiddleOptions, TuiNotificationMiddleService ``` ### pager ```text TuiPager ``` ### pagination ```text TuiPagination, TuiPaginationOptions ``` ### pin ```text TuiPin ``` ### preview ```text TuiPreview, TuiPreviewAction, TuiPreviewComponent, TuiPreviewDialog, TuiPreviewDialogDirective, TuiPreviewDialogService, TuiPreviewIcons, TuiPreviewPagination, TuiPreviewTitle, TuiPreviewZoom ``` ### progress ```text TuiProgress, TuiProgressBar, TuiProgressCircle, TuiProgressColorSegments, TuiProgressFixedGradientDirective, TuiProgressLabel, TuiProgressOptions, TuiProgressSegmented ``` ### pulse ```text TuiPulse ``` ### push ```text TuiPush, TuiPushAlert, TuiPushComponent, TuiPushDirective, TuiPushOptions, TuiPushService ``` ### radio-list ```text TuiRadioList ``` ### range ```text TuiRange, TuiRangeChange ``` ### rating ```text TuiRating, TuiRatingContext, TuiRatingOptions ``` ### segmented ```text TuiSegmented, TuiSegmentedDirective ``` ### select ```text TuiNativeSelect, TuiSelect, TuiSelectDirective, TuiSelectOption ``` ### shrink-wrap ```text TuiShrinkWrap, TuiShrinkWrapComponent, TuiShrinkWrapDirective ``` ### status ```text TuiStatus ``` ### stepper ```text TuiStep, TuiStepper, TuiStepperComponent ``` ### switch ```text TuiSwitch, TuiSwitchOptions ``` ### tabs ```text TuiTab, TuiTabs, TuiTabsDirective, TuiTabsHorizontal, TuiTabsOptions, TuiTabsVertical, TuiTabsWithMore ``` ### textarea ```text TuiTextarea, TuiTextareaComponent, TuiTextareaDirective, TuiTextareaOptions ``` ### tiles ```text TuiReorderFunction, TuiTile, TuiTileHandle, TuiTileService, TuiTiles, TuiTilesComponent ``` ### timeline ```text TuiTimeline, TuiTimelineComponent, TuiTimelineItem ``` ### toast ```text TuiToast, TuiToastComponent, TuiToastDirective, TuiToastOptions, TuiToastService, TuiToastTemplate ``` ### tree ```text TuiTree, TuiTreeAccessor, TuiTreeChildren, TuiTreeComponent, TuiTreeContext, TuiTreeController, TuiTreeControllerDirective, TuiTreeItem, TuiTreeItemContent, TuiTreeItemContext TuiTreeItemController, TuiTreeLoader, TuiTreeNode, TuiTreeService ``` **Directives:** ### appearance-proxy ```text TuiAppearanceProxy ``` ### button-group ```text TuiButtonGroup ``` ### button-select ```text TuiButtonSelect ``` ### chevron ```text TuiChevron ``` ### connected ```text TuiConnected ``` ### data-list-dropdown-manager ```text TuiDataListDropdownManager ``` ### fade ```text TuiFade ``` ### fluid-typography ```text TuiFluidTypography, TuiFluidTypographyOptions ``` ### highlight ```text TuiHighlight ``` ### password ```text TuiPassword, TuiPasswordOptions ``` ### present ```text TuiPresent ``` ### sensitive ```text TuiSensitive ``` ### shimmer ```text TuiShimmer ``` ### skeleton ```text TuiSkeleton ``` ### tooltip ```text TuiTooltip ``` ### unfinished-validator ```text TuiUnfinishedValidator ``` ### unmask-handler ```text TuiUnmaskHandler ``` **Pipes:** ### auto-color ```text TuiAutoColorPipe ``` ### emails ```text TuiEmailsPipe ``` ### flag ```text TuiFlagPipe ``` ### hide-selected ```text TuiHideSelectedPipe ``` ### initials ```text TuiInitialsPipe ``` ### sort-countries ```text TuiSortCountriesPipe ``` ### stringify ```text TuiStringifyPipe ``` ### stringify-content ```text TuiStringifyContentPipe ``` --- ## @taiga-ui/cdk **Directives:** ### active-zone ```text TuiActiveZone ``` ### animated ```text TuiAnimated ``` ### auto-focus ```text TuiAutoFocus, TuiAutofocusHandler, TuiAutofocusOptions, TuiDefaultAutofocusHandler, TuiIosAutofocusHandler, TuiSynchronousAutofocusHandler ``` ### control ```text TuiNgControl ``` ### copy-processor ```text TuiCopyProcessor ``` ### element ```text TuiElement ``` ### focus-trap ```text TuiFocusTrap ``` ### font-size ```text TuiFontSize ``` ### high-dpi ```text TuiHighDpi ``` ### hovered ```text TuiHovered, TuiHoveredService ``` ### item ```text TuiItem ``` ### media ```text TuiMedia ``` ### native-validator ```text TuiNativeValidator ``` ### obscured ```text TuiObscured, TuiObscuredService ``` ### pan ```text TuiPan, TuiPanService ``` ### platform ```text TuiPlatform ``` ### resizer ```text TuiResizable, TuiResizer ``` ### swipe ```text TuiSwipe, TuiSwipeDirection, TuiSwipeEvent, TuiSwipeOptions, TuiSwipeService ``` ### transitioned ```text TuiTransitioned ``` ### truncate ```text TuiTruncate, TuiTruncateService ``` ### validator ```text TuiValidator ``` ### value-changes ```text TuiValueChanges ``` ### vcr ```text TuiVCR ``` ### visual-viewport ```text TuiVisualViewport ``` ### with-styles ```text TuiWithStyles ``` ### zoom ```text TuiZoom, TuiZoomEvent, TuiZoomOptions, TuiZoomService ``` **Pipes:** ### filter ```text TuiFilterPipe ``` ### mapper ```text TuiMapperPipe ``` ### obfuscate ```text TuiObfuscateOptions, TuiObfuscatePipe ``` **Types:** ### root ```text TuiBooleanHandler, TuiContext, TuiHandler, TuiIdentityMatcher, TuiMapper TuiMatcher, TuiNumberHandler, TuiRounding, TuiStringHandler, TuiStringMatcher ``` **Utilitys:** ### focus ```text TuiGetClosestFocusableOptions ``` **Classs:** ### root ```text TuiNonNullableValueTransformer, TuiValidationError ``` --- ## @taiga-ui/experimental **Components:** ### popout ```text TuiPopout, TuiPopoutComponent, TuiPopoutOptions, TuiPopoutService ``` ### search-results ```text TuiSearchHistory, TuiSearchHotkey, TuiSearchResults, TuiSearchResultsComponent ``` --- ## @taiga-ui/layout **Components:** ### app-bar ```text TuiAppBar, TuiAppBarBack, TuiAppBarComponent, TuiAppBarDirective, TuiAppBarSizeDirective ``` ### block-details ```text TuiBlockDetails ``` ### block-status ```text TuiBlockStatus, TuiBlockStatusComponent, TuiBlockStatusDirective ``` ### card ```text TuiCard, TuiCardCollapsed, TuiCardLarge, TuiCardMedium, TuiCardRow ``` ### dynamic-header ```text TuiDynamicHeader, TuiDynamicHeaderAnchorDirective, TuiDynamicHeaderComponent, TuiDynamicHeaderContainerDirective ``` ### elastic-container ```text TuiElasticContainer, TuiElasticContainerDirective ``` ### floating-container ```text TuiFloatingContainer ``` ### form ```text TuiForm, TuiFormOptions ``` ### header ```text TuiHeader ``` ### input-search ```text TuiInputSearch ``` ### item-group ```text TuiItemGroup ``` ### list ```text TuiList, TuiListOptions ``` ### navigation ```text TuiAsideComponent, TuiAsideGroupComponent, TuiAsideItemDirective, TuiDrawerDirective, TuiHeaderComponent, TuiHintAsideDirective, TuiLogoComponent, TuiMainComponent, TuiNavComponent, TuiNavigation TuiSubheaderCompactComponent, TuiSubheaderComponent ``` ### pdf-viewer ```text TuiPdfViewer ``` ### search ```text TuiSearch, TuiSearchComponent, TuiSearchFilterComponent, TuiSearchFiltersComponent ``` ### slides ```text TuiSlides ``` ### surface ```text TuiSurface ``` **Tokens:** ### root ```text TuiCommonIcons, TuiLayoutIcons ``` --- ## @taiga-ui/addon-commerce **Components:** ### input-card ```text TuiInputCVCDirective, TuiInputCard, TuiInputCardComponent, TuiInputExpireDirective ``` ### input-card-group ```text TuiCard, TuiCardGroupedTexts, TuiCardInputs, TuiInputCardGroup, TuiInputCardGroupDirective, TuiInputCardGroupOptions ``` ### thumbnail-card ```text TuiThumbnailCard, TuiThumbnailCardOptions ``` **Pipes:** ### amount ```text TuiAmountOptions, TuiAmountPipe, TuiAmountSign, TuiAmountSignSymbol ``` ### currency ```text TuiCurrencyPipe ``` ### decimal ```text TuiDecimalPipe ``` ### format-card ```text TuiFormatCardPipe ``` **Types:** ### root ```text TuiCurrency, TuiCurrencyAutocompletion, TuiCurrencyCode, TuiCurrencyVariants, TuiPaymentSystem ``` --- ## @taiga-ui/addon-mobile **Components:** ### bottom-sheet ```text TuiBottomSheet ``` ### mobile-calendar ```text TuiMobileCalendar, TuiMobileCalendarStrategy ``` ### mobile-calendar-dropdown ```text TuiMobileCalendarData, TuiMobileCalendarDropdown, TuiMobileCalendarDropdownComponent ``` ### mobile-calendar-sheet ```text TuiMobileCalendarSheet ``` ### pull-to-refresh ```text TuiMobileLoaderAndroid, TuiMobileLoaderIOS, TuiPullToRefresh, TuiPullToRefreshService ``` ### sheet-dialog ```text TuiSheetDialog, TuiSheetDialogComponent, TuiSheetDialogOptions, TuiSheetDialogService ``` ### swipe-actions ```text TuiSwipeActions, TuiSwipeActionsAutoClose, TuiSwipeActionsOnboarding ``` ### tab-bar ```text TuiTabBar, TuiTabBarComponent, TuiTabBarItem, TuiTabBarItemDirective ``` **Directives:** ### dropdown-mobile ```text TuiDropdownMobile, TuiDropdownMobileComponent ``` ### dropdown-sheet ```text TuiDropdownSheet, TuiDropdownSheetComponent ``` ### elastic-sticky ```text TuiElasticSticky, TuiElasticStickyService ``` ### responsive-dialog ```text TuiResponsiveDialog, TuiResponsiveDialogOptions, TuiResponsiveDialogService ``` ### ripple ```text TuiRipple ``` ### touchable ```text TuiTouchable ``` **Services:** ### root ```text TuiKeyboardService, TuiThemeColorService ``` --- ## @taiga-ui/addon-table **Components:** ### reorder ```text TuiReorder, TuiReorderOptions ``` ### table ```text TuiSortChange, TuiSortDirection, TuiSortDirection, TuiSorterPipe, TuiStuck, TuiTable, TuiTableCaption, TuiTableCell, TuiTableDirective, TuiTableExpand TuiTableHead, TuiTableOptions, TuiTableResized, TuiTableSortBy, TuiTableSortChange, TuiTableSortKeyException, TuiTableSortPipe, TuiTableSortable, TuiTableTbody, TuiTableTd TuiTableTh, TuiTableThGroup, TuiTableThead, TuiTableTr ``` ### table-pagination ```text TuiTablePagination, TuiTablePaginationEvent, TuiTablePaginationOptions ``` **Directives:** ### table-control ```text TuiCheckboxRowDirective, TuiCheckboxTableDirective, TuiTableControl, TuiTableControlDirective ``` ### table-filters ```text TuiGenericFilter, TuiTableFilter, TuiTableFilterDirective, TuiTableFilters, TuiTableFiltersDirective, TuiTableFiltersPipe ``` **Types:** ### root ```text TuiComparator ``` --- ## @taiga-ui/addon-charts **Components:** ### arc-chart ```text TuiArcChart ``` ### axes ```text TuiAxes ``` ### bar ```text TuiBar ``` ### bar-chart ```text TuiBarChart ``` ### bar-set ```text TuiBarSet ``` ### chart-hint ```text TuiChartHint ``` ### legend-item ```text TuiLegendItem ``` ### line-chart ```text TuiLineChart, TuiLineChartHint, TuiLineChartOptions ``` ### line-days-chart ```text TuiLineDaysChart, TuiLineDaysChartHint ``` ### pie-chart ```text TuiPieChart, TuiPieChartDirective ``` ### ring-chart ```text TuiRingChart ``` **Types:** ### root ```text TuiLineChartHintContext, TuiLineHandler, TuiLineType ``` --- ## Angular Common Imports **Structural Directives (consider using Angular 17+ control flow instead):** ```typescript import {NgIf, NgFor, NgSwitch} from '@angular/common'; // Modern alternative: @if, @for, @switch ``` **Forms:** ```typescript import {FormsModule, ReactiveFormsModule} from '@angular/forms'; ``` **Other Common:** ```typescript import {CommonModule} from '@angular/common'; import {AsyncPipe, DatePipe, DecimalPipe} from '@angular/common'; ``` --- # Code Generation Checklist > **Before returning ANY generated Angular component code, verify ALL items below.** ## Imports - All `Tui*` symbols imported from the correct package (see **Import Map** section above) - `FormsModule` imported if template uses `[(ngModel)]` - `ReactiveFormsModule` imported if using `FormControl` / `FormGroup` - All used components, directives, and pipes listed in `@Component.imports` array ## Template Rules - No arrow functions in template expressions — move logic to component class (getters / methods) - No complex expressions in templates — keep templates simple, delegate to TypeScript - Null safety: use `?.` optional chaining or `@if` / `*ngIf` guards before accessing nullable values ## Event Handlers - Event handler parameters use the correct Taiga UI type (e.g. `TuiDay`, `TuiDayRange`), not DOM `Event` - Check the component's **API - Outputs** section for the exact emitted type ## CDK Types - Date/time values use Taiga UI CDK types (`TuiDay`, `TuiMonth`, `TuiTime`, etc.), not native `Date` or plain numbers - CDK types imported from `@taiga-ui/cdk` (see **CDK Types Reference** section) ## Component Setup - `changeDetection: ChangeDetectionStrategy.OnPush` is set - Do NOT copy `@demo/emulate/*` imports from demo code — use standard Angular APIs instead - Verify component properties exist in the API documentation before using them --- # Common Mistakes > **These are the most frequent errors when using Taiga UI. Avoid them.** ## 1. Wrong Import Package The #1 cause of compilation errors. Always check the **Import Map** section to find the correct package for each symbol. ## 2. Arrow Functions in Templates Angular templates do not support arrow functions in interpolation or property binding expressions. Move logic to the component class as getters or methods. **Wrong:** `{{ items.map(x => x.name).join(', ') }}` **Right:** Create a getter `get itemNames(): string` in the component class and use `{{ itemNames }}` in the template. ## 3. Missing FormsModule If using `[(ngModel)]`, you must add `FormsModule` to the component's `imports` array. For reactive forms (`formControl`, `formGroup`), add `ReactiveFormsModule`. ## 4. Null Safety Calling methods on potentially null values causes runtime errors. Use optional chaining (`?.`) in templates or guard with `@if` / `*ngIf`. ## 5. Wrong Event Parameter Type Output events from Taiga UI components emit specific types (e.g. `TuiDay`, `TuiDayRange`, `TuiMonth`), not DOM `Event`. Check the component's **API - Outputs** section. ## 6. Using Plain Numbers for Date/Time Taiga UI date components work with `TuiDay`, `TuiMonth`, `TuiYear`, `TuiTime` — not plain numbers or native `Date`. See the **CDK Types Reference** section. ## 7. Copying Demo-Only Imports Demo pages use internal imports like `@demo/emulate/change-detection`. In real code, use `ChangeDetectionStrategy.OnPush` from `@angular/core` directly. ## 8. Missing Structural Directive Imports When using structural directives (e.g. `*tuiDropdown`, `*tuiItem`), the corresponding directive class must be in the component's `imports` array. Check the **Import Map** for the correct package. --- # Getting Started ## addons.md ```bash npm i @taiga-ui/addon-charts // Components for various charts, graphs and visualizations npm i @taiga-ui/addon-commerce // Money-related extension with currencies, credit card inputs and validators npm i @taiga-ui/addon-mobile // Components and tools specific to mobile version of the app npm i @taiga-ui/addon-table // Interactive table component and related utilities npm i @taiga-ui/addon-doc // Taiga UI based library for developing documentation portals for Angular libraries npm i @taiga-ui/layout // Layout components ``` --- ## angular-json-styles.md ```json { "projects": { "my-project": { "architect": { "build": { "options": { "styles": [ "@taiga-ui/styles/taiga-ui-theme.less", "@taiga-ui/styles/taiga-ui-fonts.less", "@taiga-ui/addon-mobile/styles/taiga-ui-mobile.less" // optional ] } } } } } } ``` --- ## app-standalone.md ```ts import {TuiRoot} from '@taiga-ui/core'; // .. @Component({ selector: 'app-root', imports: [ TuiRoot, // ... ], templateUrl: './app.component.html', }) export class App {} ``` --- ## app-template.md ```html ``` --- ## assets.md ```json { "projects": { "my-project": { "architect": { "build": { // ... "assets": [ { "glob": "**/*", "input": "node_modules/@taiga-ui/icons/src", "output": "assets/taiga-ui/icons" } ] } } } } } ``` --- ## config.md ```ts export interface TuiOptions { // Omit to use OS theme, this is the default readonly mode?: 'dark' | 'light'; // Scale text with OS font size, enabled by default readonly fontScaling: boolean; // Global window scrollbars, 'custom' by default readonly scrollbars: 'custom' | 'native'; // Opt-in to experimental features as they are introduced, 'stable' by default readonly apis: 'stable' | {all: boolean} | Record; } ``` --- ## index.md ```html ... ... ``` --- ## main-standalone.md ```ts import {provideTaiga} from '@taiga-ui/core'; // ... bootstrapApplication(App, { providers: [ provideTaiga(), //... ], }).catch(console.error); ``` --- ## main.md ```bash npm i @taiga-ui/{cdk,core,kit,icons} ``` --- ## nx-add.md ```bash npm i taiga-ui nx g taiga-ui:ng-add ``` --- ## nx-assets.md ```json { "targets": { "build": { "options": { // ... "assets": [ { "glob": "**/*", "input": "node_modules/@taiga-ui/icons/src", "output": "assets/taiga-ui/icons" } ] } } } } ``` --- ## nx-migrate.md ```bash nx migrate @taiga-ui/cdk nx migrate --run-migrations=migrations.json ``` --- ## ponyfill.md ```bash npm install css-vars-ponyfill ``` --- ## project-json-styles.md ```json { "targets": { "build": { "options": { "styles": [ "@taiga-ui/styles/taiga-ui-theme.less", "@taiga-ui/styles/taiga-ui-fonts.less", "@taiga-ui/addon-mobile/styles/taiga-ui-mobile.less" // optional ] } } } } ``` --- # components/Accordion - **Package**: `KIT` - **Type**: components ### Example ```html Development kit consisting of the low level tools and abstractions used to develop Taiga UI Angular entities Basic elements needed to develop components, directives and more using Taiga UI design system ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [closeOthers] | `boolean` | Other sections are closed when user opens one | | [size] | `TuiSizeS | TuiSizeL` | Size | | [(tuiAccordion)] | `boolean` | Individual item open state | ### Usage Examples #### Example 1 **Template:** ```html @for (item of data | keyvalue; track item) { {{ item.value }} } ``` **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 {TuiAccordion} from '@taiga-ui/kit'; @Component({ imports: [KeyValuePipe, TuiAccordion], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly data = { 'Taiga UI cdk': 'Development kit consisting of the low level tools and abstractions used to develop Taiga UI Angular entities', 'Taiga UI core': 'Basic elements needed to develop components, directives and more using Taiga UI design system', 'Taiga UI kit': 'The main set of components used to build Taiga UI based Angular applications', }; } ``` #### Example 2 **Template:** ```html @for (group of operations | keyvalue: orderBy; track group) { @for (operation of group.value; track operation) {
{{ operation.title }} @if (operation.subtitle) { {{ operation.subtitle }} } @if (operation.sum) { {{ operation.sum | tuiAmount: '$' : 'start' }} {{ operation.time }} } @else { }
}
}
``` **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 {TuiAmountPipe} from '@taiga-ui/addon-commerce'; import {TuiButton, TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAccordion, TuiAvatar} from '@taiga-ui/kit'; interface Operation { title: string; subtitle?: string; sum?: number; time?: string; } @Component({ imports: [ KeyValuePipe, TuiAccordion, TuiAmountPipe, TuiAvatar, TuiButton, TuiCell, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly operations = { Today: [], Yesterday: [ { title: 'Cashback', subtitle: 'Pot of gold', sum: 237, time: '19:32', }, {title: 'Failed to load'}, ], 'January 6, 2021': [ { title: 'Salary', subtitle: 'Account number ••••237', sum: 43256, time: '11:02', }, { title: 'Shaman Hat', subtitle: 'Insurrection Apparel', sum: -99, time: '09:11', }, { title: 'Shaman Makeup', subtitle: 'Insurrection Apparel', sum: -75, time: '09:11', }, ], }; protected getIcon(operation: Operation): string { if (!operation.sum) { return '@tui.triangle-alert'; } return operation.sum > 0 ? '@tui.thumbs-up' : '@tui.thumbs-down'; } protected sum(operations: readonly Operation[]): number { return operations.reduce((acc, {sum}) => acc + (sum || 0), 0); } protected orderBy(): number { return 0; } } ``` **LESS:** ```less .accordion { inline-size: 20rem; border-radius: 0; [tuiAccordion] { border-block-start: 1px solid var(--tui-border-normal); background: transparent !important; mask: none; } tui-expand, [tuiCell] { padding-inline-start: 0; padding-inline-end: 0; box-shadow: none; } } ``` #### Example 3 **Template:** ```html Development kit consisting of the low level tools and abstractions used to develop Taiga UI Angular entities ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAccordion} from '@taiga-ui/kit'; @Component({ imports: [TuiAccordion], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Example 4 **Template:** ```html I'm lazy content I'm eager content ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAccordion} from '@taiga-ui/kit'; @Component({ imports: [TuiAccordion], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Example 5 **Template:** ```html Development kit consisting of the low level tools and abstractions used to develop Taiga UI Angular entities The main set of components used to build Taiga UI based Angular applications Basic elements needed to develop components, directives and more using Taiga UI design system ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAccordion} from '@taiga-ui/kit'; @Component({ imports: [TuiAccordion], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Example 6 **Template:** ```html @for (item of steps | keyvalue: orderBy; track item) { {{ item.value.text }} @for (step of item.value.steps; track step) { } } ``` **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 {tuiArrayToggle} from '@taiga-ui/cdk'; import {TuiCell, TuiCheckbox, TuiTitle} from '@taiga-ui/core'; import {TuiAccordion, TuiAvatar, TuiConnected} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, KeyValuePipe, TuiAccordion, TuiAvatar, TuiCell, TuiCheckbox, TuiConnected, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly steps = { 'First steps': { text: 'Getting to know your workplace', steps: ['Looks around', 'Talk to colleagues', 'Have lunch'], }, 'Work day': { text: 'Start working', steps: ['Open the project', 'Read the documentation', 'Start coding'], }, Mastery: { text: 'Become a pro', steps: ['Write tests', 'Refactor the code', 'Deploy the project'], }, }; protected selected = this.steps['First steps'].steps.concat( this.steps['Work day'].steps[0] || '', ); protected isChecked(steps: readonly string[]): boolean { return steps.every((step) => this.selected.includes(step)); } protected toggle(step: string): void { this.selected = tuiArrayToggle(this.selected, step); } protected orderBy(): number { return 0; } } ``` **LESS:** ```less .accordion { inline-size: 20rem; color: var(--tui-text-secondary); [tuiAccordion] { font: var(--tui-typography-heading-h6); padding: 0; } tui-expand { box-shadow: none; padding: 0; } [tuiCell] { margin-inline-start: -1rem; } } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {type TuiSizeL, type TuiSizeS} from '@taiga-ui/core'; import {TuiAccordion} from '@taiga-ui/kit'; @Component({ imports: [TuiAccordion, TuiDemo], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly sizeVariants: ReadonlyArray = ['s', 'm', 'l']; protected size = this.sizeVariants[2]!; protected closeOthers = true; protected open = false; } ``` --- # components/ActionBar - **Package**: `KIT` - **Type**: components It is an element on the bottom of screen to show actions by multiselect of some items. It works with custom content. ### Usage Examples #### Size M **Template:** ```html

@for (_ of '-'.repeat(5); track $index) { }
Selected: {{ selected }} of {{ items.length }} @if (!isMobile()) { }
@for (_ of '-'.repeat(5); track $index) { } @for (_ of '-'.repeat(5); track $index) { @if ($index > lastIndex) { } } @if (!isMobile()) { } @if (!isMobile()) { } @if (isMobile()) { } @if (isMobile()) { } @if (!isMobile()) { }
``` **TypeScript:** ```ts import {Component, computed, inject} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import { TUI_BREAKPOINT, TuiButton, TuiDataList, TuiDropdown, TuiIcon, TuiLink, TuiPopup, } from '@taiga-ui/core'; import {TuiActionBar, TuiFilter, TuiItemsWithMore} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiActionBar, TuiButton, TuiDataList, TuiDropdown, TuiFilter, TuiIcon, TuiItemsWithMore, TuiLink, TuiPopup, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly breakpoint = inject(TUI_BREAKPOINT); protected items = ['one', 'two', 'three', 'four']; protected control = new FormControl([]); protected expanded = false; protected readonly isMobile = computed(() => this.breakpoint() === 'mobile'); protected get value(): string[] { return this.control.value || []; } protected get open(): boolean { return this.value.length > 0; } protected get selected(): number { return this.value.length; } protected toggleSelect(): void { this.control.setValue(this.selected < this.items.length ? this.items : []); } protected close(): void { this.control.setValue([]); this.expanded = false; } } ``` #### Size S **Template:** ```html Table bar opened ``` **TypeScript:** ```ts import {Component, computed, inject, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TUI_BREAKPOINT, TuiButton, TuiPopup} from '@taiga-ui/core'; import {TuiActionBar} from '@taiga-ui/kit'; @Component({ imports: [TuiActionBar, TuiButton, TuiPopup], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly breakpoint = inject(TUI_BREAKPOINT); protected readonly open = signal(false); protected readonly isMobile = computed(() => this.breakpoint() === 'mobile'); } ``` #### Top position **Template:** ```html Table bar on top opened ``` **TypeScript:** ```ts import {Component, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiPopup} from '@taiga-ui/core'; import {TuiActionBar} from '@taiga-ui/kit'; @Component({ imports: [TuiActionBar, TuiButton, TuiPopup], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly open = signal(false); } ``` **LESS:** ```less tui-action-bar { inset-block-start: ~'max(1rem, env(safe-area-inset-top))'; inset-block-end: unset; } ``` ### 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 = ['Size M', 'Size S', 'Top position']; } ``` ### LESS ```less .label { inline-size: 6.25rem; } ``` --- # components/AppBar - **Package**: `LAYOUT` - **Type**: components Component for the main app header ### Usage Examples #### Mobile — medium size **Template:** ```html

iOS

Taiga UI Taiga UI — Components library Taiga UI

Android

Taiga UI Taiga UI — Components library Taiga UI
``` **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 {TuiProgressBar} from '@taiga-ui/kit'; import {TuiAppBar} from '@taiga-ui/layout'; @Component({ imports: [TuiAppBar, TuiButton, TuiPlatform, TuiProgressBar, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less tui-app-bar { box-shadow: var(--tui-shadow-small); inline-size: 20rem; margin-block-end: 1rem; } ``` #### Desktop — large size **Template:** ```html Taiga UI ``` **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 {TuiProgress} from '@taiga-ui/kit'; import {TuiAppBar} from '@taiga-ui/layout'; @Component({ imports: [TuiAppBar, TuiButton, TuiProgress, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less tui-app-bar { box-shadow: var(--tui-shadow-small); inline-size: 30rem; margin-block-end: 1rem; } ``` #### Variants **Template:** ```html

Customization

Taiga UI

Centered Android

``` **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 {TuiFade, TuiProgress} from '@taiga-ui/kit'; import {TuiAppBar} from '@taiga-ui/layout'; @Component({ imports: [TuiAppBar, TuiButton, TuiFade, TuiPlatform, TuiProgress, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; tui-app-bar { box-shadow: var(--tui-shadow-small); inline-size: 20rem; margin-block-end: 1rem; } .gray { box-shadow: none; background: var(--tui-background-base-alt); button { color: var(--tui-text-primary); } } .black { color: var(--tui-background-base); background: var(--tui-text-primary); button { color: var(--tui-status-warning); } } section tui-app-bar [tuiTitle] { .center-all(); text-align: center; } ``` #### Dialog **Template:** ```html

I'm a stepper

Step {{ step }}

``` **TypeScript:** ```ts import {Component, inject, type TemplateRef} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiDialogService, TuiTitle} from '@taiga-ui/core'; import {TuiProgress} from '@taiga-ui/kit'; import {TuiAppBar, TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [TuiAppBar, TuiButton, TuiCardLarge, TuiHeader, TuiProgress, TuiTitle], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly dialogs = inject(TuiDialogService); protected step = 0; protected open(template: TemplateRef): void { this.step = 0; this.dialogs .open(template, { appearance: 'fullscreen', closable: false, dismissible: false, }) .subscribe(); } } ``` #### Dynamic header **Template:** ```html
Title 1
Subtitle
@for (_ of '-'.repeat(7); track $index) {
Title
Description
}
Title 2
@for (_ of '-'.repeat(15); track $index) {
Title
Description
}
Title 3 Long title
@for (_ of '-'.repeat(15); track $index) {
Title
Description
}
``` **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 {TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiSegmented} from '@taiga-ui/kit'; import {TuiAppBar, TuiDynamicHeader, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ TuiAppBar, TuiAvatar, TuiCell, TuiDynamicHeader, TuiHeader, TuiPlatform, TuiSegmented, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected activeItemIndex = 0; } ``` **LESS:** ```less .container { max-block-size: 30rem; inline-size: 20rem; overflow: scroll; overscroll-behavior: none; } tui-app-bar { position: sticky; z-index: 1; inset-block-start: -1px; background: var(--tui-background-base); } tui-segmented { inline-size: 6rem; margin-block-end: 1rem; } .content > [tuiHeader] { padding: 0 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 = [ 'Mobile — medium size', 'Desktop — large size', 'Variants', 'Dialog', 'Dynamic header', ]; } ``` --- # components/ArcChart - **Package**: `ADDON-CHARTS` - **Type**: components ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [max] | `number` | Maximum value | | [maxLabel] | `string` | Label for maximum value | | [minLabel] | `string` | Label for minimum value | | [size] | `TuiSizeXL` | Size | | [value] | `readonly number[]` | Value | | [(activeItemIndex)] | `number` | Index of selected arc | ### Usage Examples #### Sizes **Template:** ```html
Total value Total value
Label
{{ 123456 | tuiAmount: 'RUB' }}
Not bad!
``` **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 {TuiArcChart} from '@taiga-ui/addon-charts'; import {TuiAmountPipe} from '@taiga-ui/addon-commerce'; import {TuiNumberFormat, TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiAmountPipe, TuiArcChart, TuiInputNumber, TuiNumberFormat, TuiTextfield, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [40, 30, 20, 10]; protected activeItemIndex = Number.NaN; public onTextfieldChange(value: number | null): void { this.activeItemIndex = value ?? Number.NaN; } } ``` **LESS:** ```less .wrapper { display: flex; align-items: center; margin-block-start: 1rem; --tui-chart-categorical-00: var(--tui-chart-categorical-12); --tui-chart-categorical-01: var(--tui-chart-categorical-01); --tui-chart-categorical-02: var(--tui-chart-categorical-03); --tui-chart-categorical-03: var(--tui-chart-categorical-09); } .index-controller { max-inline-size: 20rem; } ``` #### Stacked **Template:** ```html
+20%
For filling in last name
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiArcChart} from '@taiga-ui/addon-charts'; import {tuiSum} from '@taiga-ui/cdk'; @Component({ imports: [TuiArcChart], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [13769, 12367, 10172, 3018, 2592]; protected readonly sum = tuiSum(...this.value); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .wrapper { position: relative; --tui-chart-0: var(--tui-chart-categorical-03); } .stacked { .fullsize(); --tui-background-neutral-1: transparent; --tui-chart-0: var(--tui-chart-categorical-04); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiArcChart} from '@taiga-ui/addon-charts'; import {type TuiSizeXL} from '@taiga-ui/core'; @Component({ imports: [TuiArcChart, TuiDemo], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly examples = ['Sizes', 'Stacked']; protected readonly valueVariants = [ [42], [40, 30, 20, 10], [13769, 10172, 3018, 2592], ]; protected value = this.valueVariants[0]!; protected readonly maxVariants = [100, 10000, 50000]; protected max = this.maxVariants[0]!; protected readonly sizeVariants: readonly TuiSizeXL[] = ['m', 'l', 'xl']; protected size = this.sizeVariants[0]!; protected minLabel = '0%'; protected maxLabel = '100%'; protected activeItemIndex = Number.NaN; } ``` ### LESS ```less .chart { margin: 0 auto; } ``` --- # components/Avatar - **Package**: `KIT` - **Type**: components ### Example ```html
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiAvatar] | `string` | | | [badge] | `string` | Color of the dot badge indicator | | [round] | `boolean` | Use round shape | | [size] | `TuiSizeS | TuiSizeL` | Size | ### Usage Examples #### Content types **Template:** ```html

Icons and initials

Image, video and content

Alex Inkin
99+

Fallback to initials or icon

Alex Inkin
Alex Inkin
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiCell} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiCell], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Colors **Template:** ```html
Fading
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAutoColorPipe, TuiAvatar, TuiFade} from '@taiga-ui/kit'; @Component({ imports: [TuiAutoColorPipe, TuiAvatar, TuiFade], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } .text { background: var(--tui-background-accent-opposite-pressed); color: var(--tui-background-base); } ``` #### Sizes **Template:** ```html @for (size of sizes; track size) {
{{ size | uppercase }}
} ``` **TypeScript:** ```ts import {UpperCasePipe} from '@angular/common'; import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAvatar} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, UpperCasePipe], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly sizes = ['xxl', 'xl', 'l', 'm', 's', 'xs'] as const; protected readonly names = ['Jason Statham', 'Silvester Stallone', 'Jackie Chan']; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } section { display: flex; gap: 0.5rem; } ``` #### Stacking **Template:** ```html @for (size of sizes; track size; let odd = $odd) { @for (name of names; track name) {
}
99+
} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiSizeXS, type TuiSizeXXL} from '@taiga-ui/core'; import { TuiAutoColorPipe, TuiAvatar, TuiAvatarStack, TuiInitialsPipe, } from '@taiga-ui/kit'; @Component({ imports: [TuiAutoColorPipe, TuiAvatar, TuiAvatarStack, TuiInitialsPipe], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly names = ['Jason Statham', 'Silvester Stallone', 'Jackie Chan']; protected readonly sizes: ReadonlyArray = [ 'xxl', 'xl', 'l', 'm', 's', 'xs', ]; } ``` #### Options with DI **Template:** ```html
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAvatar, tuiAvatarOptionsProvider} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar], templateUrl: './index.html', encapsulation, changeDetection, providers: [ tuiAvatarOptionsProvider({size: 'l', appearance: 'secondary', round: false}), ], }) export default class Example {} ``` #### Labeled **Template:** ```html
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAvatar, TuiAvatarLabeled} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiAvatarLabeled], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } [tuiAvatar] { border: 1px solid var(--tui-border-normal); } ``` #### Outline **Template:** ```html
Alex Inkin
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAvatar, TuiAvatarOutline} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiAvatarOutline], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } ``` ### 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 {type TuiSizeXS, type TuiSizeXXL} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiDemo, TuiDocAppearance], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly examples = [ 'Content types', 'Colors', 'Sizes', 'Stacking', 'Options with DI', 'Labeled', 'Outline', ]; protected readonly sizes: ReadonlyArray = [ 'xs', 's', 'm', 'l', 'xl', 'xxl', ]; protected size = this.sizes[3]!; protected readonly srcVariants: readonly string[] = [ 'MW', '@tui.star', 'https://avatars.githubusercontent.com/u/11832552', 'https://taiga-ui.dev/assets/images/test-not-found.png', ]; protected src = this.srcVariants[0]!; protected readonly badgeVariants: readonly string[] = [ '', 'var(--tui-background-accent-1)', 'var(--tui-text-positive)', '#C86DD7', ]; protected badge = this.badgeVariants[0]!; protected round = true; } ``` --- # components/Axes - **Package**: `ADDON-CHARTS` - **Type**: components Just axes for charts ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [centeredXLabels] | `boolean` | Center X labels | | [axisXLabels] | `ReadonlyArray` | — no stroke | | [axisYInset] | `boolean` | Inset of labels on axis Y | | [axisYLabels] | `readonly string[]` | Labels for Y | | [axisYName] | `string` | Name of Y axis | | [axisYSecondaryInset] | `boolean` | Inset labels for Y | | [axisYSecondaryLabels] | `readonly string[]` | Secondary Y axis labels | | [axisYSecondaryName] | `string` | Secondary Y axis name | | [horizontalLines] | `number` | Horizontal lines number | | [horizontalLinesHandler] | `TuiLineHandler` | Horizontal lines type handler | | [verticalLines] | `number` | Number of vertical lines | | [verticalLinesHandle] | `TuiLineHandler` | Vertical lines type handler | ### Usage Examples #### Cool one **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAxes, type TuiLineHandler} from '@taiga-ui/addon-charts'; @Component({ imports: [TuiAxes], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly axisXLabels = ['Jan 2019', 'Feb', 'Mar', '']; protected readonly axisYLabels = ['', '25%', '50%', '75%', '100%']; protected readonly axisYSecondaryLabels = ['80 k', '100 k', '120 k']; protected readonly verticalLinesHandler: TuiLineHandler = (index, total) => (index && (index === total - 1 ? 'none' : 'dashed')) || 'solid'; } ``` **LESS:** ```less .axes { block-size: 18.75rem; inline-size: 37.5rem; } ``` #### With bars **Template:** ```html @for (item of value; track item) {

{{ getSetName($index) }} {{ (item[setIndex] || 0) * 1000 | tuiAmount: 'RUB' }}

}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import { TUI_ALWAYS_DASHED, TUI_ALWAYS_NONE, TuiAxes, TuiBarChart, TuiChartHint, } from '@taiga-ui/addon-charts'; import {TuiAmountPipe} from '@taiga-ui/addon-commerce'; import {tuiCeil} from '@taiga-ui/cdk'; @Component({ imports: [TuiAmountPipe, TuiAxes, TuiBarChart, TuiChartHint], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly setNames = ['cdk', 'core', 'kit', 'charts']; protected readonly value: ReadonlyArray<[number, number, number, number]> = [ [10, 20, 3, 7], [15, 18, 24, 1], [34, 23, 12, 9], [30, 14, 18, 14], ]; protected readonly maxValue = 40; protected readonly axisYSecondaryLabels = [ '', `${getMax(this.value) / 2} k`, `${getMax(this.value)} k`, ]; protected readonly axisXLabels = ['Q1', 'Q2', 'Q3', 'Q4']; protected readonly horizontalLinesHandler = TUI_ALWAYS_DASHED; protected readonly verticalLinesHandler = TUI_ALWAYS_NONE; protected getSetName(index: number): string { return this.setNames[index] ?? ''; } } function getMax(value: ReadonlyArray<[number, number, number, number]>): number { return tuiCeil( value.reduce((max, value) => Math.max(...value, max), 0), -1, ); } ``` **LESS:** ```less :host, .hint { --tui-chart-categorical-00: #c779d0; --tui-chart-categorical-01: #feac5e; --tui-chart-categorical-02: #ff5f6d; --tui-chart-categorical-03: #4bc0c8; } .axes { block-size: 18.75rem; inline-size: 37.5rem; } .chart { block-size: 100%; } .wrapper { position: relative; display: flex; flex: 1; align-items: flex-end; justify-content: center; block-size: 100%; margin-block-end: -0.0625rem; cursor: pointer; &:hover { background-color: rgba(0, 0, 0, 0.05); } } .hint { display: flex; align-items: center; } .dot { border-radius: 100%; inline-size: 0.75rem; block-size: 0.75rem; margin-inline-end: 0.5rem; } .name { margin-inline-end: 0.5rem; } ``` #### With horizontal bars **Template:** ```html
@for (bar of value; track bar) { }
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAxes, TuiBar} from '@taiga-ui/addon-charts'; @Component({ imports: [TuiAxes, TuiBar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly axisXLabels = ['0', '25', '50', '75', '100']; protected readonly value = [50, 24, 36, 95]; protected readonly largest = 100; protected getHeight(value: number): number { return Math.abs((value * 100) / this.largest); } } ``` **LESS:** ```less .axes { block-size: 18.75rem; inline-size: 37.5rem; } .t-horizontal-bars { display: flex; justify-content: space-between; align-items: flex-end; inline-size: 16.75rem; block-size: 37.5rem; transform-origin: bottom left; transform: rotate(90deg) translate(-16.75rem, 0); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import { TUI_ALWAYS_DASHED, TUI_ALWAYS_SOLID, TuiAxes, type TuiLineHandler, type TuiLineType, } from '@taiga-ui/addon-charts'; @Component({ imports: [TuiAxes, TuiDemo], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly examples = ['Cool one', 'With bars', 'With horizontal bars']; protected readonly lineVariants: readonly TuiLineType[] = [ 'solid', 'dashed', 'none', 'hidden', ]; protected readonly labelsXVariants: ReadonlyArray> = [ [], ['', '25%', '50%', '100%', ''], ['One', 'Two', 'Three', ''], ['One', null, '', 'Two and a half', 'Three', null, ''], ]; protected readonly labelsYVariants: ReadonlyArray = [ [], ['', '25%', '50%', '100%'], ['One', 'Two', 'Three'], ['One', '', 'Two and a half', 'Three'], ]; protected readonly handlerVariants: readonly TuiLineHandler[] = [ TUI_ALWAYS_SOLID, TUI_ALWAYS_DASHED, (index) => (index % 2 ? 'dashed' : 'solid'), ]; protected centeredXLabels = false; protected axisXLabels = this.labelsXVariants[0]!; protected axisYInset = false; protected axisYLabels = this.labelsYVariants[0]!; protected axisYName = ''; protected axisYSecondaryInset = false; protected axisYSecondaryLabels = this.labelsYVariants[0]!; protected axisYSecondaryName = ''; protected horizontalLines = 1; protected horizontalLinesHandler = this.handlerVariants[0]!; protected verticalLines = 1; protected verticalLinesHandler = this.handlerVariants[1]!; } ``` ### LESS ```less .axes { block-size: 12.5rem; } ``` --- # components/Badge - **Package**: `KIT` - **Type**: components Component for displaying text, pictures and icons. ### Example ```html

@if (contentType === 'with icon') {

Taiga UI
} @if (contentType === 'text') { Taiga UI } @if (contentType === 'image') { market }

``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [size] | `TuiSizeL` | Size | | [appearance] | `TuiStatus` | Appearance | ### Usage Examples #### Basic **Template:** ```html
Default
Primary
Accent
Success
Error
Warning
Warning
Neutral
Info

Custom status

Custom

Use CSS for support colors

@for (_ of '-'.repeat(20); track $index) {
Taiga
}

Sizes

Success
Success
Success
Success
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBadge, TuiStatus} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge, TuiStatus], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less [tuiBadge] { margin: 0.5rem; } ``` #### Sizes **Template:** ```html
x-large
large
medium
small
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBadge} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Content type (mobile platform) **Template:** ```html

Value with icon

x-large
large
medium
small

Icon only

Image

market market market market
``` **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 {TuiBadge} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge, TuiPlatform], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: block; min-inline-size: 18rem; } ``` #### Long value **Template:** ```html
Very long value in badge
Very long value in badge
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBadge, TuiFade} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge, TuiFade], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .badge { max-inline-size: 10rem; } .t-ellipsis { overflow: hidden; text-overflow: ellipsis; } ``` #### Customization **Template:** ```html
10 000 000 $
Taiga
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBadge} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less @import '@taiga-ui/styles/utils.less'; .custom-1 { .gradient(#0094cf, #24c0ff); box-shadow: var(--tui-shadow-small); } .custom-2 { .gradient(#c86dd7, #3023ae); } ``` #### Options with DI **Template:** ```html
10 000 000 $
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBadge, tuiBadgeOptionsProvider} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiBadgeOptionsProvider({appearance: 'primary'})], }) export default class Example {} ``` ### 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 TuiSizeS, type TuiSizeXL} from '@taiga-ui/core'; import {TuiBadge, TuiFade, TuiRadioList} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiBadge, TuiDemo, TuiFade, TuiRadioList], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly appearanceVariants = [ '', 'accent', 'primary', 'custom', 'positive', 'negative', 'warning', 'info', 'neutral', ]; protected appearance = this.appearanceVariants[0]!; protected readonly sizeVariants: ReadonlyArray = [ 's', 'm', 'l', 'xl', ]; protected size = this.sizeVariants[1]!; protected contentTypeVariants = ['text', 'with icon', 'image']; protected contentType = this.contentTypeVariants[0]!; protected readonly examples = [ 'Basic', 'Sizes', 'Content type (mobile platform)', 'Long value', 'Customization', 'Options with DI', ]; } ``` --- # components/BadgeNotification - **Package**: `KIT` - **Type**: components Simple non-interactive badge. Used in headers, cells, cards, avatars to indicate notifications, such as new messages ### Example ```html 11 ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [size] | `TuiSizeL` | Size | ### Usage Examples #### Basic **Template:** ```html

Desktop

9 9 9 9

Android

9 9 9 9

IOS

9 9 9 9

``` **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 {TuiBadgeNotification} from '@taiga-ui/kit'; @Component({ imports: [TuiBadgeNotification, TuiPlatform], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less tui-badge-notification { margin: 0.2rem; } ``` #### Custom color **Template:** ```html 10 11 12 ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBadgeNotification} from '@taiga-ui/kit'; @Component({ imports: [TuiBadgeNotification], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less @import '@taiga-ui/styles/utils.less'; tui-badge-notification { margin: 0.2rem; .gradient(#c86dd7, #3023ae); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiLabel, type TuiSizeL, type TuiSizeXS} from '@taiga-ui/core'; import {TuiBadgeNotification} from '@taiga-ui/kit'; @Component({ imports: [TuiBadgeNotification, TuiDemo, TuiLabel], templateUrl: './index.html', changeDetection, }) export default class Example { protected readonly sizeVariants: ReadonlyArray = [ 'l', 'm', 's', 'xs', ]; protected size = this.sizeVariants[0]!; protected readonly examples = ['Basic', 'Custom color']; } ``` --- # components/BadgedContent - **Package**: `KIT` - **Type**: components BadgedContent is a wrapper for other components to add badges and notifications to them. ### Example ```html 1
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | style.--t-radius | `string` | Border radius | ### Usage Examples #### Basic **Template:** ```html 99
120
99
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAvatar, TuiBadge, TuiBadgedContent, TuiBadgeNotification} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiBadge, TuiBadgedContent, TuiBadgeNotification], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } ``` #### Rounded content **Template:** ```html 8
Taiga
99
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAvatar, TuiBadge, TuiBadgedContent, TuiBadgeNotification} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiBadge, TuiBadgedContent, TuiBadgeNotification], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } ``` #### With different components **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiInput} from '@taiga-ui/core'; import {TuiBadge, TuiBadgedContent, TuiBadgeNotification} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge, TuiBadgedContent, TuiBadgeNotification, TuiButton, TuiInput], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } tui-textfield { min-inline-size: 10rem; } ``` #### With image **Template:** ```html icon
icon
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAvatar, TuiBadgedContent} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiBadgedContent], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiAvatar, TuiBadgedContent, TuiBadgeNotification} from '@taiga-ui/kit'; import {TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [TuiAvatar, TuiBadgedContent, TuiBadgeNotification, TuiDemo, TuiHeader], templateUrl: './index.html', changeDetection, }) export default class Example { protected radiusVariants = ['0.75rem', '50%']; protected radius = this.radiusVariants[0]!; protected readonly examples = [ 'Basic', 'Rounded content', 'With different components', 'With image', ]; } ``` --- # components/Bar - **Package**: `ADDON-CHARTS` - **Type**: components A bar for bar chart ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [size] | `TuiSizeS | TuiSizeL` | Size | | [value] | `readonly number[]` | An array of segments | ### 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 {TuiBar} from '@taiga-ui/addon-charts'; @Component({ imports: [TuiBar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .bar { block-size: 6.25rem; background: var(--tui-background-accent-1); } ``` #### Segments **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBar} from '@taiga-ui/addon-charts'; @Component({ imports: [TuiBar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [30, 15, 10]; } ``` **LESS:** ```less .bar { block-size: 6.25rem; --tui-chart-categorical-00: #ffd700; --tui-chart-categorical-01: #87ceeb; --tui-chart-categorical-02: #ffc0cb; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiBar} from '@taiga-ui/addon-charts'; import {type TuiSizeL, type TuiSizeS} from '@taiga-ui/core'; @Component({ imports: [TuiBar, TuiDemo], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly examples = ['Basic', 'Segments']; protected readonly sizeVariants: ReadonlyArray = ['s', 'm', 'l']; protected size = this.sizeVariants[1]!; protected readonly valueVariants = [ [30, 20, 10], [237, 50, 10, 5, 1], ]; protected value = this.valueVariants[0]!; } ``` ### LESS ```less .bar { block-size: 6.25rem; } ``` --- # components/BarChart - **Package**: `ADDON-CHARTS` - **Type**: components Bar chart that can be used as a content to axes . ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [collapsed] | `boolean` | Shows data set in a single bar | | [max] | `number` | Sets chart max manually | | [size] | `TuiSizeS | TuiSizeL | null` | for autosize) | | [value] | `ReadonlyArray` | Array of segments | ### API - Outputs | Event | Type | Description | |-------|------|-------------| | (tapColumn) | `number` | Bar column click/enter event | ### Usage Examples #### Example 1 **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAxes, TuiBarChart} from '@taiga-ui/addon-charts'; import {tuiCeil} from '@taiga-ui/cdk'; @Component({ imports: [TuiAxes, TuiBarChart], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [ [3660, 8281, 1069, 9034, 5797, 6918, 8495, 3234, 6204, 1392, 2088, 8637, 8779], [3952, 3671, 3781, 5323, 3537, 4107, 2962, 3320, 8632, 4755, 9130, 1195, 3574], ]; protected readonly labelsX = ['Jan 2019', 'Feb', 'Mar', '']; protected readonly labelsY = ['0', '10 000']; protected getHeight(max: number): number { return (max / tuiCeil(max, -3)) * 100; } } ``` **LESS:** ```less .axes { block-size: 18.75rem; inline-size: 37.5rem; } ``` #### 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 {TuiAxes, TuiBarChart, TuiChartHint} from '@taiga-ui/addon-charts'; import {type TuiContext} from '@taiga-ui/cdk'; import {tuiFormatNumber, TuiTextfield} from '@taiga-ui/core'; import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiAxes, TuiBarChart, TuiChartHint, TuiChevron, TuiDataListWrapper, TuiSelect, TuiTextfield, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [ [1000, 8000, 4000, 3000, 4000], [6000, 2000, 4500, 7000, 5000], ]; protected readonly labelsX = ['Jan 2021', 'Feb', 'Mar', '']; protected readonly labelsY = ['0', '10 000']; protected readonly appearances = ['floating', 'accent']; protected appearance = this.appearances[0]!; protected readonly hint = ({$implicit}: TuiContext): string => this.value .reduce( (result, set) => `${result}$${tuiFormatNumber(set[$implicit] ?? 0)}\n`, '', ) .trim(); } ``` **LESS:** ```less .axes { block-size: 18.75rem; inline-size: 37.5rem; &:first-child { --tui-chart-categorical-00: #ffd700; --tui-chart-categorical-01: #800080; } &:last-child { --tui-chart-categorical-00: #87ceeb; --tui-chart-categorical-01: #ee82ee; } } .flex { display: flex; min-inline-size: 31.25rem; } .select { max-inline-size: 20rem; } ``` ### 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 {TuiBarChart} from '@taiga-ui/addon-charts'; import {type TuiSizeL, type TuiSizeS} from '@taiga-ui/core'; @Component({ imports: [TuiBarChart, TuiDemo], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { public readonly examples = ['With axes', 'Same values with collapsed mode']; protected collapsed = false; protected readonly sizeVariants: ReadonlyArray = ['s', 'm', 'l']; protected size: TuiSizeL | TuiSizeS | null = null; protected max = 0; protected readonly valueVariants = [ [ [30000, 20500, 12345], [12422, 16124, 22424], ], [ [30, 65, 30, 80, 54], [98, 48, 33, 25, 11], [55, 10, 27, 36, 73], ], ]; protected value = this.valueVariants[0]!; protected readonly routes = DemoRoute; } ``` ### LESS ```less .chart { block-size: 12.5rem; } ``` --- # components/BarSet - **Package**: `ADDON-CHARTS` - **Type**: components A group of bars for bar chart ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [collapsed] | `boolean` | Shows data set in a single bar | | [size] | `TuiSizeS | TuiSizeL | null` | for autosize) | | [value] | `readonly number[]` | Array of segments | ### Usage Examples #### Dynamic size **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBarSet} from '@taiga-ui/addon-charts'; @Component({ imports: [TuiBarSet], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [30, 15, 10]; } ``` **LESS:** ```less .bars { block-size: 6.25rem; inline-size: 10rem; box-shadow: 0 1px var(--tui-border-normal); } ``` #### Fixed size **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBarSet} from '@taiga-ui/addon-charts'; @Component({ imports: [TuiBarSet], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [30, 15, 10]; } ``` **LESS:** ```less .bars { block-size: 6.25rem; inline-size: 10rem; box-shadow: 0 1px var(--tui-border-normal); } ``` #### With negative values **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBarSet} from '@taiga-ui/addon-charts'; @Component({ imports: [TuiBarSet], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [30, -15]; } ``` **LESS:** ```less .bars { block-size: 6.25rem; inline-size: 3.75rem; margin-block-end: 3.125rem; box-shadow: 0 1px var(--tui-border-normal); --tui-chart-categorical-00: var(--tui-background-accent-1); --tui-chart-categorical-01: var(--tui-background-accent-1); } ``` #### Horizontal **Template:** ```html
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBarSet} from '@taiga-ui/addon-charts'; @Component({ imports: [TuiBarSet], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [30, 45, 12, 6, 20]; } ``` **LESS:** ```less .wrapper { block-size: 6.25rem; } .bars { block-size: 12.5rem; inline-size: 6.25rem; margin-block-end: 3.125rem; box-shadow: 0 1px var(--tui-border-normal); transform-origin: bottom left; transform: rotate(90deg) translate(-12.5rem, 0); --tui-chart-categorical-00: linear-gradient(#ffc500, #c21500); --tui-chart-categorical-01: linear-gradient(#26a0da, #314755); --tui-chart-categorical-02: linear-gradient(#f64f59, #c471ed, #12c2e9); --tui-chart-categorical-03: linear-gradient(#c94b4b, #4b134f); --tui-chart-categorical-04: linear-gradient(#114357, #f29492); } ``` #### With value label **Template:** ```html {{ sum | tuiFormatNumber }} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBarSet} from '@taiga-ui/addon-charts'; import {TuiFormatNumberPipe} from '@taiga-ui/core'; @Component({ imports: [TuiBarSet, TuiFormatNumberPipe], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly value = [451, 302, 203, 124, 65]; protected readonly sum = this.value.reduce((a, b) => a + b, 0); } ``` **LESS:** ```less .bars { block-size: 7.5rem; inline-size: 5rem; margin-block-start: 2rem; box-shadow: 0 1px var(--tui-border-normal); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiBarSet} from '@taiga-ui/addon-charts'; import {type TuiSizeL, type TuiSizeS} from '@taiga-ui/core'; @Component({ imports: [TuiBarSet, TuiDemo], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly examples = [ 'Dynamic size', 'Fixed size', 'With negative values', 'Horizontal', 'With value label', ]; protected collapsed = false; protected readonly sizeVariants: ReadonlyArray = ['s', 'm', 'l']; protected size: TuiSizeL | TuiSizeS | null = null; protected readonly valueVariants = [ [30, 20, 10], [237, -50, 10, 5, 1], ]; protected value = this.valueVariants[0]!; } ``` ### LESS ```less .bars { block-size: 10rem; inline-size: 6.25rem; box-shadow: 0 1px var(--tui-border-normal); } ``` --- # components/Block - **Package**: `KIT` - **Type**: components Block is a special presentation of a checkbox/radiobutton which can display actual control or be a control itself ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiBlock] | `TuiSizeL | TuiSizeS` | Size of the block | ### Usage Examples #### Sizes **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 {TuiCheckbox, TuiIcon, TuiRadio, TuiTitle} from '@taiga-ui/core'; import {TuiBlock, TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiBlock, TuiCheckbox, TuiIcon, TuiRadio, TuiTitle, TuiTooltip, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly form = new FormGroup({ testValue1: new FormControl(true), testValue2: new FormControl({value: false, disabled: true}), testValue3: new FormControl({value: true, disabled: true}), testValue4: new FormControl(false), testValue5: new FormControl(), }); } ``` **LESS:** ```less form { display: flex; inline-size: max-content; align-items: flex-start; gap: 1rem; margin-block-end: 1rem; } ``` #### Groups **Template:** ```html

Horizontal group

Vertical group

Without checkbox indicators

``` **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, TuiFade} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiBlock, TuiFade, TuiGroup, TuiRadio], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly form = new FormGroup({value: new FormControl('orange')}); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .group { max-inline-size: 33rem; margin-block-end: 1.5rem; } .title { font: var(--tui-typography-heading-h5); margin: 0 0 1rem; } ``` #### Custom **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 {TuiCheckbox, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiBlock, TuiSwitch, TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiAvatar, TuiBlock, TuiCheckbox, TuiIcon, TuiSwitch, TuiTitle, TuiTooltip, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly form = new FormGroup({ testValue1: new FormControl(false), testValue2: new FormControl(false), testValue3: new FormControl(false), testValue4: new FormControl(false), }); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; form { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; 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 TuiSizeL, type TuiSizeS, TuiTitle} from '@taiga-ui/core'; import {TuiBlock, TuiSwitch} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiBlock, TuiDemo, TuiDocAppearance, TuiDocIcons, TuiSwitch, TuiTitle, ], templateUrl: './index.html', changeDetection, }) export default class Example { protected readonly examples = ['Sizes', 'Groups', 'Custom']; protected readonly sizes: ReadonlyArray = ['s', 'm', 'l']; protected readonly appearances = ['outline-grayscale', 'secondary']; protected value = false; protected size = this.sizes[2]!; } ``` --- # components/BlockDetails - **Package**: `LAYOUT` - **Type**: components Layout directive for describing details. For example, transaction details ### Usage Examples #### Example 1 **Template:** ```html

John W
money transfers

{{ -1050 | tuiAmount: 'USD' }} today extra subtitle Birthday gift
private
fast
Pending
``` **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'; import {TuiAmountPipe} from '@taiga-ui/addon-commerce'; import {TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiBadge, TuiComment} from '@taiga-ui/kit'; import {TuiBlockDetails} from '@taiga-ui/layout'; @Component({ imports: [ TuiAmountPipe, TuiAvatar, TuiBadge, TuiBlockDetails, TuiComment, TuiIcon, TuiTitle, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly isMobile = inject(WA_IS_MOBILE); } ``` #### Example 2 **Template:** ```html

Auchan
grocery • MMC 5350

{{ 0.5 | tuiAmount: 'USD' }} promotion (long value with fade)
cashback

Uber
taxi • MMC 5550

{{ -10.5 | tuiAmount: 'USD' }}
``` **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'; import {tuiAmountOptionsProvider, TuiAmountPipe} from '@taiga-ui/addon-commerce'; import {TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiBadge, TuiFade} from '@taiga-ui/kit'; import {TuiBlockDetails} from '@taiga-ui/layout'; @Component({ imports: [TuiAmountPipe, TuiAvatar, TuiBadge, TuiBlockDetails, TuiFade, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [tuiAmountOptionsProvider({sign: 'always'})], }) export default class Example { protected readonly isMobile = inject(WA_IS_MOBILE); } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 5rem; } [tuiSubtitle] { white-space: nowrap; max-inline-size: 11rem; } ``` ### 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 = ['Full', 'Customization']; } ``` --- # components/BlockStatus - **Package**: `LAYOUT` - **Type**: components Component for status screens, result screens and zero screens ### Example ```html hidden content

Title

Description
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [card] | `boolean` | Enable border radius and padding for card view | | [size] | `TuiSizeL` | Size (for desktop only) | ### Usage Examples #### Basic **Template:** ```html not found

Not found

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) { survived

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) {
hidden content We hide the unwanted block something wrong Something happened in this block
} ``` **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', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly breakpoint = inject(TUI_BREAKPOINT); protected readonly size = computed(() => this.breakpoint() === 'mobile' ? 'm' : 'l', ); } ``` **LESS:** ```less .container { display: flex; gap: 1rem; :host-context(tui-root._mobile) & { flex-direction: column; } } .image { inline-size: 5.5rem; block-size: 5.5rem; } .card { background-color: var(--tui-background-base-alt); block-size: auto; } ``` #### Customization **Template:** ```html
@for (user of users; track user) {
}
You can put other content instead of image using tui-block-content css class
Alex Inkin

Alex Inkin

``` **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 { TuiAutoColorPipe, TuiAvatar, TuiAvatarStack, TuiInitialsPipe, } from '@taiga-ui/kit'; import {TuiBlockStatus} from '@taiga-ui/layout'; @Component({ imports: [ TuiAutoColorPipe, TuiAvatar, TuiAvatarStack, TuiBlockStatus, TuiButton, TuiInitialsPipe, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly users = [ 'Alex Inkin', 'Vladimir Potekhin', 'Nikita Barsukov', 'Maxim Ivanov', ]; } ``` **LESS:** ```less .container { display: flex; gap: 1rem; :host-context(tui-root._mobile) & { flex-direction: column; } } .card { background-color: var(--tui-background-base-alt); block-size: auto; } .avatar[tuiSlot='top'] { inline-size: 6.25rem; block-size: 6.25rem; border-radius: 50%; } ``` #### Mobile **Template:** ```html
hidden content We hide the unwanted block something wrong 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 survived

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
hidden content

No operations

hidden content

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
What's up?
@for (message of messages; track message) {
}
``` **TypeScript:** ```ts import {Component, ElementRef, viewChild} 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, TuiScrollbar} from '@taiga-ui/core'; import {TuiMessage, TuiTextarea} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiBottomSheet, TuiButton, TuiCardLarge, TuiMessage, TuiScrollbar, TuiTextarea, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly sheet = viewChild(TuiBottomSheet, {read: ElementRef}); protected messages = ['Check that awesome bottom sheet out!']; protected value = ''; protected onClick(message: string): void { this.messages = this.messages.concat(message); this.sheet()?.nativeElement.scrollTo({top: 0, behavior: 'smooth'}); } protected onSubmit(): void { this.messages = this.messages.concat(this.value); this.value = ''; } } ``` **LESS:** ```less :host { display: flex; flex-direction: column; block-size: 30rem; } .messages { position: relative; flex: 1; padding-inline-start: 1rem; overflow: hidden; } .message { display: block; margin: 1rem 1rem 1rem auto; max-inline-size: 80%; white-space: pre-wrap; &:last-child { margin-block-end: 4rem; } } .form { padding: 0 1rem 1rem; background: var(--tui-background-elevation-1); } .textarea { margin: 1rem 0; } .actions { display: grid; gap: 1rem; grid-template-columns: 1fr 1fr; :last-child { grid-column: span 2; } } ``` #### Reacting to scroll **Template:** ```html

London United Kingdom

Population 8,866,180
Area 1,572 square km
Time zone UTC+00:00 (Greenwich Mean Time)
Established 47 AD
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

@for (item of items; track item) { }

Scroll Putting Fade directive on entire component

@for (item of items; track item) { }

Collapse Using itemsLimit: number

@for (item of items; track item) { } ``` **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 {TuiItem} from '@taiga-ui/cdk'; import {TuiHint, TuiLink, TuiTitle} from '@taiga-ui/core'; import {TuiBreadcrumbs, tuiBreadcrumbsOptionsProvider, TuiFade} from '@taiga-ui/kit'; @Component({ imports: [RouterLink, TuiBreadcrumbs, TuiFade, TuiHint, TuiItem, TuiLink, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [tuiBreadcrumbsOptionsProvider({icon: '/'})], }) export default class Example { protected readonly fade = DemoRoute.Fade; protected readonly items = [ 'First item', 'Very very long second item that must overflow', 'Third item', 'One last super long item that is never gonna fit', ]; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: block; max-inline-size: 30rem; } hr, h3 { margin: 1rem 0; } hr { block-size: 1px; background: var(--tui-border-normal); border: 0; } .link { .text-truncate(); &_last { font-weight: bold; color: var(--tui-text-primary); } } ``` ### TypeScript ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiItem} from '@taiga-ui/cdk'; import {TuiLink, type TuiSizeL} from '@taiga-ui/core'; import {TUI_BREADCRUMBS_OPTIONS, TuiBreadcrumbs} from '@taiga-ui/kit'; @Component({ imports: [TuiBreadcrumbs, TuiDemo, TuiItem, TuiLink], templateUrl: './index.html', changeDetection, }) export default class Example { private readonly options = inject(TUI_BREADCRUMBS_OPTIONS); protected readonly examples = ['Basic', 'Overflow']; protected readonly items = [ 'Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6', 'Item 7', 'Item 8', ]; protected readonly sizeVariants: readonly TuiSizeL[] = ['m', 'l']; protected size = this.options.size; protected itemsLimit = this.options.itemsLimit; } ``` --- # components/Button - **Package**: `CORE` - **Type**: components Button is a basic component used for both icon buttons and regular buttons with optional icons on either side. It can be applied to button and a tags. When used as tuiIconButton don't forget to still put text label within the tag for accessibility. ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [size] | `TuiSizeXS | TuiSizeL` | Size of the button | | [loading] | `boolean` | ) | ### 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 {TuiButton} from '@taiga-ui/core'; @Component({ imports: [TuiButton], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } ``` #### Appearance **Template:** ```html
Use tuiAppearanceMode to emulate :checked / :invalid CSS state for outline appearance:
``` **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'; @Component({ imports: [TuiButton], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; flex-wrap: wrap; } ``` #### Icons **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiIcon} from '@taiga-ui/core'; import {TuiAvatar, TuiChevron} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiButton, TuiChevron, TuiIcon], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; } ``` #### Loading **Template:** ```html ``` **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} from '@taiga-ui/cdk'; import {TuiButton} from '@taiga-ui/core'; import {TuiButtonLoading} from '@taiga-ui/kit'; import {map, startWith, Subject, switchMap, timer} from 'rxjs'; @Component({ imports: [AsyncPipe, TuiButton, TuiButtonLoading], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly trigger$ = new Subject(); protected readonly loading$ = this.trigger$.pipe( switchMap(() => timer(2000).pipe(map(TUI_FALSE_HANDLER), startWith('Loading'))), ); } ``` #### Options with DI **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, tuiButtonOptionsProvider} from '@taiga-ui/core'; @Component({ imports: [TuiButton], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiButtonOptionsProvider({size: 's'})], }) export default class Example {} ``` #### Vertical **Template:** ```html ``` **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 {TuiAvatar, TuiFade} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiButton, TuiFade], templateUrl: './index.html', styles: ':host { display: flex; gap: 1rem; align-items: flex-start; }', encapsulation, changeDetection, }) export default class Example {} ``` #### Two labels **Template:** ```html

``` **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 {TuiFade} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiFade], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .button { inline-size: 15rem; justify-content: space-between; } ``` ### 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 {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiButton, type TuiSizeL, type TuiSizeXS} from '@taiga-ui/core'; import {TuiButtonLoading} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiButtonLoading, TuiDemo, TuiDocAppearance, TuiDocIcons], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly examples = [ 'Sizes', 'Appearance', 'Icons', 'Loading', 'Options with DI', 'Vertical', 'Two labels', ]; protected readonly sizes: ReadonlyArray = ['xs', 's', 'm', 'l']; protected size = this.sizes[3]!; protected loading = false; protected readonly routes = DemoRoute; } ``` --- # components/ButtonGroup - **Package**: `KIT` - **Type**: components ### Usage Examples #### Elevated **Template:** ```html
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAppearance, TuiIcon} from '@taiga-ui/core'; import {TuiButtonGroup} from '@taiga-ui/kit'; @Component({ imports: [TuiAppearance, TuiButtonGroup, TuiIcon], 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; gap: 1rem; inline-size: 21.5625rem; @media @tui-mobile { inline-size: 100%; } } .custom { color: var(--tui-status-warning); &::after { color: var(--tui-text-action); } } ``` #### Flat **Template:** ```html
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAppearance, TuiIcon} from '@taiga-ui/core'; import {TuiButtonGroup} from '@taiga-ui/kit'; @Component({ imports: [TuiAppearance, TuiButtonGroup, TuiIcon], 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; gap: 1rem; inline-size: 21.5625rem; @media @tui-mobile { inline-size: 100%; } } ``` #### Dark **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'; import {TuiButtonGroup} from '@taiga-ui/kit'; @Component({ imports: [TuiButtonGroup, TuiIcon], 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; gap: 1rem; inline-size: 21.5625rem; @media @tui-mobile { inline-size: 100%; } } [tuiButtonGroup] { background: linear-gradient(334.83deg, #7d8ca0 0%, #647382 100%); button { color: #fff; } } ``` #### Swiped animation **Template:** ```html

BANK {{ items[index]?.title }}

{{ items[index]?.content }}
@switch (effective()) { @case (0) { } @case (1) { } @case (2) { } }
``` **TypeScript:** ```ts import {Component, linkedSignal, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiCarousel, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiBadge, TuiButtonGroup, TuiPager} from '@taiga-ui/kit'; import {TuiCardMedium, TuiElasticContainer} from '@taiga-ui/layout'; @Component({ imports: [ TuiBadge, TuiButtonGroup, TuiCardMedium, TuiCarousel, TuiElasticContainer, TuiIcon, TuiPager, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly index = signal(1); protected readonly opacity = signal(1); protected readonly effective = linkedSignal(this.index); protected readonly items = [ { title: 'RUB Account', content: '10 000 ₽', gradient: 'linear-gradient(334.83deg, #7d8ca0 0%, #647382 100%)', color: '#7d8ca0', }, { title: 'USD Account', content: '2 000 000 $', gradient: 'linear-gradient(-90deg, #cf77f3 0%, #009bff 47%, #2ac9db 100%)', color: 'rgb(0, 155, 255)', }, { title: 'EUR Account', content: '30 000 €', gradient: 'linear-gradient(135deg, #1AC07E, #DEA683)', color: 'rgb(158 178 129)', }, ]; protected onScroll({scrollLeft, clientWidth}: HTMLElement): void { const scrolled = ((Math.abs(scrollLeft) - clientWidth) / clientWidth) % 1; const progress = this.index() || !scrolled ? scrolled : 1 + scrolled; if (progress) { this.opacity.set(4 * Math.abs(Math.abs(progress) - 0.5)); this.effective.set(this.index() + Math.round(progress)); } } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: flex; flex-direction: column; gap: 1rem; inline-size: 21rem; block-size: 21rem; @media @tui-mobile { inline-size: 100%; } } [tuiButtonGroup] button { color: var(--tui-text-primary); } [tuiCardMedium] { inline-size: calc(100% - 3rem); padding: 0.75rem; block-size: 8rem; color: var(--tui-text-primary); } tui-pager { margin-inline: auto; } [tuiBadge] { background: #ffdd2d; color: #333; } ``` ### 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 = ['Elevated', 'Flat', 'Dark', 'Swiped animation']; } ``` --- # components/ButtonSelect - **Package**: `KIT` - **Type**: components ButtonSelect is a form control for selecting one or multiple values, similar to a standard select element but with a button-based interface. ### 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 {TuiButton, TuiDropdown} from '@taiga-ui/core'; import {TuiButtonSelect, TuiDataListWrapper} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiButton, TuiButtonSelect, TuiDataListWrapper, TuiDropdown], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items: string[] = inject('Pythons' as any); protected value = this.items[0]; } ``` #### Multiselect **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 {TuiButton, TuiDataList, TuiDropdown} from '@taiga-ui/core'; import {TuiButtonSelect, TuiMultiSelect} from '@taiga-ui/kit'; interface User { readonly id: number; readonly name: string; } @Component({ imports: [ FormsModule, TuiButton, TuiButtonSelect, TuiDataList, TuiDropdown, TuiMultiSelect, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items: User[] = [ {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 = this.items; } ``` ### 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', 'Multiselect']; } ``` --- # components/ButtonX - **Package**: `Core` - **Type**: components A simple preset directive to configure a cleaner/close button, used in textfields, dialogs etc. ### 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 {TuiButtonX} from '@taiga-ui/core'; @Component({ imports: [TuiButtonX], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; 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 {} ``` --- # components/Calendar - **Package**: `CORE` - **Type**: components A simple calendar. If you want a textfield with date, see InputDate and InputDateRange ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [disabledItemHandler] | `TuiBooleanHandler` | | | [showAdjacent] | `boolean` | Show adjacent months days | | [(hoveredItem)] | `TuiDay | null` | Hovered date | | [markerHandler] | `TuiMarkerHandler | null` | A handler that gets date and returns null or a tuple with circled marker colors | | [max] | `TuiDay | null` | Maximal date to choose | | [maxViewedMonth] | `TuiMonth | null` | Maximal month to access | | [min] | `TuiDay | null` | Minimum date to choose | | [minViewedMonth] | `TuiMonth | null` | Minimum month to access | | [(month)] | `TuiMonth` | Current month | | [value] | `TuiDay | TuiDayRange | null` | Selected day or range | ### API - Outputs | Event | Type | Description | |-------|------|-------------| | (dayClick) | `TuiDay` | Date click | ### Usage Examples #### Basic **Template:** ```html @if (value) {
Chosen date: {{ value }}
} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiDay} from '@taiga-ui/cdk'; import {TuiCalendar} from '@taiga-ui/core'; @Component({ imports: [TuiCalendar], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: TuiDay | null = null; protected onDayClick(day: TuiDay): void { this.value = day; } } ``` #### Range **Template:** ```html
@if (value) {
Chosen dates: {{ value }}
} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiDay, TuiDayRange, TuiMonth} from '@taiga-ui/cdk'; import {TuiCalendar} from '@taiga-ui/core'; @Component({ imports: [TuiCalendar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value: TuiDayRange | null = null; protected firstMonth = TuiMonth.currentLocal(); protected middleMonth = TuiMonth.currentLocal().append({month: 1}); protected lastMonth = TuiMonth.currentLocal().append({month: 2}); protected hoveredItem: TuiDay | null = null; protected onDayClick(day: TuiDay): void { if (!this.value?.isSingleDay) { this.value = new TuiDayRange(day, day); } this.value = TuiDayRange.sort(this.value.from, day); } protected onMonthChangeFirst(month: TuiMonth): void { this.firstMonth = month; this.middleMonth = month.append({month: 1}); this.lastMonth = month.append({month: 2}); } protected onMonthChangeMiddle(month: TuiMonth): void { this.firstMonth = month.append({month: -1}); this.middleMonth = month; this.lastMonth = month.append({month: 1}); } protected onMonthChangeLast(month: TuiMonth): void { this.firstMonth = month.append({month: -2}); this.middleMonth = month.append({month: -1}); this.lastMonth = month; } } ``` **LESS:** ```less .wrapper { display: flex; } ``` #### With markers **Template:** ```html
@if (value) {
Chosen dates: {{ value }}
} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiDay, TuiDayRange, TuiMonth} from '@taiga-ui/cdk'; import {TuiCalendar, type TuiMarkerHandler} from '@taiga-ui/core'; const TWO_DOTS: [string, string] = [ 'var(--tui-background-accent-1)', 'var(--tui-status-info)', ]; const ONE_DOT: [string] = ['var(--tui-status-positive)']; @Component({ imports: [TuiCalendar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value: TuiDayRange | null = null; protected firstMonth = TuiMonth.currentLocal(); protected middleMonth = TuiMonth.currentLocal().append({month: 1}); protected lastMonth = TuiMonth.currentLocal().append({month: 2}); protected hoveredItem: TuiDay | null = null; protected readonly markerHandler: TuiMarkerHandler = (day: TuiDay) => // Attention: do not create new arrays in handler, use constants instead day.day % 2 === 0 ? TWO_DOTS : ONE_DOT; protected onDayClick(day: TuiDay): void { if (!this.value?.isSingleDay) { this.value = new TuiDayRange(day, day); } this.value = TuiDayRange.sort(this.value.from, day); } protected onMonthChangeFirst(month: TuiMonth): void { this.firstMonth = month; this.middleMonth = month.append({month: 1}); this.lastMonth = month.append({month: 2}); } protected onMonthChangeMiddle(month: TuiMonth): void { this.firstMonth = month.append({month: -1}); this.middleMonth = month; this.lastMonth = month.append({month: 1}); } protected onMonthChangeLast(month: TuiMonth): void { this.firstMonth = month.append({month: -2}); this.middleMonth = month.append({month: -1}); this.lastMonth = month; } } ``` **LESS:** ```less .wrapper { display: flex; } ``` #### 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 {TuiDay, TuiDayOfWeek} from '@taiga-ui/cdk'; import {TuiCalendar, tuiCalendarOptionsProvider} from '@taiga-ui/core'; @Component({ imports: [TuiCalendar], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiCalendarOptionsProvider({weekStart: signal(TuiDayOfWeek.Sunday)})], }) export default class Example { protected value = new TuiDay(2025, 6, 4); } ``` #### Color customization **Template:** ```html ``` **TypeScript:** ```ts import {Component, ViewEncapsulation} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {type TuiDay, type TuiHandler} from '@taiga-ui/cdk'; import {TuiCalendar, tuiCalendarOptionsProvider} from '@taiga-ui/core'; const dayType: TuiHandler = (day) => { if (day.day === 10) { return 'holiday'; } return day.isWeekend ? 'weekend' : 'weekday'; }; @Component({ imports: [TuiCalendar], templateUrl: './index.html', styleUrl: './index.less', encapsulation: ViewEncapsulation.None, changeDetection, providers: [tuiCalendarOptionsProvider({dayType})], }) export default class Example {} ``` **LESS:** ```less tui-calendar-sheet [data-type='holiday']::before { background-color: var(--tui-chart-categorical-09); } ``` #### Select multiple dates **Template:** ```html
Chosen dates: {{ value }}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiDay} from '@taiga-ui/cdk'; import {TuiCalendar} from '@taiga-ui/core'; @Component({ imports: [TuiCalendar], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: readonly TuiDay[] = []; protected onDayClick(day: TuiDay): void { this.value = this.value.find((item) => item.daySame(day)) ? this.value.filter((item) => !item.daySame(day)) : this.value.concat(day); } } ``` #### Open in year view **Template:** ```html @if (value) {
Chosen date: {{ value }}
} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiDay} from '@taiga-ui/cdk'; import {TuiCalendar} from '@taiga-ui/core'; @Component({ imports: [TuiCalendar], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: TuiDay | null = null; protected onDayClick(day: TuiDay): void { this.value = day; } } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {RouterModule} from '@angular/router'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import { TUI_FALSE_HANDLER, TUI_FIRST_DAY, TUI_LAST_DAY, type TuiBooleanHandler, TuiDay, TuiDayRange, TuiMonth, } from '@taiga-ui/cdk'; import {TuiCalendar, type TuiMarkerHandler} from '@taiga-ui/core'; const TWO_DOTS: [string, string] = [ 'var(--tui-background-accent-1)', 'var(--tui-status-info)', ]; const ONE_DOT: [string] = ['var(--tui-status-positive)']; @Component({ imports: [RouterModule, TuiCalendar, TuiDemo], templateUrl: './index.html', changeDetection, }) export default class Example { protected readonly examples = [ 'Basic', 'Range', 'With markers', 'Localization', 'Color customization', 'Select multiple dates', 'Open in year view', ]; protected showAdjacent = true; protected readonly minVariants = [ TUI_FIRST_DAY, new TuiDay(2017, 2, 5), new TuiDay(1900, 0, 1), ]; protected min = this.minVariants[0]!; protected readonly maxVariants = [ TUI_LAST_DAY, new TuiDay(2020, 3, 30), new TuiDay(2300, 0, 1), ]; protected max = this.maxVariants[0]!; protected readonly minViewedMonthVariants = [ new TuiMonth(0, 0), new TuiMonth(2017, 2), new TuiMonth(1900, 0), ]; protected minViewedMonth = this.minViewedMonthVariants[0]!; protected readonly maxViewedMonthVariants = [ TUI_LAST_DAY, new TuiMonth(2020, 3), new TuiMonth(2300, 0), ]; protected maxViewedMonth = this.maxViewedMonthVariants[0]!; protected readonly disabledItemHandlerVariants: ReadonlyArray< TuiBooleanHandler > = [TUI_FALSE_HANDLER, ({day}) => day % 3 === 0]; protected disabledItemHandler = this.disabledItemHandlerVariants[0]!; protected readonly markerHandlerVariants: readonly TuiMarkerHandler[] = [ (day: TuiDay) => (day.day % 2 === 0 ? TWO_DOTS : ONE_DOT), ]; protected markerHandler: TuiMarkerHandler | null = null; protected readonly valueVariants: ReadonlyArray = [ TuiDay.currentLocal(), new TuiDayRange(TuiDay.currentLocal(), TuiDay.currentLocal().append({day: 3})), new TuiDay(2020, 3, 21), ]; protected value: TuiDay | TuiDayRange | null = null; protected month = TuiMonth.currentLocal(); protected hoveredItem: TuiDay | null = null; protected readonly routes = DemoRoute; } ``` --- # components/CalendarMonth - **Package**: `KIT` - **Type**: components Month picker component. If you want a textfield, see InputMonth ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [disabledItemHandler] | `TuiBooleanHandler` | | | [max] | `TuiMonth | null` | Maximal month | | [min] | `TuiMonth | null` | Minimal month | | [maxLength] | `number` | Maximum length | | [minLength] | `number` | Minimum length | | [value] | `TuiMonth | TuiMonthRange | null` | A single month or a range of months | | [(year)] | `TuiYear` | Current year | ### API - Outputs | Event | Type | Description | |-------|------|-------------| | (monthClick) | `TuiMonth` | Selected month | ### Usage Examples #### Basic **Template:** ```html

Selected month: {{ value }}

Hovered month: {{ hoveredMonth }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiMonth} from '@taiga-ui/cdk'; import {TuiCalendarMonth} from '@taiga-ui/kit'; @Component({ imports: [TuiCalendarMonth], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: TuiMonth | null = null; protected hoveredMonth: TuiMonth | null = null; protected onMonthClick(month: TuiMonth): void { this.value = month; } protected onMonthHovered(month: TuiMonth | null): void { this.hoveredMonth = month; } } ``` #### Range **Template:** ```html

Selected value: {{ value }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiMonth, TuiMonthRange} from '@taiga-ui/cdk'; import {TuiCalendarMonth, tuiCalendarMonthOptionsProvider} from '@taiga-ui/kit'; @Component({ imports: [TuiCalendarMonth], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiCalendarMonthOptionsProvider({rangeMode: true})], }) export default class Example { protected value: TuiMonth | TuiMonthRange | null = null; protected max = TuiMonth.currentLocal().append({year: 1}); protected min = new TuiMonth(2019, 7); protected onMonthClick(month: TuiMonth): void { this.value = this.value instanceof TuiMonth ? TuiMonthRange.sort(this.value, month) : month; } } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {RouterModule} from '@angular/router'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import { TUI_FALSE_HANDLER, TUI_FIRST_DAY, TUI_LAST_DAY, type TuiBooleanHandler, TuiDay, TuiMonth, TuiMonthRange, TuiYear, } from '@taiga-ui/cdk'; import {TuiLink} from '@taiga-ui/core'; import {TuiCalendarMonth} from '@taiga-ui/kit'; @Component({ imports: [RouterModule, TuiCalendarMonth, TuiDemo, TuiLink], templateUrl: './index.html', changeDetection, }) export default class Example { protected readonly examples = ['Basic', 'Range']; protected readonly minVariants = [ TUI_FIRST_DAY, new TuiMonth(2019, 2), new TuiMonth(2007, 0), ]; protected readonly maxVariants = [ TUI_LAST_DAY, new TuiMonth(2020, 2), new TuiMonth(2023, 0), new TuiMonth(2019, 4), ]; protected min = this.minVariants[0]!; protected max = this.maxVariants[0]!; protected maxLength = 0; protected minLength = 0; protected readonly disabledItemHandlerVariants: ReadonlyArray< TuiBooleanHandler > = [TUI_FALSE_HANDLER, ({month}) => month % 3 === 0]; protected disabledItemHandler = this.disabledItemHandlerVariants[0]!; protected readonly valueVariants: ReadonlyArray = [ TuiDay.currentLocal(), new TuiMonthRange( TuiDay.currentLocal(), TuiDay.currentLocal().append({month: 3}), ), new TuiMonth(2007, 2), ]; protected value: TuiMonth | TuiMonthRange | null = null; protected readonly yearVariants: readonly TuiYear[] = [ TuiDay.currentLocal(), new TuiYear(2007), ]; protected year = this.yearVariants[0]!; protected readonly routes = DemoRoute; } ``` --- # components/CalendarRange - **Package**: `KIT` - **Type**: components Component for choosing date range in calendar ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [defaultViewedMonth] | `TuiMonth` | Default month to show | | [disabledItemHandler] | `TuiBooleanHandler` | | | [items] | `TuiDayRangePeriod[]` | Fixed intervals (shows 2 calendars with empty array) | | [markerHandler] | `TuiMarkerHandler | null` | A handler that gets date and returns null or a tuple with circled marker colors | | [min] | `TuiDay | null` | Min date | | [max] | `TuiDay | null` | Max date | | [minLength] | `TuiDayLike | null` | Minimal length of range | | [maxLength] | `TuiDayLike | null` | Maximal length of range | ### API - Outputs | Event | Type | Description | |-------|------|-------------| | (rangeChange) | `TuiDayRange` | Selected date range | ### 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 {TuiCalendarRange} from '@taiga-ui/kit'; @Component({ imports: [TuiCalendarRange], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### With value **Template:** ```html {{ value | json }} ``` **TypeScript:** ```ts import {JsonPipe} from '@angular/common'; import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiDay, TuiDayRange} from '@taiga-ui/cdk'; import {TuiCalendarRange} from '@taiga-ui/kit'; @Component({ imports: [JsonPipe, TuiCalendarRange], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value = new TuiDayRange(new TuiDay(2019, 2, 11), new TuiDay(2019, 2, 14)); } ``` #### With ranges **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiCalendarRange, tuiCreateDefaultDayRangePeriods} from '@taiga-ui/kit'; @Component({ imports: [TuiCalendarRange], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected items = tuiCreateDefaultDayRangePeriods(); } ``` #### 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 {TuiDayOfWeek} from '@taiga-ui/cdk'; import {tuiCalendarOptionsProvider} from '@taiga-ui/core'; import {TuiCalendarRange} from '@taiga-ui/kit'; @Component({ selector: 'example-4', imports: [TuiCalendarRange], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiCalendarOptionsProvider({weekStart: signal(TuiDayOfWeek.Sunday)})], }) export default class Example {} ``` #### With another range switcher **Template:** ```html @if (isLastVisible) {

} @if (isSelected && !isDefault) {

You are seeing {{ selected }}. @if (!isLastVisible) { }

} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiDay, TuiDayRange} from '@taiga-ui/cdk'; import {TuiLink} from '@taiga-ui/core'; import {TuiCalendarRange, TuiDayRangePeriod} from '@taiga-ui/kit'; const today = TuiDay.currentLocal(); const startOfWeek = today.append({day: -today.dayOfWeek()}); const startOfMonth = today.append({day: 1 - today.day}); const startOfQuarter = startOfMonth.append({month: -(startOfMonth.month % 3)}); @Component({ imports: [TuiCalendarRange, TuiLink], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ new TuiDayRangePeriod( new TuiDayRange(today.append({day: -30}), today), 'Default', ), new TuiDayRangePeriod(new TuiDayRange(startOfWeek, today), 'Week'), new TuiDayRangePeriod(new TuiDayRange(startOfMonth, today), 'Month'), new TuiDayRangePeriod(new TuiDayRange(startOfQuarter, today), 'Quarter'), ]; protected selected: TuiDayRangePeriod | null = this.default; protected value: TuiDayRange | null = this.default.range; public get default(): TuiDayRangePeriod { return this.items[0]!; } public get isDefault(): boolean { return this.selected === this.default; } public get isSelected(): boolean { return !!this.items.find((item) => item === this.selected); } public get isLastVisible(): boolean { return this.selected === this.items[this.items.length - 1]; } public get opposite(): TuiDayRangePeriod | null { if (!this.isSelected) { return null; } switch (this.selected) { case this.default: return null; case this.items[1]: return this.items[2]!; case this.items[2]: return this.items[3]!; case this.items[3]: return null; default: return null; } } public onValue(value: TuiDayRange | null): void { this.value = value; } public reset(): void { this.selected = this.default; this.value = this.selected.range; } public toggle(): void { this.selected = this.opposite; this.value = this.selected?.range ?? null; } } ``` ### 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 { TUI_FALSE_HANDLER, TUI_FIRST_DAY, TUI_LAST_DAY, type TuiBooleanHandler, TuiDay, type TuiDayLike, TuiMonth, } from '@taiga-ui/cdk'; import {type TuiMarkerHandler} from '@taiga-ui/core'; import { TuiCalendarRange, tuiCreateDefaultDayRangePeriods, type TuiDayRangePeriod, } from '@taiga-ui/kit'; const TWO_DOTS: [string, string] = [ 'var(--tui-background-accent-1)', 'var(--tui-status-info)', ]; const ONE_DOT: [string] = ['var(--tui-status-positive)']; @Component({ imports: [TuiCalendarRange, TuiDemo], templateUrl: './index.html', changeDetection, }) export default class Example { protected readonly examples = [ 'Basic', 'With value', 'With ranges', 'Localization', 'With another range switcher', ]; protected readonly minVariants = [ TUI_FIRST_DAY, new TuiDay(2017, 2, 5), new TuiDay(1900, 0, 1), ]; protected readonly maxVariants = [ TUI_LAST_DAY, new TuiDay(2018, 9, 30), new TuiDay(2020, 2, 5), new TuiDay(2300, 0, 1), TuiDay.currentLocal(), ]; protected readonly disabledItemHandlerVariants: ReadonlyArray< TuiBooleanHandler > = [TUI_FALSE_HANDLER, ({day}) => day % 3 === 0]; protected readonly defaultViewedMonthVariants: readonly TuiMonth[] = [ TuiMonth.currentLocal(), TuiMonth.currentLocal().append({month: 1}), new TuiMonth(2007, 5), ]; protected readonly itemsVariants: ReadonlyArray = [ [], tuiCreateDefaultDayRangePeriods(), ]; protected readonly minLengthVariants: readonly TuiDayLike[] = [ {day: 3}, {day: 15}, {month: 1}, {month: 1, day: 1}, ]; protected readonly maxLengthVariants: readonly TuiDayLike[] = [ {day: 5}, {month: 1}, {year: 1}, ]; protected readonly markerHandlerVariants: readonly TuiMarkerHandler[] = [ (day: TuiDay) => (day.day % 2 === 0 ? TWO_DOTS : ONE_DOT), ]; protected markerHandler: TuiMarkerHandler | null = null; protected min = this.minVariants[0]!; protected max = this.maxVariants[0]!; protected cleaner = false; protected disabledItemHandler = this.disabledItemHandlerVariants[0]!; protected items = this.itemsVariants[0]!; protected defaultViewedMonth = this.defaultViewedMonthVariants[0]!; protected minLength: TuiDayLike | null = null; protected maxLength: TuiDayLike | null = null; protected readonly routes = DemoRoute; } ``` --- # components/CardCollapsed - **Package**: `LAYOUT` - **Type**: components ### Usage Examples #### Basic **Template:** ```html

[31344] Error finding account number when executing the deal

Some clients could encounter an error “Account not found” when selecting the account on the checkout screen

Status
Pending
Category
Angular Open Source
Project Version Description
Taiga UI 4.32.0 Angular UI kit for awesome people
Maskito 3.5.0 Holy Grail of input masking for the Web
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
@if (step() === 1) {

Leave your feedback It will only take 3 minutes

} @if (step() === 2) {

Why so?

}
@if (step() === 2) { }
``` **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 {TuiAnimated, TuiAutoFocus} from '@taiga-ui/cdk'; import {TuiButton, TuiPopup, TuiTitle} from '@taiga-ui/core'; import {TuiRating, TuiTextarea} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAnimated, TuiAutoFocus, TuiButton, TuiCardLarge, TuiPopup, TuiRating, TuiTextarea, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly step = signal(0); protected rating = 0; protected comment = ''; protected close(): void { this.rating = 0; this.comment = ''; this.step.set(0); } } ``` **LESS:** ```less .popover { position: fixed; inset-inline-end: 1.5rem; inset-block-end: 1.5rem; inline-size: 20rem; --tui-from: translateX(100%); &.tui-enter, &.tui-leave { animation-name: tuiFade, tuiSlide; } } .close { position: absolute; inset-block-start: 0.5rem; inset-inline-end: 0.75rem; } .footer { display: flex; justify-content: flex-end; gap: 0.5rem; } ``` #### Avatar **Template:** ```html

Desktop

Title Subtitle

iOS/Android

Title Subtitle

``` **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 {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [TuiAvatar, TuiButton, TuiCardLarge, TuiHeader, TuiPlatform, TuiTitle], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Single item **Template:** ```html
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAppearance, TuiCell, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ TuiAppearance, TuiAvatar, TuiCardLarge, TuiCell, TuiHeader, TuiIcon, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1.25rem; inline-size: 20rem; } .actions { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; } ``` #### Cards List **Template:** ```html

Title Subtitle

@for (_ of '-'.repeat(3); track $index) {

Title Subtitle

}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiCardLarge, TuiCardMedium, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [TuiButton, TuiCardLarge, TuiCardMedium, TuiHeader, TuiIcon, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less section { display: flex; gap: 0.75rem; margin: 0 -1.5rem; padding: 0 1.5rem; overflow: scroll; } .plus { background: var(--tui-background-accent-1); border-radius: 0.25rem; color: #fff; } ``` #### Cell List **Template:** ```html

Title Subtitle

@for (_ of '-'.repeat(3); track $index) {
Title
Description
}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiHeader, TuiTitle], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Cell List (2 columns) **Template:** ```html

Title Subtitle

@for (_ of '-'.repeat(6); track $index) {
Title
Description
}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiHeader, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .actions { display: grid; grid-template-columns: 1fr 1fr; gap: inherit; } ``` #### Cell List (actions) **Template:** ```html

Title Subtitle

@for (_ of '-'.repeat(3); track $index) { }
@for (item of ['Edit', 'Download', 'Rename', 'Delete']; track item) { }
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import { TuiAppearance, TuiCell, TuiDataList, TuiDropdown, TuiIcon, TuiLink, TuiTitle, } from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ TuiAppearance, TuiAvatar, TuiCardLarge, TuiCell, TuiDataList, TuiDropdown, TuiHeader, TuiIcon, TuiLink, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .actions { display: flex; flex-direction: column; } ``` #### Cell List (headless) **Template:** ```html
@for (_ of '-'.repeat(3); track $index) {
Title
Description
}
Title
Description
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiTitle], templateUrl: './index.html', styles: ':host { display: flex; flex-direction: column; gap: 2rem}', encapsulation, changeDetection, }) export default class Example {} ``` #### Footer alignment **Template:** ```html

Title

Title
Description

Title

Some text

Card with text content

Use description for text content of a card with spacings according to the specs.
Remaining $25 120.23
out of $100 000
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiCell, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiProgressBar, TuiTooltip} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiHeader, TuiIcon, TuiProgressBar, TuiTitle, TuiTooltip, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: min-content 1fr; gap: 1rem; inline-size: 30rem; > :last-child { grid-column: span 2; } } [tuiSubtitle] { font: var(--tui-typography-body-m); } ``` ### 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', 'Avatar', 'Single item', 'Cards List', 'Cell List', 'Cell List (2 columns)', 'Cell List (actions)', 'Cell List (headless)', 'Footer alignment', 'Image', 'Cell with close', 'Paddings and radii', 'Map', 'In portal', ]; } ``` --- # components/CardMedium - **Package**: `LAYOUT` - **Type**: components A layout component used to create various cards for the interface. Define visual styles of the cards yourself or combine with Surface for visual presets. ### Usage Examples #### Avatar and text **Template:** ```html

Title Subtitle

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 {TuiAppearance, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardMedium} from '@taiga-ui/layout'; @Component({ imports: [TuiAppearance, TuiAvatar, TuiCardMedium, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } .star { background: rgb(66, 139, 250); color: #fff; } ``` #### Icon **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 {TuiAppearance, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiCardMedium} from '@taiga-ui/layout'; @Component({ imports: [TuiAppearance, TuiCardMedium, TuiIcon, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } .plus { background: #428bfa; color: #fff; border-radius: 0.25rem; } ``` #### Badge **Template:** ```html
10 %

Title Subtitle

Title Subtitle

10 %
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAppearance, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiBadge} from '@taiga-ui/kit'; import {TuiCardMedium} from '@taiga-ui/layout'; @Component({ imports: [TuiAppearance, TuiBadge, TuiCardMedium, TuiIcon, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1rem; } .badge { background: #aff218; color: #333; } ``` #### Stacking **Template:** ```html
@for (url of urls; track url) {
}

Title Subtitle

Title Subtitle

@for (url of urls; track url) {
}
``` **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 {TuiAutoColorPipe, TuiAvatar, TuiAvatarStack} from '@taiga-ui/kit'; import {TuiCardMedium} from '@taiga-ui/layout'; @Component({ imports: [TuiAutoColorPipe, TuiAvatar, TuiAvatarStack, TuiCardMedium, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly urls = [ 'https://avatars.githubusercontent.com/u/11832552', 'https://avatars.githubusercontent.com/u/10106368', 'https://avatars.githubusercontent.com/u/46284632', ]; } ``` **LESS:** ```less :host { display: flex; gap: 1rem; } ``` #### Customization **Template:** ```html

Poster Cinema, concerts, theaters and sports up to 25%

10%

Flights

10%

Google -2.7%

$3,605.2

Microsoft -12.44%

$1,095.3
``` **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 {TuiBadge} from '@taiga-ui/kit'; import {TuiCardMedium, TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [TuiBadge, TuiCardMedium, TuiIcon, TuiSurface, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1.2rem; min-inline-size: 40.625rem; } .badge { background: #aff218; color: #333; } .money { background: #ffdd2d; color: #333; } .poster { background: rgb(88, 192, 190); padding: 0.75rem; color: #fff; inline-size: 13.9375rem; block-size: 8rem; [tuiSubtitle] { color: #fff; } &::after { background: url('/assets/images/poster.svg') bottom right no-repeat; } &:hover::after { transform: scale(1.1); } } .fly { background: rgb(101, 174, 234); padding: 0.75rem; color: #fff; inline-size: 8rem; block-size: 8rem; &::after { background: url('/assets/images/fly.svg') bottom right no-repeat; } &:hover::after { transform: scale(1.1); } } .google, .microsoft { padding: 0.75rem; background: var(--tui-background-base-alt); block-size: 8rem; inline-size: 8rem; &:hover::after { transform: scale(1.1); } } .google { [tuiSubtitle] { color: #f00; } &::after { background: url('/assets/images/google.svg') bottom right no-repeat; } } .microsoft { [tuiSubtitle] { color: #00b92d; } &::after { background: url('/assets/images/microsoft.svg') bottom right no-repeat; } } ``` #### Long text **Template:** ```html
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 {TuiHint, TuiTitle} from '@taiga-ui/core'; import {TuiFade} from '@taiga-ui/kit'; import {TuiCardMedium} from '@taiga-ui/layout'; @Component({ imports: [TuiCardMedium, TuiFade, TuiHint, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: flex; gap: 1rem; } .fade { inline-size: 100%; block-size: 2rem; white-space: nowrap; overflow: auto; } .fade-vertical { block-size: 5rem; overflow: auto; } .nowrap { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; max-inline-size: 100%; } ``` #### Selectable **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 {TuiThumbnailCard} from '@taiga-ui/addon-commerce'; import {TuiRipple} from '@taiga-ui/addon-mobile'; import {TuiFade} from '@taiga-ui/kit'; import {TuiCardMedium} from '@taiga-ui/layout'; @Component({ imports: [FormsModule, TuiCardMedium, TuiFade, TuiRipple, TuiThumbnailCard], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value = 0; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: flex; gap: 1rem; } .card { block-size: 6.5rem; inline-size: 6.5rem; } .mir { background: #2b9aff linear-gradient(110deg, transparent 70%, #0780ff 71%, #db028b 100%); } .visa { background: linear-gradient(45deg, rgba(255, 170, 0, 0.82), #fa0), url('/assets/taiga-ui/icons/star.svg'); } img { inline-size: 1.5rem; block-size: 1.5rem; } .cards { display: flex; flex-direction: row; gap: 0.25rem; inline-size: 100%; margin: 0 -0.4375rem; padding: 0 0.4375rem; overflow: auto; } ``` ### 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 Example { protected readonly examples = [ 'Avatar and text', 'Icon', 'Badge', 'Stacking', 'Customization', 'Long text', 'Selectable', ]; protected readonly surface = DemoRoute.Surface; } ``` --- # components/Carousel - **Package**: `CORE` - **Type**: components Generic swipeable container to scroll through content ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [min] | `number` | Min index | | [max] | `number` | Max index | | [(index)] | `number` | Current index | | *tuiItem='let index' | `TemplateRef>` | is index of item in scroller. | ### Usage Examples #### Basic **Template:** ```html {{ index }} ``` **TypeScript:** ```ts import {Component, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiCarousel} from '@taiga-ui/core'; @Component({ imports: [TuiButton, TuiCarousel], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly index = signal(0); } ``` **LESS:** ```less :host { display: grid; gap: 1rem; grid-template-columns: min-content 1fr min-content; font: var(--tui-typography-heading-h3); } ``` #### Looped **Template:** ```html
@let current = items.at(index % items.length);
{{ current?.title }} {{ current?.subtitle }}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiCarousel, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCard} from '@taiga-ui/layout'; @Component({ imports: [TuiAvatar, TuiCard, TuiCarousel, TuiTitle], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ { title: 'Taiga UI', subtitle: 'Angular UI Kit', icon: '@img.assets/images/taiga.svg', }, { title: 'Maskito', subtitle: 'Masking library', icon: '@img.https://raw.githubusercontent.com/taiga-family/maskito/main/projects/demo/src/assets/icons/maskito.svg', }, { title: 'Editor', subtitle: 'WYSIWYG', icon: '@tui.pencil', }, ]; } ``` #### Automatic **Template:** ```html
@let current = items.at(index % items.length);
{{ current?.title }} {{ current?.subtitle }}
``` **TypeScript:** ```ts import {Component, computed, 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 {TuiCarousel, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiPager, TuiProgress} from '@taiga-ui/kit'; import {TuiCard} from '@taiga-ui/layout'; @Component({ imports: [TuiAvatar, TuiCard, TuiCarousel, TuiPager, TuiProgress, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly isE2E = inject(WA_IS_E2E); protected readonly index = signal(0); protected readonly items = [ { title: 'Taiga UI', subtitle: 'Angular UI Kit', icon: '@img.assets/images/taiga.svg', }, { title: 'Maskito', subtitle: 'Masking library', icon: '@img.https://raw.githubusercontent.com/taiga-family/maskito/main/projects/demo/src/assets/icons/maskito.svg', }, { title: 'Editor', subtitle: 'WYSIWYG', icon: '@tui.pencil', }, ]; protected readonly clamped = computed( () => ((this.index() % this.items.length) + this.items.length) % this.items.length, ); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: flex; flex-direction: column; align-items: center; gap: 1rem; inline-size: 12rem; } .progress { .transition(all); inline-size: 1rem; &:not(&_active) { inline-size: 0.5rem; color: transparent; } } ``` #### Multiple **Template:** ```html @for (_ of '-'.repeat(4); track $index) { @let item = items.at((index * 4 + $index) % this.items.length); } ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiCarousel} from '@taiga-ui/core'; import {TuiAvatar, TuiAvatarLabeled} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiAvatarLabeled, TuiCarousel], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ { label: 'Alex Inkin', avatar: 'https://avatars.githubusercontent.com/u/11832552', }, { label: 'Vladimir Potekhin', avatar: 'https://avatars.githubusercontent.com/u/46284632', }, { label: 'Nikita Barsukov', avatar: 'https://avatars.githubusercontent.com/u/35179038', }, { label: 'Max Ivanov', avatar: 'https://avatars.githubusercontent.com/u/12021443', }, { label: 'German Panov', avatar: 'https://avatars.githubusercontent.com/u/87331898', }, ]; } ``` **LESS:** ```less tui-carousel { inline-size: 16rem; } tui-avatar-labeled { inline-size: 4rem; scroll-snap-align: start; scroll-snap-stop: always; } ``` ### 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 = ['Basic', 'Looped', 'Automatic', 'Multiple']; } ``` ### LESS ```less .bar { block-size: 6.25rem; } ``` --- # components/Cell - **Package**: `CORE` - **Type**: components ### Example ```html
@if (height !== 'compact') {
}
Title @if (height !== 'compact') {
Description
}
Secondary title @if (height !== 'compact') {
Another description
}
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiCell] | `'l' | 'm' | 's'` | Layout size | | [tuiCellHeight] | `'normal' | 'compact' | 'spacious'` | Height mode | ### Usage Examples #### Basic **Template:** ```html @for (size of sizes; track size) {
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 {TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiCell, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly sizes = ['s', 'm', 'l'] as const; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 2rem; } [tuiCell] { max-inline-size: 25rem; } ``` #### 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 {TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiBadge, TuiBadgedContent, TuiSwitch} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiAvatar, TuiBadge, TuiBadgedContent, TuiCell, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected incoming = false; protected outgoing = true; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } [tuiCell] { max-inline-size: 25rem; } ``` #### Left side **Template:** ```html
$30 our of $100
Saving for a Benjamin Franklin portrait
+$30
 
+2
Waterplea
100 ₽
Music
Buy
1234
Primary Card
**** **** **** 1234
Poster
Ridley Scott, 1982
Blade Runner
A blade runner must pursue and terminate four replicants who stole a ship in space and have returned to Earth to find their creator.
``` **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 {TuiThumbnailCard} from '@taiga-ui/addon-commerce'; import {TuiCell, TuiCheckbox, TuiIcon, TuiLink, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiAvatarStack, TuiBadge, TuiProgress} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiAvatar, TuiAvatarStack, TuiBadge, TuiCell, TuiCheckbox, TuiIcon, TuiLink, TuiProgress, TuiThumbnailCard, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value = false; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } [tuiCell] { max-inline-size: 25rem; } ``` #### Right side **Template:** ```html
Notifications
3
Read
Salary
Enough
Sky's the limit
``` **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 {TuiCell, TuiCheckbox, TuiIcon, TuiLoader, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiBadgeNotification, TuiSensitive, TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiAvatar, TuiBadgeNotification, TuiCell, TuiCheckbox, TuiIcon, TuiLoader, TuiSensitive, TuiTitle, TuiTooltip, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value = false; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } [tuiCell] { max-inline-size: 25rem; } ``` #### Long content **Template:** ```html
Long title in a cell will wrap to multiple lines and so will the subtitle
Use tuiAccessories to keep your side content properly aligned if you have many lines of text
Alternatively you can use fade to hide extra text using nowrap CSS
Works the same for subtitle when fade directive is applied to the top
Works with the right side
Works with fade on both sides
You can control proportions
Proportions are controlled with flex
Flex shrink is set to 70-30 by default
Alexander
Taiga UI developer
+$1000
Bonus for tuiCell component
Awesome!
Main account
USD
By default unless arrested
$123 456
Careful, content may overlap
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiCell, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiBadge, TuiBadgeNotification, TuiFade} from '@taiga-ui/kit'; @Component({ imports: [ TuiAvatar, TuiBadge, TuiBadgeNotification, TuiCell, TuiFade, TuiIcon, 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; } ``` #### Actions **Template:** ```html
Single action
Description of the action
Multiple actions
With no content on the right
}
Hover over
Put it before the right side
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiCell, TuiDropdown, TuiGroup, TuiTitle} from '@taiga-ui/core'; import {TuiDataListWrapper} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiCell, TuiDataListWrapper, TuiDropdown, TuiGroup, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ { icon: '@tui.phone', toString: () => 'Call now', }, { icon: '@tui.star', toString: () => 'Add to favorites', }, { icon: '@tui.trash', toString: () => 'Remove item', }, ]; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } [tuiCell] { max-inline-size: 25rem; } ``` #### Combinations **Template:** ```html

Inside a block

@for (item of items; track item) { }
Cell is used inside DataList options and Textfield template by default.
{{ item.title }} {{ item.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 {TuiCell, TuiNotification, TuiTextfield, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiCardLarge, TuiCell, TuiChevron, TuiDataListWrapper, TuiNotification, TuiSelect, TuiTextfield, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ { icon: '@tui.eye', title: 'Show more', subtitle: 'Ctrl + Shift + M', }, { icon: '@tui.mail', title: 'Send message', subtitle: 'Keep it short', }, { icon: '@tui.lock', title: 'Access', subtitle: 'Block your account', }, ]; protected value = this.items[0]!; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; max-inline-size: 20rem; gap: 1rem; } ``` #### Connected **Template:** ```html
Can be attached as host directive: hostDirectives: [TuiConnected]

Inside a block

@for (item of items; track 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 {TuiCell, TuiNotification, TuiTitle} from '@taiga-ui/core'; import { TuiAvatar, TuiBadge, TuiBadgedContent, TuiConnected, TuiSwitch, } from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiBadge, TuiBadgedContent, TuiCardLarge, TuiCell, TuiConnected, TuiNotification, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ { icon: '@tui.eye', title: 'Show more', subtitle: 'Ctrl + Shift + M', }, { icon: '@tui.mail', title: 'Send message', subtitle: 'Allow outgoing unusual call that can change your life in an unusual way', }, { icon: '@tui.lock', title: 'Access', subtitle: 'Block your account', }, ]; protected value = this.items[0]!; protected incoming = false; protected outgoing = true; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; max-inline-size: 20rem; gap: 1rem; } ``` #### Disabled state **Template:** ```html
Hover over
Put it before the right side
``` **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 { TuiAppearance, TuiButton, TuiCell, TuiCheckbox, TuiIcon, TuiLoader, TuiTitle, } from '@taiga-ui/core'; import {TuiBadgeNotification, TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiAppearance, TuiBadgeNotification, TuiButton, TuiCell, TuiCheckbox, TuiIcon, TuiLoader, TuiTitle, TuiTooltip, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value = false; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } .cell, [tuiCell] { inline-size: 100%; max-inline-size: 25rem; box-sizing: border-box; } ``` ### 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 {TuiCell, type TuiCellOptions, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiCell, TuiDemo, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', changeDetection, providers: [tuiDocExampleOptionsProvider({fullsize: true})], }) export default class Example { protected sizes = ['l', 'm', 's'] as const; protected size: TuiCellOptions['size'] = this.sizes[0]; protected heights = ['normal', 'compact', 'spacious'] as const; protected height: TuiCellOptions['height'] = this.heights[0]; protected readonly examples = [ 'Basic', 'Label', 'Left side', 'Right side', 'Long content', 'Actions', 'Combinations', 'Connected', 'Disabled state', ]; } ``` ### LESS ```less :host { tui-doc-demo::ng-deep .t-content { background: var(--tui-background-neutral-1); } [tuiCell] { background: var(--tui-background-elevation-1); } } ``` --- # components/Checkbox - **Package**: `CORE` - **Type**: components A checkbox 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 ### Usage Examples #### Example 1 **Template:** ```html @for (platform of platforms; track $index) {
{{ platform }}
} ``` **TypeScript:** ```ts import {Component, type OnInit} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiPlatform} from '@taiga-ui/cdk'; import {TuiCheckbox, type TuiSizeS} from '@taiga-ui/core'; @Component({ imports: [FormsModule, ReactiveFormsModule, TuiCheckbox, TuiPlatform], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example implements OnInit { protected readonly platforms: ReadonlyArray<'android' | 'ios' | 'web'> = [ 'web', 'web', 'ios', 'android', ]; protected readonly invalidTrue = new FormControl(true, () => ({invalid: true})); protected readonly invalidFalse = new FormControl(false, () => ({invalid: true})); public ngOnInit(): void { this.invalidTrue.markAsTouched(); this.invalidFalse.markAsTouched(); } protected getSize(first: boolean): TuiSizeS { return first ? 'm' : 's'; } } ``` **LESS:** ```less :host { display: flex; --tui-background-accent-2: var(--tui-status-info); } .wrapper { display: flex; flex-direction: column; justify-content: space-between; align-items: center; flex: 1; gap: 1rem; padding: 1rem; &_web { border: 1px solid var(--tui-border-normal); border-inline-start-width: 0; &:first-child { border-inline-end-width: 0; border-inline-start-width: 1px; } } } ``` #### Example 2 **Template:** ```html

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiCheckbox} from '@taiga-ui/core'; @Component({ imports: [TuiButton, TuiCheckbox], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected checked = false; } ``` #### Example 3 **Template:** ```html @if (currentQuestion < 2) {

{{ currentQuestion + 1 }}. {{ questionTitles[currentQuestion] }}

@for (option of questions[currentQuestion]; track option) { }
} @else {

Your answers

@for (options of results; track options; let i = $index) {

{{ i + 1 }}. {{ questionTitles[i] }}

@for (question of questions[i]; track question; let j = $index) { }
} } ``` **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, TuiCheckbox} from '@taiga-ui/core'; @Component({ imports: [FormsModule, ReactiveFormsModule, TuiButton, TuiCheckbox], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly questionTitles = [ 'What framework do you like?', 'What library do you like?', ]; protected readonly questions = [ ['Angular', 'React', 'Vue'], ['Taiga UI', 'Material UI', 'PrimeNG'], ]; protected currentQuestion = 0; protected results: boolean[][] = []; protected form = new FormGroup({ 0: new FormControl(false), 1: new FormControl(false), 2: new FormControl(false), }); protected nextQuestion(): void { this.currentQuestion++; this.results.push(Object.values(this.form.value).map(Boolean)); this.form = new FormGroup({ 0: new FormControl(false), 1: new FormControl(false), 2: new FormControl(false), }); } } ``` **LESS:** ```less label { display: flex; align-items: center; gap: 0.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 { public readonly examples = ['Platforms', 'Decorative', 'Form']; } ``` --- # components/Chip - **Package**: `KIT` - **Type**: components Chip component is used to display array data and can also be interactive depending on the tag used. ### Example ```html Chip ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [size] | `TuiSizeXS | TuiSizeL` | Size of the chip | ### Usage Examples #### Basic **Template:** ```html Default appearance is Neutral Primary Accent Positive Negative Warning Info Outline Floating ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiChip} from '@taiga-ui/kit'; @Component({ imports: [TuiChip], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less [tuiChip] { margin: 0.5rem; } ``` #### Sizes and content **Template:** ```html
@for (size of sizes; track size) { Text }
@for (size of sizes; track size) { Single icon }
@for (size of sizes; track size) { Duo icon }
@for (size of sizes; track size) { @if (size === 'm' || size === 's') { Avatar } Image }
@for (size of sizes; track size) { @if (size === 'm' || size === 's') {
} Avatar
}
Button @for (size of sizes | slice: 1; track size) { Button }
@for (size of sizes; track size) { Badge @if (size !== 'xxs') {
1
}
}
``` **TypeScript:** ```ts import {SlicePipe} from '@angular/common'; 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 {TuiAvatar, TuiBadge, TuiChip} from '@taiga-ui/kit'; @Component({ imports: [SlicePipe, TuiAvatar, TuiBadge, TuiButton, TuiChip], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly sizes = ['m', 's', 'xs', 'xxs'] as const; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } section { display: flex; align-items: center; gap: 0.5rem; } ``` #### Interactive **Template:** ```html

Checkbox

@for (_ of '-'.repeat(checked.length); track $index) { }

Radio

@for (_ of '-'.repeat(3); track $index) { }

Label outline

@for (_ of '-'.repeat(checked.length); track $index) { }

Label accent

@for (_ of '-'.repeat(checked.length); track $index) { }

Button

@for (_ of '-'.repeat(3); track $index) { }
@for (_ of '-'.repeat(3); track $index) { Close button {{ $index + 1 }} }

Input

@for (_ of '-'.repeat(values.length); track $index) { {{ values[$index] }} }
``` **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 {TuiButton, TuiCheckbox, TuiNotificationService} from '@taiga-ui/core'; import {TuiChip} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiButton, TuiCheckbox, TuiChip], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly alerts = inject(TuiNotificationService); protected readonly checked = [true, false, true]; protected readonly values = ['test', 'Some text', 'WOW!']; protected onChip(index: number): void { this.alerts.open(`Clicked chip ${index + 1}`).subscribe(); } protected onX(index: number): void { this.alerts .open(`Removed chip ${index + 1}`, {appearance: 'negative'}) .subscribe(); } } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } h3 { margin: 0; } section { display: flex; align-items: center; gap: 0.5rem; } ``` #### Use cases **Template:** ```html
Very long value in chip
{{ 123000 | tuiAmount: 'RUB' }}
Very long value in chip
Notifications Customized chip Toggle chip ``` **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 {TuiButton} from '@taiga-ui/core'; import {TuiBadgedContent, TuiBadgeNotification, TuiChip, TuiFade} from '@taiga-ui/kit'; @Component({ imports: [ TuiAmountPipe, TuiBadgedContent, TuiBadgeNotification, TuiButton, TuiChip, TuiFade, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected selected = false; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; } .fade { max-inline-size: 11rem; } .ellipsis { max-inline-size: 7rem; .text-overflow(); } [data-appearance='custom'] { color: #fff; background-image: linear-gradient(43deg, #4158d0 0%, #c850c0 46%, #ffcc70 100%); font-weight: bold; } .toggle { .transition(transform); color: inherit !important; } ``` #### Auto color **Template:** ```html @for (chip of chips; track chip) { {{ chip }} } ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAutoColorPipe, TuiChip} from '@taiga-ui/kit'; @Component({ imports: [TuiAutoColorPipe, TuiChip], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected chips = [ 'Free', 'Base', 'Pro', 'Enterprise', 'Premium', 'Ultimate', 'Starter', 'Advanced', 'Business', 'Enterprise', 'Personal', 'Standard', 'Essential', 'Professional', 'Deluxe', 'Gold', 'Silver', 'Bronze', 'Plus', 'Basic', ]; } ``` **LESS:** ```less [tuiChip] { margin: 0.5rem; } ``` #### Fade in complex designs **Template:** ```html
Supermarkets
{{ 9000 | tuiAmount: 'USD' }}
AI
{{ 9870043000 | tuiAmount: 'USD' }}
Long category name

Use CSS to set a custom priority for the fade:

Miscellaneous goods
{{ 105000 | tuiAmount: 'USD' }}
``` **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; ``` **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) { } } }
``` **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) { }

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 ``` ### 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 ``` **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 account 1234 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) {

{{ font.key }}

} ``` **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

Roman Sedov

``` **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

250g
``` **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) { } ``` ### 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 @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 @let frenchFries = 'French Fries'; @let item = 'Ice Cream'; @for (burger of burgers; track burger) { } @for (drink of drinks; track 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

} @for (drink of drinks; track drink) { }

{{ value }}

Separate toggles

``` **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
  • {{ 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 @for (group of groups; track group) { @for (item of group.items; track 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 ``` ### 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

``` **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
``` **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 ``` **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
``` **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

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

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.

@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

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

``` **TypeScript:** ```ts import {Component, ViewEncapsulation} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiButton, TuiDialog, TuiTitle} from '@taiga-ui/core'; import {TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [TuiButton, TuiDialog, TuiHeader, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation: ViewEncapsulation.None, changeDetection, }) export default class Example { protected augmented = false; protected custom = false; } ``` **LESS:** ```less [data-appearance~='compact'] { padding: 1rem !important; border-radius: var(--tui-radius-s) !important; } [data-appearance='sheet'] { inline-size: 100vw; align-self: flex-end; padding: 1.25rem 1rem ~'max(1.25rem, env(safe-area-inset-bottom))'; --tui-from: translate3d(0, 100vh, 0); &.tui-enter, &.tui-leave { animation-name: tuiSlide; } > header, > ng-component > header { font: var(--tui-typography-heading-h6); } > p, > ng-component > p { margin: 1rem 0 0; } > button { background: none !important; } } ``` ### TypeScript ```ts import {Component, inject, type TemplateRef} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiAutoFocus} from '@taiga-ui/cdk'; import { TUI_DIALOG_OPTIONS, TuiButton, type TuiDialogContext, TuiDialogService, TuiInput, TuiNotificationService, } from '@taiga-ui/core'; import {TuiForm} from '@taiga-ui/layout'; import {switchMap} from 'rxjs'; @Component({ imports: [TuiAutoFocus, TuiButton, TuiDemo, TuiForm, TuiInput], templateUrl: './index.html', changeDetection, }) export default class Page extends Array { private readonly alerts = inject(TuiNotificationService); private readonly dialogs = inject(TuiDialogService); private readonly options = inject(TUI_DIALOG_OPTIONS); protected readonly routes = DemoRoute; protected readonly examples = [ 'String', 'Directive', 'Component', 'Confirmation', 'Closing', 'Fullscreen', 'Customization', ]; protected readonly [2] = { 'component.ts': import('./examples/3/component.ts?raw', {with: {loader: 'text'}}), }; protected readonly [4] = { 'service.ts': import('./examples/5/service.ts?raw', {with: {loader: 'text'}}), }; protected data = 'Data'; protected label = 'Label'; protected closable = this.options.closable; protected required = this.options.required; protected dismissible = this.options.dismissible; protected readonly appearances = ['taiga', 'fullscreen'] as const; protected appearance = this.options.appearance; protected readonly sizes = ['s', 'm', 'l'] as const; protected size = this.options.size; protected showDialog(content: TemplateRef>): void { const {data, label, required, closable, dismissible, size, appearance} = this; this.dialogs .open(content, { data, label, required, closable, dismissible, size, appearance, }) .pipe(switchMap((response) => this.alerts.open(response))) .subscribe(); } } ``` --- # components/Routable - **Package**: `null` - **Type**: null ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import Example1 from './1'; import Example2 from './2'; import Example3 from './3'; @Component({ imports: [Example1, Example2, Example3, TuiDemo], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly example1 = { HTML: import('./1/index.html'), Typescript: import('./1/index.ts?raw', {with: {loader: 'text'}}), Routes: import('./routes.ts?raw', {with: {loader: 'text'}}), Dialog: import('./1/dialog.component.ts?raw', {with: {loader: 'text'}}), }; protected readonly example2 = { HTML: import('./2/index.html'), TypeScript: import('./2/index.ts?raw', {with: {loader: 'text'}}), Routes: import('./routes.ts?raw', {with: {loader: 'text'}}), Dialog: import('./2/dialog.component.ts?raw', {with: {loader: 'text'}}), }; protected readonly example3 = { HTML: import('./3/index.html'), TypeScript: import('./3/index.ts?raw', {with: {loader: 'text'}}), Routes: import('./routes.ts?raw', {with: {loader: 'text'}}), Dialog: import('./3/dialog.component.ts?raw', {with: {loader: 'text'}}), }; } ``` --- # components/Drawer - **Package**: `KIT` - **Type**: components ### Example ```html

Header

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

Caption・caption Drawer title
Label
In publishing and graphic design, Lorem ipsum is a placeholder text commonly used.

Action 2
@for (_ of '-'.repeat(15); track $index) {

Content

}
``` **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

Sticky header

@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 }} ``` **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
}
``` **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 @for (item of items; track item) {

Nested form

}
``` **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

If you want to show a validation message as soon as a user starts typing, subscribe on form value changes and call markAsTouched on control on first value change.
Below is an error for the entire form:
``` **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

Contact list

@for (phone of form.controls.phones.controls; track $index) { }
``` **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 @for (control of controls; track $index) { }
Name Price
{{ 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.

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 @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 @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 }}
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 TuiHandler, type TuiIdentityMatcher} from '@taiga-ui/cdk'; import {TuiFilter} from '@taiga-ui/kit'; interface Operations { operations: readonly Operation[]; title: string; } interface Operation { amount: number; } const COMPLETED = { title: 'Done', operations: [{amount: 100}, {amount: 200}], }; @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([{title: 'Drafts'}]), }); protected items: readonly Operations[] = [ COMPLETED, { title: 'Drafts', operations: [{amount: 100}, {amount: 200}, {amount: 100}, {amount: 100}], }, { title: 'For sign', operations: [], }, { title: 'Queue', operations: [ {amount: 100}, {amount: 200}, {amount: 100}, {amount: 200}, {amount: 100}, {amount: 200}, ], }, ]; protected identityMatcher: TuiIdentityMatcher = ( item1: Operations, item2: Operations, ) => item1.title === item2.title; protected badgeHandler: TuiHandler = (item) => item.operations.length; } ``` **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; } ``` #### Custom **Template:** ```html
{{ item }}
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 {TuiIcon} from '@taiga-ui/core'; import {TuiFilter} from '@taiga-ui/kit'; const getIcon: Record = { Calendar: '@tui.calendar', Favorite: '@tui.star', Messages: '@tui.message-square', FAQ: '@tui.circle-help', Settings: '@tui.settings', }; @Component({ imports: [JsonPipe, ReactiveFormsModule, TuiFilter, TuiIcon], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected items = ['Calendar', 'Favorite', 'Messages', 'FAQ', 'Settings']; protected form = new FormGroup({filters: new FormControl([])}); protected getItemIcon(title: string): string { return getIcon[title] ?? ''; } } ``` **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 all button **Template:** ```html
Choose a department:
``` **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 {TuiButton} from '@taiga-ui/core'; import {TuiFilter} from '@taiga-ui/kit'; import {BehaviorSubject, map} from 'rxjs'; const Department = { IT: 'IT', HR: 'HR', HeadOffice: 'Heads', Delivery: 'Delivery', Admin: 'Administrative', Business: 'Business lines', MB: 'Business technologies', Finance: 'Corporate Finance', Payment: 'Payment Systems', Operating: 'Operating service lines', Marketing: 'Media-marketing', Security: 'Security Service', } as const; @Component({ imports: [AsyncPipe, FormsModule, TuiButton, TuiFilter], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = Object.values(Department); protected readonly filters$ = new BehaviorSubject([]); protected readonly checked$ = this.filters$.pipe( map(({length}) => (length === this.items.length ? 'checked' : '')), ); protected readonly model$ = this.filters$.pipe( map((value) => (value.length === this.items.length ? [] : value)), ); protected onModelChange(model: readonly string[]): void { this.filters$.next(model); } protected toggleAll(): void { this.filters$.next( this.items.length === this.filters$.value.length ? [] : [...this.items], ); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .item { display: inline; vertical-align: bottom; } ``` ### 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 {TUI_FALSE_HANDLER, type TuiBooleanHandler, type TuiHandler} from '@taiga-ui/cdk'; import {type TuiSizeL, type TuiSizeS} from '@taiga-ui/core'; import {TuiFilter} from '@taiga-ui/kit'; class ItemWithBadge { constructor( public readonly text: string, public readonly badgeValue?: number, ) {} public toString(): string { return this.text; } public valueOf(): number | null { return this.badgeValue || null; } } @Component({ imports: [ReactiveFormsModule, TuiDemo, TuiFilter], templateUrl: './index.html', changeDetection, }) export default class Page { protected initialItems = ['Alex Inkin', 'Roman Sedov']; protected itemsVariants: Array> = [ ['Alex Inkin', 'Roman Sedov'], [ new ItemWithBadge('Focused Zone', 10), new ItemWithBadge('Dropdown', 100), new ItemWithBadge('Menu Items', 30), new ItemWithBadge('Accordion'), ], ]; protected readonly examples = ['Basic', 'With badges', 'Custom', 'With all button']; protected badgeHandlerVariants: ReadonlyArray> = [ (item) => Number(item), (item) => String(item).length, ]; protected badgeHandler = this.badgeHandlerVariants[0]!; protected disabledItemHandlerVariants: ReadonlyArray< TuiBooleanHandler > = [ TUI_FALSE_HANDLER, (item) => item === 'Roman Sedov', (item) => (Number(item.valueOf()) || 0) >= 30, ]; protected disabledItemHandler = this.disabledItemHandlerVariants[0]!; protected items = this.itemsVariants[0]!; protected control = new FormControl(this.initialItems); protected readonly sizeVariants: ReadonlyArray = ['s', 'm', 'l']; protected size = this.sizeVariants[2]!; } ``` --- # components/FloatingContainer - **Package**: `LAYOUT` - **Type**: components FloatingContainer is a special container for creating different animated sticky footers ### Example ```html

@for (_ of '-'.repeat(30); track $index) {
Title
Description
} @if (floating) {
}
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiFloatingContainer] | `string` | Background | ### Usage Examples #### Basic **Template:** ```html

Scroll to see the floating

@for (_ of '-'.repeat(30); track $index) {
Title
Description
} @if (floating) {
}
``` **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} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiButton, TuiCell, TuiExpand, TuiFloatingContainer, TuiLabel, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected floating = false; protected secondAction = false; protected onScroll(el: HTMLElement): void { this.floating = el.scrollTop > 100; this.secondAction = el.scrollTop > 500; } } ``` **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; } ``` #### Sheet **Template:** ```html

Title

Subtitle

@for (item of items | tuiFilter: filter : search; track item) {
{{ item.title }}
{{ item.description }}
}
@if (floating) {
}
``` **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 {TuiSheetDialog} from '@taiga-ui/addon-mobile'; import {TUI_DEFAULT_MATCHER, TuiFilterPipe, type TuiMatcher} from '@taiga-ui/cdk'; import {TuiButton, TuiCell, TuiExpand, TuiLabel, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiSwitch} from '@taiga-ui/kit'; import {TuiFloatingContainer, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiButton, TuiCell, TuiExpand, TuiFilterPipe, TuiFloatingContainer, TuiHeader, TuiLabel, TuiSheetDialog, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected open = false; protected floating = true; protected secondAction = false; protected search = ''; protected readonly items = Array.from({length: 15}, (_, index) => ({ title: `Title ${index + 1}`, description: `Description ${index + 1}`, })); protected readonly filter: TuiMatcher<[(typeof this.items)[0], string]> = ( item, search, ) => TUI_DEFAULT_MATCHER(item.title, search); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .content { flex-grow: 1; margin: 0 -1rem; } .input { .tui-prevent-ios-scroll(); inline-size: 100%; margin-block-start: 0.25rem; } ``` #### Text **Template:** ```html

@for (_ of '-'.repeat(30); track $index) {
Title
Description
} @if (floating) {
@if (additional) { } @else {
Legal text, max 3 lines
}
}
``` **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, TuiIcon, TuiLabel, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiSwitch} from '@taiga-ui/kit'; import {TuiElasticContainer, TuiFloatingContainer, TuiSlides} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiButton, TuiCell, TuiElasticContainer, TuiFloatingContainer, TuiIcon, TuiLabel, TuiSlides, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected floating = true; protected additional = false; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils.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; } .clamp { .tui-line-clamp(); margin-block-start: 0.5rem; min-block-size: 2rem; &.tui-enter, &.tui-leave { animation-name: tuiFade; } } ``` #### Content **Template:** ```html

@for (_ of '-'.repeat(30); track $index) {
Title
Description
} @if (value === 'primary') {
} @if (value === 'card') {
1000 $
With price
} @if (value === 'actions') {
}
``` **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, TuiLabel, TuiRadio, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge, TuiFloatingContainer, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiFloatingContainer, TuiHeader, TuiLabel, TuiRadio, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value = ''; } ``` **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); } .static { animation: none; } footer { margin-inline: 1rem; } ``` #### Overlay **Template:** ```html

@for (_ of '-'.repeat(30); track $index) {
Title
Description
} @if (floating) {
}
``` **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, TuiInputColor, tuiInputColorOptionsProvider, TuiSwitch, } from '@taiga-ui/kit'; import {TuiFloatingContainer} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiButton, TuiCell, TuiExpand, TuiFloatingContainer, TuiInputColor, TuiLabel, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [tuiInputColorOptionsProvider({format: 'hexa', align: 'end'})], }) export default class Example { protected floating = true; protected secondAction = false; protected background = true; protected color = 'rgba(255, 221, 45, 0.8)'; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils.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; } .label { .tui-slider-ticks-labels(); } ``` #### Crossfade **Template:** ```html

@for (_ of '-'.repeat(30); track $index) {
Title
Description
} @if (floating) {
@if (action) { } @else { }
}
``` **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

Registration form Tell us about yourself

Authenticity required Please be honest and use your real data

@if (!form.controls.basic.value) { }
``` **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

A header Form with large controls

from 50 000 ₽ to 3 000 000 ₽
``` **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 @switch (segmentedIndex) { @case (0) { } @case (1) { } } @switch (index()) { @case (0) {

Registration form Tell us about yourself

Optional features
} @case (1) {

Configuration network Fill network settings

} } ``` **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
``` ### 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
``` **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) { } ``` **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) { } ``` **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
@for (item of items; track item) { }
``` **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 custom-icon ``` **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

``` **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) { } ``` **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 ``` **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 ``` **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 ``` **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
``` **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') { } ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [align] | `TuiHorizontalDirection` | Alignment of the color picker | | [format] | `'hex' | 'hexa'` | Color format | ### Usage Examples #### Basic **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 {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 ``` **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') { } ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [min] | `TuiDay` | Min date | | [max] | `TuiDay` | Max date | ### 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 {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 ``` **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 ``` **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

{{ 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 ``` **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 ``` **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 @for (date of dates | keyvalue: asIs; track date) { }
@for (date of dates | keyvalue: asIs; track date) { }
``` **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 ``` **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 ``` **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') { } ``` ### 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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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') { } ``` ### 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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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) { }
@for (date of dates | keyvalue: asIs; track date) { }
``` **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) { } @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 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
(Show placeholder if control is empty)
``` **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 }} }

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 ``` **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 ``` **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') { } ``` ### 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

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

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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` ### 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 ``` **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 @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

{{ 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 Flag of the United States ``` **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') { } ``` ### 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 ``` **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 ``` **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 ``` **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 ``` **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
@for (label of ticksLabels; track label) { {{ label }} }

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 {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: {{ 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 {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: {{ value | json }}

``` **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
20% 40% 60% 80%
``` **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
@for (label of ticksLabels; track label) { {{ label }} }

Control value: {{ 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 TuiKeySteps} from '@taiga-ui/core'; import {TuiInputSlider} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputSlider], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value = 10; protected readonly min = 0; protected readonly max = 1_000; protected readonly step = 5; // 100% / 5% = 20 total steps protected readonly ticksLabels = ['0', '10', '100', '500', '1000']; protected readonly keySteps: TuiKeySteps = [ // [percent, value] [0, this.min], [25, 10], [50, 100], [75, 500], [100, this.max], ]; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .slider-ticks-labels { .tui-slider-ticks-labels(); } ``` #### Quantum **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 {TuiInputSlider} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, JsonPipe, TuiInputSlider], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value = 0.5; // 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; } ``` ### TypeScript ```ts import {Component, computed, type Signal, signal} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {TuiDocControl} from '@demo/components/control'; import {TuiDocIcons} from '@demo/components/icons'; 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, TuiNumberFormat, TuiTitle} from '@taiga-ui/core'; import {TuiInputSlider} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiDemo, TuiDocControl, TuiDocIcons, TuiDocNumberFormat, TuiDocTextfield, TuiInputSlider, TuiNumberFormat, TuiTitle, ], templateUrl: './index.html', changeDetection, }) export default class PageComponent { protected readonly routes = DemoRoute; protected readonly control = new FormControl(0); protected readonly min = signal(0); protected readonly max = signal(100); protected prefix = ''; protected postfix = ''; protected quantum = 0; protected segments: number[] | number = 1; protected step = 1; protected keySteps: TuiKeySteps | null = null; protected thumbSize = 12; protected readonly segmentsVariants = [1, 5, 3, [0.2, 0.5], [0.1, 0.3]]; protected readonly keyStepsVariants: Signal = computed(() => [ [ [0, this.min()], [50, 0.1 * this.max()], [100, this.max()], ], ]); protected readonly textfieldContentVariants = computed(() => [ '', 'TOP SECRET', ({$implicit: val}: TuiContext) => (val === this.max() ? 'MAX' : val), ({$implicit: val}: TuiContext) => (val === this.min() ? 'MIN' : val), ({$implicit: val}: TuiContext) => (val === 5 ? 'FIVE' : val), ]); protected readonly examples = [ 'Textfield customization', 'InputNumber customization', 'Slider customization', 'KeySteps', 'Quantum', ]; } ``` --- # components/InputTime - **Package**: `KIT` - **Type**: components InputTime allows users to enter and edit time values using a keyboard, dropdown or even browser native picker. ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [mode] | `MaskitoTimeMode` | Time format mode | | [prefix] | `string` | time | | [postfix] | `string` | time | | [accept] | `ReadonlyArray` | values to choose | ### Usage Examples #### Mode **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 MaskitoTimeMode} from '@maskito/kit'; import {TuiTime} from '@taiga-ui/cdk'; import {TuiInputTime} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputTime], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly mode: MaskitoTimeMode = 'HH:MM:SS.MSS'; protected value: TuiTime | null = new TuiTime(23, 59, 59, 999); } ``` #### Native picker with suggestions **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 TuiTime} from '@taiga-ui/cdk'; import {TuiInputTime} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputTime], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: TuiTime | null = null; } ``` #### 12-hour format with AM/PM **Template:** ```html

Control value:

{{ 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 {TuiTime} from '@taiga-ui/cdk'; import {TuiInputTime} from '@taiga-ui/kit'; @Component({ imports: [JsonPipe, ReactiveFormsModule, TuiInputTime], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly control = new FormControl(new TuiTime(17, 0)); } ``` #### Form control 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 {TuiInputTime, TuiUnfinishedValidator} from '@taiga-ui/kit'; import {TuiForm} from '@taiga-ui/layout'; @Component({ imports: [ ReactiveFormsModule, TuiButton, TuiError, TuiForm, TuiInputTime, 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(), }); } ``` #### Options **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 {TuiTime} from '@taiga-ui/cdk'; import {TuiInputTime, tuiInputTimeOptionsProvider} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputTime], templateUrl: './index.html', encapsulation, changeDetection, providers: [ tuiInputTimeOptionsProvider({ icon: '@tui.timer', mode: 'HH:MM:SS.MSS', timeSegmentMaxValues: {hours: 99}, }), ], }) export default class Example { protected value: TuiTime | null = new TuiTime(99, 59, 59, 999); } ``` #### 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 {TuiTime} from '@taiga-ui/cdk'; import {TuiIcon} from '@taiga-ui/core'; import {TuiInputTime, tuiInputTimeOptionsProvider, TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiIcon, TuiInputTime, TuiTooltip], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiInputTimeOptionsProvider({icon: ''})], }) export default class Example { protected value: TuiTime | null = new TuiTime(9, 0); } ``` #### Strict mode **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 {TuiTime} from '@taiga-ui/cdk'; import {TuiIcon} from '@taiga-ui/core'; import {tuiCreateTimePeriods, TuiInputTime, TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiIcon, TuiInputTime, TuiTooltip], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: TuiTime | null = null; protected acceptableValues = [ // Array of TuiTime from 10:00 to 18:00 every half of hour ...tuiCreateTimePeriods(10, 18, [0, 30]), new TuiTime(18, 0), ]; } ``` #### Dropdown with DataList **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 {type TuiBooleanHandler, TuiTime} from '@taiga-ui/cdk'; import { type TuiFilterByInputOptions, TuiFilterByInputPipe, tuiItemsHandlersProvider, } from '@taiga-ui/core'; import {tuiCreateTimePeriods, TuiDataListWrapper, TuiInputTime} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiDataListWrapper, TuiFilterByInputPipe, TuiInputTime], templateUrl: './index.html', encapsulation, changeDetection, providers: [ /** * You can also use input props of `Textfield` * (they will have more priority): * ```html * * ``` */ tuiItemsHandlersProvider({ stringify: signal((x: TuiTime) => x.toString('HH:MM')), identityMatcher: signal( (a: TuiTime | null, b: TuiTime | null) => a?.valueOf() === b?.valueOf(), ), // disabledItemHandler: signal((x: TuiTime) => x.hours > 18), }), ], }) export default class Example { protected value: TuiTime | null = null; protected items: readonly TuiTime[] = [ new TuiTime(16, 20), new TuiTime(16, 45), new TuiTime(17, 0), ...tuiCreateTimePeriods(18, 20, [0, 15, 30, 45]), ]; protected readonly disabledItemHandler: TuiBooleanHandler = (x) => x?.valueOf() === this.items[0]!.valueOf(); protected readonly filter: TuiFilterByInputOptions['filter'] = ( items, query, ) => items.filter((time) => time.toString('HH:MM').startsWith(query)); } ``` #### Transformer **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 {TuiTime} from '@taiga-ui/cdk'; import {TuiInputTime, tuiInputTimeOptionsProvider} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, JsonPipe, TuiInputTime], templateUrl: './index.html', encapsulation, changeDetection, providers: [ tuiInputTimeOptionsProvider({ valueTransformer: { fromControlValue(controlValue: string): TuiTime | null { return controlValue ? TuiTime.fromString(controlValue) : null; }, toControlValue(time: TuiTime | null): string { return time ? time.toString() : ''; }, }, }), ], }) export default class Example { protected value = ''; } ``` #### Native picker **Template:** ```html @for (mode of supportedModes; track mode) { } ``` **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 MaskitoTimeMode} from '@maskito/kit'; import {TuiTime} from '@taiga-ui/cdk'; import {TuiInputTime} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputTime], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly supportedModes: readonly MaskitoTimeMode[] = [ 'HH:MM', 'HH:MM AA', 'HH:MM:SS', 'HH:MM:SS AA', 'HH:MM:SS.MSS', 'HH:MM:SS.MSS AA', ]; protected readonly initialValue = new TuiTime(23, 59, 59, 999); } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } ``` ### 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 {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {type MaskitoTimeMode} from '@maskito/kit'; import {TuiTime} from '@taiga-ui/cdk'; import {TuiInput} from '@taiga-ui/core'; import {TuiInputTime} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiDemo, TuiDocControl, TuiDocInput, TuiDocTextfield, TuiInput, TuiInputTime, ], templateUrl: './index.html', changeDetection, }) export default class PageComponent { protected readonly routes = DemoRoute; protected readonly control = new FormControl(null); protected readonly examples = [ 'Mode', '12-hour format with AM/PM', 'Form control validation', 'Options', 'Textfield customization', 'Strict mode', 'Dropdown with DataList', 'Transformer', 'Native picker', 'Native picker with suggestions', ]; protected readonly modeVariants = [ 'HH:MM', 'HH:MM AA', 'HH:MM:SS', 'HH:MM:SS AA', 'HH:MM:SS.MSS', 'HH:MM:SS.MSS AA', 'MM:SS', ] as const satisfies readonly MaskitoTimeMode[]; protected readonly acceptVariants = [ [], [12, 13, 14, 15, 16, 17, 18].map((x) => new TuiTime(x, 0)), ] as const satisfies ReadonlyArray; protected mode: MaskitoTimeMode = this.modeVariants[0]; protected accept: readonly TuiTime[] = this.acceptVariants[0]; protected prefix = ''; protected postfix = ''; } ``` --- # components/InputYear - **Package**: `KIT` - **Type**: components Component to input a single year ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [min] | `number` | Minimum year | | [max] | `number` | Maximum year | ### 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 {TuiInputYear} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputYear], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: number | null = null; } ``` #### Limits **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} from '@taiga-ui/cdk'; import {TuiInputYear} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputYear], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: number | null = null; protected readonly disabledHandler: TuiBooleanHandler = (value) => [2020, 2022].includes(value); } ``` #### Transformer **Template:** ```html

Control value: {{ 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 {TuiYear} from '@taiga-ui/cdk'; import {tuiInputInputYearOptionsProvider, TuiInputYear} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputYear], templateUrl: './index.html', encapsulation, changeDetection, providers: [ tuiInputInputYearOptionsProvider({ valueTransformer: { fromControlValue: (date: TuiYear | null): number | null => date?.year ?? null, toControlValue: (year: number | null): TuiYear | null => typeof year === 'number' ? new TuiYear(year) : null, }, }), ], }) export default class Example { protected value: TuiYear | null = new TuiYear(new Date().getFullYear()); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule, Validators} 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 {TUI_FIRST_DAY, TUI_LAST_DAY} from '@taiga-ui/cdk'; import {TuiDropdown} from '@taiga-ui/core'; import {TuiInputYear} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, ReactiveFormsModule, TuiDemo, TuiDocControl, TuiDocDropdown, TuiDocInput, TuiDocItemsHandlers, TuiDocTextfield, TuiDropdown, TuiInputYear, ], templateUrl: './index.html', changeDetection, }) export default class PageComponent { protected readonly examples = ['Basic', 'Limits', 'Transformer']; protected readonly value = new FormControl(null); protected readonly minVariants = [TUI_FIRST_DAY.year, 2019, 2007]; protected readonly maxVariants = [TUI_LAST_DAY.year, 2020, 2023]; protected min = this.minVariants[0]!; protected max = this.maxVariants[0]!; protected readonly routes = DemoRoute; public control = new FormControl(null, Validators.required); protected readonly handler = (year: number): boolean => year % 3 === 0; } ``` --- # components/ItemGroup - **Package**: `LAYOUT` - **Type**: components ### Example ```html
@for (chip of chips; track chip) { }
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | horizontal | `boolean` | Horizontal layout (for mobile devices) | | autoscroll | `boolean` | Enable scrolling to selected chip (for interactive chips in horizontal layout) | ### Usage Examples #### Basic **Template:** ```html
@for (chip of chips; track chip) { {{ chip }} }
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiChip} from '@taiga-ui/kit'; import {TuiItemGroup} from '@taiga-ui/layout'; @Component({ imports: [TuiChip, TuiItemGroup], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly chips = [ 'Indian cuisine', 'Wi-Fi', 'Free parking', 'Pets allowed', 'Pool', 'Air conditioning', 'Breakfast', 'Gym', 'Kitchen', 'Laundry', 'Luggage storage', 'Outdoor seating', 'Room service', 'Smoking allowed', ]; } ``` #### Single choice **Template:** ```html
@for (chip of chips; track chip) { }
``` **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 {TuiPlatform} from '@taiga-ui/cdk'; import {TuiChip} from '@taiga-ui/kit'; import {TuiItemGroup} from '@taiga-ui/layout'; @Component({ imports: [FormsModule, TuiChip, TuiItemGroup, TuiPlatform], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly chips = [ 'Indian cuisine', 'Wi-Fi', 'Free parking', 'Pets allowed', 'Pool', 'Air conditioning', 'Breakfast', 'Gym', 'Kitchen', 'Laundry', 'Luggage storage', 'Outdoor seating', 'Room service', 'Smoking allowed', ]; protected selected = 'Wi-Fi'; } ``` **LESS:** ```less :host { display: block; max-inline-size: 30rem; } ``` #### Multiple choice **Template:** ```html
@for (chip of chips; track chip) { }
``` **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_FALSE_HANDLER} from '@taiga-ui/cdk'; import {TuiChip} from '@taiga-ui/kit'; import {TuiItemGroup} from '@taiga-ui/layout'; @Component({ imports: [FormsModule, TuiChip, TuiItemGroup], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly chips = [ 'Indian cuisine', 'Wi-Fi', 'Free parking', 'Pets allowed', 'Pool', 'Air conditioning', 'Breakfast', 'Gym', 'Kitchen', 'Laundry', 'Luggage storage', 'Outdoor seating', 'Room service', 'Smoking allowed', ]; protected checked = this.chips.map(TUI_FALSE_HANDLER); } ``` **LESS:** ```less :host { display: block; max-inline-size: 30rem; } ``` #### With more **Template:** ```html
@for (chip of chips; track chip) { }
``` **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 {TuiChip, TuiItemsWithMore} from '@taiga-ui/kit'; import {TuiItemGroup} from '@taiga-ui/layout'; @Component({ imports: [FormsModule, TuiChip, TuiItemGroup, TuiItemsWithMore], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected linesLimit = 2; protected readonly chips = [ 'Indian cuisine', 'Wi-Fi', 'Free parking', 'Pets allowed', 'Pool', 'Air conditioning', 'Breakfast', 'Gym', 'Kitchen', 'Laundry', 'Luggage storage', 'Outdoor seating', 'Room service', 'Smoking allowed', ]; protected selected = 'Wi-Fi'; } ``` **LESS:** ```less :host { display: block; max-inline-size: 30rem; } ``` ### 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 {TuiChip} from '@taiga-ui/kit'; import {TuiItemGroup} from '@taiga-ui/layout'; @Component({ selector: 'example-chip', imports: [FormsModule, TuiChip, TuiDemo, TuiItemGroup], templateUrl: './index.html', changeDetection, }) export default class Example { protected readonly examples = [ 'Basic', 'Single choice', 'Multiple choice', 'With more', ]; protected readonly chips = [ 'Indian cuisine', 'Wi-Fi', 'Free parking', 'Pets allowed', 'Pool', 'Air conditioning', 'Breakfast', 'Gym', 'Kitchen', 'Laundry', 'Luggage storage', 'Outdoor seating', 'Room service', 'Smoking allowed', ]; protected selected = this.chips[7]; protected horizontal = false; protected autoscroll = false; } ``` --- # components/ItemsWithMore - **Package**: `KIT` - **Type**: components Component to hide overflown items behind custom content. Resize the screen to see extra items disappear ### Example ```html @for (item of items; track item) { {{ item }} } and now! ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [itemsLimit] | `number` | Artificial limit on visible items | | [linesLimit] | `number` | Limit on visible lines | | [required] | `number` | Index of an item that must remain visible | | [side] | `number` | Side of the "See more" content (for one-line mode only) | ### Usage Examples #### Basic **Template:** ```html @for (item of items; track item) { {{ item }} } and {{ getRemaining(index) }} more ``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiItem} from '@taiga-ui/cdk'; import {TuiChip, TuiItemsWithMore} from '@taiga-ui/kit'; @Component({ imports: [TuiChip, TuiItem, TuiItemsWithMore], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Page { protected readonly items = inject('Pythons' as any); protected readonly required = 3; protected getRemaining(index: number): number { const offset = index < this.required ? index + 2 : index + 1; return this.items.length - offset; } } ``` #### Dropdown **Template:** ```html @for (item of items; track item) { } @for (item of items; track item) { @if ($index > lastIndex) { } } ``` **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 {TUI_FALSE_HANDLER, TuiItem} from '@taiga-ui/cdk'; import {TuiButton, TuiDataList, TuiDropdown, TuiGroup, TuiIcon} from '@taiga-ui/core'; import {TuiBadge, TuiBlock, TuiItemsWithMore} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiBadge, TuiBlock, TuiButton, TuiDataList, TuiDropdown, TuiGroup, TuiIcon, TuiItem, TuiItemsWithMore, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Page { protected readonly items = inject('Pythons' as any); protected value = this.items.map(TUI_FALSE_HANDLER) as boolean[]; } ``` **LESS:** ```less .item { border-radius: inherit; font-weight: normal; } ._hidden { visibility: hidden; } tui-icon { inline-size: 1rem; block-size: 1rem; } ``` #### Side **Template:** ```html @for (item of items; track item) { @if (!$first) { } } @for (item of items; track item) { @if ($index < index) { } } ``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiDataList, TuiDropdown, TuiIcon} from '@taiga-ui/core'; import {TuiItemsWithMore} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiDataList, TuiDropdown, TuiIcon, TuiItemsWithMore], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Page { protected readonly items = inject('Pythons' as any); } ``` #### Multiline **Template:** ```html @for (item of items; track item) { {{ item }} item } ``` **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 {TuiChip, TuiItemsWithMore} from '@taiga-ui/kit'; @Component({ imports: [TuiChip, TuiItem, TuiItemsWithMore], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Page { protected readonly items = [ 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', ]; protected linesLimit = 2; protected lastIndex = Infinity; protected getRemaining(index: number): number { const offset = index + 1; return this.items.length - offset; } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: block; max-inline-size: 30rem; } .container { max-block-size: 4.5rem; overflow: hidden; &_expanded { max-block-size: 100%; } } .item { .transition(~'transform, opacity'); opacity: 0; transform: scale(0.9); margin: 0 0.25rem 0.25rem 0; &_visible { opacity: 1; transform: scale(1); } } .more { margin: 0 0.25rem 0.25rem 0; } ``` #### Cell **Template:** ```html {{ item.name }}  • {{ item.number }}
@if (index < 0) { {{ item.name }} }  *{{ item.number.slice(-4) }}
${{ item.value | tuiFormatNumber }}
``` **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 {TuiFormatNumberPipe, TuiTextfield, TuiTitle} from '@taiga-ui/core'; import { TuiAvatar, TuiChevron, TuiDataListWrapper, TuiItemsWithMore, TuiSelect, } from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiAvatar, TuiChevron, TuiDataListWrapper, TuiFormatNumberPipe, TuiItemsWithMore, TuiSelect, TuiTextfield, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Page { protected readonly items = [ { name: 'Very very long account name', number: '1234 5678 9101 2345', value: 12345678, }, { name: 'Short title', number: '8888 8888 8888 8888', value: 237, }, { name: 'Taiga UI is a super awesome library', number: '4444 3333 2222 1111', value: 76543, }, ]; protected value = this.items[0]; } ``` **LESS:** ```less [tuiCell], [tuiTitle] { flex: 1; } div { display: flex; } span:first-child { overflow: hidden; text-overflow: ellipsis; } ``` ### 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 {TuiChip, TuiItemsWithMore} from '@taiga-ui/kit'; @Component({ imports: [TuiChip, TuiDemo, TuiItemsWithMore], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly routes = DemoRoute; protected readonly examples = ['Basic', 'Dropdown', 'Side', 'Multiline', 'Cell']; protected readonly items = inject('Pythons' as any); protected readonly requiredVariants = [-1, 2, 4]; protected readonly itemsLimitVariants = [Infinity, 4, 2]; protected readonly linesLimitVariants = [1, 2, Infinity]; protected readonly sideVariants = ['start', 'end'] as const; protected side: 'end' | 'start' = this.sideVariants[1]; protected required = this.requiredVariants[0]!; protected itemsLimit = this.itemsLimitVariants[0]!; protected linesLimit = this.linesLimitVariants[0]!; } ``` --- # components/Label - **Package**: `CORE` - **Type**: components Label is used to show text related to textfields, checkboxes, toggles and radio buttons ### Usage Examples #### Basic **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 {TuiCheckbox, TuiIcon, TuiLabel, TuiTitle} from '@taiga-ui/core'; import {TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiCheckbox, TuiIcon, TuiLabel, TuiTitle, TuiTooltip], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected form = new FormGroup({ testValue1: new FormControl(true), testValue2: new FormControl(false), testValue3: new FormControl(false), }); } ``` #### Small size **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 {TuiLabel, TuiRadio} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiLabel, TuiRadio], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected form = new FormGroup({value: new FormControl()}); } ``` #### Switch **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 {TuiLabel} from '@taiga-ui/core'; import {TuiSwitch} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiLabel, TuiSwitch], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected form = new FormGroup({ testValue1: new FormControl(true), testValue2: new FormControl(false), testValue3: new FormControl(false), }); } ``` #### Textfield **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 {TuiInput} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiInput], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected form = new FormGroup({value: new FormControl('Input 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 examples = ['Basic', 'Small size', 'Switch', 'Textfield']; } ``` --- # components/LegendItem - **Package**: `ADDON-CHARTS` - **Type**: components A button for a legend of ring or pie charts ### Example ```html {{ 123456 | tuiAmount: 'RUB' }}

tuiAmount pipe is used to format currency and fraction

``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [active] | `boolean` | Active state from outside | | [color] | `TuiColor | string | null` | Indicator color | | [disabled] | `boolean` | Disabled item (i.e. hidden from the related chart) | | [size] | `TuiSizeS` | Size | | [value] | `string` | Text inside | ### Usage Examples #### With a ring chart **Template:** ```html
{{ sum | tuiAmount: 'RUB' }}
Total
@for (label of labels; track label) { {{ value[$index] || 0 | tuiAmount: 'RUB' }} }
``` **TypeScript:** ```ts import {Component} 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 {TuiHovered, tuiSum} from '@taiga-ui/cdk'; @Component({ imports: [TuiAmountPipe, TuiHovered, TuiLegendItem, TuiRingChart], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected activeItemIndex = Number.NaN; protected readonly value = [13769, 12367, 10172, 3018, 2592]; protected readonly sum = tuiSum(...this.value); protected readonly labels = ['Food', 'Cafe', 'OSS', 'Taxi', 'Other']; protected isItemActive(index: number): boolean { return this.activeItemIndex === index; } protected onHover(index: number, hovered: boolean): void { this.activeItemIndex = hovered ? index : Number.NaN; } } ``` **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; } .wrapper { display: flex; align-items: center; @media @tui-mobile { flex-direction: column; } } .legend { margin: 0 0 0 2rem; @media @tui-mobile { margin: 2rem 0 0; } } .item { margin: 0 0.5rem 0.75rem 0; } ``` #### Toggling **Template:** ```html
In case you need to be able to toggle a category by separate action, for example, if clicking on it should expand it for more details
@for (label of labels; track label) { {{ data[$index] || 0 | tuiAmount: 'RUB' }} }
``` **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 }}

``` **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()) { }
@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
``` **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 {TuiLineClamp} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiLineClamp], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected linesLimit = 2; protected toggle(): void { this.linesLimit = this.collapsed ? 12 : 2; } private get collapsed(): boolean { return this.linesLimit === 2; } } ``` **LESS:** ```less .island { max-inline-size: 20rem; padding: 1rem; border: 1px solid var(--tui-border-normal); border-radius: 1.75rem; } .clamp { pointer-events: none; } ``` #### Resize parent container **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.
``` **TypeScript:** ```ts import {ChangeDetectorRef, Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {WA_WINDOW} from '@ng-web-apis/common'; import {WaResizeObserver} from '@ng-web-apis/resize-observer'; import {TuiLineClamp} from '@taiga-ui/kit'; @Component({ imports: [TuiLineClamp, WaResizeObserver], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly win = inject(WA_WINDOW); private readonly cdr = inject(ChangeDetectorRef); protected lineHeight = Number.NaN; protected lineLimit = Number.NaN; protected getDynamicLineHeight(element: HTMLDivElement): number { return Number.parseInt(this.win.getComputedStyle(element).lineHeight, 10); } protected getDynamicLineLimit(element: HTMLDivElement): number { return Math.floor(element.offsetHeight / 24); } protected onResize(element: HTMLDivElement): void { this.lineHeight = this.getDynamicLineHeight(element); this.lineLimit = this.getDynamicLineLimit(element); this.cdr.detectChanges(); } } ``` **LESS:** ```less .example { min-block-size: 15rem; max-block-size: 15rem; } .line-clamp-box { block-size: 5.75rem; min-block-size: 1.5rem; resize: both; overflow: auto; padding: 0.5rem; border-radius: var(--tui-radius-l); border: 1px solid var(--tui-border-normal); } ``` #### Clamp inside dropdown **Template:** ```html

Dropdown —

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

{{ user.email }}

User ID: {{ user.id }}

First Name: {{ user.firstName }}

Last Name: {{ user.lastName }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiHint} from '@taiga-ui/core'; import {TuiLineClamp} from '@taiga-ui/kit'; interface User { email: string; firstName: string; id: string; lastName: string; } @Component({ imports: [TuiHint, TuiLineClamp], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly user: User = { id: '5a006cb3-2b69-4b23', email: 'extremely.long.information@example.com', firstName: 'John', lastName: 'Doe', }; } ``` **LESS:** ```less .island { inline-size: 20rem; margin-block-end: 1rem; box-sizing: border-box; padding: 1rem; border: 1px solid var(--tui-border-normal); border-radius: 1rem; } .email { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } ``` #### Virtual content **Template:** ```html
#{{ user.id }}: {{ user.email }}

User ID: {{ user.id }}

First Name: {{ user.firstName }}

Last Name: {{ user.lastName }}

``` **TypeScript:** ```ts import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, } from '@angular/cdk/scrolling'; 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 {TuiLineClamp} from '@taiga-ui/kit'; @Component({ imports: [ CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, TuiLineClamp, TuiScrollable, TuiScrollbar, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly names = [ 'Time', 'Past', 'Future', 'Dev', 'Fly', 'Flying', 'Soar', 'Soaring', 'Power', 'Falling', 'Fall', 'Jump', 'Cliff', 'Mountain', 'Rend', 'Red', 'Blue', 'Green', 'Yellow', 'Gold', 'Demon', 'Demonic', 'Panda', 'Cat', 'Kitty', 'Kitten', 'Zero', 'Memory', 'Trooper', 'XX', 'Bandit', 'Fear', 'Light', 'Glow', 'Tread', 'Deep', 'Deeper', 'Deepest', 'Mine', 'Your', 'Worst', 'Enemy', 'Hostile', 'Force', 'Video', 'Game', 'Donkey', 'Mule', 'Colt', 'Cult', 'Cultist', 'Magnum', 'Gun', 'Assault', 'Recon', 'Trap', 'Trapper', 'Redeem', 'Code', 'Script', 'Writer', 'Near', 'Close', 'Open', 'Cube', 'Circle', 'Geo', 'Genome', 'Germ', 'Shot', 'Echo', 'Beta', 'Alpha', 'Gamma', 'Omega', 'Seal', 'Squid', 'Money', 'Cash', 'Lord', 'King', 'Duke', 'Rest', 'Fire', 'Flame', 'Morrow', 'Break', 'Breaker', 'Numb', 'Ice', 'Cold', 'Rotten', 'Sick', 'Sickly', 'Janitor', 'Camel', 'Rooster', 'Sand', 'Desert', 'Dessert', 'Hurdle', 'Racer', 'Eraser', 'Erase', 'Big', 'Small', 'Short', 'Tall', 'Sith', 'Bounty', 'Hunter', 'Cracked', 'Broken', 'Sad', 'Happy', 'Joy', 'Joyful', 'Crimson', 'Destiny', 'Deceit', 'Lies', 'Lie', 'Honest', 'Destined', 'Hawk', 'Eagle', 'Hawker', 'Walker', 'Zombie', 'Sarge', 'Capt', 'Captain', 'Punch', 'One', 'Two', 'Uno', 'Slice', 'Slash', 'Melt', 'Melted', 'Melting', 'Fell', 'Wolf', 'Hound', 'Legacy', 'Sharp', 'Dead', 'Mew', 'Chuckle', 'Bubba', 'Bubble', 'Sandwich', 'Smasher', 'Extreme', 'Multi', 'Universe', 'Ultimate', 'Death', 'Ready', 'Monkey', 'Elevator', 'Wrench', 'Grease', 'Head', 'Theme', 'Grand', 'Cool', 'Kid', 'Boy', 'Girl', 'Vortex', 'Paradox', ]; protected readonly users = Array.from({length: 10_000}, (_, index) => { const firstName = this.names[Math.floor(Math.random() * this.names.length)] ?? ''; const lastName = this.names[Math.floor((Math.random() * this.names.length) / 2)] ?? ''; return { id: index + 1, firstName, lastName, email: `${firstName}_${lastName}@gmail.com`.toLowerCase(), } as const; }); } ``` **LESS:** ```less cdk-virtual-scroll-viewport { block-size: 12.5rem; border: 1px solid; overscroll-behavior: none; } div { block-size: 3.125rem; padding: 0.625rem; box-sizing: border-box; overscroll-behavior: none; } ``` #### Custom font-size and line-height **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
``` **TypeScript:** ```ts import {Component, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton} from '@taiga-ui/core'; import {TuiLineClamp} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiLineClamp], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly hasOverflownContent = signal(null); protected linesLimit = 2; protected toggle(): void { this.linesLimit = this.collapsed ? 12 : 2; } private get collapsed(): boolean { return this.linesLimit === 2; } } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiLineClamp} from '@taiga-ui/kit'; @Component({ imports: [TuiDemo, TuiLineClamp], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected linesLimit = 1; protected lineHeight = 24; protected maxWidth = 100; protected content = ''; protected readonly examples = [ 'Styles change', 'Expanding', 'Resize parent container', 'Clamp inside dropdown', 'Custom content workaround', 'Virtual content', 'Custom font-size and line-height', ]; } ``` ### LESS ```less .text { max-inline-size: 60%; } ``` --- # components/LineDaysChart - **Package**: `ADDON-CHARTS` - **Type**: components Line chart but for days ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [dots] | `boolean` | Show dots on chart | | [hintContent] | `PolymorpheusContent` | | | [height] | `number` | Axis Y range, pixel scale is 1:1 | | [y] | `number` | Start of Y axis | | [smoothingFactor] | `number` | Smoothing factor from 0 to 99 | | [value] | `[TuiDay, number][]` | 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 #### Basic **Template:** ```html

``` **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 {WA_IS_E2E} from '@ng-web-apis/platform'; import {TuiAxes, TuiLineDaysChart} from '@taiga-ui/addon-charts'; import { TuiDay, type TuiDayLike, TuiDayRange, TuiMonth, type TuiStringHandler, } from '@taiga-ui/cdk'; import {TUI_MONTHS, TuiTextfield} from '@taiga-ui/core'; import {TuiInputDateRange} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiAxes, TuiInputDateRange, TuiLineDaysChart, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly isE2E = inject(WA_IS_E2E); private readonly months = inject(TUI_MONTHS); protected readonly maxLength: TuiDayLike = {month: 12}; protected readonly range = signal( new TuiDayRange(TuiDay.currentLocal(), TuiDay.currentLocal().append({year: 1})), ); protected readonly value = computed(({to, from} = this.range()) => Array.from({length: TuiDay.lengthBetween(from, to) + 1}).reduce< ReadonlyArray<[TuiDay, number]> >( (array, _, i) => [ ...array, [ from.append({day: i}), this.isE2E ? 100 : (i ? (array[i - 1]?.[1] ?? 0) : 100) + Math.random() * 10 - 5, ], ], [], ), ); protected readonly labels = computed(({to, from} = this.range()) => [ ...Array.from( {length: TuiMonth.lengthBetween(from, to) + 1}, (_, i) => this.months()[from.append({month: i}).month] ?? '', ), null, ]); protected readonly xStringify = computed>( () => ({month, day}) => `${this.months()[month]}, ${day}`, ); protected readonly yStringify: TuiStringHandler = (y) => `${(10 * y).toLocaleString('en-US', {maximumFractionDigits: 0})} $`; } ``` **LESS:** ```less :host { display: block; inline-size: 50rem; } .axes { block-size: 12.5rem; color: #bc71c9; } ``` #### Complex **Template:** ```html
TuiLineDaysChart is used to show data of several months to simplify working with different number of days in months

@for (chart of days(); track chart) { Chart {{ $index + 1 }} }

@if (getWidth(show()) > 90) { @for (chart of days(); track chart) { } } @else { @for (chart of days(); track chart) { } }
{{ getDate(data[0][0], range().from) }}
@for (point of data; track point) {
${{ point[1].toFixed(0) }}
}
``` **TypeScript:** ```ts import {Component, computed, inject, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {WA_IS_E2E} from '@ng-web-apis/platform'; import { TuiAxes, TuiLineChart, TuiLineDaysChart, TuiLineDaysChartHint, } from '@taiga-ui/addon-charts'; import { TuiDay, type TuiDayLike, TuiDayRange, TuiFilterPipe, type TuiMapper, TuiMapperPipe, type TuiMatcher, TuiMonth, } from '@taiga-ui/cdk'; import {TUI_MONTHS, TuiNotification, type TuiPoint, TuiTextfield} from '@taiga-ui/core'; import {TuiInputDateRange} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiAxes, TuiFilterPipe, TuiInputDateRange, TuiLineChart, TuiLineDaysChart, TuiLineDaysChartHint, TuiMapperPipe, TuiNotification, TuiTextfield, ], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Example { private readonly isE2E = inject(WA_IS_E2E); private readonly months = inject(TUI_MONTHS); protected readonly data = signal( new TuiDayRange(TuiDay.currentLocal(), TuiDay.currentLocal().append({month: 5})), ); protected readonly show = signal(this.data()); protected readonly days = computed(() => this.random(this.data())); protected readonly maxLength: TuiDayLike = {month: 6}; protected readonly range = computed(() => { const range = this.show(); const {from, to} = range; const length = TuiDay.lengthBetween(from, to); const dayOfWeekFrom = from.dayOfWeek(); const dayOfWeekTo = to.dayOfWeek(); const mondayFrom = dayOfWeekFrom ? from.append({day: 7 - dayOfWeekFrom}) : from; const mondayTo = dayOfWeekTo ? to.append({day: 7 - dayOfWeekTo}) : to; const mondaysLength = TuiDay.lengthBetween(mondayFrom, mondayTo); if (length > 90) { return range; } if (length > 60) { return new TuiDayRange( mondayFrom, mondayTo.append({day: mondaysLength % 14}), ); } if (length > 14) { return new TuiDayRange(mondayFrom, mondayTo); } return new TuiDayRange(from, to.append({day: length % 2})); }); protected readonly labels = computed(() => { const {from, to} = this.show(); const length = TuiDay.lengthBetween(from, to); const months = this.months(); if (length > 90) { return [ ...Array.from( {length: TuiMonth.lengthBetween(from, to) + 1}, (_, i) => months[from.append({month: i}).month] ?? '', ), '', ]; } const range = Array.from({length}, (_, day) => from.append({day})); const mondays = onlyMondays(range); const days = range.map(String); if (length > 60) { return [...even(mondays), '']; } if (length > 14) { return [...mondays, '']; } if (length > 7) { return [...even(days), '']; } return [...days, '']; }); protected getWidth({from, to}: TuiDayRange): number { return TuiDay.lengthBetween(from, to); } protected getDate(day: TuiDay | number, date: TuiDay): TuiDay { return day instanceof TuiDay ? day : date.append({day}); } protected readonly filter: TuiMatcher<[readonly [TuiDay, number], TuiDayRange]> = ( [day], {from, to}, ) => day.daySameOrAfter(from) && day.daySameOrBefore(to); protected readonly toNumbers: TuiMapper< [ReadonlyArray, TuiDayRange], readonly TuiPoint[] > = (days, {from}) => days.map(([day, value]) => [TuiDay.lengthBetween(from, day), value]); private generateRandomData( {from, to}: TuiDayRange, initial: number, ): ReadonlyArray<[TuiDay, number]> { return Array.from({length: TuiDay.lengthBetween(from, to) + 1}) .reduce>( (array, _, i) => [ ...array, [ from.append({day: i}), this.isE2E ? initial : Math.max( (i ? (array[i - 1]?.[1] ?? 0) : initial) + Math.random() * 10 - 5, 0, ), ], ], [], ) .filter(([day]) => day.dayOfWeek() < 5); } private random(data: TuiDayRange): ReadonlyArray> { return [ this.generateRandomData(data, 100), this.generateRandomData(data, 75), this.generateRandomData(data, 50), ]; } } function onlyMondays(range: readonly TuiDay[]): readonly string[] { return range.filter((day) => !day.dayOfWeek()).map(String); } function even(array: readonly T[]): readonly T[] { return array.filter((_, i) => !(i % 2)); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .color() { color: var(--tui-chart-categorical-01); &:first-child { color: var(--tui-chart-categorical-08); } &:last-child { color: var(--tui-chart-categorical-12); } } .axes { block-size: 12.5rem; } .controls { display: flex; tui-textfield { flex: 1; } } .legend { display: flex; justify-content: center; align-items: center; } .item { .color(); display: flex; align-items: center; margin: 0 0.75rem; &::before { content: ''; border-block-end: 0.125rem solid; inline-size: 1rem; margin-inline-end: 0.5rem; } } .name { color: var(--tui-text-primary); } .value { color: #fff; } .chart { .color(); .fullsize(); } ``` ### TypeScript ```ts import {Component, computed, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiAxes, TuiLineDaysChart} from '@taiga-ui/addon-charts'; import {type TuiContext, TuiDay, type TuiStringHandler} from '@taiga-ui/cdk'; import {TUI_MONTHS} from '@taiga-ui/core'; import {type PolymorpheusContent} from '@taiga-ui/polymorpheus'; @Component({ imports: [TuiAxes, TuiDemo, TuiLineDaysChart], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly months = inject(TUI_MONTHS); protected readonly examples = ['Basic', 'Complex']; protected readonly valueVariants: ReadonlyArray> = [ Array.from({length: 91}).reduce>( (array, _, i) => [ ...array, [ new TuiDay(2020, 0, 1).append({day: i}), (i ? (array[i - 1]?.[1] ?? 0) : 100) + Math.random() * 20 - 10, ], ], [], ), [ [new TuiDay(2020, 1, 10), 10], [new TuiDay(2020, 1, 15), 150], [new TuiDay(2020, 1, 17), 10], [new TuiDay(2020, 1, 20), 10], [new TuiDay(2020, 1, 25), 150], [new TuiDay(2020, 1, 27), 10], ], ]; protected value = this.valueVariants[0]!; protected readonly labels = computed(() => Array.from({length: 4}, (_, i) => this.months()?.[i] ?? ''), ); protected readonly yStringifyVariants: ReadonlyArray> = [ (y) => `${(10 * y).toLocaleString('en-US', {maximumFractionDigits: 0})} $`, ]; protected readonly xStringifyVariants = computed(() => [ ({month, day}: TuiDay) => `${this.months()?.[month]}, ${day}`, ]); protected readonly hintContentVariants = computed(() => [ '', ({$implicit}: {$implicit: [TuiDay, number]}) => `${this.months()?.[$implicit[0].month]}, ${$implicit[0].day}\n${( 10 * $implicit[1] ).toLocaleString('en-US', {maximumFractionDigits: 0})} $`, ]); protected yStringify: TuiStringHandler | null = null; protected xStringify: TuiStringHandler | null = null; protected hintContent: PolymorpheusContent> = ''; protected dots = false; protected smoothingFactor = 0; protected y = 0; protected height = 200; } ``` ### LESS ```less .axes { block-size: 12.5rem; inline-size: 45.5rem; color: #bc71c9; } ``` --- # components/Link - **Package**: `CORE` - **Type**: components Link component. It has focus highlight and can be customized with an icon Use textContent binding or wrapping entire content with a span to avoid extra whitespace introduced by browser to a tag when closing tag wraps to a new line — otherwise distance to the icons will be bigger than it should be ### Usage Examples #### Basic **Template:** ```html Link ``` **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 {TuiLink} from '@taiga-ui/core'; @Component({ imports: [RouterLink, TuiLink], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly routes = DemoRoute; } ``` #### Icons **Template:** ```html

``` **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 appearance which you can override with or

If you use no appearance, links would 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 dashed underline for any appearance using text-decoration-line: underline or switch 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
  1. Eurasia
  2. Africa
  3. Australia
  4. Antarctica
  5. North America
  6. South America

Body M

List of Continents
  1. Eurasia
  2. Africa
  3. Australia
  4. Antarctica
  5. North America
  6. South America

UI S

List of Continents
  1. Eurasia
  2. Africa
  3. Australia
  4. Antarctica
  5. North America
  6. 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

  1. First level
    1. Second level
    2. Second level
      1. Third level
      2. Third level
    3. Second level
      1. Third level
      2. Third level
        1. Fourth level
        2. Fourth level
  2. 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
  1. If a list item is long, the bullet stays aligned to the top.
  2. 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!
Loading
You can use a template with HTML here
``` ### 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 content Right Comment with very long content

Multiline

Very long content that goes on the second line Very 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
{{ 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
{{ 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
{{ 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
} }
A very very long product name
Test

@for (_ of '-'.repeat(10); track $index) {

Registration form Tell us about yourself

Sidebar content Use CSS grid to position

}
``` **TypeScript:** ```ts import {KeyValuePipe, NgTemplateOutlet} from '@angular/common'; import {Component, Directive, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {RouterLink} from '@angular/router'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {DemoRoute} from '@demo/routes'; import {TuiPortals, TuiPortalService, tuiProvide, TuiVCR} from '@taiga-ui/cdk'; import { TuiButton, TuiDataList, TuiDropdown, TuiIcon, TuiInput, TuiLink, TuiPopupService, TuiTitle, } from '@taiga-ui/core'; import { TuiAvatar, TuiBadge, TuiBadgeNotification, TuiBreadcrumbs, TuiChevron, TuiDataListDropdownManager, TuiFade, TuiSwitch, TuiTabs, } from '@taiga-ui/kit'; import {TuiCardLarge, TuiForm, TuiHeader, TuiNavigation} from '@taiga-ui/layout'; const ICON = "data:image/svg+xml,%0A%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='32' height='32' rx='8' fill='url(%23paint0_linear_2036_35276)'/%3E%3Cmask id='mask0_2036_35276' style='mask-type:alpha' maskUnits='userSpaceOnUse' x='6' y='5' width='20' height='21'%3E%3Cpath d='M18.2399 9.36607C21.1347 10.1198 24.1992 9.8808 26 7.4922C26 7.4922 21.5645 5 16.4267 5C11.2888 5 5.36726 8.69838 6.05472 16.6053C6.38707 20.4279 6.65839 23.7948 6.65839 23.7948C8.53323 22.1406 9.03427 19.4433 8.97983 16.9435C8.93228 14.7598 9.55448 12.1668 12.1847 10.4112C14.376 8.94865 16.4651 8.90397 18.2399 9.36607Z' fill='url(%23paint1_linear_2036_35276)'/%3E%3Cpath d='M11.3171 20.2647C9.8683 17.1579 10.7756 11.0789 16.4267 11.0789C20.4829 11.0789 23.1891 12.8651 22.9447 18.9072C22.9177 19.575 22.9904 20.2455 23.2203 20.873C23.7584 22.3414 24.7159 24.8946 24.7159 24.8946C23.6673 24.5452 22.8325 23.7408 22.4445 22.7058L21.4002 19.921L21.2662 19.3848C21.0202 18.4008 20.136 17.7104 19.1217 17.7104H17.5319L17.6659 18.2466C17.9119 19.2306 18.7961 19.921 19.8104 19.921L22.0258 26H10.4754C10.7774 24.7006 12.0788 23.2368 11.3171 20.2647Z' fill='url(%23paint2_linear_2036_35276)'/%3E%3C/mask%3E%3Cg mask='url(%23mask0_2036_35276)'%3E%3Crect x='4' y='4' width='24' height='24' fill='white'/%3E%3C/g%3E%3Cdefs%3E%3ClinearGradient id='paint0_linear_2036_35276' x1='0' y1='0' x2='32' y2='32' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23A681D4'/%3E%3Cstop offset='1' stop-color='%237D31D4'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint1_linear_2036_35276' x1='6.0545' y1='24.3421' x2='28.8119' y2='3.82775' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.0001' stop-opacity='0.996458'/%3E%3Cstop offset='0.317708'/%3E%3Cstop offset='1' stop-opacity='0.32'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint2_linear_2036_35276' x1='6.0545' y1='24.3421' x2='28.8119' y2='3.82775' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.0001' stop-opacity='0.996458'/%3E%3Cstop offset='0.317708'/%3E%3Cstop offset='1' stop-opacity='0.32'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E%0A"; // Ignore portal related code, it is only here to position drawer inside the example block @Directive({ selector: '[portal]', providers: [tuiProvide(TuiPopupService, TuiPortalService)], }) class Portal {} @Component({ imports: [ FormsModule, KeyValuePipe, NgTemplateOutlet, Portal, RouterLink, TuiAvatar, TuiBadge, TuiBadgeNotification, TuiBreadcrumbs, TuiButton, TuiCardLarge, TuiChevron, TuiDataList, TuiDataListDropdownManager, TuiDropdown, TuiFade, TuiForm, TuiHeader, TuiIcon, TuiInput, TuiLink, TuiNavigation, TuiSwitch, TuiTabs, TuiTitle, TuiVCR, ], templateUrl: './index.html', encapsulation, changeDetection, providers: [{provide: TuiPortalService, useClass: TuiPopupService}], }) export default class Example extends TuiPortals { protected readonly expanded = signal(false); protected open = false; protected switch = false; protected readonly routes = DemoRoute; protected readonly breadcrumbs = ['Home', 'Angular', 'Repositories', 'Taiga UI']; protected readonly drawer = { Components: [ {name: 'Button', icon: ICON}, {name: 'Input', icon: ICON}, {name: 'Tooltip', icon: ICON}, ], Essentials: [ {name: 'Getting started', icon: ICON}, {name: 'Showcase', icon: ICON}, {name: 'Typography', icon: ICON}, ], }; protected handleToggle(): void { this.expanded.update((e) => !e); } } ``` #### Subheader compact **Template:** ```html
  1. Wrap your button in another tag
  2. Move dropdown directives to that tag
  3. Reset navigation appearance by tuiDropdownAppearance=""
  4. Set tuiTheme="dark" on the button so it fits the header
  5. Get app theme: readonly dark = inject(TUI_DARK_MODE)
  6. 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
@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.
Alex Inkin Edited 6 minutes ago Private

} @if (current === 'input') {

Projects

} @if (current === 'card') { Code Repositories Taiga UI

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

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

``` **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
I am title
I am content of the notification and I can even wrap to multiple lines.
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 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 This is a declarative directive alert ``` **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 &ngsp; ``` **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' }}
`, 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 Notification example ``` ### 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 ``` **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   ``` **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!
}
``` **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 ``` **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
``` **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 {TuiPager} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiPager], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected count = 10; protected index = 0; protected prev(): void { this.index = Math.max(this.index - 1, 0); } protected next(): void { this.index = Math.min(this.index + 1, this.count - 1); } } ``` #### Icons **Template:** ```html @if (index === count - 2) { } @if (index === count - 1) { }
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiIcon} from '@taiga-ui/core'; import {TuiPager} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiIcon, TuiPager], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected count = 8; protected activeIndex = 0; protected prev(): void { this.activeIndex = Math.max(this.activeIndex - 1, 0); } protected next(): void { this.activeIndex = Math.min(this.activeIndex + 1, this.count - 1); } } ``` #### Dynamic width **Template:** ```html
``` **TypeScript:** ```ts import {AsyncPipe, isPlatformServer} from '@angular/common'; import {Component, inject, PLATFORM_ID, 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 {TuiMapperPipe} from '@taiga-ui/cdk'; import {TuiButton} from '@taiga-ui/core'; import {TuiPager, TuiProgress} from '@taiga-ui/kit'; import {map, type Observable, of, takeWhile, tap, timer} from 'rxjs'; @Component({ imports: [AsyncPipe, TuiButton, TuiMapperPipe, TuiPager, TuiProgress], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected static = inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID)); protected count = 10; protected readonly activeIndex = signal(0); protected readonly toProgress = (active: boolean): Observable => active && !this.static ? timer(0, 100).pipe( map((i) => i * 5 + 20), takeWhile((value) => value <= 100), tap({complete: () => this.next()}), ) : of(100); protected prev(): void { this.activeIndex.update((index) => Math.max(index - 1, 0)); } protected next(): void { this.activeIndex.update((index) => Math.min(index + 1, this.count - 1)); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .progress { .transition(~'color, inline-size'); inline-size: 1rem; &:not(&_active) { inline-size: 0.5rem; color: transparent; } } ``` ### TypeScript ```ts import {Component, type TemplateRef, viewChild} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiIcon, type TuiSizeS} from '@taiga-ui/core'; import {TuiPager} from '@taiga-ui/kit'; @Component({ imports: [TuiDemo, TuiIcon, TuiPager], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly templateRef = viewChild>('templateRef'); protected readonly examples = ['Basic', 'Icons', 'Dynamic width']; protected index = 0; protected max = 6; protected sizes: TuiSizeS[] = ['m', 's']; protected size = this.sizes[0]!; protected countVariants = [10, 15, 1, 2, 3, 4, 5, 6, 8, 100]; protected count = this.countVariants[0]!; protected templateVariants = ['', 'Template']; protected selectedTemplate = this.templateVariants[0]!; protected get valueContent(): TemplateRef | undefined { const template = this.templateRef(); return template && this.selectedTemplate ? template : undefined; } } ``` --- # components/Pagination - **Package**: `KIT` - **Type**: components Pagination component enables the user to select a specific page from a range of pages ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [focusable] | `boolean` | Accepts focus with keyboard | | [(index)] | `number` | Active page index | | [length] | `number` | Total pages count | | [size] | `TuiSizeL` | Size | | [activePadding] | `number` | Amount of visible pages around active page | | [sidePadding] | `number` | Amount of visible pages at the edges | ### Usage Examples #### Basic **Template:** ```html index ``` **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 {TuiInputSlider, TuiPagination} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputSlider, TuiPagination, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Page { protected length = 64; protected index = 10; protected goToPage(index: number): void { this.index = index; console.info('New page:', index); } } ``` **LESS:** ```less .slider { inline-size: 12.5rem; margin-block-end: 1em; } ``` #### Visible pages around active **Template:** ```html activePadding ``` **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 {TuiInputSlider, TuiPagination} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputSlider, TuiPagination, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Page { protected activePadding = 2; } ``` **LESS:** ```less .slider { inline-size: 12.5rem; margin-block-end: 1em; } ``` #### Visible edge pages **Template:** ```html sidePadding ``` **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 {TuiInputSlider, TuiPagination} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputSlider, TuiPagination, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Page { protected sidePadding = 3; } ``` **LESS:** ```less .slider { inline-size: 12.5rem; margin-block-end: 1em; } ``` #### Custom **Template:** ```html {{ days[index] }} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiPagination} from '@taiga-ui/kit'; @Component({ imports: [TuiPagination], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Page { protected readonly days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiPagination} from '@taiga-ui/kit'; @Component({ imports: [TuiDemo, TuiPagination], templateUrl: './index.html', changeDetection, }) export default class Page { protected focusable = true; protected index = 0; protected length = 8; protected readonly sizeVariants = ['m', 'l'] as const; protected size = this.sizeVariants[1]; protected activePadding = 1; protected sidePadding = 1; protected readonly examples = [ 'Basic', 'Visible pages around active', 'Visible edge pages', 'Custom', ]; } ``` --- # components/PdfViewer - **Package**: `LAYOUT` - **Type**: components Wrapper component for viewing PDF files in an iframe PDF display in browsers is handled by each browser independently, using their own homegrown or 3rd-party code, as this is not part of the HTML spec. Keep in mind most mobile devices do not support displaying PDFs in iframe. Check it here . The only way to enforce rendering consistency in all browsers is to do the rendering server-side, bundle your own JS PDF renderer, or use a 3rd-party rendering service. If you want to display it yourself, so you need to rely on WA_IS_MOBILE token to provide suitable alternative behavior. For example, you can use third-party service https://drive.google.com/viewerng/viewer?embedded=true&url=$YOUR_PUBLIC_PATH_TO_PDF or your own service to render PDF by pdf.js. ### Usage Examples #### Basic **Template:** ```html

file.pdf

``` **TypeScript:** ```ts import {Component, inject} 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, TuiTitle} from '@taiga-ui/core'; import {TuiPdfViewer} from '@taiga-ui/layout'; @Component({ imports: [TuiButton, TuiDialog, 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( this.isMobile ? `https://drive.google.com/viewerng/viewer?embedded=true&url=https://taiga-ui.dev/${this.pdf}` : this.pdf, ); } ``` #### With responsive dialog **Template:** ```html

file.pdf

``` **TypeScript:** ```ts import {Component, inject} 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 {TuiResponsiveDialog} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiNotificationService, TuiTitle} from '@taiga-ui/core'; import {TuiPdfViewer} from '@taiga-ui/layout'; @Component({ imports: [TuiButton, TuiPdfViewer, TuiResponsiveDialog, TuiTitle], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly sanitizer = inject(DomSanitizer); protected readonly alerts = inject(TuiNotificationService); protected readonly isMobile = inject(WA_IS_MOBILE); protected readonly pdf = '/assets/media/taiga.pdf'; protected open = false; protected readonly url = this.sanitizer.bypassSecurityTrustResourceUrl( this.isMobile ? `https://drive.google.com/viewerng/viewer?embedded=true&url=https://taiga-ui.dev/${this.pdf}` : this.pdf, ); } ``` #### Loading and error states **Template:** ```html

file.pdf

@if (loading()) { } @else if (!error()) { } @else { not found

Something went wrong

Try again later
}
``` **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
avatar
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 ``` **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

``` **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

} ``` **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

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
{{ titles[index] }} preview

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.

logo
``` **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 {{ titles[index] }} preview ``` **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 ``` **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 {{ title$ | async }} @if (contentUnavailable$ | async) {
Preview unavailable
} @if (imageSrc$ | async; as src) { img source } @if (loading$ | async) { }
``` **TypeScript:** ```ts import {AsyncPipe} from '@angular/common'; import {Component, inject, type TemplateRef, viewChild} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {tuiIsPresent} from '@taiga-ui/cdk'; import {TuiButton, type TuiDialogContext, TuiIcon, TuiLoader} from '@taiga-ui/core'; import {TuiPreview, TuiPreviewDialogService} from '@taiga-ui/kit'; import { BehaviorSubject, filter, map, type Observable, of, startWith, switchMap, timer, } from 'rxjs'; @Component({ imports: [AsyncPipe, TuiButton, TuiIcon, TuiLoader, TuiPreview], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly previewDialogService = inject(TuiPreviewDialogService); protected readonly preview = viewChild>('preview'); protected readonly items = [ { title: 'some table.xlsx', hasPreview: false, }, { title: 'Content #2', hasPreview: true, }, ]; protected readonly index$$ = new BehaviorSubject(0); protected readonly item$ = this.index$$.pipe( map((index) => this.items[index]), filter(tuiIsPresent), ); protected readonly title$ = this.item$.pipe(map((item) => item.title)); protected readonly contentUnavailable$ = this.item$.pipe( map((item) => !item.hasPreview), ); protected readonly imageSrc$ = this.item$.pipe( switchMap((item) => item.hasPreview ? this.emulateBackendRequest().pipe(startWith('')) : of(null), ), ); protected readonly loading$ = this.imageSrc$.pipe(map((src) => src === '')); protected show(): void { this.previewDialogService.open(this.preview() || '').subscribe(); } protected download(): void { console.info('downloading...'); } protected emulateBackendRequest(): Observable { return timer(1500).pipe( map(() => 'https://ng-web-apis.github.io/dist/assets/images/web-api.svg'), ); } } ``` **LESS:** ```less .content { background-color: rgb(245, 241, 241); inline-size: 25rem; block-size: 37.5rem; padding: 2.5rem; border-radius: 0.75rem; } .t-container { display: flex; flex-direction: column; align-items: center; color: var(--tui-text-secondary); } .t-icon { margin-block-end: 0.75rem; font-size: 5rem; } .t-loader { inline-size: 4rem; } ``` ### 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 = [ 'Full preview', 'Preview with directive', 'Simple mode', 'With loading and unavailable image', ]; } ``` --- # components/ProgressBar - **Package**: `KIT` - **Type**: components tuiProgressBar – attribute component for native html tag '"> . Usage: '"> . ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [value] | `number` | | | [max] | `number` | | | [size] | `TuiSizeXS | TuiSizeXXL` | Size of the progress element | | [color] | `string` | | ### 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, timer} from 'rxjs'; @Component({ imports: [AsyncPipe, TuiProgress], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly value$ = inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID)) ? of(40) : timer(300, 300).pipe( map((i) => i + 30), startWith(30), ); } ``` #### Multicolor **Template:** ```html
Single color

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) { } ``` **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) { } ``` **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

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, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {WA_IS_ANDROID, WA_IS_IOS} from '@ng-web-apis/platform'; import { TUI_ANDROID_LOADER, TUI_PULL_TO_REFRESH_COMPONENT, TUI_PULL_TO_REFRESH_LOADED, TuiPullToRefresh, } from '@taiga-ui/addon-mobile'; import {TuiButton, TuiNotificationService} from '@taiga-ui/core'; import {Subject} from 'rxjs'; @Component({ selector: 'example-1', imports: [TuiButton, TuiPullToRefresh], templateUrl: './index.html', encapsulation, changeDetection, providers: [ { provide: WA_IS_IOS, useValue: false, }, { provide: WA_IS_ANDROID, useValue: true, }, { provide: TUI_PULL_TO_REFRESH_COMPONENT, useValue: TUI_ANDROID_LOADER, }, { provide: TUI_PULL_TO_REFRESH_LOADED, useClass: Subject, }, ], }) export default class Example { private readonly alerts = inject(TuiNotificationService); private readonly loaded$ = inject>(TUI_PULL_TO_REFRESH_LOADED); protected onPull(): void { this.alerts.open('Loading...').subscribe(); } protected finishLoading(): void { this.loaded$.next(); } } ``` #### iOS **Template:** ```html

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, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {WA_IS_ANDROID, WA_IS_IOS} from '@ng-web-apis/platform'; import { TUI_IOS_LOADER, TUI_PULL_TO_REFRESH_COMPONENT, TUI_PULL_TO_REFRESH_LOADED, TuiPullToRefresh, } from '@taiga-ui/addon-mobile'; import {TuiButton, TuiNotificationService} from '@taiga-ui/core'; import {Subject} from 'rxjs'; @Component({ imports: [TuiButton, TuiPullToRefresh], templateUrl: './index.html', encapsulation, changeDetection, providers: [ { provide: WA_IS_IOS, useValue: true, }, { provide: WA_IS_ANDROID, useValue: false, }, { provide: TUI_PULL_TO_REFRESH_COMPONENT, useValue: TUI_IOS_LOADER, }, { provide: TUI_PULL_TO_REFRESH_LOADED, useClass: Subject, }, ], }) export default class Example { private readonly alerts = inject(TuiNotificationService); private readonly loaded$ = inject>(TUI_PULL_TO_REFRESH_LOADED); protected onPull(): void { this.alerts.open('Loading...').subscribe(); } protected finishLoading(): void { this.loaded$.next(); } } ``` #### Virtual scroll **Template:** ```html
{{ item }}
``` **TypeScript:** ```ts import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, } from '@angular/cdk/scrolling'; import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {WA_IS_ANDROID, WA_IS_IOS} from '@ng-web-apis/platform'; import { TUI_ANDROID_LOADER, TUI_PULL_TO_REFRESH_COMPONENT, TUI_PULL_TO_REFRESH_LOADED, TuiPullToRefresh, } from '@taiga-ui/addon-mobile'; import {TuiNotificationService, TuiScrollable, TuiScrollbar} from '@taiga-ui/core'; import {Subject} from 'rxjs'; @Component({ imports: [ CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, TuiPullToRefresh, TuiScrollable, TuiScrollbar, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [ { provide: WA_IS_IOS, useValue: false, }, { provide: WA_IS_ANDROID, useValue: true, }, { provide: TUI_PULL_TO_REFRESH_COMPONENT, useValue: TUI_ANDROID_LOADER, }, { provide: TUI_PULL_TO_REFRESH_LOADED, useClass: Subject, }, ], }) export default class Example { private readonly alerts = inject(TuiNotificationService); protected items = Array.from({length: 10000}).map((_, i) => `Item #${i}`); protected onPull(): void { this.alerts.open('Loading...').subscribe(); } } ``` **LESS:** ```less .example-viewport { block-size: 12.5rem; border: 1px solid; overscroll-behavior: none; } .example-item { block-size: 3.125rem; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiScrollbar} from '@taiga-ui/core'; import {PolymorpheusComponent, PolymorpheusOutlet} from '@taiga-ui/polymorpheus'; @Component({ imports: [PolymorpheusOutlet, TuiDemo, TuiScrollbar], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly example1 = import('./examples/1').then( ({default: component}) => new PolymorpheusComponent(component), ); protected readonly example2 = import('./examples/2').then( ({default: component}) => new PolymorpheusComponent(component), ); } ``` ### LESS ```less .scrollbar { max-block-size: 20rem; overscroll-behavior: none; } ``` --- # components/Pulse - **Package**: `KIT` - **Type**: components ### 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 {TuiButton} from '@taiga-ui/core'; import {TuiPulse} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiPulse], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected playing = true; } ``` #### Popover **Template:** ```html
@if (step()) {

You can have images!
Or any content really

Alex Inkin
}

Welcome to the tutorial!
This is the first step.

} ``` **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. ``` ### 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? 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. ``` **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 ``` **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 I have a bad feeling about this... ``` **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) {
{{ platform }}
} ``` **TypeScript:** ```ts import {Component, type OnInit} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiPlatform} from '@taiga-ui/cdk'; import {TuiRadio, type TuiSizeS} from '@taiga-ui/core'; @Component({ imports: [FormsModule, ReactiveFormsModule, TuiPlatform, TuiRadio], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example implements OnInit { protected readonly platforms: ReadonlyArray<'android' | 'ios' | 'web'> = [ 'web', 'web', 'ios', 'android', ]; protected readonly invalidTrue = new FormControl(true, () => ({invalid: true})); protected readonly invalidFalse = new FormControl(false, () => ({invalid: true})); public ngOnInit(): void { this.invalidTrue.markAsTouched(); this.invalidFalse.markAsTouched(); } protected getSize(first: boolean): TuiSizeS { return first ? 'm' : 's'; } } ``` **LESS:** ```less :host { display: flex; --tui-background-accent-2: var(--tui-status-info); } .wrapper { display: flex; flex-direction: column; justify-content: space-between; align-items: center; flex: 1; gap: 1rem; padding: 1rem; &_web { border: 1px solid var(--tui-border-normal); border-inline-start-width: 0; &:first-child { border-inline-end-width: 0; border-inline-start-width: 1px; } } } ``` #### Identity matcher **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 {TuiButton, TuiLabel, TuiRadio} from '@taiga-ui/core'; interface TestValue { test: string; } @Component({ imports: [FormsModule, TuiButton, TuiLabel, TuiRadio], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value: TestValue | null = null; protected identityMatcher = (a: TestValue, b: TestValue): boolean => a?.test === b?.test; } ``` #### List **Template:** ```html
{{ data.name }} {{ data.description }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators, } from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiBooleanHandler} from '@taiga-ui/cdk'; import {TuiTitle} from '@taiga-ui/core'; import {TuiRadioList} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, ReactiveFormsModule, TuiRadioList, TuiTitle], templateUrl: './index.html', styles: ` hr { block-size: 1px; background: var(--tui-border-normal); border: 0; margin: 1rem 0; } `, encapsulation, changeDetection, }) export default class Example { protected readonly form = new FormGroup({ vertical: new FormControl(null, Validators.required), disabled: new FormControl({value: null, disabled: true}), }); protected readonly objects = [ { name: 'King Arthur', description: 'Graham Chapman', }, { name: "It's Man", description: 'Michael Palin', }, { name: 'Silly Walks', description: 'John Cleese', }, ]; protected readonly strings = ['King Arthur', "It's Man", 'Silly Walks']; protected horizontal = this.strings[0]!; protected readonly handler: TuiBooleanHandler = (item) => item === this.strings[2]!; } ``` ### 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 = ['Platforms', 'Identity matcher', 'List']; } ``` --- # components/Range - **Package**: `KIT` - **Type**: components A two-thumb slider for selecting a range of values ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [disabled] | `boolean` | ) | | [min] | `number` | | | [max] | `number` | | | [step] | `number` | | | [segments] | `number` | | | [keySteps] | `TuiKeySteps | null` | | | [limit] | `number` | | | [margin] | `number` | | | [style.--tui-thumb-size.px] | `number` | Size of thumb | ### Usage Examples #### Size **Template:** ```html

Custom

``` **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 {TuiRange} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiRange], templateUrl: './index.html', styles: ':host {display: flex; flex-direction: column;}', encapsulation, changeDetection, }) export default class Example { protected value = [40, 60]; } ``` #### Segments **Template:** ```html
@for (label of labels; track label) {
@if (label !== 75) { {{ label | i18nPlural: pluralMap }} } @else {
3/4
}
}

Control value: {{ value | json }}

``` **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 {TuiIcon} from '@taiga-ui/core'; import {TuiRange} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, I18nPluralPipe, JsonPipe, TuiIcon, TuiRange], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly min = 0; protected readonly max = 100; protected readonly step = 25; protected readonly segments = 4; protected readonly labels = Array.from( {length: this.segments + 1}, (_, i) => this.min + this.step * i, ); protected value = [0, 25]; // https://angular.dev/api/common/I18nPluralPipe#example protected pluralMap = {'=0': '0', '=1': '# item', '=100': 'MAX', other: '# items'}; } ``` **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(); } tui-icon::before { font-size: 1rem; } ``` #### KeySteps **Template:** ```html
@for (label of ticksLabels; track label) { {{ label }} }

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 {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 ``` **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 ``` **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 ``` **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
top: {{ scrollTop }}
left: {{ scrollLeft }}

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, ElementRef, viewChild} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiScrollbar} from '@taiga-ui/core'; const SOME_OFFSET_CONST = 20; @Component({ imports: [TuiButton, TuiScrollbar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly scrollBar = viewChild(TuiScrollbar, {read: ElementRef}); protected someOffsetConst = SOME_OFFSET_CONST; protected get scrollTop(): number { return this.scrollBar()?.nativeElement.scrollTop ?? 0; } protected get scrollLeft(): number { return this.scrollBar()?.nativeElement.scrollLeft ?? 0; } protected onClick(): void { const scrollbar = this.scrollBar(); if (!scrollbar) { return; } scrollbar.nativeElement.scrollTop = scrollbar.nativeElement.scrollTop < SOME_OFFSET_CONST ? scrollbar.nativeElement.scrollHeight : 0; } } ``` **LESS:** ```less .box { inline-size: 16rem; block-size: 16rem; border: 1px solid; } .content { padding: 0 0.6875rem; } p { white-space: nowrap; } ``` #### Hidden **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: '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
{{ item }}
``` **TypeScript:** ```ts import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, } from '@angular/cdk/scrolling'; 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'; @Component({ imports: [ CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, TuiScrollable, TuiScrollbar, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected items = Array.from({length: 10000}).map((_, i) => `Item #${i}`); protected add(): void { this.items = [...this.items, `Item #${this.items.length}`]; } } ``` **LESS:** ```less .example-viewport { block-size: 12.5rem; border: 1px solid; } .example-item { block-size: 3.125rem; } ``` #### Show scroll bars on hover **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({ 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: `
@for ( item of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; track $index ) {
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!
}
`, styles: ` :host { display: block; margin: 1.125rem; } tui-scrollbar { inline-size: 30rem; block-size: 30rem; outline: 1px solid; } `, changeDetection: ChangeDetectionStrategy.OnPush, providers: [tuiScrollbarOptionsProvider({mode: 'hover'})], }) export class Child {} @Component({ imports: [Child, TuiScrollbar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [tuiScrollbarOptionsProvider({mode: 'always'})], }) export default class Parent {} ``` **LESS:** ```less p { margin: 1.125rem; } tui-scrollbar { outline: 1px solid; resize: block; } ``` ### 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 = [ 'Vertical', 'Horizontal', 'All', 'Hidden', 'Light scrollbar', 'Virtual scroll', 'Show scroll bars on hover', 'Native scrollbar', 'Nested scrollbar', ]; } ``` --- # components/Search - **Package**: `LAYOUT` - **Type**: components A wrapping component for search or filtering the table ### Usage Examples #### Filters **Template:** ```html
Filters @for (control of filters.controls; track control) { }

{{ form.value | json }}

``` **TypeScript:** ```ts import {JsonPipe} from '@angular/common'; import {Component} from '@angular/core'; import {FormArray, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiInput} from '@taiga-ui/core'; import {TuiItemsWithMore} from '@taiga-ui/kit'; import {TuiSearch} from '@taiga-ui/layout'; @Component({ imports: [ JsonPipe, ReactiveFormsModule, TuiButton, TuiInput, TuiItemsWithMore, TuiSearch, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly filters = new FormArray( Array.from({length: 5}, () => new FormControl()), ); protected readonly form = new FormGroup({filters: this.filters}); } ``` #### Small **Template:** ```html
@for (segment of segments; track segment) { } Results: 999

``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {toSignal} from '@angular/core/rxjs-interop'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {tuiCountFilledControls} from '@taiga-ui/cdk'; import {TuiButton, TuiInput, TuiLink} from '@taiga-ui/core'; import { TuiChevron, TuiDataListWrapper, TuiFilter, TuiSegmented, TuiSelect, TuiSwitch, } from '@taiga-ui/kit'; import {TuiSearch} from '@taiga-ui/layout'; import {map} from 'rxjs'; @Component({ imports: [ ReactiveFormsModule, TuiButton, TuiChevron, TuiDataListWrapper, TuiFilter, TuiInput, TuiLink, TuiSearch, TuiSegmented, TuiSelect, TuiSwitch, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly form = new FormGroup({ search: new FormControl(), select: new FormControl(), date: new FormControl(), switch: new FormControl(), filter: new FormControl(), segmented: new FormControl(), }); protected readonly items = inject('Pythons' as any); protected readonly filters = ['Python', 'JavaScript', 'TypeScript']; protected readonly segments = [null, 'Unread', 'Archived']; protected readonly count = toSignal( this.form.valueChanges.pipe(map(() => tuiCountFilledControls(this.form))), {initialValue: 0}, ); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiDemo} from '@demo/utils'; @Component({ imports: [TuiDemo], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Page { protected readonly examples = ['Filters', 'Small']; } ``` --- # components/Segmented - **Package**: `KIT` - **Type**: components Segmented is used for links and buttons to navigate within the application. It can also work as a radio button to toggle between different states. ### Usage Examples #### 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 {TuiIcon} from '@taiga-ui/core'; import {TuiBadgeNotification, TuiSegmented} from '@taiga-ui/kit'; @Component({ imports: [TuiBadgeNotification, TuiIcon, TuiSegmented], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly sizes = ['s', 'm', 'l'] as const; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; } ``` #### Width **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiFade, TuiSegmented} from '@taiga-ui/kit'; @Component({ imports: [TuiFade, TuiSegmented], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; } .full { inline-size: 100%; & > * { flex: 1; min-inline-size: 0; } } ``` #### Customization **Template:** ```html
@for (button of buttons; track button) { }
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSegmented} from '@taiga-ui/kit'; @Component({ imports: [TuiSegmented], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly buttons = ['Track active index', 'To color tabs', 'Differently']; protected active = 0; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; } header { background: var(--tui-background-base-alt); box-shadow: 0 -10rem 0 10.5rem var(--tui-background-base-alt); } .colors { background: var(--tui-background-base); &::before { color: var(--tui-text-action); box-shadow: none; } .active { color: #fff; } } ``` #### Content **Template:** ```html Buttons
Links Use routerLink And routerLinkActive To work with links
Icons
Control ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {type IsActiveMatchOptions, RouterLink, RouterLinkActive} from '@angular/router'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {DemoRoute} from '@demo/routes'; import {TuiButton, TuiIcon} from '@taiga-ui/core'; import {TuiSegmented} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, RouterLink, RouterLinkActive, TuiButton, TuiIcon, TuiSegmented, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected selected = 'a'; protected readonly options: IsActiveMatchOptions = { matrixParams: 'exact', queryParams: 'exact', paths: 'exact', fragment: 'exact', }; protected readonly routes = DemoRoute; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; } hr { inline-size: 100%; block-size: 1px; background: var(--tui-border-normal); border: 0; } ``` ### 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 = ['Sizes', 'Width', 'Customization', 'Content']; } ``` --- # components/Select - **Package**: `KIT` - **Type**: components Select is a form control for selecting a single value from a set of options, similar to the native <select> element. example of [ 'Textfield customization', 'Items handlers', 'Customize content', 'With DataList', 'Choose form control output', 'Virtual scroll', 'Mobile', 'Native picker with disabled option', 'Native picker with grouping options', ]; track example ) { ### Example ```html @let showPlaceholder = textfieldDoc.size === 's' || !control.value; @if (!showPlaceholder) { } ``` ### 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 {TuiIcon} from '@taiga-ui/core'; import {TuiChevron, TuiDataListWrapper, TuiSelect, TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiChevron, TuiDataListWrapper, TuiIcon, TuiSelect, TuiTooltip, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly users = [ 'Dmitriy Demenskiy', 'Alex Inkin', 'Vladimir Potekhin', 'Nikita Barsukov', 'Maxim Ivanov', 'German Panov', ]; protected value: string | null = null; } ``` #### Override option component You can override default behavior and appearance of all options inside dropdown. Just provide your custom component by tuiAsOptionContent -utility. Double check if you really need this feature! For the most cases <tui-data-list-wrapper [itemContent]="..." /> can be enough for your task. Explore this example for more details. **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} from '@taiga-ui/core'; import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit'; import {Option} from './option'; @Component({ imports: [ReactiveFormsModule, TuiChevron, TuiDataListWrapper, TuiSelect], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiAsOptionContent(Option)], }) export default class Example { protected readonly items = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4', 'Option 5', ] as const; protected readonly control = new FormControl(this.items[2]); } ``` #### Example 2 **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 {tuiItemsHandlersProvider} from '@taiga-ui/core'; import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit'; interface Character { readonly id: number; readonly name: string; } @Component({ imports: [FormsModule, TuiChevron, TuiDataListWrapper, TuiSelect], 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] } ``` #### Example 3 **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 {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit'; interface Card { name: string; number: string; paymentSystem: TuiPaymentSystem; } @Component({ imports: [FormsModule, TuiChevron, TuiDataListWrapper, TuiSelect, 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 stringify: TuiStringHandler = (x) => x.name; } ``` **LESS:** ```less .card { display: flex; align-items: center; gap: 0.5rem; } ``` #### Example 4 **Template:** ```html @for (group of taigaFamilyLibs; track group) { } } ``` **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, TuiDropdown} from '@taiga-ui/core'; import {TuiChevron, TuiDataListDropdownManager, TuiSelect} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiChevron, TuiDataList, TuiDataListDropdownManager, TuiDropdown, TuiSelect, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected taigaFamilyLibs = [ { name: 'Taiga UI', libraries: [ '@taiga-ui/cdk', '@taiga-ui/core', '@taiga-ui/kit', '@taiga-ui/addon-doc', '@taiga-ui/addon-charts', '@taiga-ui/addon-commerce', '@taiga-ui/addon-table', '@taiga-ui/addon-mobile', ], }, { name: 'Maskito', libraries: [ '@maskito/core', '@maskito/kit', '@maskito/phone', '@maskito/react', '@maskito/angular', '@maskito/vue', ], }, { name: 'Web APIs for Angular', libraries: [ '@ng-web-apis/common', '@ng-web-apis/platform', '@ng-web-apis/intersection-observer', '@ng-web-apis/resize-observer', '@ng-web-apis/mutation-observer', '@ng-web-apis/view-transition', '@ng-web-apis/universal', '@ng-web-apis/storage', '@ng-web-apis/geolocation', ], }, ]; protected value: string | null = null; } ``` #### Example 5 **Template:** ```html @for (item of items; track item) { }

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} 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 ``` **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 ``` **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

``` **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

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 ``` **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

Passionate Angular dev, musician and OSS author.

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSheetDialog, type TuiSheetDialogOptions} from '@taiga-ui/addon-mobile'; import {TuiButton} from '@taiga-ui/core'; import {TuiFloatingContainer} from '@taiga-ui/layout'; @Component({ imports: [TuiButton, TuiFloatingContainer, TuiSheetDialog], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected open = false; protected readonly options: Partial = { label: 'Alexander Inkin', closable: false, }; } ``` #### Advanced **Template:** ```html

Monty Python

And the Holy Grail

Cast:

John Cleese

Eric Idle

Michael Palin

Graham Chapman

Terry Gilliam

Terry Jones

Carol Cleveland


Directed by:

Terry Gilliam

Terry Jones


Produced by:

Mark Forstater

Michael White


Written by:

John Cleese

Eric Idle

Michael Palin

Graham Chapman

Terry Gilliam

Terry Jones


Budget:

{{ 400000 | tuiAmount: 'USD' }}


Box office:

{{ 5000000 | tuiAmount: 'USD' }}


Release date

April 3, 1975


Running time

92 minutes

© EMI Films
``` **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 {TuiSheetDialog} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiTitle} from '@taiga-ui/core'; import {TuiFloatingContainer, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ TuiAmountPipe, TuiButton, TuiFloatingContainer, TuiHeader, TuiSheetDialog, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected open = false; } ``` **LESS:** ```less .buttons { display: flex; margin: 0 0 1rem; gap: 0.5rem; & button { flex: 1; } } .footer { padding: 1rem 0 1.25rem; border-image: conic-gradient(var(--tui-background-base-alt) 0 0) fill 0/0/0 100vh 100vh; } .floating { margin-block-start: -1rem; padding-block-start: 1rem; } ``` #### Sticky elements **Template:** ```html
@for (user of users$ | async; track user) { }
Opens a separate app
``` **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 {TuiSheetDialog} from '@taiga-ui/addon-mobile'; import {TUI_DEFAULT_MATCHER, tuiControlValue} from '@taiga-ui/cdk'; import {TuiButton, TuiInput} from '@taiga-ui/core'; import {TuiAutoColorPipe, TuiAvatar, TuiInitialsPipe} from '@taiga-ui/kit'; import {TuiFloatingContainer} from '@taiga-ui/layout'; import {map} from 'rxjs'; const USERS = [ 'John Doe', 'Jane Doe', 'John Smith', 'Jane Smith', 'John Johnson', 'Jane Johnson', 'John Williams', 'Jane Williams', 'John Brown', 'Jane Brown', 'John Davis', 'Jane Davis', 'John Miller', 'Jane Miller', 'John Wilson', 'Jane Wilson', ]; @Component({ imports: [ AsyncPipe, ReactiveFormsModule, TuiAutoColorPipe, TuiAvatar, TuiButton, TuiFloatingContainer, TuiInitialsPipe, TuiInput, TuiSheetDialog, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected open = false; protected readonly offset = 16; protected readonly search = new FormControl(''); protected readonly users$ = tuiControlValue(this.search).pipe( map((search) => USERS.filter((user) => TUI_DEFAULT_MATCHER(user, search))), ); protected toggle(open: boolean): void { this.open = open; if (open) { this.search.setValue(''); } } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils.less'; .header { padding-block: 0.25rem 1rem; } .container { flex-grow: 1; } .item { all: unset; display: flex; block-size: 3rem; align-items: center; gap: 1rem; } .legal { .tui-line-clamp(); margin-block-start: 0.5rem; min-block-size: 2rem; } ``` #### Responsive **Template:** ```html
This dialog would show up as regular Dialog on desktop and as a SheetDialog on a mobile device.
``` **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 { TuiResponsiveDialog, type TuiResponsiveDialogOptions, } from '@taiga-ui/addon-mobile'; import {TuiButton, TuiLink} from '@taiga-ui/core'; @Component({ imports: [RouterLink, TuiButton, TuiLink, TuiResponsiveDialog], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly routes = DemoRoute; protected open = false; protected readonly options: Partial = { label: 'Responsive', size: 's', }; } ``` #### AppBar **Template:** ```html
When mobile styles are enabled you can use input[type='search'][tuiSearch] to imitate iOS native input

Search contacts
@for (item of items | slice: 0 : 6; track item) {
}
@for (item of items | tuiFilter: filter : search; track item) { }
``` **TypeScript:** ```ts import {SlicePipe} 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 {TuiSheetDialog} from '@taiga-ui/addon-mobile'; import {TUI_DEFAULT_MATCHER, TuiFilterPipe, type TuiMatcher} from '@taiga-ui/cdk'; import {TuiButton, TuiCell, TuiNotification, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiAvatarLabeled, TuiFade} from '@taiga-ui/kit'; import {TuiAppBar, TuiFloatingContainer} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, SlicePipe, TuiAppBar, TuiAvatar, TuiAvatarLabeled, TuiButton, TuiCell, TuiFade, TuiFilterPipe, TuiFloatingContainer, TuiNotification, TuiSheetDialog, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly open = signal(false); protected search = ''; protected readonly items = [ { name: 'Grigori Constantinopolsky', avatar: 'https://avatars.githubusercontent.com/u/10106368', email: 'grigori@gmail.com', }, { name: 'Nikolai Rimsky-Korsakov', avatar: 'https://avatars.githubusercontent.com/u/11832552', email: 'nikolai@gmail.com', }, { name: 'Hubert Wolfflegelstainhausenbergedorf', avatar: 'https://avatars.githubusercontent.com/u/46284632', email: 'hubert@gmail.com', }, { name: 'Arkhangelsky Constantine', avatar: 'https://avatars.githubusercontent.com/u/35179038', email: 'contantine@gmail.com', }, { name: 'Zoya Kosmodemyanskaya', avatar: 'https://avatars.githubusercontent.com/u/8158578', email: 'zoya@gmail.com', }, { name: 'Johann Gambolputty', avatar: '', email: 'johann@gmail.com', }, ...inject('Pythons' as any).map((name) => ({ name, avatar: '', email: `${name.split(' ')[0]}@gmail.com`, })), ]; protected readonly filter: TuiMatcher<[(typeof this.items)[0], string]> = ( item, search, ) => TUI_DEFAULT_MATCHER(item.name, search); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .input { .tui-prevent-ios-scroll(); inline-size: 100%; margin-block-start: 0.75rem; } .favorites { .scrollbar-hidden(); display: flex; gap: 0.75rem; margin: 1rem -1rem; padding: 0 0.5rem; overflow: auto; } .items { min-block-size: calc(100 * var(--tui-viewport-vh) - var(--tui-offset) - 19.5rem); } .cell { inline-size: 100%; margin: 0 -1rem; white-space: nowrap; overflow: hidden; border-radius: 0; } .button { .transition(inset-block-end); } ``` #### Fullscreen **Template:** ```html Email Telegram Music

Passionate Angular dev, musician and OSS author.

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSheetDialog, type TuiSheetDialogOptions} from '@taiga-ui/addon-mobile'; import {TuiButton} from '@taiga-ui/core'; import {TuiFloatingContainer} from '@taiga-ui/layout'; @Component({ imports: [TuiButton, TuiFloatingContainer, TuiSheetDialog], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected open = false; protected readonly options: Partial = { label: 'Alexander Inkin', closable: true, appearance: 'fullscreen', }; } ``` ### TypeScript ```ts import {Component, inject, type TemplateRef} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import { TUI_SHEET_DIALOG_DEFAULT_OPTIONS, TuiSheetDialogService, } from '@taiga-ui/addon-mobile'; import {TuiButton, type TuiDialogContext, TuiNotificationService} from '@taiga-ui/core'; import {TuiFloatingContainer} from '@taiga-ui/layout'; import {switchMap} from 'rxjs'; @Component({ imports: [TuiButton, TuiDemo, TuiFloatingContainer], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly sheetDialogs = inject(TuiSheetDialogService); private readonly alerts = inject(TuiNotificationService); protected readonly exampleComponent = import('./examples/import/component.md'); protected readonly examples = [ 'String', 'Basic', 'Advanced', 'Sticky elements', 'Responsive', 'AppBar', 'Fullscreen', ]; protected closable = TUI_SHEET_DIALOG_DEFAULT_OPTIONS.closable; protected appearance = TUI_SHEET_DIALOG_DEFAULT_OPTIONS.appearance; protected bar = TUI_SHEET_DIALOG_DEFAULT_OPTIONS.bar; protected initial = TUI_SHEET_DIALOG_DEFAULT_OPTIONS.initial; protected stops = TUI_SHEET_DIALOG_DEFAULT_OPTIONS.stops; protected label = TUI_SHEET_DIALOG_DEFAULT_OPTIONS.label; protected offset = TUI_SHEET_DIALOG_DEFAULT_OPTIONS.offset; protected required = TUI_SHEET_DIALOG_DEFAULT_OPTIONS.required; protected open = false; protected readonly stopsVariants = [this.stops, ['100px'], ['10rem', '20rem']]; protected readonly appearanceVariants = [this.appearance, 'fullscreen']; protected showDialog(content: TemplateRef>): void { const {appearance, required, closable, stops, initial, bar, offset, label} = this; this.sheetDialogs .open(content, { appearance, label, stops, initial, bar, offset, required, closable, }) .pipe(switchMap((response) => this.alerts.open(String(response)))) .subscribe(); } protected navigate(): void { void this.router.navigate(['./'], {relativeTo: this.route}); } } ``` ### LESS ```less .label { display: flex; gap: 0.75rem; } ``` --- # components/ShrinkWrap - **Package**: `KIT` - **Type**: components A tight shrink wrapping implementation in modern browsers using progressive enhancement concept, see examples below for visual explanation. Requires scroll-driven animations to work, gracefully ignored in older browsers. ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiShrinkWrap] | `string` | Max size of the container, 100% by default | ### Usage Examples #### Chat **Template:** ```html
I'm a short message
I'm a broken long message wrapping to a new line
I'm a fixed long message wrapping to a new line
I'm a short message
I'm a broken long message wrapping to a new line
I'm a fixed long message wrapping to a new line
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiMessage, TuiShrinkWrap} from '@taiga-ui/kit'; @Component({ imports: [TuiMessage, TuiShrinkWrap], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; inline-size: 16rem; } .incoming { background: var(--tui-background-neutral-1); border-end-start-radius: 0; white-space: normal; & + .incoming { margin-block-start: -0.5rem; } } .outgoing { background: var(--tui-background-accent-1); color: var(--tui-text-primary-on-accent-1); align-self: flex-end; white-space: normal; border-end-end-radius: 0; & + .outgoing { margin-block-start: -0.5rem; } } ``` #### Toasts **Template:** ```html

With ShrinkWrap

Long messages are wrapping perfectly to fit into max width of the toast

Without ShrinkWrap

Long messages are wrapping perfectly to fit into max width of the toast
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiShrinkWrap, TuiToast} from '@taiga-ui/kit'; import {TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [TuiHeader, TuiShrinkWrap, TuiToast], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } ``` #### Custom width **Template:** ```html
Title Long messages are wrapping perfectly to fit into max width of the cell
``` **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, TuiShrinkWrap} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiCell, TuiShrinkWrap, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } [tuiCell] { background: var(--tui-background-neutral-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 = ['Chat', 'Toasts', 'Custom width']; } ``` --- # components/Slider - **Package**: `CORE` - **Type**: components Taiga UI styling of native html tag '"> to choose a value from a limited range Read more about this input type in MDN Docs ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [disabled] | `boolean` | ) | | [max] | `number` | | | [min] | `number` | | | [step] | `number` | | | [segments] | `number[] | number` | for no ticks) | | [style.--tui-thumb-size.px] | `number` | Size of thumb | ### Usage Examples #### Size **Template:** ```html

Custom

``` **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 {TuiSlider} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiSlider], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly formControl = new FormControl(60); } ``` #### Colors **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSlider} from '@taiga-ui/core'; @Component({ imports: [TuiSlider], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .first { color: var(--tui-chart-categorical-01); } .second { color: var(--tui-chart-categorical-03); } .third { color: var(--tui-chart-categorical-12); } ``` #### Segments **Template:** ```html
@for (label of labels; track label) { }

Control value: {{ formControl.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 {TuiSlider} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiSlider], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly labels = [0, 250, 500, 750, 1000]; protected readonly formControl = new FormControl(250); protected patchValue(newValue: number): void { this.formControl.patchValue(newValue); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .ticks-labels { .tui-slider-ticks-labels(); } .tick-label { .button-clear(); outline: 0; cursor: pointer; } ``` #### Disabled **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSlider} from '@taiga-ui/core'; @Component({ imports: [TuiSlider], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### KeySteps **Template:** ```html
@for (label of labels; track label) { {{ label }} }

Control value: {{ formControl.value | number }}

``` **TypeScript:** ```ts import {DecimalPipe} 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 TuiKeySteps, TuiSlider} from '@taiga-ui/core'; @Component({ imports: [DecimalPipe, ReactiveFormsModule, TuiSlider], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly labels = ['5 000', '100 000', '300 000', '1 000 000']; protected readonly formControl = new FormControl(720_000); protected readonly segments = this.labels.length - 1; protected readonly steps = this.segments * 10; protected readonly keySteps: TuiKeySteps = [ [0, 5_000], [100 / 3, 100_000], [(100 / 3) * 2, 300_000], [100, 1_000_000], ]; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: block; inline-size: 27rem; } .ticks-labels { .tui-slider-ticks-labels(); } ``` #### Complex **Template:** ```html
``` **TypeScript:** ```ts import {AsyncPipe, PercentPipe} 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 {TUI_FALSE_HANDLER, tuiClamp} from '@taiga-ui/cdk'; import {TuiButton, TuiHint, TuiSlider} from '@taiga-ui/core'; import {BehaviorSubject, distinctUntilChanged, map, of, switchMap, timer} from 'rxjs'; @Component({ imports: [AsyncPipe, FormsModule, PercentPipe, TuiButton, TuiHint, TuiSlider], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, host: { '(pointerdown)': 'onKeydown(true)', '(document:pointerup)': 'onKeydown(false)', }, }) export default class Example { protected min = 0.5; protected max = 2; protected value = 1; protected readonly active$ = new BehaviorSubject(false); protected readonly showHint$ = this.active$.pipe( distinctUntilChanged(), switchMap((active) => active ? of(true) : timer(1000).pipe(map(TUI_FALSE_HANDLER)), ), ); protected onKeydown(show: boolean): void { this.active$.next(show); } protected change(step: number): void { this.value = tuiClamp(this.value + step, this.min, this.max); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; @border-radius: 1rem; .zoom-controller { display: flex; border-radius: @border-radius; background: var(--tui-background-neutral-1-pressed); block-size: var(--tui-height-s); justify-content: space-between; align-items: center; max-inline-size: 18rem; padding: 0 0.25rem; gap: 0.5rem; @media @tui-mobile { max-inline-size: 100%; } } .slider-wrapper { flex: 1; } .minus { border-radius: @border-radius 0 0 @border-radius; } .plus { border-radius: 0 @border-radius @border-radius 0; } ``` #### Vertical **Template:** ```html ``` **TypeScript:** ```ts import {Component, ViewEncapsulation} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiButton, TuiDropdown, TuiSlider} from '@taiga-ui/core'; @Component({ imports: [FormsModule, TuiButton, TuiDropdown, TuiSlider], templateUrl: './index.html', styleUrl: './index.less', encapsulation: ViewEncapsulation.None, changeDetection, }) export default class Example { protected value = 80; } ``` **LESS:** ```less tui-dropdown[data-appearance] { min-block-size: 8rem; background: var(--tui-background-neutral-1); box-shadow: none; border: none; backdrop-filter: blur(1rem); [tuiSlider] { position: absolute; inline-size: 7rem; transform-origin: left; transform: rotate(-90deg) translate(-100%, 1rem); // chromium browsers &::-webkit-slider-thumb { cursor: ns-resize; } // firefox &::-moz-range-thumb { cursor: ns-resize; } } } ``` ### TypeScript ```ts import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {TuiDemo} from '@demo/utils'; import {TuiSlider, TuiTitle} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiDemo, TuiSlider, TuiTitle], templateUrl: './index.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export default class Page { protected readonly examples = [ 'Size', 'Colors', 'Segments', 'Disabled', 'KeySteps', 'Complex', 'Vertical', ]; protected readonly segmentsVariants = [1, 5, 3, [0.2, 0.5], [0.1, 0.3]]; protected readonly control = new FormControl(1); protected max = 5; protected min = 0; protected step = 1; protected segments: number[] | number = this.max; protected thumbSize = 12; protected get disabled(): boolean { return this.control.disabled; } protected set disabled(value: boolean) { if (value) { this.control.disable(); return; } this.control.enable(); } } ``` --- # components/Slides - **Package**: `LAYOUT` - **Type**: components A component for displaying dynamic content animated between states. Use negative value for tuiSlides to indicate backward direction, positive for forward direction and 0 for static crossfade. Important: each child must be exactly one DOM element. ### Example ```html

@for (_ of '-'.repeat(3); track $index) { @if ($index === current) {

Slide #{{ $index }}
} }

``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiSlides] | `number` | Transition direction | ### Usage Examples #### Crossfade **Template:** ```html
@for (item of items; track item) { @if ($index === index) {
{{ item }}
{{ item.repeat(10) }}
} }
``` **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
@for (form of forms; track form; let i = $index) { @if (i === index) {

Registration form Tell us about yourself

@for (control of form.controls | keyvalue; track control; let j = $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 @if (step > 1) { }
@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) {
Title
Description
}
} @case (3) {

Title Subtitle

} }
``` **TypeScript:** ```ts import {Component, inject, TemplateRef, viewChild} 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'; import {TuiSheetDialogService} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiCell, TuiDialogService, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiProgressBar} from '@taiga-ui/kit'; import { TuiAppBar, TuiCard, TuiFloatingContainer, TuiHeader, TuiSlides, } from '@taiga-ui/layout'; @Component({ imports: [ TuiAppBar, TuiAvatar, TuiButton, TuiCard, TuiCell, TuiFloatingContainer, TuiHeader, TuiProgressBar, TuiSlides, TuiTitle, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly template = viewChild(TemplateRef); private readonly dialogs = inject(WA_IS_MOBILE) ? inject(TuiSheetDialogService) : inject(TuiDialogService); protected step = 1; protected direction = 0; protected onClick(): void { this.step = 1; this.direction = 0; this.dialogs.open(this.template(), {appearance: 'fullscreen'}).subscribe(); } protected onStep(step: number): void { this.direction = step; this.step += step; } } ``` ### TypeScript ```ts import {ViewportScroller} from '@angular/common'; import {Component, inject, ViewEncapsulation} from '@angular/core'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {Router, Scroll} from '@angular/router'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; import {TuiSlides} from '@taiga-ui/layout'; import {filter, map} from 'rxjs'; import {Home} from './examples/3/home'; import {Notifications} from './examples/3/notifications'; import {Settings} from './examples/3/settings'; @Component({ imports: [ FormsModule, ReactiveFormsModule, TuiDemo, TuiInputNumber, TuiSlides, TuiTextfield, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation: ViewEncapsulation.None, changeDetection, host: {class: 'tui-slides'}, }) export default class Page { protected readonly routes = DemoRoute; protected readonly examples = ['Crossfade', 'Stepper', 'Routing', 'Dialog']; protected readonly directions = [-1, 0, 1]; protected direction = 0; protected current = 0; constructor() { const scroller = inject(ViewportScroller); const router = inject(Router); const route = router.config.find( ({path}) => path === this.routes.Slides.replace('/', ''), ); const length = route?.children?.length ?? 0; router.events .pipe( filter((event) => event instanceof Scroll), map(() => scroller.getScrollPosition()), takeUntilDestroyed(), ) .subscribe((position) => { setTimeout(() => scroller.scrollToPosition(position)); }); if (length > 1) { return; } route?.children?.unshift({path: '1', component: Home}); route?.children?.unshift({path: '2', component: Notifications}); route?.children?.unshift({path: '3', component: Settings}); router.resetConfig(router.config); } } ``` ### LESS ```less .tui-slides { tui-doc-example .t-demo { overflow: hidden; } [tuiSlides] ng-component { display: block; &.tui-slides { display: none; } } } ``` --- # components/Status - **Package**: `KIT` - **Type**: components ### Usage Examples #### Basic **Template:** ```html Success

Status is automatically colored within some badge appearances

Success ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiBadge, TuiStatus} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge, TuiStatus], 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 {} ``` --- # components/Stepper - **Package**: `KIT` - **Type**: components ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [orientation] | `TuiOrientation` | Steps direction | | [(activeItemIndex)] | `number` | Active step index | ### 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 {TuiStepper} from '@taiga-ui/kit'; @Component({ imports: [TuiStepper], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Vertical **Template:** ```html @for (step of steps; track step) { } ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiStepper} from '@taiga-ui/kit'; @Component({ imports: [TuiStepper], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly steps = ['Start Up', 'Cash In', 'Sell Out', 'Bro Down']; } ``` #### Vertical autoscroll **Template:** ```html @for (step of steps; track step) { } ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiStepper} from '@taiga-ui/kit'; @Component({ imports: [TuiStepper], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly steps = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven']; } ``` **LESS:** ```less .stepper { max-block-size: 10rem; border: 1px solid var(--tui-border-normal); } ``` #### Vertical connected **Template:** ```html @for (step of steps; track step) { } ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiConnected, TuiStepper} from '@taiga-ui/kit'; @Component({ imports: [TuiConnected, TuiStepper], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly steps = [ 'Start Up', 'Cash In', 'Sell out this huge amount that you have been saving up for many years of hard work', 'Bro Down', ]; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {type TuiOrientation} from '@taiga-ui/core'; import {TuiStepper} from '@taiga-ui/kit'; @Component({ imports: [TuiDemo, TuiStepper], templateUrl: './index.html', changeDetection, }) export default class Page { protected activeItemIndex = 0; protected readonly examples = [ 'Basic', 'Vertical', 'Vertical autoscroll', 'Vertical connected', ]; protected readonly orientationVariants: readonly TuiOrientation[] = [ 'horizontal', 'vertical', ]; protected orientation = this.orientationVariants[0]!; protected readonly iconVariants = ['', '@tui.clock', '@tui.heart']; protected icon = this.iconVariants[0]!; protected readonly stateVariants = ['normal', 'pass', 'error'] as const; protected state: 'error' | 'normal' | 'pass' = this.stateVariants[0]; } ``` --- # components/Surface - **Package**: `LAYOUT` - **Type**: components General purpose container used in Taiga UI interfaces. Often used in conjunction with Card component. ### Usage Examples #### Behaviors **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'; import {TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [TuiAppearance, TuiSurface], templateUrl: './index.html', styleUrls: ['./base.less', './index.less'], encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .scale { @media @tui-mouse { &:hover { transform: scale(1.15); } } &:active { transform: scale(0.95); } } .overlay { &::after { opacity: 0; background: linear-gradient( -45deg, #a1a1b3 0.36%, #d4d1d8 46.96%, #f7fafa 67.14%, #d4d1d8 83.19%, #a1a1b3 93.03% ); } @media @tui-mouse { &:hover::after { opacity: 0.5; } } &:active::after { opacity: 1; } } .highlight { @media @tui-mouse { &:hover::before { backdrop-filter: brightness(1.1); } } &:active::before { backdrop-filter: brightness(0.9); } } .offset:hover { transform: translate3d(0, -0.25rem, 0); } .background { &::after { background: url('/assets/images/not-found.svg') top; background-size: 300%; } &:hover::after { transform: scale(1.15); } } .shadow:hover { box-shadow: var(--tui-shadow-small-hover); } ``` #### Presets **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'; import {TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [TuiAppearance, TuiSurface], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: grid; grid-template-columns: repeat(3, 8rem); gap: 1rem; } [tuiSurface] { padding: 1.25rem; border-radius: var(--tui-radius-l); } ``` #### Blur **Template:** ```html

backdrop-filter

You can use backdrop-filter on tuiSurface element to blur the background behind it.

Never use this mode with shadow
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [TuiSurface], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: block; background: url('/assets/images/big-wallpaper.jpg'); background-size: cover; padding: 2rem; } .blur { backdrop-filter: blur(1rem); background: var(--tui-background-neutral-1); color: var(--tui-text-primary); padding: 1rem 1.25rem; } .title { margin: 0; font: var(--tui-typography-heading-h6); } ``` #### Video **Template:** ```html

Big Buck Bunny

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [TuiSurface], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .surface { display: flex; align-items: flex-end; justify-content: center; block-size: 18rem; color: #fff; font: var(--tui-typography-body-l); &::after { background: var(--tui-background-neutral-1); box-shadow: inset 0 -7rem 6rem -6rem #000; mix-blend-mode: multiply; } } ``` #### Selectable **Template:** ```html @for (_ of '-'.repeat(4); track $index) { } ``` **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 {TuiRipple} from '@taiga-ui/addon-mobile'; import {TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [FormsModule, TuiRipple, TuiSurface], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected value = null; } ``` **LESS:** ```less :host { display: flex; gap: 1rem; white-space: nowrap; * > { flex-shrink: 0; } } ``` #### Spacing compensation **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'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [TuiAppearance, TuiAvatar, TuiSurface], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .button { display: flex; inline-size: 20rem; text-align: start; gap: 1rem; border-radius: 1rem; padding: 1rem 1rem 1rem 1.25rem; font: var(--tui-typography-body-m); } .title { display: block; font: var(--tui-typography-heading-h6); margin-block-end: 0.25rem; } .bad { margin-block-start: 1rem; padding: 1.25rem; } ``` #### Examples **Template:** ```html

RESTAURANT

Eat all you can

Taiga UI reviews

@for (review of reviews; track review) {
{{ review.body }}
}

My bank account

{{ 23742 | tuiAmount: 'USD' : 'start' }}

1234 5678
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiAmountPipe, TuiThumbnailCard} from '@taiga-ui/addon-commerce'; import { TuiAppearance, TuiButton, TuiCell, TuiLink, TuiScrollbar, tuiScrollbarOptionsProvider, TuiTitle, } from '@taiga-ui/core'; import {TuiAvatar, TuiBadge} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader, TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [ TuiAmountPipe, TuiAppearance, TuiAvatar, TuiBadge, TuiButton, TuiCardLarge, TuiCell, TuiHeader, TuiLink, TuiScrollbar, TuiSurface, TuiThumbnailCard, TuiTitle, ], templateUrl: './index.html', styleUrls: ['./index.less', './surface.less'], encapsulation, changeDetection, providers: [tuiScrollbarOptionsProvider({mode: 'hidden'})], }) export default class Example { protected readonly reviews = [ { name: 'Alex Inkin', time: '2 days ago', body: 'It is an absolute blast!', }, { name: 'Alex Inkin', time: '3 days ago', body: 'I am starting to kind of enjoy this library.', }, { name: 'Alex Inkin', time: '4 days ago', body: 'This library seems interesting but I hesitate refactoring our entire codebase...', }, ]; } ``` **LESS:** ```less :host { display: grid; grid-template-columns: repeat(6, 6.5rem); grid-auto-rows: 7rem; align-items: start; gap: 1rem; } .card { display: flex; block-size: 7rem; flex-direction: column; &:first-child { grid-column: span 2; } [tuiTitle] { color: var(--tui-text-primary); margin-block-end: auto; } } .restaurant { display: flex; block-size: 15rem; grid-column: span 3; grid-row: span 2; flex-direction: column; justify-content: space-between; .footer { display: flex; justify-content: space-between; button { border-radius: 5rem; backdrop-filter: blur(1rem) brightness(0.5); } } } .blur { display: flex; grid-column: span 3; align-items: center; gap: 1rem; padding: 1rem; color: var(--tui-text-primary); .button { border-radius: 100%; margin-inline-start: auto; } } .reviews { grid-column: span 3; grid-row: span 3; .scrollbar { margin: 0.75rem -1.5rem 1.25rem; scroll-snap-type: x mandatory; overscroll-behavior-x: contain; } .wrapper { display: flex; padding: 0 1.5rem; gap: 0.625rem; &::after { content: ''; min-inline-size: 0.625rem; } } .review { display: flex; flex-direction: column; min-inline-size: calc(100% - 0.75rem); padding: 0.75rem 1rem 1rem; border-radius: 1rem; scroll-snap-align: start; scroll-margin: 1.5rem; } [tuiCell] { margin-block-start: auto; } } .mask { grid-column: span 3; grid-row: span 3; padding: 4rem 2rem 7rem; .footer { display: flex; gap: 0.75rem; margin-block-start: 4rem; } .add { inline-size: 3rem; border-radius: var(--tui-radius-xs); } } ``` ### 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 {TuiTitle} from '@taiga-ui/core'; @Component({ imports: [TuiDemo, TuiTitle], templateUrl: './index.html', changeDetection, }) export default class Example { protected readonly examples = [ 'Behaviors', 'Presets', 'Blur', 'Video', 'Selectable', 'Spacing compensation', ]; protected readonly layerExample = { 'surface.less': import('./examples/7/surface.less'), }; protected readonly routes = DemoRoute; } ``` --- # components/SwipeActions - **Package**: `ADDON-MOBILE` - **Type**: components Component should be used on mobile devices only. ### Usage Examples #### Basic **Template:** ```html
{{ 10000 | tuiAmount: 'USD' }}
Dollar account
{{ 23000 | tuiAmount: 'EUR' }}
Goal
{{ 5000 | tuiAmount: 'EUR' }}
Vacations
``` **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 {TuiSwipeActions} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ TuiAmountPipe, TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiSwipeActions, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; inline-size: 100%; flex-direction: column; gap: 1rem; } [tuiCardLarge] { margin: 3rem 1.5rem; } tui-swipe-actions { margin: -3rem -1.5rem; } button[tuiSwipeAction] { border-radius: 100%; } ``` #### Custom **Template:** ```html

Alex Inkin

``` **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 {TuiSwipeActions} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiSwitch} from '@taiga-ui/kit'; import {TuiSurface} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiButton, TuiSurface, TuiSwipeActions, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected checkbox = false; } ``` **LESS:** ```less .blur { z-index: 1; display: flex; background-image: url('/assets/images/restaurant-2.jpg'); border-radius: 1rem; padding: 1.25rem; justify-content: space-between; align-items: center; color: #fff; &::before { backdrop-filter: blur(1rem); } } ``` #### Autoclose **Template:** ```html
{{ 10000 | tuiAmount: 'USD' }}
Dollar account
{{ 23000 | tuiAmount: 'EUR' }}
Goal
``` **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 {TuiSwipeActions, TuiSwipeActionsAutoClose} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ TuiAmountPipe, TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiSwipeActions, TuiSwipeActionsAutoClose, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } tui-swipe-actions { margin: -3rem -1.5rem; } [tuiCardLarge] { margin: 3rem 1.5rem; } button[tuiSwipeAction] { border-radius: 100%; } ``` #### Dynamic actions **Template:** ```html
{{ 10000 | tuiAmount: 'USD' }}
Dollar account
@if (editButton) { } @if (shareButton) { }
``` **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 {TuiSwipeActions} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiCell, TuiCheckbox, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAmountPipe, TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiCheckbox, TuiSwipeActions, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected shareButton = false; protected editButton = true; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; } tui-swipe-actions { margin: -3rem -1.5rem -1.5rem; } [tuiCardLarge] { margin: 3rem 1.5rem; } button[tuiSwipeAction] { border-radius: 100%; } label { display: flex; align-items: center; margin-block-start: 0.5rem; } input[type='checkbox'] { margin-inline-end: 0.5rem; } ``` #### Fallback for desktop **Template:** ```html
{{ 10000 | tuiAmount: 'USD' }}
Dollar account
``` **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 {TuiSwipeActions} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiCell, TuiDataList, TuiDropdown, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ TuiAmountPipe, TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiDataList, TuiDropdown, TuiSwipeActions, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: flex; } .fallback { .center-top(); inset-inline-end: 2rem; } tui-swipe-actions { margin: -3rem -1.5rem; } [tuiCardLarge] { margin: 3rem 1.5rem; } button[tuiSwipeAction] { border-radius: 100%; } @media @tui-touch { .fallback { display: none; } } ``` #### Onboarding **Template:** ```html
{{ 10000 | tuiAmount: 'USD' }}
Dollar account
{{ 23000 | tuiAmount: 'EUR' }}
Goal
``` **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 {TuiAmountPipe} from '@taiga-ui/addon-commerce'; import {TuiSwipeActions, TuiSwipeActionsOnboarding} from '@taiga-ui/addon-mobile'; import {TuiButton, TuiCell, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar} from '@taiga-ui/kit'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAmountPipe, TuiAvatar, TuiButton, TuiCardLarge, TuiCell, TuiSwipeActions, TuiSwipeActionsOnboarding, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly onboarding = signal(false); } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; } [tuiCardLarge] { margin: 3rem 1.5rem; } tui-swipe-actions { margin: -3rem -1.5rem; } button[tuiSwipeAction] { border-radius: 100%; } ``` ### 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 = [ 'Basic', 'Custom', 'Autoclose', 'Dynamic actions', 'Fallback for desktop', 'Onboarding', ]; } ``` ### LESS ```less :host ::ng-deep tui-doc-example .t-demo { overflow: hidden; } ``` --- # components/Switch - **Package**: `KIT` - **Type**: components A switch 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 ### Usage Examples #### Platforms **Template:** ```html @for (platform of platforms; track $index) {
} ``` **TypeScript:** ```ts import {Component, type OnInit} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiPlatform} from '@taiga-ui/cdk'; import {type TuiSizeS} from '@taiga-ui/core'; import {TuiSwitch} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, ReactiveFormsModule, TuiPlatform, TuiSwitch], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example implements OnInit { protected readonly platforms: ReadonlyArray<'android' | 'ios' | 'web'> = [ 'web', 'web', 'android', 'ios', ]; protected readonly invalidTrue = new FormControl(true, () => ({invalid: true})); protected readonly invalidFalse = new FormControl(false, () => ({invalid: true})); public ngOnInit(): void { this.invalidTrue.markAsTouched(); this.invalidFalse.markAsTouched(); } protected getSize(first: boolean): TuiSizeS { return first ? 'm' : 's'; } } ``` **LESS:** ```less :host { display: flex; --tui-background-accent-2: #428bf9; } .wrapper { display: flex; flex-direction: column; justify-content: space-around; align-items: center; flex: 1; gap: 1rem; padding: 1rem; &_web { border: 1px solid var(--tui-border-normal); border-inline-start-width: 0; &:first-child { border-inline-end-width: 0; border-inline-start-width: 1px; } } } ``` #### Same color **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 {TuiSwitch, tuiSwitchOptionsProvider} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiSwitch], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [ tuiSwitchOptionsProvider({showIcons: false, appearance: () => 'primary'}), ], }) export default class Example { protected value = false; } ``` **LESS:** ```less :host { display: flex; --tui-background-accent-2: var(--tui-status-info); } .wrapper { display: flex; flex-direction: column; justify-content: space-around; align-items: center; flex: 1; gap: 1rem; padding: 1rem; &_web { border: 1px solid var(--tui-border-normal); border-inline-start-width: 0; &:first-child { border-inline-end-width: 0; border-inline-start-width: 1px; } } } ``` ### 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 = ['Platforms', 'Same color']; } ``` --- # components/TabBar - **Package**: `ADDON-MOBILE` - **Type**: components Component for creating mobile navigation. ### Usage Examples #### Buttons **Template:** ```html ``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTabBar} from '@taiga-ui/addon-mobile'; import {TuiNotificationService} from '@taiga-ui/core'; interface Item { badge?: number; icon: string; text: string; } @Component({ selector: 'tui-tab-bar-example', imports: [TuiTabBar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly alerts = inject(TuiNotificationService); protected activeItemIndex = 1; protected readonly items = [ { text: 'Favorites', icon: '@tui.heart', badge: 3, }, { text: 'Calls', icon: '@tui.phone', badge: 1234, }, { text: 'Profile', icon: '@tui.user', }, { text: 'Settings and configuration', icon: '@tui.settings', badge: 100, }, { text: 'More', icon: '@tui.ellipsis', }, ]; protected onClick(item: Item): void { item.badge = 0; this.alerts.open(this.activeItemIndex, {label: item.text}).subscribe(); } } ``` **LESS:** ```less .tabs { min-inline-size: 25rem; padding-block-end: env(safe-area-inset-bottom); } ``` #### Links **Template:** ```html ``` **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 {TuiTabBar} from '@taiga-ui/addon-mobile'; @Component({ imports: [RouterLink, RouterLinkActive, TuiTabBar], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly routes = DemoRoute; } ``` #### Customization **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTabBar} from '@taiga-ui/addon-mobile'; @Component({ imports: [TuiTabBar], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ { text: 'Home', icon: '@tui.home', }, { text: 'Photos', icon: '@tui.image', }, { text: 'Navigation', icon: '@tui.map-pin', }, ]; } ``` **LESS:** ```less .tabs { min-inline-size: 25rem; color: var(--tui-text-action); --tui-active-color: var(--tui-background-accent-2); } ``` #### Skeleton **Template:** ```html

``` **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 {TuiTabBar} from '@taiga-ui/addon-mobile'; import {TuiButton} from '@taiga-ui/core'; import {map, startWith, Subject, switchMap, timer} from 'rxjs'; @Component({ imports: [AsyncPipe, TuiButton, TuiTabBar], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly load$ = new Subject(); protected readonly items$ = this.load$.pipe( startWith(null), switchMap(() => timer(3000).pipe( map(() => [ { text: 'Favorites', icon: '@tui.heart', }, { text: 'Calls', icon: '@tui.phone', }, { text: 'Profile', icon: '@tui.user', }, { text: 'Settings and configuration', icon: '@tui.settings', }, ]), startWith([]), ), ), ); } ``` ### 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 {TuiCheckbox, TuiLabel, TuiPopup} from '@taiga-ui/core'; import TuiTabBarExample from './examples/1'; @Component({ imports: [FormsModule, TuiCheckbox, TuiDemo, TuiLabel, TuiPopup, TuiTabBarExample], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly examples = ['Buttons', 'Links', 'Customization', 'Skeleton']; protected fixed = false; } ``` ### LESS ```less .bar { inline-size: 25rem; } .fixed { position: fixed; inset-block-end: 0; inline-size: 100%; } ``` --- # components/Table - **Package**: `ADDON-TABLE` - **Type**: components This module allows you to create various tables, both static and editable. ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [columns] | `readonly string[]` | An array of keys to set up columns order | | [size] | `TuiSizeS | TuiSizeL` | Cells size | | [(sorter)] | `TuiComparator` | Sort function (basic JavaScript array sort API) | | [(direction)] | `-1 | 1` | Direction for sorting | ### Usage Examples #### Basic **Template:** ```html @for (item of data; track item) { }
Name Balance
{{ item.name }} {{ item.balance | tuiFormatNumber }}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTable} from '@taiga-ui/addon-table'; import {TuiFormatNumberPipe} from '@taiga-ui/core'; @Component({ imports: [TuiFormatNumberPipe, TuiTable], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly data = [ { name: 'Alex Inkin', balance: 1323525, }, { name: 'Roman Sedov', balance: 423242, }, ] as const; } ``` #### Toggle rows **Template:** ```html @for (item of basicData; track item) { } @for (item of manualOpenData; track item) { }
First name Last name Role Balance
{{ basicData.length }} Developers (basic usage) dev {{ basicData | tuiMapper: getSumBalance | tuiFormatNumber }}
{{ item.firstName }}
{{ item.lastName }} {{ item.role }} {{ item.balance | tuiFormatNumber }}
{{ manualOpenData.length }} Designers (manual handling) design {{ manualOpenData | tuiMapper: getSumBalance | tuiFormatNumber }}
{{ item.firstName }} {{ item.lastName }} {{ item.role }} {{ item.balance | tuiFormatNumber }}
Custom content (click on chevron) all {{ customContentData | tuiMapper: getSumBalance | tuiFormatNumber }}
@for (item of customContentData; track item) { {{ item.firstName }} {{ item.lastName }} }
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTable, TuiTableExpand} from '@taiga-ui/addon-table'; import {TuiMapperPipe} from '@taiga-ui/cdk'; import {TuiButton, TuiExpand, TuiFormatNumberPipe, TuiHint} from '@taiga-ui/core'; import {TuiChevron, TuiChip} from '@taiga-ui/kit'; interface Item { firstName: string; lastName: string; role: string; balance: number; } @Component({ imports: [ TuiButton, TuiChevron, TuiChip, TuiExpand, TuiFormatNumberPipe, TuiHint, TuiMapperPipe, TuiTable, TuiTableExpand, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly basicData: Item[] = [ { firstName: 'Alex', lastName: 'Inkin', role: 'dev', balance: 1323525, }, { firstName: 'Roman', lastName: 'Sedov', role: 'dev', balance: 423242, }, { firstName: 'Andrei', lastName: 'Serebrennikov', role: 'dev', balance: 4223242, }, ]; protected manualOpenData: Item[] = [ { firstName: 'Joe', lastName: 'Wilson', role: 'design', balance: 423242, }, { firstName: 'Julia', lastName: 'Johnson', role: 'design', balance: 4223242, }, ]; protected readonly customContentData = [...this.basicData, ...this.manualOpenData]; protected readonly columns = ['action', 'firstName', 'lastName', 'role', 'balance']; protected manualOpen = false; protected customOpen = false; public getSumBalance(people: Item[]): number { return people.reduce((res, item) => { res += item.balance; return res; }, 0); } protected manualToggle(): void { this.manualOpen = !this.manualOpen; } protected customToggle(): void { this.customOpen = !this.customOpen; } } ``` **LESS:** ```less th { white-space: nowrap; } .table { inline-size: 36rem; } .chips { display: flex; gap: 0.5rem; flex-wrap: wrap; padding: 0.5rem; } .last-name-col { inline-size: 10rem; } .expand-heading-row { cursor: pointer; td { background: var(--tui-background-neutral-1); } &:hover td { background: var(--tui-background-neutral-1-hover); } } ``` #### Controls **Template:** ```html
Growing height Static height
@if (isMobile) { } @if (!isMobile) { } @if (!isMobile) { }
@if (!card) { InputCardGroup }
``` **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_MOBILE} from '@ng-web-apis/platform'; import {TuiInputCardGroup} from '@taiga-ui/addon-commerce'; import {TuiTable} from '@taiga-ui/addon-table'; import {TuiInput} from '@taiga-ui/core'; import { TuiChevron, TuiDataListWrapper, TuiInputChip, TuiInputDateMulti, TuiInputRange, TuiInputSlider, TuiMultiSelect, TuiSelect, TuiTextarea, } from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiChevron, TuiDataListWrapper, TuiInput, TuiInputCardGroup, TuiInputChip, TuiInputDateMulti, TuiInputRange, TuiInputSlider, TuiMultiSelect, TuiSelect, TuiTable, TuiTextarea, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected textarea = ''; protected input = ''; protected chip = []; protected multiselect = []; protected date = []; protected select = null; protected slider = null; protected range = null; protected card = null; protected readonly items = ['One', 'Two', 'Three', 'Four', 'Five']; protected readonly isMobile = inject(WA_IS_MOBILE); } ``` #### Custom **Template:** ```html
{{ value === 's' ? 'Small' : value === 'm' ? 'Medium' : 'Large' }}
@for (item of data; track item) { }
Checkbox
Title Cell Status Items Progress Actions
{{ item.checkbox.title }} {{ item.checkbox.subtitle }}
{{ item.title.title }} @if (item.title.chip && item.title.subtitle) { {{ item.title.chip }} } @if (!item.title.subtitle && item.title.chip) { {{ item.title.chip }} } @else { {{ item.title.subtitle }} }
{{ item.cell.name }} {{ item.cell.email }}
{{ item.status.value }} @for (chip of item.items; track chip) {
{{ chip }}
}
@for (chip of item.items; track chip) { @if ($index > number) {
{{ chip }}
} }
{{ item.progress }}ms
``` **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 {TuiTable, TuiTableControl} from '@taiga-ui/addon-table'; import { TuiButton, TuiCell, TuiCheckbox, TuiDropdown, TuiIcon, TuiLink, TuiTitle, } from '@taiga-ui/core'; import { TuiAutoColorPipe, TuiAvatar, TuiBadge, TuiChip, TuiInitialsPipe, TuiItemsWithMore, TuiProgressBar, TuiRadioList, TuiStatus, } from '@taiga-ui/kit'; import {TuiItemGroup} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAutoColorPipe, TuiAvatar, TuiBadge, TuiButton, TuiCell, TuiCheckbox, TuiChip, TuiDropdown, TuiIcon, TuiInitialsPipe, TuiItemGroup, TuiItemsWithMore, TuiLink, TuiProgressBar, TuiRadioList, TuiStatus, TuiTable, TuiTableControl, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly sizes = ['l', 'm', 's'] as const; protected size = this.sizes[0]; protected selected = []; protected readonly data = [ { checkbox: { title: 'Data point 1', subtitle: 'The first element', }, title: { icon: '@tui.file', title: 'This is title', chip: 'Chip', subtitle: 'More information ・ Data', }, cell: { name: 'John Cleese', email: 'silly@walk.uk', }, status: { value: 'Success', color: 'var(--tui-status-positive)', }, items: ['Some', 'items', 'displayed', 'here', 'and', 'can', 'overflow'], progress: 78, }, { checkbox: { title: 'Some title', subtitle: 'Some more text', }, title: { icon: '@tui.heart', title: 'More info', chip: 'Chips can be here', }, cell: { name: 'Eric Idle', email: 'cool@dude.com', }, status: { value: 'Failure', color: 'var(--tui-status-negative)', }, items: ['One', 'Item'], progress: 91, }, { checkbox: { title: 'And now', subtitle: 'Completely different', }, title: { icon: '@tui.star', title: 'Wow', }, cell: { name: 'Michael Palin', email: 'its@man.com', }, status: { value: 'Pending', color: 'var(--tui-status-warning)', }, items: [], progress: 32, }, ]; } ``` **LESS:** ```less [tuiTh], [tuiTd] { border-inline-start: none; border-inline-end: none; } [tuiTable][data-size='s'] [tuiTitle] { flex-direction: row; gap: 0.375rem; } ``` #### Editable **Template:** ```html @let sortedPythons = pythons | tuiTableSort; @for (item of sortedPythons; track trackByIndex($index)) { @if (item.price <= 1000) { } } @let sortedStarwars = starwars | tuiTableSort; @for (item of sortedStarwars; track trackByIndex($index)) { }
Name Price, $ Purchase Date Total
Quantity Units
{{ getTotal(item) | tuiFormatNumber }}
{{ getTotal(item) | tuiFormatNumber }}
Star Wars ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {FormsModule, type ValidatorFn} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {WaIntersectionObserver} from '@ng-web-apis/intersection-observer'; import {type TuiComparator, TuiTable} from '@taiga-ui/addon-table'; import {TuiDay, tuiDefaultSort, TuiValidator} from '@taiga-ui/cdk'; import { TuiFormatNumberPipe, TuiIcon, TuiNumberFormat, TuiScrollbar, tuiScrollbarOptionsProvider, } from '@taiga-ui/core'; import { TuiChevron, TuiDataListWrapper, TuiInputDate, TuiInputNumber, TuiSelect, TuiTextarea, } from '@taiga-ui/kit'; interface Item { readonly date: TuiDay; readonly name: string; readonly price: number; readonly quantity: number; readonly unit: string; } @Component({ imports: [ FormsModule, TuiChevron, TuiDataListWrapper, TuiFormatNumberPipe, TuiIcon, TuiInputDate, TuiInputNumber, TuiNumberFormat, TuiScrollbar, TuiSelect, TuiTable, TuiTextarea, TuiValidator, WaIntersectionObserver, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [tuiScrollbarOptionsProvider({mode: 'hidden'})], }) export default class Example { protected readonly options = {updateOn: 'blur'} as const; protected readonly units = ['items', 'kg', 'm']; protected pythons: readonly Item[] = [ { name: 'Holy Grail', price: 999999, quantity: 1, unit: this.units[0] ?? '', date: TuiDay.currentLocal(), }, { name: 'Foot', price: 29.95, quantity: 5, unit: this.units[2] ?? '', date: TuiDay.currentLocal().append({day: -42}), }, { name: 'Shed', price: 499, quantity: 2, unit: this.units[0] ?? '', date: TuiDay.currentLocal().append({day: -237}), }, ]; protected starwars: readonly Item[] = [ { name: 'Lightsaber', price: 4999, quantity: 3, unit: this.units[0] ?? '', date: TuiDay.currentLocal(), }, { name: 'Spaceship', price: 19999, quantity: 1, unit: this.units[0] ?? '', date: TuiDay.currentLocal().append({day: -237}), }, { name: 'Stormtrooper helmet', price: 14.95, quantity: 5, unit: this.units[0] ?? '', date: TuiDay.currentLocal().append({day: -42}), }, ]; protected readonly columns = [ 'name', 'price', 'quantity', 'unit', 'date', 'total', ] as const; protected readonly minPrice: ValidatorFn = ({value}) => value > 400 ? null : {minPrice: 'Price must be above $400'}; protected readonly totalSorter: TuiComparator = (a, b) => tuiDefaultSort(a.price * a.quantity, b.price * b.quantity); protected trackByIndex(index: number): number { return index; } protected getTotal({price, quantity}: Item): number { return price * quantity; } protected onValueChange( value: Item[K], key: K, current: Item, data: readonly Item[], ): void { const updated = {...current, [key]: value}; this.pythons = data === this.pythons ? this.pythons.map((item) => (item === current ? updated : item)) : this.pythons; this.starwars = data === this.starwars ? this.starwars.map((item) => (item === current ? updated : item)) : this.starwars; } } ``` **LESS:** ```less .table { table-layout: fixed; } .number { text-align: end; flex-direction: row-reverse; } .first { min-inline-size: 11rem; max-inline-size: 11rem; } .second { inset-inline-start: 11rem; } .text { vertical-align: top; padding-block-start: 1rem; } // Due to rowSpan this item appears to be the first child // but it shouldn't have the left border in reality .border { border-inline-start: none; } .select { inline-size: 6.25rem; } .scrollbar { max-block-size: 18.75rem; } ``` #### Sorting **Template:** ```html

Sort key: {{ sortKey$ | async }}, direction: {{ direction$ | async }}

@if (data$ | async; as data) { @let sortedData = data | tuiTableSort; @for (item of sortedData; track item) { }
Name Date of Birth Age
{{ item.name }} {{ item.dob }} {{ item.age }}
}
``` **TypeScript:** ```ts import {AsyncPipe} from '@angular/common'; import {Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import { type TuiComparator, TuiReorder, type TuiSortChange, TuiSortDirection, TuiTable, TuiTablePagination, type TuiTablePaginationEvent, } from '@taiga-ui/addon-table'; import { TUI_DEFAULT_MATCHER, tuiControlValue, TuiDay, tuiDefaultSort, tuiIsPresent, tuiToInt, } from '@taiga-ui/cdk'; import { TuiButton, TuiCheckbox, TuiDropdown, TuiInput, TuiLabel, TuiLoader, TuiNumberFormat, } from '@taiga-ui/core'; import {TuiChevron, TuiInputNumber} from '@taiga-ui/kit'; import { BehaviorSubject, combineLatest, debounceTime, filter, map, type Observable, share, startWith, switchMap, tap, timer, } from 'rxjs'; interface User { readonly dob: TuiDay; readonly name: string; readonly age: number; } const TODAY = TuiDay.currentLocal(); const FIRST = [ 'John', 'Jane', 'Jack', 'Jill', 'James', 'Joan', 'Jim', 'Julia', 'Joe', 'Julia', ]; const LAST = [ 'Smith', 'West', 'Brown', 'Jones', 'Davis', 'Miller', 'Johnson', 'Jackson', 'Williams', 'Wilson', ]; const DATA: readonly User[] = Array.from({length: 300}, () => { const dob = TODAY.append({day: -Math.floor(Math.random() * 4000) - 7500}), age = getAge(dob), name = `${LAST[Math.floor(Math.random() * 10)]}, ${FIRST[Math.floor(Math.random() * 10)]}`; return {name, dob, age}; }); const KEYS: Record = { Name: 'name', Age: 'age', 'Date of Birth': 'dob', }; function sortBy(key: keyof User, direction: TuiSortDirection): TuiComparator { return (a, b) => direction * tuiDefaultSort(a[key], b[key]); } function getAge(dob: TuiDay): number { const years = TODAY.year - dob.year; const months = TODAY.month - dob.month; const days = TODAY.day - dob.day; const offset = tuiToInt(months > 0 || (!months && days > 9)); return years + offset; } @Component({ imports: [ AsyncPipe, FormsModule, ReactiveFormsModule, TuiButton, TuiCheckbox, TuiChevron, TuiDropdown, TuiInput, TuiInputNumber, TuiLabel, TuiLoader, TuiNumberFormat, TuiReorder, TuiTable, TuiTablePagination, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly size$ = new BehaviorSubject(10); protected readonly page$ = new BehaviorSubject(0); protected readonly direction$ = new BehaviorSubject( TuiSortDirection.Desc, ); protected readonly sortKey$ = new BehaviorSubject('name'); protected readonly minAge = new FormControl(21); protected readonly minAge$ = tuiControlValue(this.minAge).pipe( debounceTime(1000), tap(() => this.page$.next(0)), ); protected readonly request$ = combineLatest([ this.sortKey$, this.direction$, this.page$, this.size$, this.minAge$, ]).pipe( // zero time debounce for a case when both key and direction change debounceTime(0), switchMap((query) => this.getData(...query).pipe(startWith(null))), share(), ); protected initial: readonly string[] = ['Name', 'Date of Birth', 'Age']; protected enabled = this.initial; protected columns = ['name', 'dob', 'age']; protected dob = false; protected search = ''; protected readonly loading$ = this.request$.pipe(map((v) => !v)); protected readonly total$ = this.request$.pipe( filter(tuiIsPresent), map(({length}) => length), startWith(1), ); protected readonly data$: Observable = this.request$.pipe( filter(tuiIsPresent), map((users) => users.filter(tuiIsPresent)), startWith([]), ); protected onEnabled(enabled: readonly string[]): void { this.enabled = enabled; this.columns = this.initial .filter((column) => enabled.includes(column)) .map((column) => KEYS[column] ?? ''); } protected onPagination({page, size}: TuiTablePaginationEvent): void { this.page$.next(page); this.size$.next(size); } protected isMatch(value: unknown): boolean { return !!this.search && TUI_DEFAULT_MATCHER(value, this.search); } protected change({sortKey, sortDirection}: TuiSortChange): void { this.sortKey$.next(sortKey!); this.direction$.next(sortDirection); } private getData( key: keyof User, direction: TuiSortDirection, page: number, size: number, minAge: number, ): Observable> { console.info(`Sort by ${key} , direction : ${direction}`); const start = page * size; const end = start + size; const result = [...DATA] .sort(sortBy(key, direction)) .filter((user) => user.age >= minAge) .map((user, index) => (index >= start && index < end ? user : null)); // Imitating server response return timer(Math.random() * 1e3 + 1e3).pipe(map(() => result)); } } ``` **LESS:** ```less .table { inline-size: 100%; } .filters { display: flex; gap: 1rem; white-space: nowrap; align-items: center; } .input { flex: 1; } .columns { inline-size: 10.625rem; } .match { background: var(--tui-service-selection-background); } ``` #### Virtual scroll **Template:** ```html
Name Date of Birth Age
{{ item.name }} {{ item.dob }} {{ getAge(item) }}
``` **TypeScript:** ```ts import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, } from '@angular/cdk/scrolling'; import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiComparator, TuiTable} from '@taiga-ui/addon-table'; import {TuiDay, tuiToInt} from '@taiga-ui/cdk'; import {TuiScrollable, TuiScrollbar} from '@taiga-ui/core'; interface User { readonly dob: TuiDay; readonly name: string; } const TODAY = TuiDay.currentLocal(); const FIRST = [ 'John', 'Jane', 'Jack', 'Jill', 'James', 'Joan', 'Jim', 'Julia', 'Joe', 'Julia', ]; const LAST = [ 'Smith', 'West', 'Brown', 'Jones', 'Davis', 'Miller', 'Johnson', 'Jackson', 'Williams', 'Wilson', ]; const DATA: readonly User[] = Array.from({length: 300}, () => ({ name: `${LAST[Math.floor(Math.random() * 10)]}, ${ FIRST[Math.floor(Math.random() * 10)] }`, dob: TODAY.append({day: -Math.floor(Math.random() * 4000) - 7500}), })); function getAge({dob}: User): number { const years = TODAY.year - dob.year; const months = TODAY.month - dob.month; const days = TODAY.day - dob.day; const offset = tuiToInt(months > 0 || (!months && days > 9)); return years + offset; } @Component({ imports: [ CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, TuiScrollable, TuiScrollbar, TuiTable, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly data = DATA; protected readonly getAge = getAge; protected readonly ageSorter: TuiComparator = (a: User, b: User) => getAge(a) - getAge(b); } ``` **LESS:** ```less table { inline-size: 100%; th { inset-block-start: inherit; } tr { block-size: 2.8125rem; } td { &:first-child { inline-size: 10rem; font-weight: bold; } &:last-child { inline-size: 3rem; } } } .viewport { block-size: 15.625rem; } ``` #### Columns **Template:** ```html @for (col of columns; track col) { } @let sortedData = data | tuiTableSort; @for (item of sortedData; track item) { @for (col of columns; track col) { } }
{{ col }}
{{ item[col] }}
``` **TypeScript:** ```ts import {Component} 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} from '@taiga-ui/core'; @Component({ imports: [TuiButton, TuiTable], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected data: Array> = [{id: 1, name: 'name'}]; protected get columns(): string[] { return Object.keys(this.data[0] ?? {}); } protected addColumn(): void { this.data = this.data.map((item) => ({ ...item, [`extra-${this.columns.length + 1}`]: `extra column ${ this.columns.length + 1 }`, })); } protected addRows(): void { this.data = [...this.data, {...this.data[0], id: this.data.length + 1}]; } } ``` **LESS:** ```less .table { inline-size: 100%; } ``` #### Footer **Template:** ```html @for (item of data; track item) { }
999 rows
Name Balance
{{ item.name }} {{ item.balance | tuiFormatNumber }}
``` **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 {TuiTable} from '@taiga-ui/addon-table'; import {type TuiContext, type TuiStringHandler} from '@taiga-ui/cdk'; import {TuiButton, TuiFormatNumberPipe, TuiTextfield} from '@taiga-ui/core'; import {TuiButtonSelect, TuiDataListWrapper, TuiPagination} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiButton, TuiButtonSelect, TuiDataListWrapper, TuiFormatNumberPipe, TuiPagination, TuiTable, TuiTextfield, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly data = [ { name: 'Alex Inkin', balance: 1323525, }, { name: 'Roman Sedov', balance: 423242, }, ] as const; protected index = 4; protected length = 10; protected size = 10; protected readonly items = [10, 50, 100]; protected readonly content: TuiStringHandler> = ({$implicit}) => `${$implicit} items per page`; } ``` #### Resizing **Template:** ```html @for (item of data; track item) { }
Name Items Balance
{{ item.name }}
``` **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 {TuiTable} from '@taiga-ui/addon-table'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputChip, TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, ReactiveFormsModule, TuiInputChip, TuiInputNumber, TuiTable, TuiTextfield, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly data = [ { name: 'Alex Inkin', balance: 1323525, items: ['Wallet', 'Phone'], }, { name: 'Roman Sedov', balance: '', items: ['Wallet'], }, ]; } ``` #### Expandable rows **Template:** ```html @for (item of data; track $index; let i = $index) { @if (item.children.length) { @for (child of item.children; track $index; let j = $index) { } } }
# Name Date of Birth
{{ i + 1 }} {{ item.name }} {{ item.dob }}
{{ i + 1 }}.{{ j + 1 }} {{ child.name }} {{ child.dob }}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTable} from '@taiga-ui/addon-table'; import {TuiDay} from '@taiga-ui/cdk'; import {TuiButton} from '@taiga-ui/core'; import {TuiChevron} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiChevron, TuiTable], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly state: Record = {}; protected readonly data = [ { dob: new TuiDay(1947, 6, 30), name: 'John Matrix', children: [ { dob: new TuiDay(1975, 6, 15), name: 'Jenny Matrix', }, ], }, { dob: new TuiDay(1946, 6, 4), name: 'John Rambo', children: [], }, { dob: new TuiDay(1955, 2, 19), name: 'John McClane', children: [ { dob: new TuiDay(1982, 7, 10), name: 'Lucy McClane', }, { dob: new TuiDay(1985, 3, 5), name: 'Jack McClane', }, ], }, ] as const; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; tui-table-expand { td { background: var(--tui-background-base-alt); } tr:hover td { background: var(--tui-background-neutral-1-hover); } } tr:hover td { background: var(--tui-background-base-alt); } [tuiTh] { border-block-start: none; } [tuiTh], [tuiTd] { min-inline-size: 3rem; border-inline: none; white-space: nowrap; } ``` ### 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'; import {TuiAccordion} from '@taiga-ui/kit'; @Component({ imports: [TuiAccordion, TuiDemo, TuiLink], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly examples = [ 'Basic', 'Custom', 'Editable', 'Sorting', 'Virtual scroll', 'Columns', 'Footer', 'Resizing', 'Expandable rows', 'Toggle rows', 'Controls', ]; } ``` --- # components/TableFilters - **Package**: `ADDON-TABLE` - **Type**: components This module allows you to filter table data in a flexible way. ### Usage Examples #### Basic **Template:** ```html
@let items = $any(data | tuiTableFilters | async); @for (item of items; track item) { }
Name Balance
{{ item.name }} {{ item.balance | tuiFormatNumber }}
``` **TypeScript:** ```ts import {AsyncPipe} from '@angular/common'; 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 {TuiTable, TuiTableFilters} from '@taiga-ui/addon-table'; import {TuiFormatNumberPipe, TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber, TuiSwitch} from '@taiga-ui/kit'; @Component({ imports: [ AsyncPipe, FormsModule, ReactiveFormsModule, TuiFormatNumberPipe, TuiInputNumber, TuiSwitch, TuiTable, TuiTableFilters, TuiTextfield, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly form = new FormGroup({balance: new FormControl(0)}); protected readonly data = [ {name: 'Alex Inkin', balance: 1323525}, {name: 'Roman Sedov', balance: 523242}, {name: 'Vladimir Potekhin', balance: 645465}, {name: 'Nikita Barsukov', balance: 468468}, {name: 'Maxim Ivanov', balance: 498654}, ] as const; protected readonly columns = Object.keys(this.data[0]); protected readonly filter = (item: number, value: number): boolean => item >= value; protected onToggle(enabled: boolean): void { if (enabled) { this.form.enable(); } else { this.form.disable(); } } } ``` **LESS:** ```less .toggle { display: flex; align-items: center; inline-size: fit-content; gap: 1rem; margin: 1rem 0; } .table { inline-size: 100%; } ``` #### FormArray **Template:** ```html
@let data = $any(items() | tuiTableFilters | async); @for (item of data; track item) { }
Name Balance
``` **TypeScript:** ```ts import {AsyncPipe} from '@angular/common'; import {Component} from '@angular/core'; import {toSignal} from '@angular/core/rxjs-interop'; import { FormArray, FormControl, FormGroup, FormsModule, ReactiveFormsModule, } from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTable, TuiTableFilters} from '@taiga-ui/addon-table'; import {TuiButton, TuiInput} from '@taiga-ui/core'; import {TuiInputNumber, TuiSwitch} from '@taiga-ui/kit'; import {map} from 'rxjs'; @Component({ imports: [ AsyncPipe, FormsModule, ReactiveFormsModule, TuiButton, TuiInput, TuiInputNumber, TuiSwitch, TuiTable, TuiTableFilters, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly testData = [ {name: 'James', balance: 10000}, {name: 'Michael', balance: 20000}, {name: 'Richard', balance: 30000}, {name: 'Robert', balance: 40000}, {name: 'Daniel', balance: 50000}, ] as const; protected readonly filterForm = new FormGroup({balance: new FormControl(0)}); protected readonly array = new FormArray([]); protected readonly items = toSignal( this.array.valueChanges.pipe(map(() => [...this.array.controls])), {initialValue: []}, ); protected testIndex = 0; protected readonly columns = ['name', 'balance']; public addRow(): void { const name = this.testData[this.testIndex]?.name ?? ''; const balance = this.testData?.[this.testIndex]?.balance ?? 0; this.array.push( new FormGroup({ name: new FormControl(name), balance: new FormControl(balance, {updateOn: 'blur'}), }), ); this.testIndex++; } protected readonly filter = ( {balance}: Record, value: number, ): boolean => balance?.value >= value; protected onToggle(enabled: boolean): void { if (enabled) { this.filterForm.enable(); } else { this.filterForm.disable(); } } } ``` **LESS:** ```less .toggle { display: flex; align-items: center; inline-size: fit-content; gap: 1rem; margin: 1rem 0; } .table { inline-size: 100%; } ``` ### 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', 'FormArray']; } ``` --- # components/TablePagination - **Package**: `ADDON-TABLE` - **Type**: components Component to show pagination in table footer ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [total] | `number` | Total amount of items/lines in the table. | | [size] | `number` | | | [page] | `number` | | | [items] | `readonly number[]` | Options to select amount of lines per page. | ### API - Outputs | Event | Type | Description | |-------|------|-------------| | (paginationChange) | `TuiTablePagination` | changes. | ### Usage Examples #### Usage **Template:** ```html

Current page: {{ page }}

Items per page: {{ size }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTablePagination, type TuiTablePaginationEvent} from '@taiga-ui/addon-table'; @Component({ imports: [TuiTablePagination], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected page = 3; protected size = 10; protected onPagination({page, size}: TuiTablePaginationEvent): void { this.page = page; this.size = size; } } ``` #### Custom size-option content **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import { TuiTablePagination, tuiTablePaginationOptionsProvider, } from '@taiga-ui/addon-table'; @Component({ selector: 'example-2', imports: [TuiTablePagination], templateUrl: './index.html', encapsulation, changeDetection, providers: [ tuiTablePaginationOptionsProvider({ sizeOptionContent: ({$implicit, total}) => { switch ($implicit) { case 10: return 'Ten'; case total: return 'Show all rows'; default: return $implicit; } }, }), ], }) export default class Example { protected total = 350; protected sizeOptions = [10, 50, 100, this.total]; } ``` #### Toggle pages label **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import { TuiTablePagination, tuiTablePaginationOptionsProvider, } from '@taiga-ui/addon-table'; @Component({ imports: [TuiTablePagination], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiTablePaginationOptionsProvider({showPages: false})], }) export default class Example { protected total = 350; protected sizeOptions = [10, 50, 100, this.total]; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiDemo} from '@demo/utils'; import {TuiTablePagination, type TuiTablePaginationEvent} from '@taiga-ui/addon-table'; @Component({ imports: [TuiDemo, TuiTablePagination], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Page { protected readonly examples = [ 'Usage', 'Custom size-option content', 'Toggle pages label', ]; protected readonly itemsVariants = [ [10, 20, 50, 100], [10, 100, 500], ]; protected total = 1000; protected page = 5; protected items = this.itemsVariants[0]!; protected size = this.items[0]!; protected update({page, size}: TuiTablePaginationEvent): void { this.page = page; this.size = size; } protected totalChange(total: number): void { this.total = total; this.size = Math.min(this.size, Math.max(total, 1)); } } ``` --- # components/Tabs - **Package**: `KIT` - **Type**: components Component for creating tabs. If you use routerLink you must also add routerLinkActive directive. ### Example ```html @for (button of buttons; track button) { } ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [(activeItemIndex)] | `number` | Active index item | | [size] | `TuiSizeM | TuiSizeL` | Size | | [itemsLimit] | `number` | | | [moreContent] | `PolymorpheusContent` | | | [dropdownContent] | `PolymorpheusContent` | | ### 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 {TuiNotificationService, TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber, TuiTabs} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputNumber, TuiTabs, TuiTextfield], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly alerts = inject(TuiNotificationService); protected activeItemIndex = 0; protected onClick(item: string): void { this.alerts.open(item).subscribe(); } } ``` #### TabsWithMore **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 {TuiNotificationService, TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber, TuiTabs} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInputNumber, TuiTabs, TuiTextfield], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly alerts = inject(TuiNotificationService); protected activeItemIndex = 0; protected onClick(item: string): void { this.alerts.open(item).subscribe(); } } ``` #### Complex **Template:** ```html

Monty Python

@for (tab of tabs; track tab) { @if (isString(tab)) { } @else { } }
Currently active: {{ activeElement }}
@for (collaborator of collaborators; track collaborator) { } ``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {tuiIsString} from '@taiga-ui/cdk'; import {TuiDataList, TuiDropdown, TuiIcon} from '@taiga-ui/core'; import {TuiChevron, TuiDataListDropdownManager, TuiTabs} from '@taiga-ui/kit'; @Component({ imports: [ TuiChevron, TuiDataList, TuiDataListDropdownManager, TuiDropdown, TuiIcon, TuiTabs, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly collaborators = ['Carol Cleveland', 'Neil Innes']; protected readonly tabs = [ this.collaborators, ...inject('Pythons' as any), ]; protected activeElement = String(this.collaborators[0]); protected get activeItemIndex(): number { if (this.collaborators.includes(this.activeElement)) { return this.tabs.indexOf(this.collaborators); } return this.tabs.indexOf(this.activeElement); } protected stop(event: Event): void { // We need to stop tab custom event so parent component does not think its active event.stopPropagation(); } protected onClick(activeElement: string): void { this.activeElement = activeElement; } protected isString(tab: unknown): tab is string { return tuiIsString(tab); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: block; margin: -2rem; background: #454e58; color: #fff; :host-context(tui-root._mobile) { margin: -1rem; } } .wrapper { display: flex; align-items: center; padding: 2rem 2rem 0; box-shadow: inset 0 -1px rgba(255, 255, 255, 0.24); } .content { padding: 2rem 2rem 4rem; } .title { min-inline-size: 15.625rem; margin: 0; } .tabs { inline-size: ~'calc(100% - 15.625rem)'; justify-content: flex-end; box-shadow: none; } .icon { .transition(transform); margin-inline-start: 0.25rem; &_rotated { transform: rotate(180deg); } } ``` #### Stepper **Template:** ```html @for (step of steps; track step) { @if (!$last) { } } ``` **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 { TuiIcon, TuiNotificationService, TuiNumberFormat, TuiTextfield, } from '@taiga-ui/core'; import {TuiInputNumber, TuiTabs} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiIcon, TuiInputNumber, TuiNumberFormat, TuiTabs, TuiTextfield, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly alerts = inject(TuiNotificationService); protected activeItemIndex = 0; protected readonly steps = ['Sales', 'Settings', 'News']; protected onClick(item: string): void { this.alerts.open(item).subscribe(); } } ``` **LESS:** ```less .step { margin: 0; color: var(--tui-text-action); &._active { color: var(--tui-text-primary); } &:hover { color: var(--tui-text-action-hover); } } .separator { align-self: center; color: var(--tui-text-tertiary); margin: 0 1rem; font-size: 1rem; } ``` #### Closing **Template:** ```html @for (item of items; track item) { }

{{ items[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

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 ``` **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') { } ``` ### 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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 external 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) { } @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) {
@if (item.content === 'rick') { } @else {

{{ item.content }}

Order - {{ order.get($index) ?? $index }} }
}
``` **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 {TuiTiles} from '@taiga-ui/kit'; @Component({ imports: [TuiIcon, TuiTiles], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected items = [ {w: 1, h: 1, content: 'Item 1'}, {w: 1, h: 1, content: 'Item 2'}, {w: 2, h: 1, content: 'Item 3'}, {w: 1, h: 1, content: 'Item 4'}, {w: 3, h: 1, content: 'Item 5'}, {w: 1, h: 1, content: 'Item 6'}, {w: 2, h: 2, content: 'rick'}, {w: 1, h: 1, content: 'Item 8'}, {w: 1, h: 1, content: 'Item 9'}, ]; protected order = new Map(); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .tiles { gap: 1rem; grid-auto-rows: minmax(6.25rem, auto); } .tile::before { content: 'Drop here'; display: flex; block-size: 100%; justify-content: center; align-items: center; box-sizing: border-box; color: var(--tui-border-normal); border-radius: var(--tui-radius-l); border: 2px dashed var(--tui-border-normal); } .content { .transition(box-shadow); block-size: 100%; padding: 1rem; background: var(--tui-background-base); box-sizing: border-box; border-radius: var(--tui-radius-l); border: 1px solid var(--tui-border-normal); overflow: hidden; tui-tile._dragged & { box-shadow: var(--tui-shadow-small-hover); } } .rick { inline-size: 100%; block-size: 100%; padding: 0; } .title { margin: 0 0 1rem; } .handle { .transition(opacity); position: absolute; inset-inline-end: 0.75rem; inset-block-start: 1rem; background: var(--tui-background-base); opacity: 0; cursor: move; tui-tiles:not(._dragged) tui-tile:hover &, tui-tile._dragged & { opacity: 0.7; } } ``` #### Vertical **Template:** ```html @for (item of items; track item) {
{{ item }}
}
``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTiles} from '@taiga-ui/kit'; @Component({ imports: [TuiTiles], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = inject('Pythons' as any); protected order = new Map(); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .tiles { inline-size: 10rem; gap: 1rem; grid-auto-rows: minmax(var(--tui-height-m), auto); } @media @tui-mobile { .tile_tall { --tui-height: 2; } } .content { .transition(box-shadow); display: flex; block-size: 100%; align-items: center; padding: 0 1rem; background: var(--tui-background-base); border-radius: var(--tui-radius-l); border: 1px solid var(--tui-border-normal); cursor: ns-resize; tui-tile._dragged & { box-shadow: var(--tui-shadow-small-hover); } } ``` #### Nested tiles **Template:** ```html @for (item of items; track item; let i = $index) {
{{ item.content }}

Order - {{ order.get(i) ?? i }}

@for (child of items; track child; let j = $index) {
{{ child.content }}
}
}
``` **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 {TuiTiles} from '@taiga-ui/kit'; @Component({ imports: [TuiIcon, TuiTiles], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected items = [ {content: 'Item 1', order: new Map()}, {content: 'Item 2', order: new Map()}, {content: 'Item 3', order: new Map()}, {content: 'Item 4', order: new Map()}, ]; protected order = new Map(); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .tiles { gap: 1rem; grid-template-columns: 1fr 1fr; grid-auto-rows: 15rem; } .nested { gap: 1rem; grid-template-columns: 1fr 1fr; grid-auto-rows: 3.5rem; } .tile::before { content: 'Drop here'; display: flex; block-size: 100%; justify-content: center; align-items: center; box-sizing: border-box; color: var(--tui-border-normal); border-radius: var(--tui-radius-l); border: 2px dashed var(--tui-border-normal); } .content { .transition(box-shadow); block-size: 100%; padding: 1rem; background: var(--tui-background-base); box-sizing: border-box; border-radius: var(--tui-radius-l); border: 1px solid var(--tui-border-normal); overflow: hidden; tui-tile._dragged & { box-shadow: var(--tui-shadow-small-hover); } tui-tile._dragged tui-tile:not(._dragged) & { box-shadow: none; } } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiTitle} from '@taiga-ui/core'; import {TuiTiles} from '@taiga-ui/kit'; @Component({ imports: [TuiDemo, TuiTiles, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected debounce = 0; protected order = new Map(); protected width = 1; protected height = 1; protected readonly items = [ {id: 1, name: 'John Cleese'}, {id: 2, name: 'Eric Idle'}, {id: 3, name: 'Graham Chapman'}, ]; protected readonly examples = ['Basic', 'Vertical', 'Nested tiles']; } ``` ### LESS ```less @import '@taiga-ui/styles/utils'; .tiles { inline-size: 10rem; gap: 1rem; grid-auto-rows: minmax(var(--tui-height-m), auto); } @media @tui-mobile { .tile_tall { --tui-height: 2; } } .content { .transition(box-shadow); display: flex; block-size: 100%; align-items: center; padding: 0 1rem; background: var(--tui-background-base); border-radius: var(--tui-radius-l); border: 1px solid var(--tui-border-normal); cursor: ns-resize; tui-tile._dragged & { box-shadow: var(--tui-shadow-small-hover); } } ``` --- # components/Timeline - **Package**: `KIT` - **Type**: components An interactive timeline component for arranging events on a linear axis with no built-in styles ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [orientation] | `TuiOrientation` | Vertical/horizontal orientation | | [template] | `TemplateRef>` | Template for gaps between items | | [max] | `number` | Upper limit of the timeline | | [draggable] | `boolean` | Allow moving this item around | | [resizable] | `boolean` | Allow resizing this item by dragging edges | | [(value)] | `[number, number]` | Beginning and end of the item | ### Usage Examples #### Basic **Template:** ```html @for (_ of items; track $index) { } ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTimeline} from '@taiga-ui/kit'; @Component({ imports: [TuiTimeline], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items: ReadonlyArray<[number, number]> = [ [0, 5], [12, 15], [24, 40], ]; } ``` **LESS:** ```less tui-timeline { inline-size: 18rem; block-size: 3rem; box-sizing: border-box; background: var(--tui-background-base-alt); border: 0.125rem solid transparent; border-radius: var(--tui-radius-s); } label { clip-path: inset(0.125rem round var(--tui-radius-xs)); box-shadow: inset 0 0 0 0.375rem var(--tui-background-neutral-1-hover); } ``` #### Full-fledged **Template:** ```html @for (_ of items(); track $index) {
} @let gap = timeline.gaps()[index] ?? [0, 0]; @if (gap[1] - gap[0] > max / 5) { }
``` **TypeScript:** ```ts import {Component, signal, type WritableSignal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiHint} from '@taiga-ui/core'; import {TuiRange, TuiTimeline} from '@taiga-ui/kit'; import {TuiForm} from '@taiga-ui/layout'; @Component({ imports: [FormsModule, TuiButton, TuiForm, TuiHint, TuiRange, TuiTimeline], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly max = 50; protected readonly items = signal>>([ signal([0, 12]), signal([24, 40]), ]); protected remove(index: number): void { this.items.update((items) => items.filter((_, i) => i !== index)); } protected add(index: number): void { const start = this.items()[index - 1]?.()[1] || 0; const end = this.items()[index]?.()[0] || this.max; const copy = [...this.items()]; copy.splice(index, 0, signal([start, end])); this.items.set(copy); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; tui-timeline { inline-size: 18rem; block-size: 10rem; box-shadow: inset 0 0 0 0.375rem var(--tui-background-neutral-1); overflow: hidden; border-radius: var(--tui-radius-s); filter: drop-shadow(0 0.125rem 0.25rem rgba(0, 0, 0, 0.25)); button { .transition(opacity); position: absolute; inset: 50%; opacity: 0; transform: translate(-50%, -50%); } &:hover button, button:focus-visible { opacity: 1; } } label { border-radius: inherit; background: var(--tui-border-normal); box-shadow: inset 0 0 0 0.375rem var(--tui-background-elevation-3); } span { display: flex; writing-mode: horizontal-tb; inline-size: 100%; block-size: 100%; justify-content: center; align-items: center; } form footer { inline-size: fit-content; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiTitle} from '@taiga-ui/core'; @Component({ imports: [TuiDemo, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly examples = ['Basic', 'Full-fledged']; } ``` ### LESS ```less tr ::ng-deep td:last-child:not(:only-child) { display: none; } ``` --- # components/Title - **Package**: `CORE` - **Type**: components A directive for title with optional subtitle ### Usage Examples #### Basic **Template:** ```html

I am a title

I am a title
I'm a subtitle

Caption
I am a title

Caption
I am a title
I'm a subtitle

``` **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'; @Component({ imports: [TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 2rem; } ``` #### Sizes **Template:** ```html

Caption
I am a title
I'm a subtitle

Caption
I am a title

I am a title
I'm a subtitle

``` **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'; @Component({ imports: [TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 2rem; } ``` #### Custom **Template:** ```html

I am a title
I'm a subtitle

Taiga UI — GitHub
Drop us a star!
``` **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
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' }}

With action
With icon
With badge
Avatar
Everything @if (platform === 'web') { }
The text of the notification telling what happened is in three lines because there is a lot of information
} ``` **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 {TuiAvatar, TuiBadge, TuiShrinkWrap, TuiToast} from '@taiga-ui/kit'; @Component({ imports: [TuiAvatar, TuiBadge, TuiButton, TuiPlatform, TuiShrinkWrap, TuiToast], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly platforms = ['web', 'ios'] as const; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; background: var(--tui-background-base-alt); box-shadow: 0 0 0 100rem var(--tui-background-base-alt); } section { display: flex; gap: 1rem; align-items: flex-start; } [tuiToast]::before { color: var(--tui-background-accent-1); } ``` #### Customization **Template:** ```html

Desktop

Sending to printer
Updating...

Mobile

Updating...
Added
``` **TypeScript:** ```ts import {isPlatformServer} from '@angular/common'; import {Component, inject, PLATFORM_ID} from '@angular/core'; import {toSignal} from '@angular/core/rxjs-interop'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {WA_IS_E2E} from '@ng-web-apis/platform'; import {TuiPlatform} from '@taiga-ui/cdk'; import {TuiButton, TuiLoader} from '@taiga-ui/core'; import {TuiProgressCircle, TuiToast} from '@taiga-ui/kit'; import {BehaviorSubject, of, switchMap, take, timer} from 'rxjs'; @Component({ imports: [TuiButton, TuiLoader, TuiPlatform, TuiProgressCircle, TuiToast], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly trigger$ = new BehaviorSubject(0); protected readonly value = toSignal( inject(WA_IS_E2E) || isPlatformServer(inject(PLATFORM_ID)) ? of(30) : this.trigger$.pipe(switchMap(() => timer(0, 200).pipe(take(100)))), {initialValue: 0}, ); } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1rem; background: var(--tui-background-base-alt); box-shadow: 0 0 0 100rem var(--tui-background-base-alt); } section { display: flex; gap: 1rem; } ``` #### Service **Template:** ```html Check out source code ``` **TypeScript:** ```ts import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiIcon} from '@taiga-ui/core'; import {TuiToast, TuiToastService} from '@taiga-ui/kit'; import {PolymorpheusComponent} from '@taiga-ui/polymorpheus'; @Component({ imports: [TuiIcon, TuiToast], template: `
Lost connection.
Restore your internet to continue
`, changeDetection: ChangeDetectionStrategy.OnPush, }) export class Toast {} @Component({ imports: [TuiButton, TuiToast], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly toast = inject(TuiToastService); protected readonly template = signal(false); protected primitive(): void { this.toast .open('Alarm is set for next Sunday, March 8, 2026, 10:00', { autoClose: 0, data: '@tui.alarm-clock', }) .subscribe(); } protected component(): void { this.toast.open(new PolymorpheusComponent(Toast), {closable: false}).subscribe(); } } ``` **LESS:** ```less :host { display: flex; gap: 1rem; } ``` ### TypeScript ```ts import {Component, inject, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiButton} from '@taiga-ui/core'; import {TUI_TOAST_OPTIONS, TuiToast} from '@taiga-ui/kit'; @Component({ imports: [TuiButton, TuiDemo, TuiToast], templateUrl: './index.html', changeDetection, }) export default class Example { private readonly options = inject(TUI_TOAST_OPTIONS); protected readonly examples = ['Basic', 'Customization', 'Service']; protected readonly toast = signal(false); protected readonly autoCloseVariants = [0, 3000, 5000, 1000, 500]; protected autoClose = this.options.autoClose; protected content = 'Notification'; protected appearance = ''; protected closable = this.options.closable; protected readonly routes = DemoRoute; } ``` --- # components/Tooltip - **Package**: `KIT` - **Type**: components Component to show icons with a hint by hover ### Usage Examples #### Basic **Template:** ```html

Component with a static text...

...or any custom HTML or logic with PolymorpheusContent :

Example of wrapping tooltip

@let isLoading = (isLoading$ | async)!; {{ isLoading ? '' : 'Error 502: Bad Gateway' }} ``` **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 {TuiIcon, TuiLoader} from '@taiga-ui/core'; import {TuiTooltip} from '@taiga-ui/kit'; import {interval, map, startWith} from 'rxjs'; @Component({ imports: [AsyncPipe, TuiIcon, TuiLoader, TuiTooltip], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected isLoading$ = interval(2000).pipe( map((i) => Boolean(i % 2)), startWith(true), ); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .tooltip { block-size: 1.25rem; min-inline-size: 6.25rem; } .wrapping-tooltip { inline-size: fit-content; max-inline-size: 11rem; background: var(--tui-background-base-alt); border-radius: 0.5rem; line-height: 1.5rem; padding: 0.375rem 1.875rem 0.375rem 0.5rem; resize: horizontal; overflow: auto; & [tuiTooltip] { position: absolute; } } ``` #### Custom host **Template:** ```html

Custom host can be set with tuiHint directive

❤️
What is love ?
Baby don't hurt me
Don't hurt me
No more...
``` **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 {TuiHint, TuiLink} from '@taiga-ui/core'; import {TuiAutoColorPipe, TuiAvatar} from '@taiga-ui/kit'; @Component({ imports: [RouterLink, TuiAutoColorPipe, TuiAvatar, TuiHint, TuiLink], 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); } ``` #### Repeating template **Template:** ```html Allowed symbols: ♠ ♣ ♦ ♥ ``` **TypeScript:** ```ts import {Component} from '@angular/core'; 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: [TuiIcon, TuiInput, TuiTooltip], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: block; inline-size: 25rem; } .primary { color: var(--tui-background-accent-1); } .input { display: inline-flex; inline-size: 18.75rem; margin: 0.75rem 0.75rem 0.75rem 0; vertical-align: middle; } ``` #### Options **Template:** ```html

Modified icon

Modified appearance

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {tuiHintOptionsProvider, TuiIcon} from '@taiga-ui/core'; import {TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [TuiIcon, TuiTooltip], templateUrl: './index.html', encapsulation, changeDetection, providers: [tuiHintOptionsProvider({icon: '@tui.camera'})], }) 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', 'Custom host', 'Repeating template', 'Options', ]; } ``` --- # components/Tree - **Package**: `KIT` - **Type**: components Component to display tree-like data structure ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiTreeController] | `boolean` | input is the default state. | ### Usage Examples #### Manual **Template:** ```html
Fruits Apples Granny Smith Red Delicious Oranges Animals Cats Dogs
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTree} from '@taiga-ui/kit'; @Component({ imports: [TuiTree], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Array **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTree} from '@taiga-ui/kit'; @Component({ imports: [TuiTree], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly data = [ 'Top level 1', ['Second level item', ['Third level 1', 'Third level 2', 'Third level 3']], 'Top level 2', 'Top level 3', ['Second 1', 'Second 2'], ]; } ``` **LESS:** ```less tui-tree { margin-inline-start: -3.5rem; } ``` #### Template **Template:** ```html
@if (value.icon) { } {{ value.text }}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiHandler} from '@taiga-ui/cdk'; import {TuiIcon} from '@taiga-ui/core'; import {TuiTree} from '@taiga-ui/kit'; interface TreeNode { readonly children?: readonly TreeNode[]; readonly icon?: string; readonly text: string; } @Component({ imports: [TuiIcon, TuiTree], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly data: TreeNode = { text: 'Topmost', children: [ { text: 'Top level 1', icon: '@tui.heart', children: [ { text: 'Another item', children: [ {text: 'Next level 1', icon: '@tui.heart'}, {text: 'Next level 2', icon: '@tui.heart'}, {text: 'Next level 3'}, ], }, ], }, {text: 'Top level 2'}, { text: 'Top level 3', children: [{text: 'Test 1'}, {text: 'Test 2', icon: '@tui.heart'}], }, ], }; protected readonly handler: TuiHandler = (item) => item.children || []; } ``` **LESS:** ```less .wrapper { display: flex; align-items: center; } .t-icon::before { font-size: 1rem; } ``` #### Programmatic control **Template:** ```html {{ item.text }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiHandler} from '@taiga-ui/cdk'; import {TuiButton} from '@taiga-ui/core'; import {TuiTree} from '@taiga-ui/kit'; interface TreeNode { readonly children?: readonly TreeNode[]; readonly text: string; } @Component({ imports: [TuiButton, TuiTree], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly data: TreeNode = { text: 'Topmost', children: [ { text: 'Top level 1', children: [ { text: 'Another item', children: [ {text: 'Next level 1'}, {text: 'Next level 2'}, {text: 'Next level 3'}, ], }, ], }, {text: 'Top level 2'}, { text: 'Top level 3', children: [{text: 'Test 1'}, {text: 'Test 2'}], }, ], }; protected map = new Map(); protected readonly handler: TuiHandler = (item) => item.children || []; protected toggleTopmost(): void { this.map = new Map(this.map.set(this.data, !this.map.get(this.data))); } protected toggleLevel(index: number): void { const nodes = this.data.children || []; const key = nodes[index]; if (key) { this.map = new Map(this.map.set(key, !this.map.get(key))); } } } ``` #### Custom **Template:** ```html @for (item of data.children; track item) { } {{ item.text }} ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiHandler} from '@taiga-ui/cdk'; import {TUI_TREE_CONTENT, TuiTree} from '@taiga-ui/kit'; import {PolymorpheusComponent} from '@taiga-ui/polymorpheus'; import {Folders} from './content'; interface TreeNode { readonly children?: readonly TreeNode[]; readonly text: string; } @Component({ imports: [TuiTree], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [ { provide: TUI_TREE_CONTENT, useValue: new PolymorpheusComponent(Folders), }, ], }) export default class Example { protected readonly data: TreeNode = { text: 'Topmost', children: [ { text: 'Top level 1', children: [ { text: 'Another item', children: [ {text: 'Next level 1'}, {text: 'Next level 2'}, {text: 'Next level 3'}, ], }, ], }, {text: 'Top level 2'}, { text: 'Top level 3', children: [{text: 'Test 1'}, {text: 'Test 2'}], }, ], }; protected readonly handler: TuiHandler = (item) => item.children || []; } ``` **LESS:** ```less tui-tree { overflow: hidden; } ``` #### Checkbox **Template:** ```html @for (item of data.children; track 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 {type TuiHandler, TuiMapperPipe} from '@taiga-ui/cdk'; import {TuiCheckbox, TuiLabel} from '@taiga-ui/core'; import {TuiTree} from '@taiga-ui/kit'; interface TreeNode { readonly children?: readonly TreeNode[]; readonly text: string; } function flatten(item: TreeNode): readonly TreeNode[] { return item.children ? item.children.map(flatten).reduce((arr, item) => [...arr, ...item], []) : [item]; } @Component({ imports: [FormsModule, TuiCheckbox, TuiLabel, TuiMapperPipe, TuiTree], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected map = new Map(); protected readonly data: TreeNode = { text: 'Topmost', children: [ { text: 'Top level 1', children: [ { text: 'Another item', children: [ {text: 'Next level 1'}, {text: 'Next level 2'}, {text: 'Next level 3'}, ], }, ], }, {text: 'Top level 2'}, { text: 'Top level 3', children: [{text: 'Test 1'}, {text: 'Test 2'}], }, ], }; protected readonly handler: TuiHandler = (item) => item.children || []; protected readonly getValue = ( item: TreeNode, map: Map, ): boolean | null => { let result: boolean | null = null; const flat = flatten(item); const key = flat[0]!; if (key) { result = !!map.get(key); } for (const item of flat) { if (result !== !!map.get(item)) { return null; } } return result; }; protected onChecked(node: TreeNode, value: boolean): void { flatten(node).forEach((item) => this.map.set(item, value)); this.map = new Map(this.map.entries()); } } ``` #### Asynchronous **Template:** ```html @if (item === loading) { } @else { {{ item.text }} } ``` **TypeScript:** ```ts import {AsyncPipe} from '@angular/common'; import {Component, inject, Injectable} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiHandler} from '@taiga-ui/cdk'; import {TuiLoader} from '@taiga-ui/core'; import { TUI_TREE_LOADER, TUI_TREE_LOADING, TUI_TREE_START, TuiTree, type TuiTreeLoader, TuiTreeService, } from '@taiga-ui/kit'; import {map, type Observable, timer} from 'rxjs'; interface Item { readonly children?: boolean; readonly text: string; } @Injectable() class TreeLoader implements TuiTreeLoader { public loadChildren({text}: Item): Observable { return timer(3000).pipe( map(() => [ {text: `${text} 1`, children: Math.random() > 0.5}, {text: `${text} 2`, children: Math.random() > 0.5}, {text: `${text} 3`, children: Math.random() > 0.5}, ]), ); } public hasChildren({children}: Item): boolean { return !!children; } } @Component({ imports: [AsyncPipe, TuiLoader, TuiTree], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [ TuiTreeService, { provide: TUI_TREE_START, useValue: {text: 'Topmost'}, }, { provide: TUI_TREE_LOADER, useClass: TreeLoader, }, ], }) export default class Example { protected readonly loading = inject(TUI_TREE_LOADING); protected readonly service = inject(TuiTreeService); protected map = new Map(); protected childrenHandler: TuiHandler = (item) => this.service.getChildren(item); protected onToggled(item: Item): void { this.service.loadChildren(item); } } ``` **LESS:** ```less .loader { inline-size: 2rem; margin: 1rem 0; } ``` #### Drag and drop **Template:** ```html @if (!value.children) {
{{ value.text }}
} @else { {{ value.text }} }
``` **TypeScript:** ```ts import {ChangeDetectorRef, Component, inject, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiHandler} from '@taiga-ui/cdk'; import {TuiTiles, TuiTree} from '@taiga-ui/kit'; interface TreeNode { children?: readonly TreeNode[]; text: string; } @Component({ imports: [TuiTiles, TuiTree], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly drag = signal(null); protected readonly cd = inject(ChangeDetectorRef); protected readonly data: TreeNode = { text: 'Topmost', children: [ { text: 'Top level 1', children: [ { text: 'Another item', children: [ {text: 'Next level 1'}, {text: 'Next level 2'}, {text: 'Next level 3'}, ], }, ], }, {text: 'Top level 2'}, { text: 'Top level 3', children: [{text: 'Test 1'}, {text: 'Test 2'}], }, ], }; protected readonly handler: TuiHandler = (item) => item.children || []; protected onDrag(drag: TreeNode): void { this.drag.set(drag); } protected onDrop(target: TreeNode, position = 0): void { const drag = this.drag(); if (!drag) { return; } const dragParent = findParent(drag, this.data); const targetParent = findParent(target, this.data); if (dragParent) { dragParent.children = dragParent?.children?.filter((item) => item !== drag); } const index = (targetParent?.children?.indexOf(target) ?? 0) + position; if (targetParent?.children) { targetParent.children = [ ...targetParent.children.slice(0, index), drag, ...targetParent.children.slice(index), ]; } this.drag.set(null); } } function findParent(item: TreeNode, node: TreeNode): TreeNode | null { if (!node.children) { return null; } if (node.children.includes(item)) { return node; } for (const iterateItem of node.children) { const parent = findParent(item, iterateItem); if (parent) { return parent; } } return null; } ``` **LESS:** ```less .tree._dragged { .drop { pointer-events: auto; &:hover { opacity: 1; } } } .wrapper { position: relative; inline-size: 100%; } .content { display: flex; inline-size: 100%; align-items: center; } .tiles { inline-size: 100%; grid-template-rows: 1.5rem; } .drop { position: absolute; z-index: 1; inline-size: 100%; block-size: 0.5rem; margin-block-start: -0.25rem; background: #87ceeb; border-radius: 1rem; opacity: 0; pointer-events: none; } ``` ### 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 customContent = { 'content.ts': import('./examples/5/content.ts?raw', {with: {loader: 'text'}}), 'content.less': import('./examples/5/content.less'), }; protected readonly routes = DemoRoute; protected readonly examples = [ 'Manual', 'Array', 'Template', 'Programmatic control', 'Custom', 'Checkbox', 'Asynchronous', 'Drag and drop', ]; } ``` --- # utils/DOM utils - **Package**: `CDK` - **Type**: components/utils ### How to Use (Import) ```ts import {isElementAtPoint} from '@taiga-ui/cdk'; //... isAtPoint = isElementAtPoint(element, point.x, point.y); //... ``` ### 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 { protected readonly component = import('./examples/import/import.md'); } ``` ### LESS ```less @import '@taiga-ui/styles/utils'; .description-header { font: var(--tui-typography-body-l); font-weight: bold; line-height: 2.875rem; block-size: 2.1875rem; } ``` --- # utils/format - **Package**: `CDK / CORE` - **Type**: components/utils A set of format utils ### Usage Examples #### px **Template:** ```html '{{ px }}' = px(value);
``` **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 {tuiPx} from '@taiga-ui/cdk'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiInputNumber, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected parametersForm = new FormGroup({value: new FormControl(11)}); protected get px(): string { const {value} = this.parametersForm.value; return tuiPx(value ?? 0); } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` #### getCurrencySymbol **Template:** ```html {{ currency }} = getCurrencySymbol(currency);
``` **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 {type TuiCurrencyVariants, tuiGetCurrencySymbol} from '@taiga-ui/addon-commerce'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiChevron, TuiDataListWrapper, TuiSelect, TuiTextfield, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ 'USD', 'RUB', '643', 'KZT', '051', 'KRW', 'CHF', 'EUR', 'GBP', ]; protected parametersForm = new FormGroup({ currency: new FormControl(null), }); protected get currency(): string | null { const {currency} = this.parametersForm.value; return currency ? tuiGetCurrencySymbol(currency) : null; } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` #### formatNumber **Template:** ```html '{{ formattedNumber }}' = tuiFormatNumber(value, precision, decimalSeparator, thousandSeparator);
``` **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 {type TuiDecimalSymbol, tuiFormatNumber, TuiInput} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiInput], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected parametersForm = new FormGroup({ value: new FormControl(123456.789), precision: new FormControl(2), decimalSeparator: new FormControl('.'), thousandSeparator: new FormControl(' '), }); protected get formattedNumber(): string { const {value, precision, decimalSeparator, thousandSeparator} = this.parametersForm.value; return tuiFormatNumber(value ?? 123456.789, { precision: precision ?? 2, decimalSeparator: decimalSeparator ?? '.', thousandSeparator: thousandSeparator ?? ' ', }); } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` ### 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 = ['px', 'getCurrencySymbol', 'formatNumber']; } ``` --- # utils/Math - **Package**: `CDK` - **Type**: components/utils A set of utils to calculate math ### Usage Examples #### round **Template:** ```html

{{ rounded }} = round(value, precision);

{{ floored }} = floor(value, precision);

{{ ceiled }} = ceil(value, precision);

``` **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 {tuiCeil, tuiFloor, tuiRound} from '@taiga-ui/cdk'; import {TuiNumberFormat, TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiInputNumber, TuiNumberFormat, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected parametersForm = new FormGroup({ value: new FormControl(1.005), precision: new FormControl(2), }); protected get rounded(): number { const {value, precision} = this.parametersForm.value; return tuiRound(value ?? 1.005, precision ?? 2); } protected get floored(): number { const {value, precision} = this.parametersForm.value; return tuiFloor(value ?? 1.005, precision ?? 2); } protected get ceiled(): number { const {value, precision} = this.parametersForm.value; return tuiCeil(value ?? 1.005, precision ?? 2); } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` #### inRange **Template:** ```html {{ ranged }} = inRange(value, fromInclude, toExclude);
@for (parameter of ['value', 'fromInclude', 'toExclude']; track parameter) { }
``` **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 {tuiInRange} from '@taiga-ui/cdk'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiInputNumber, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected parametersForm = new FormGroup({ value: new FormControl(13), fromInclude: new FormControl(5), toExclude: new FormControl(42), }); protected get ranged(): boolean { const {value, fromInclude, toExclude} = this.parametersForm.value; return tuiInRange(value ?? 13, fromInclude ?? 5, toExclude ?? 42); } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` #### normalizeToIntNumber **Template:** ```html {{ normalized }} = normalizeToIntNumber(value, min, max);
@for (parameter of ['value', 'min', 'max']; track parameter) { }
``` **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 {tuiNormalizeToIntNumber} from '@taiga-ui/cdk'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiInputNumber, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected parametersForm = new FormGroup({ value: new FormControl(0), min: new FormControl(5), max: new FormControl(42), }); protected get normalized(): number { const {value, min, max} = this.parametersForm.value; return tuiNormalizeToIntNumber(value ?? 0, min ?? 5, max ?? 42); } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` #### quantize **Template:** ```html {{ quantized }} = quantize(value, quantum);
@for (parameter of ['value', 'quantum']; track parameter) { }
``` **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 {tuiQuantize} from '@taiga-ui/cdk'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiInputNumber, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected parametersForm = new FormGroup({ value: new FormControl(3), quantum: new FormControl(2), }); protected get quantized(): number { const {value, quantum} = this.parametersForm.value; return tuiQuantize(value ?? 3, quantum ?? 2); } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` #### clamp **Template:** ```html {{ clamped }} = clamp(value, min, max);
@for (parameter of ['value', 'min', 'max']; track parameter) { }
``` **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 {tuiClamp} from '@taiga-ui/cdk'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiInputNumber, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected parametersForm = new FormGroup({ value: new FormControl(0), min: new FormControl(5), max: new FormControl(42), }); protected get clamped(): number { const {value, min, max} = this.parametersForm.value; return tuiClamp(value ?? 0, min ?? 5, max ?? 42); } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` ### 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 component = import('./examples/import/component.md'); protected readonly examples = [ 'round', 'inRange', 'normalizeToIntNumber', 'quantize', 'clamp', ]; } ``` --- # utils/Miscellaneous - **Package**: `CDK` - **Type**: components/utils Some utils to simplify the development process ### Usage Examples #### assert **Template:** ```html

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; @Component({ templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected get assertResult(): string { const dayOfWeek = new Date().getDay(); const isFriday = dayOfWeek === 5; ngDevMode && console.assert(isFriday, 'Today is not a friday'); return isFriday ? 'Nothing in console' : 'There is a console assert:
"Today is not a friday"'; } } ``` #### getPaymentSystem **Template:** ```html '{{ paymentSystem }}' = getPaymentSystem(cardNumber);
``` **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 {tuiGetPaymentSystem} from '@taiga-ui/addon-commerce'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiChevron, TuiDataListWrapper, TuiSelect, TuiTextfield, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ '6734567890123456', '5536567890123456', '2202567890123456', '4405567890123456', '4000567890123456', ]; protected parametersForm = new FormGroup({cardNumber: new FormControl('')}); protected get paymentSystem(): string | null { const {cardNumber} = this.parametersForm.value; return tuiGetPaymentSystem(cardNumber ?? ''); } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 14rem; } ``` #### isPresent **Template:** ```html {{ isPresent }} = isPresent(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 {tuiIsPresent} from '@taiga-ui/cdk'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiChevron, TuiDataListWrapper, TuiSelect} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiChevron, TuiDataListWrapper, TuiSelect, TuiTextfield], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items = ['String', 'null', 'undefined']; protected value: 'null' | 'String' | 'undefined' | null = null; protected get isPresent(): boolean { return tuiIsPresent(this.objectifyValue(this.value ?? 'null')); } private objectifyValue(value: string): string | null | undefined { switch (value) { case 'null': return null; case 'undefined': return undefined; default: return value; } } } ``` **LESS:** ```less .parameters { margin-block-start: 0.75rem; inline-size: 13.75rem; } ``` #### markControlAsTouchedAndValidate **Template:** ```html

``` **TypeScript:** ```ts import {Component, type OnInit} 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 {TuiError, TuiInput} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiError, TuiInput], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example implements OnInit { protected userDetailsForm = new FormGroup({ name: new FormControl('', Validators.required), address: new FormGroup({ street: new FormControl('', Validators.required), city: new FormControl('', Validators.required), zipCode: new FormControl('', Validators.required), }), }); public ngOnInit(): void { tuiMarkControlAsTouchedAndValidate(this.userDetailsForm); } } ``` ### 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 component = import('./examples/import/component.md'); protected readonly examples = [ 'assert', 'getPaymentSystem', 'isPresent', 'markControlAsTouchedAndValidate', ]; } ``` --- # utils/Tokens - **Package**: `CDK` - **Type**: components/utils ### Usage Examples #### TUI_BREAKPOINT **Template:** ```html

Token to observe changes in the current breakpoint. Change the viewport of this window to see changes in breakpoint

CSS Service
Mobile
Desktop small
Desktop large
@if (breakpoint() === 'mobile') {
Mobile
} @if (breakpoint() === 'desktopSmall') {
Desktop small
} @if (breakpoint() === 'desktopLarge') {
Desktop large
}
``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TUI_BREAKPOINT} from '@taiga-ui/core'; @Component({ templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly breakpoint = inject(TUI_BREAKPOINT); } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :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; } .mobile { display: block; } .desktop-small { display: none; } .desktop-large { display: none; } @media @tui-tablet-min { .mobile { display: none; } .desktop-small { display: block; } .desktop-large { display: none; } } @media @tui-desktop-lg-min { .mobile { display: none; } .desktop-small { display: none; } .desktop-large { display: block; } } ``` #### WA_IS_ANDROID **Template:** ```html

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.
For example: 10 500,33
Can be customized as: 10/500.33

Defaults:

  • decimalSeparator = ,
  • thousandSeparator = CHAR_NO_BREAK_SPACE
  • zeroPadding = true
  • rounding = truncate

Components that are customizable:

``` **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 {TuiLink} from '@taiga-ui/core'; @Component({ imports: [RouterLink, TuiLink], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly routes = DemoRoute; } ``` #### TUI_DATE_FORMAT **Template:** ```html
Using TUI_DATE_FORMAT injection token you can customize numbers formatting.
For example: 10.01.2024
Can be customized as: 2024/01/10

Description:

mode
active date format ( 'dd/mm/yyyy' | 'mm/dd/yyyy' | 'yyyy/mm/dd' )
separator
single-character date's separator (dot, slash etc.)

Defaults:

  • mode = dd/mm/yyyy
  • separator = .

Components that are customizable:

``` **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 {TuiLink} from '@taiga-ui/core'; @Component({ imports: [RouterLink, TuiLink], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly routes = DemoRoute; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TUI_DOC_CODE_EDITOR} from '@taiga-ui/addon-doc'; @Component({ imports: [TuiDemo], templateUrl: './index.html', changeDetection, providers: [ { provide: TUI_DOC_CODE_EDITOR, useValue: null, }, ], }) export default class Page { protected readonly examples = [ 'TUI_BREAKPOINT', 'WA_IS_ANDROID', 'WA_IS_IOS', 'WA_IS_MOBILE', 'TUI_NUMBER_FORMAT', 'TUI_DATE_FORMAT', ]; } ``` --- # directives/ActiveZone - **Package**: `CDK` - **Type**: directives tuiActiveZone allows to track a scope that user interacts with. For example, for closing dropdown on blur ### Usage Examples #### Composite zone **Template:** ```html

Parent zone: {{ parentActive }}

Child zone: {{ childActive }}

Parent zone

Child zone

You can bind different elements with [tuiActiveZoneParent] directive

Zone keeps active after browser tab change

``` **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 {TuiActiveZone} from '@taiga-ui/cdk'; import {TuiButton, TuiInput} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiActiveZone, TuiButton, TuiInput], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly control = new FormControl(''); protected childActive = false; protected parentActive = false; protected items = [1, 2, 3]; protected onParentActiveZone(active: boolean): void { this.parentActive = active; } protected onChildActiveZone(active: boolean): void { this.childActive = active; } protected onClick(el: HTMLInputElement): void { el.focus(); } } ``` **LESS:** ```less .active-zone { padding: 1.25rem; border: 2px solid; &_active { border-color: var(--tui-background-accent-1); } } ``` #### Dialogs **Template:** ```html

Zone: {{ active }}

Zone

``` **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 @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 ``` ### 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
``` **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 ``` **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) { }
} ``` **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 @if (showInput) { } ``` **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
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
``` **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
Here can be any content

You can even insert other components:

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 } ``` **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

You can ask any questions about and will gladly answer! ``` **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 {TuiDropdown, TuiLink} from '@taiga-ui/core'; import {TuiAvatar, TuiSwitch} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiAvatar, TuiDropdown, TuiLink, TuiSwitch], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected open = false; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .dropdown { display: flex; inline-size: 14rem; padding: 0.375rem 0.75rem; } .toggle { display: flex; gap: 0.5rem; align-items: center; } .text { padding: 0 0.75rem; } .label { font: var(--tui-typography-body-m); color: var(--tui-text-tertiary); } .name { font: var(--tui-typography-heading-h6); } .account { font: var(--tui-typography-body-s); margin-block-start: 0.25rem; color: var(--tui-text-secondary); } ``` #### Change detection **Template:** ```html ``` **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 ``` **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
@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

Right click on me to see a dropdown Hello there!

``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiDropdown] | `PolymorpheusContent` | Content | ### Usage Examples #### Basic **Template:** ```html

Make right click on this icon -> Nothing special

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiDropdown, TuiIcon} from '@taiga-ui/core'; @Component({ imports: [TuiButton, TuiDropdown, TuiIcon], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .text { display: inline-block; margin: 0.4rem 1rem; } .icon { cursor: context-menu; } ``` #### Context menu **Template:** ```html

Right-click any table row.

@for (column of tableColumns; track column) { } @for (rowInfo of tableData; track rowInfo) { @for (value of getObjectValues(rowInfo); track value) { } @for (item of menuItems; track item) { } @for (option of moreOptions; track option) { } }
{{ column }}
{{ value }}
``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTable} from '@taiga-ui/addon-table'; import {TuiDataList, TuiDialogService, TuiDropdown} from '@taiga-ui/core'; import {TuiDataListDropdownManager} from '@taiga-ui/kit'; @Component({ imports: [TuiDataList, TuiDataListDropdownManager, TuiDropdown, TuiTable], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly dialogs = inject(TuiDialogService); protected readonly menuItems = [ {title: 'View', iconName: '@tui.eye'}, {title: 'Copy', iconName: '@tui.copy'}, {title: 'Delete', iconName: '@tui.trash'}, {title: 'Move', iconName: '@tui.folder'}, ] as const; protected readonly tableData = [ {character: 'Ross Geller', actor: 'David Schwimmer'}, {character: 'Chandler Bing', actor: 'Matthew Perry'}, {character: 'Joey Tribbiani', actor: 'Matt LeBlanc'}, {character: 'Phoebe Buffay', actor: 'Lisa Kudrow'}, {character: 'Monica Geller', actor: 'Courteney Cox'}, {character: 'Rachel Green', actor: 'Jennifer Aniston'}, ] as const; protected readonly tableColumns = Object.keys(this.tableData[0]); protected readonly moreOptions = ['Option 1', 'Option 2', 'Option 3']; protected getObjectValues = (obj: Record): unknown[] => Object.values(obj); protected printToConsole(action: string, contextInfo: unknown): void { this.dialogs.open(`[${action}]: ${JSON.stringify(contextInfo)}`).subscribe(); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .context-menu { inline-size: 8rem; } [tuiTable] { inline-size: 100%; } [tuiTh], [tuiTd] { border-inline-start: none; border-inline-end: none; } [tuiTh] { border-block-start: none; } [tuiTd] { font: var(--tui-typography-body-m) !important; } ``` #### Report mistake form **Template:** ```html

Some text with a mistake. Right-click it.

Another text

``` **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

``` ### 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
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

Nested content!

@for (item of items; track 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
``` **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 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
@if (template) { } @else { {{ content }} }
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiDropdown] | `PolymorpheusContent` | Content inside a dropdown | | [(tuiDropdownOpen)] | `boolean` | Content inside a dropdown | ### Usage Examples #### Menu **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 {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
@for (item of items; track 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) { }
}
``` **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
@for (item of items; track item) { }
``` **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 ``` **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

``` ### 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 @for (item of items | tuiMapper: filter : search.replace('@', ''); track item) { } ``` **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!

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

component instanceof TuiAvatar : {{ isLink(component) }}

element instanceof ElementRef : {{ isElement(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) { }
``` **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 {TuiFade} from '@taiga-ui/kit'; @Component({ imports: [TuiFade, TuiLink], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected expanded = false; protected toggle(): void { this.expanded = !this.expanded; } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .wrapper { position: relative; } .fade { .transition(max-block-size); max-block-size: calc(3 * (1.25rem + var(--tui-font-offset))); &_expanded { max-block-size: 15rem; } } .expand { .transition(opacity, visibility); position: absolute; inset-block-end: 0; inset-inline-end: 0; transition-delay: var(--tui-duration); &_hidden { opacity: 0; visibility: hidden; } } ``` #### Vertical **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.
``` **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
``` **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 @for (row of rows; track row) { @for (cell of row; track cell) { } }
Member Nickname Fate
{{ 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
❤️
What is love ?
Baby don't hurt me
Don't hurt me
Counter: {{ counter() }}
No more...
``` **TypeScript:** ```ts import {Component, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiButton, TuiHint} from '@taiga-ui/core'; import {TuiAutoColorPipe, TuiAvatar} from '@taiga-ui/kit'; @Component({ imports: [TuiAutoColorPipe, TuiAvatar, TuiButton, TuiHint], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly counter = signal(1); protected onClick(): void { this.counter.update((n) => n + 1); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { display: block; background: #3e4757; box-shadow: 0 0 0 100rem #3e4757; } ``` #### Customizing **Template:** ```html
❤️ You can expose the bubble component with *tuiHint directive to customize it
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiHint} from '@taiga-ui/core'; import {TuiAutoColorPipe, TuiAvatar} from '@taiga-ui/kit'; @Component({ imports: [TuiAutoColorPipe, TuiAvatar, TuiHint], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .hint { color: #fff; background: linear-gradient(43deg, #4158d0 0%, #c850c0 46%, #ffcc70 100%); font-weight: bold; &::before { display: none; } } ``` #### Nested **Template:** ```html Hover me Hover me again Nested hint ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiHint} from '@taiga-ui/core'; import {TuiBadge} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge, TuiHint], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Auto **Template:** ```html
Resize so text overflows
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiHint} from '@taiga-ui/core'; @Component({ imports: [TuiHint], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; div { .text-overflow(); resize: horizontal; } ``` #### Form **Template:** ```html
You will pay in equal installments on a monthly basis You will pay in equal installments on a monthly basis
``` **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 { TuiFilterByInputPipe, TuiHint, TuiIcon, TuiLink, TuiTextfield, } from '@taiga-ui/core'; import { TuiChevron, TuiDataListWrapper, TuiSelect, TuiStringifyContentPipe, TuiTooltip, } from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiChevron, TuiDataListWrapper, TuiFilterByInputPipe, TuiHint, TuiIcon, TuiLink, TuiSelect, TuiStringifyContentPipe, TuiTextfield, TuiTooltip, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { 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 form = new FormGroup({period: new FormControl()}); protected readonly stringify = (item: {name: string; surname: string}): string => `${item.name} ${item.surname}`; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {TuiDocHint} from '@demo/components/hint'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiHint} from '@taiga-ui/core'; @Component({ imports: [TuiDemo, TuiDocHint, TuiHint], templateUrl: './index.html', changeDetection, }) export default class PageComponent { protected readonly examples = ['Basic', 'Customizing', 'Nested', 'Auto', 'Form']; protected showDelay = 500; protected hideDelay = 200; protected readonly routes = DemoRoute; } ``` --- # directives/HintDescribe - **Package**: `CORE` - **Type**: directives Directive to show a hint in accessible way upon keyboard focus ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiHint] | `PolymorpheusContent` | Content | | [tuiHintDescribe] | `string` | Id of the element | ### 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 {TuiButton, TuiHint, TuiIcon, TuiInput} from '@taiga-ui/core'; import {TuiTooltip} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiButton, TuiHint, TuiIcon, TuiInput, TuiTooltip], templateUrl: './index.html', changeDetection, }) export default class Example { protected value = ''; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {TuiDocHint} from '@demo/components/hint'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiHint} from '@taiga-ui/core'; @Component({ imports: [TuiDemo, TuiDocHint, TuiHint], templateUrl: './index.html', changeDetection, }) export default class PageComponent { protected id = ''; } ``` --- # directives/HintManual - **Package**: `CORE` - **Type**: directives Directive to show a hint manually ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiHint] | `PolymorpheusContent` | Content | | [tuiHintManual] | `boolean` | Show/hide hint | ### Usage Examples #### Basic **Template:** ```html Use Hint ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {RouterLink} from '@angular/router'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiButton, TuiHint, TuiLink} from '@taiga-ui/core'; @Component({ imports: [RouterLink, TuiButton, TuiHint, TuiLink], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Example { protected hintShown = false; protected toggleHint(): void { this.hintShown = !this.hintShown; } } ``` **LESS:** ```less :host { display: block; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {TuiDocHint} from '@demo/components/hint'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiButton, TuiHint} from '@taiga-ui/core'; @Component({ imports: [TuiButton, TuiDemo, TuiDocHint, TuiHint], templateUrl: './index.html', changeDetection, }) export default class PageComponent { protected show = false; protected sayHi(): void { console.info('Hi all!'); } } ``` --- # directives/HintPointer - **Package**: `CORE` - **Type**: directives A directive to show a hint above the cursor ### Example ```html
It is followed inside the block
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [tuiHint] | `PolymorpheusContent` | Hint content | | [tuiHintShowDelay] | `number` | Show delay (ms) | | [tuiHintHideDelay] | `number` | Hide delay (ms) | ### Usage Examples #### Basic **Template:** ```html

In this block hint follows cursor

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiHint} from '@taiga-ui/core'; @Component({ imports: [TuiHint], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Example {} ``` **LESS:** ```less .block { border: 1px solid var(--tui-border-normal); border-radius: 1rem; padding: 5rem 3.125rem; inline-size: 18.75rem; text-align: center; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {TuiDocHint} from '@demo/components/hint'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiHint} from '@taiga-ui/core'; @Component({ imports: [TuiDemo, TuiDocHint, TuiHint], templateUrl: './index.html', changeDetection, }) export default class PageComponent { protected showDelay = 0; protected hideDelay = 0; } ``` --- # directives/HoveredChange - **Package**: `CDK` - **Type**: directives tuiHoveredChange is used for emitting true/false when users hovers over an element or moves cursor away from it. ### Usage Examples #### Basic **Template:** ```html

Hidden Text Appears Here: You Just Hovered Over The Button!

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiHovered} from '@taiga-ui/cdk'; import {TuiButton} from '@taiga-ui/core'; @Component({ imports: [TuiButton, TuiHovered], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected hovered = false; protected onHovered(hovered: boolean): void { this.hovered = hovered; } } ``` **LESS:** ```less .text-style { font-size: 16px; color: #f00; } .hidden { display: none; } ``` ### 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/Media - **Package**: `CDK` - **Type**: directives Directive for declarative work with HTML5 video and audio ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [(currentTime)] | `number` | Current time (seconds) | | [(paused)] | `boolean` | Paused state | | [playbackRate] | `number` | Playback speed | | [(volume)] | `number` | Volume | ### Usage Examples #### Native controls **Template:** ```html

currentTime: {{ currentTime }}

volume: {{ volume }}

paused: {{ paused }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiHighDpi, TuiMedia} from '@taiga-ui/cdk'; @Component({ imports: [TuiHighDpi, TuiMedia], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected currentTime = 0; protected volume = 1; protected paused = true; } ``` **LESS:** ```less :host { display: block; } .video { float: left; margin-inline-end: 1.5rem; // Safari 15+ @supports (float: inline-start) { float: inline-start; } } ``` #### Video **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 {SECONDS_IN_MINUTE, TuiMedia} from '@taiga-ui/cdk'; import {TuiButton, TuiSlider} from '@taiga-ui/core'; @Component({ imports: [FormsModule, TuiButton, TuiMedia, TuiSlider], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected currentTime = 0; protected paused = true; protected get icon(): string { return this.paused ? '@tui.play' : '@tui.pause'; } protected getTime(time: number): string { const integer = Math.round(time || 0); const seconds = integer % SECONDS_IN_MINUTE; const minutes = (integer - seconds) / SECONDS_IN_MINUTE; const secondsString = String(seconds); const minutesString = String(minutes); const paddedSeconds = secondsString.length === 1 ? `0${secondsString}` : secondsString; const paddedMinutes = minutesString.length === 1 ? `0${minutesString}` : minutesString; return `${paddedMinutes}:${paddedSeconds}`; } protected toggleState(): void { this.paused = !this.paused; } } ``` **LESS:** ```less :host { display: block; } .video { display: block; } .player { position: relative; inline-size: 20rem; } .controls { position: absolute; display: flex; inset-block-end: 0; inline-size: 100%; align-items: center; padding: 0.75rem 0.75rem 0.5rem; box-sizing: border-box; color: var(--tui-text-primary); background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.56)); } .slider { flex: 1; margin-inline-start: 0.75rem; } .time { flex-shrink: 0; margin-inline-start: 0.75rem; font-size: 0.8125rem; } ``` #### Audio **Template:** ```html
Waterplea — Strays
``` **TypeScript:** ```ts import {Component, ViewEncapsulation} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiMedia} from '@taiga-ui/cdk'; import {TuiButton, TuiLink, TuiSlider} from '@taiga-ui/core'; @Component({ imports: [FormsModule, TuiButton, TuiLink, TuiMedia, TuiSlider], templateUrl: './index.html', styleUrl: './index.less', encapsulation: ViewEncapsulation.None, changeDetection, }) export default class Example { protected currentTime = 0; protected paused = true; protected get icon(): string { return this.paused ? '@tui.play' : '@tui.pause'; } protected toggleState(): void { this.paused = !this.paused; } } ``` **LESS:** ```less .tui-player { display: flex; inline-size: 20rem; border-radius: 6.25rem; background: var(--tui-background-neutral-1); --tui-background-accent-1: var(--tui-text-action); --tui-background-accent-1-hover: var(--tui-text-action-hover); --tui-background-accent-1-pressed: var(--tui-text-action-hover); & > div { flex: 1; margin: 0.375rem 1.75rem 0 0.375rem; } } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiMedia} from '@taiga-ui/cdk'; @Component({ imports: [TuiDemo, TuiMedia], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly volumeVariants: readonly number[] = [1, 0.5, 0.25, 0]; protected playbackRate = 1; protected currentTime = 0; protected volume = this.volumeVariants[0]!; protected paused = true; protected readonly examples = ['Native controls', 'Video', 'Audio']; } ``` --- # directives/NumberFormat - **Package**: `CORE` - **Type**: directives Directive allows to customize TuiInputNumber , TuiInputSlider , TuiInputRange number format. ### 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 {TuiFormatNumberPipe, TuiNumberFormat, TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiFormatNumberPipe, TuiInputNumber, TuiNumberFormat, TuiTextfield, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value = 123456.789; } ``` ### 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/Pan - **Package**: `CDK` - **Type**: directives tuiPan The directive emits delta between mousemove / touchmove events. You can use it to change the coordinates of an element as in example below ### Usage Examples #### Basic **Template:** ```html
``` **TypeScript:** ```ts import {AsyncPipe} from '@angular/common'; import {Component, inject} from '@angular/core'; import {DomSanitizer} from '@angular/platform-browser'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiPan} from '@taiga-ui/cdk'; import {BehaviorSubject, map} from 'rxjs'; @Component({ imports: [AsyncPipe, TuiPan], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly sanitizer = inject(DomSanitizer); protected readonly coordinates$ = new BehaviorSubject([0, 0]); protected readonly transform$ = this.coordinates$.pipe( map((coords) => this.sanitizer.bypassSecurityTrustStyle( `translate(${coords[0]}px, ${coords[1]}px)`, ), ), ); protected get currentCoords(): number[] { return this.coordinates$.value; } protected onPan(delta: readonly [number, number]): void { this.coordinates$.next([ (this.currentCoords[0] ?? 0) + delta[0], (this.currentCoords[1] ?? 0) + delta[1], ]); } } ``` **LESS:** ```less .container { display: flex; justify-content: center; align-items: center; inline-size: 12rem; block-size: 12rem; background-color: var(--tui-background-neutral-1); overflow: hidden; } .circle { inline-size: 6rem; block-size: 6rem; border-radius: 100%; touch-action: none; background-color: var(--tui-chart-categorical-01); box-shadow: 0.25rem 0.25rem 0.5rem 0 rgba(34, 60, 80, 0.2); cursor: move; will-change: transform; } ``` ### 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/Present - **Package**: `KIT` - **Type**: directives tuiPresent allows to detect appearance of elements in DOM ### Usage Examples #### Basic **Template:** ```html

Hover I am a component hidden with CSS @if (hovered) { I am a component hidden with *ngIf }

Counter of component appearance minus counter of its disappearance:

CSS: {{ counterCSS }}

ngIf: {{ counterIf }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiHovered} from '@taiga-ui/cdk'; import {TuiBadge, TuiPresent} from '@taiga-ui/kit'; @Component({ imports: [TuiBadge, TuiHovered, TuiPresent], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected counterCSS = 0; protected counterIf = 0; protected hovered = false; protected onHovered(hovered: boolean): void { this.hovered = hovered; } protected onCSS(visible: boolean): void { this.counterCSS += visible ? 1 : -1; } protected onIf(visible: boolean): void { this.counterIf += visible ? 1 : -1; } } ``` **LESS:** ```less .hidden { display: none; } ``` ### 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/ProgressSegmented - **Package**: `KIT` - **Type**: directives ProgressSegmented is a component to visually represent the completion of a process or operation (as a segmented bar). It shows how much has been completed and how much remains. Actually, this component is the same ProgressBar processed by css-property mask . ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | [segments] | `number` | | | [value] | `number` | | | [max] | `number` | | | [size] | `TuiSizeXS | TuiSizeXXL` | Height of the progress | | [tuiProgressColorSegments] | `string[]` | | ### 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 {TuiProgress} from '@taiga-ui/kit'; @Component({ imports: [TuiProgress], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### 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: 1.5rem; } ``` #### 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 { protected readonly arrayColors = [ '#39b54a', '#ffd450', '#ffd450', '#fcc521', '#fab619', '#f8a34d', '#e01f19', ]; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1.5rem; } .green { color: var(--tui-chart-categorical-20); } ``` #### With labels **Template:** ```html
``` **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 {TuiTitle} from '@taiga-ui/core'; import {TuiProgress} from '@taiga-ui/kit'; @Component({ imports: [TuiAmountPipe, TuiProgress, TuiTitle], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less .description { display: flex; justify-content: space-between; & > *:first-child { text-align: start; } & > *:last-child { text-align: end; } [tuiSubtitle] { color: var(--tui-text-secondary); } } ``` #### No round 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]._segmented { --tui-segment-gap: 0.25rem; border-radius: 0; mask-image: linear-gradient(to left, transparent 0 var(--tui-segment-gap), #999 var(--tui-segment-gap) 100%); } ``` ### TypeScript ```ts import {ChangeDetectionStrategy, Component} from '@angular/core'; import {RouterLink} from '@angular/router'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiLink, type TuiSizeXXL, type TuiSizeXXS} from '@taiga-ui/core'; import {TuiProgress} from '@taiga-ui/kit'; @Component({ imports: [RouterLink, TuiDemo, TuiLink, TuiProgress], templateUrl: './index.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export default class Page { protected readonly routes = DemoRoute; protected value = 3; protected max = 5; protected segments = this.max; protected examples = ['Basic', 'Sizes', 'Colors', 'With labels', 'No round corners']; protected readonly sizeVariants: ReadonlyArray = [ 'xs', 's', 'm', 'l', 'xl', 'xxl', ]; protected size = this.sizeVariants[2]!; protected readonly colorsVariants: readonly string[][] = [ ['var(--tui-background-accent-1)'], ['#39b54a', '#ffd450', '#ffd450', '#fcc521', '#fab619', '#f8a34d', '#e01f19'], Array.from( {length: 20}, (_, index) => `var(--tui-chart-categorical-${String(index + 1).padStart(2, '0')})`, ), ]; protected colors = this.colorsVariants[0] ?? []; protected get computedColors(): string[] { return this.colors.slice(0, this.segments); } } ``` --- # directives/Resizer - **Package**: `CDK` - **Type**: directives Directive to resize container in multiple directions. ### Usage Examples #### Basic **Template:** ```html
[1, 1]
[0, 1]
[-1, 1]
[-1, 0]
[-1, -1]
[0, -1]
[1, -1]
[1, 0]
All
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiResizable, TuiResizer} from '@taiga-ui/cdk'; @Component({ imports: [TuiResizable, TuiResizer], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; :host { position: relative; display: block; block-size: 30rem; } .item { position: absolute; display: flex; background: #87ceeb; align-items: center; text-align: center; justify-content: center; inline-size: 5rem; block-size: 5rem; } .handle { position: absolute; background: #ffc0cb; inline-size: 1.25rem; block-size: 1.25rem; transform: inherit; } .left-top { inset-inline-start: 0; inset-block-start: 0; } .right-bottom-handle { inset-inline-end: 0; inset-block-end: 0; } .top-middle { .center-left(); inset-block-start: 0; } .top-middle-handle { inset-inline-start: 50%; inset-block-end: 0; } .top-right { inset-block-start: 0; inset-inline-end: 0; } .top-right-handle { inset-inline-start: 0; inset-block-end: 0; } .middle-right { .center-top(); inset-inline-end: 0; } .middle-right-handle { inset-inline-start: 0; inset-block-start: 50%; } .bottom-right { inset-block-end: 0; inset-inline-end: 0; } .bottom-right-handle { inset-inline-start: 0; inset-block-start: 0; } .middle-bottom { .center-left(); inset-block-end: 0; } .middle-bottom-handle { inset-inline-start: 50%; inset-block-start: 0; } .bottom-left { inset-block-end: 0; inset-inline-start: 0; } .bottom-left-handle { inset-inline-end: 0; inset-block-start: 0; } .left-middle { inset-inline-start: 0; inset-block-start: 50%; transform: translateY(-50%); } .left-middle-handle { inset-inline-end: 0; inset-block-start: 50%; } .middle-middle { .center-all(); } .middle-middle-top-handle { .center-left(); inset-block-start: 0; } .middle-middle-right-handle { .center-top(); inset-inline-end: 0; } .middle-middle-bottom-handle { .center-left(); inset-block-end: 0; } .middle-middle-left-handle { .center-top(); inset-inline-start: 0; } ``` ### 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/Ripple - **Package**: `ADDON-MOBILE` - **Type**: directives Directive for «ripple» effect on mobile devices ### 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
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$ ``` **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

12 000$ 12 000$ 12 000$

``` **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

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?" }}

@for (avatar of avatars; track avatar) {
}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiButton, TuiLabel, TuiTitle} from '@taiga-ui/core'; import { TuiAvatar, TuiAvatarStack, TuiShimmer, TuiSkeleton, TuiSwitch, } from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiAvatarStack, TuiButton, TuiCardLarge, TuiHeader, TuiLabel, TuiShimmer, TuiSkeleton, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Example { protected loading = true; protected readonly avatars = [ 'https://avatars.githubusercontent.com/mdlufy', 'https://avatars.githubusercontent.com/splincode', 'https://avatars.githubusercontent.com/nsbarsukov', 'https://avatars.githubusercontent.com/vladimirpotekhin', 'https://avatars.githubusercontent.com/marsibarsi', 'https://avatars.githubusercontent.com/waterplea', ]; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1.5rem; } h3 { font: var(--tui-typography-heading-h6); margin: 0; } footer { display: flex; gap: 1.25rem; button { flex: 1; } } ``` #### Disabled animations **Template:** ```html
You got $237 000,42 left

Where's the money, Lebowski?

@for (avatar of avatars; track $index) {
}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiButton, TuiLabel, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiAvatarStack, TuiShimmer, TuiSwitch} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiAvatarStack, TuiButton, TuiCardLarge, TuiHeader, TuiLabel, TuiShimmer, TuiSwitch, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Example { protected loading = true; protected readonly avatars = [ 'https://avatars.githubusercontent.com/mdlufy', 'https://avatars.githubusercontent.com/splincode', 'https://avatars.githubusercontent.com/nsbarsukov', 'https://avatars.githubusercontent.com/vladimirpotekhin', 'https://avatars.githubusercontent.com/marsibarsi', 'https://avatars.githubusercontent.com/waterplea', ]; } ``` **LESS:** ```less :host { display: flex; flex-direction: column; gap: 1.5rem; } section { --tui-duration: 0; } footer { display: flex; gap: 1.25rem; button { flex: 1; } } ``` ### 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, TuiLink, TuiTitle} from '@taiga-ui/core'; import {TuiAvatar, TuiAvatarStack, TuiShimmer} from '@taiga-ui/kit'; import {TuiCardLarge, TuiHeader} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, TuiAvatar, TuiAvatarStack, TuiButton, TuiCardLarge, TuiDemo, TuiHeader, TuiLink, TuiShimmer, TuiTitle, ], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected readonly routes = DemoRoute; protected readonly examples = ['Basic', 'Disabled animations']; protected readonly avatars = [ 'https://avatars.githubusercontent.com/mdlufy', 'https://avatars.githubusercontent.com/splincode', 'https://avatars.githubusercontent.com/nsbarsukov', 'https://avatars.githubusercontent.com/vladimirpotekhin', 'https://avatars.githubusercontent.com/marsibarsi', 'https://avatars.githubusercontent.com/waterplea', ]; protected shimmer = true; } ``` ### LESS ```less footer { display: flex; gap: 1.25rem; button { flex: 1; } } ``` --- # directives/Skeleton - **Package**: `KIT` - **Type**: directives ### Usage Examples #### Components **Template:** ```html

Card Subtitle

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

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

{{ 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
Swiped {{ swiped }}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSwipe, type TuiSwipeEvent} from '@taiga-ui/cdk'; @Component({ imports: [TuiSwipe], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, host: {'[class]': 'swiped'}, }) export default class Example { protected swiped = 'default'; protected onSwipe(swipe: TuiSwipeEvent): void { this.swiped = swipe.direction; } } ``` **LESS:** ```less .box { display: flex; inline-size: 12.5rem; block-size: 12.5rem; background-color: var(--tui-background-accent-1); transition: all 0.5s ease-out; justify-content: center; align-items: center; touch-action: none; &.left { background-color: var(--tui-chart-categorical-12); } &.right { background-color: var(--tui-chart-categorical-03); } &.top { background-color: var(--tui-chart-categorical-08); } &.bottom { background-color: var(--tui-chart-categorical-10); } } ``` #### With sidebar **Template:** ```html
Swipe left to open Swipe right to close
``` **TypeScript:** ```ts import {Component, signal} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiSwipe, type TuiSwipeEvent} from '@taiga-ui/cdk'; import {TuiPopup} from '@taiga-ui/core'; import {TuiDrawer} from '@taiga-ui/kit'; @Component({ imports: [TuiDrawer, TuiPopup, TuiSwipe], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly open = signal(false); protected onSwipe(swipe: TuiSwipeEvent): void { console.info(swipe.direction); if (swipe.direction === 'left') { this.open.set(true); } if (swipe.direction === 'right') { this.open.set(false); } } } ``` **LESS:** ```less .container { display: flex; justify-content: center; align-items: center; inline-size: 12.5rem; block-size: 12.5rem; } .drawer { inline-size: 17.25rem; } ``` ### 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', 'With sidebar']; } ``` --- # directives/Theme - **Package**: `null` - **Type**: directives tuiTheme allows to set style for a DOM branch. By default dark and light are included. Importing is not required. ### Usage Examples #### Themes **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 {TuiInputNumber, TuiSwitch} from '@taiga-ui/kit'; @Component({ imports: [FormsModule, TuiInput, TuiInputNumber, TuiSwitch], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected toggle = false; protected text = ''; protected money = 237; } ``` **LESS:** ```less .dark { inline-size: 18.75rem; padding: 0.625rem 1.25rem 1.25rem; background: #454e58; border-radius: 0.25rem; } .light { padding: 0.625rem 1.25rem; background: var(--tui-background-base); border-radius: 0.25rem; color: var(--tui-text-primary); } ``` #### Toggling **Template:** ```html Dark mode enabled: {{ darkMode() }}

Add to Root to enable:

<tui-root [attr.tuiTheme]="darkMode() ? 'dark' : null"></tui-root> ``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {WA_LOCAL_STORAGE, WA_WINDOW} from '@ng-web-apis/common'; import {TUI_DARK_MODE, TUI_DARK_MODE_KEY, TuiButton} from '@taiga-ui/core'; @Component({ imports: [TuiButton], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { private readonly key = inject(TUI_DARK_MODE_KEY); private readonly storage = inject(WA_LOCAL_STORAGE); private readonly media = inject(WA_WINDOW).matchMedia('(prefers-color-scheme: dark)'); protected readonly darkMode = inject(TUI_DARK_MODE); protected reset(): void { this.darkMode.set(this.media.matches); this.storage?.removeItem(this.key); } } ``` **LESS:** ```less p { display: flex; gap: 1rem; } code { white-space: nowrap !important; } ``` ### 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 = ['Themes', 'Toggling']; } ``` --- # directives/Touchable - **Package**: `ADDON-MOBILE` - **Type**: directives Directive to emulate native iOS touches ### Usage Examples #### Basic **Template:** ```html

transform

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
{{ content }}
``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | | `string` | Content | | [tuiTruncate] | `number` | Visible lines | ### Usage Examples #### Basic **Template:** ```html

Poster @let text = `Look deep into nature, and then you will understand everything better: cinema, concerts, theaters and sports up to 25%`;

10%
50 min

Order #31 8375 Brook Avenue Brooklyn, NY 11206

filename_long_long_long_long_long_long_long_long_long_longname.doc
@let filename = `filename_long_long_long_long_long_long_long_long_long_longname.doc`;
{{ filename }}
``` **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 {TuiTruncate} from '@taiga-ui/cdk'; import {TuiCell, TuiHint, TuiIcon, TuiTitle} from '@taiga-ui/core'; import {TuiBadge} from '@taiga-ui/kit'; import {TuiCard} from '@taiga-ui/layout'; @Component({ imports: [ FormsModule, ReactiveFormsModule, TuiBadge, TuiCard, TuiCell, TuiHint, TuiIcon, TuiTitle, TuiTruncate, ], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example {} ``` **LESS:** ```less :host { display: flex; gap: 1.2rem; flex-wrap: wrap; inline-size: 23.25rem; } .badge { background: #aff218; color: #333; } [tuiTruncate] { inline-size: 100%; } [tuiSubtitle][tuiSubtitle] { color: #fff; } h2 { inline-size: 100%; } .poster { background: #4158d0 linear-gradient(43deg, #4158d0 0%, #c850c0 46%, #ffcc70 100%); padding: 0.75rem; color: #fff; inline-size: 13.9375rem; block-size: 8rem; } .fly { background: #0093e9 linear-gradient(160deg, #0093e9 0%, #80d0c7 100%); padding: 0.75rem; color: #fff; inline-size: 8rem; block-size: 8rem; } .one-line { background: #00dbde linear-gradient(90deg, #00dbde 0%, #fc00ff 100%); inline-size: 100%; } .two-line { background: #fbab7e linear-gradient(62deg, #fbab7e 0%, #f7ce68 100%); inline-size: 15.9375rem; } [tuiCardLarge] [tuiCell], [tuiCardLarge] tui-icon { color: #fff; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiTruncate} from '@taiga-ui/cdk'; @Component({ imports: [TuiDemo, TuiTruncate], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export default class Page { protected content = 'I am very very long text that gets truncated'; protected lines = 1; protected padding = 16; protected fontSize = 16; protected wordBreakStyles = ['break-word', 'break-all', 'normal']; protected wordBreak = this.wordBreakStyles[0]; } ``` ### LESS ```less .demo { background: #87ceeb; overflow: hidden; resize: horizontal; } ``` --- # directives/Validator - **Package**: `CDK` - **Type**: directives tuiValidator allows set validators for form control on the fly ### Usage Examples #### Basic **Template:** ```html
@if (type === items[0]!) { } @else { }
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators, } from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiValidator} from '@taiga-ui/cdk'; import {TuiInput} from '@taiga-ui/core'; import {TuiChevron, TuiDataListWrapper, TuiInputPhone, TuiSelect} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, ReactiveFormsModule, TuiChevron, TuiDataListWrapper, TuiInput, TuiInputPhone, TuiSelect, TuiValidator, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items = ['Email', 'Phone']; protected type = this.items[0]!; protected readonly group = new FormGroup({ name: new FormControl('', Validators.required), contact: new FormControl('', Validators.required), }); protected readonly validator = Validators.email; } ``` ### 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/ValueChanges - **Package**: `CDK` - **Type**: directives This directive allows you to access reactive control or container value changes as an output ### Usage Examples #### Control **Template:** ```html
``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiValueChanges} from '@taiga-ui/cdk'; import {TuiInput, TuiLabel, TuiNotificationService} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiInput, TuiLabel, TuiValueChanges], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly alerts = inject(TuiNotificationService); protected readonly form = new FormGroup({ control: new FormControl('', {updateOn: 'blur'}), }); protected onChanges(value: string): void { this.alerts.open(value).subscribe(); } } ``` #### Container **Template:** ```html
``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiValueChanges} from '@taiga-ui/cdk'; import {TuiInput, TuiNotificationService} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiInput, TuiInputNumber, TuiValueChanges], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { private readonly alerts = inject(TuiNotificationService); protected readonly form = new FormGroup({ name: new FormControl('', {updateOn: 'blur'}), age: new FormControl(null), }); protected onChanges(value: string): void { this.alerts.open(JSON.stringify(value)).subscribe(); } } ``` ### 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 = ['Control', 'Container']; } ``` --- # directives/Zoom - **Package**: `CDK` - **Type**: directives tuiZoom directive emits delta between wheel events or between pinch on mobile devices. It emits coordinates of the zoom center as well. You can use it to change the scale of an element as in example below ### Usage Examples #### Simple **Template:** ```html
{{ scale$ | async | number: '1.0-3' }}
``` **TypeScript:** ```ts import {AsyncPipe, DecimalPipe} from '@angular/common'; import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {tuiClamp, TuiZoom, type TuiZoomEvent} from '@taiga-ui/cdk'; import {map, scan, startWith, Subject} from 'rxjs'; @Component({ imports: [AsyncPipe, DecimalPipe, TuiZoom], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly delta$ = new Subject(); protected readonly scale$ = this.delta$.pipe( scan((scale, next) => tuiClamp(scale + next, 0.5, 3), 1), startWith(1), ); protected readonly transform$ = this.scale$.pipe(map((scale) => `scale(${scale})`)); protected onZoom({delta}: TuiZoomEvent): void { this.delta$.next(delta); } } ``` **LESS:** ```less .t-container, .t-zoomable { display: flex; align-items: center; justify-content: center; } .t-container { inline-size: 12rem; block-size: 12rem; background-color: var(--tui-background-neutral-1); } .t-zoomable { inline-size: 3rem; block-size: 3rem; background-color: var(--tui-background-accent-1); border-radius: var(--tui-radius-l); } ``` ### 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 {} ``` --- # customization/Appearances - **Package**: `null` - **Type**: null ### Usage Examples #### Imitate material **Template:** ```html ``` **TypeScript:** ```ts import { ChangeDetectionStrategy, Component, signal, ViewEncapsulation, } from '@angular/core'; import {FormsModule} from '@angular/forms'; import { TuiButton, tuiButtonOptionsProvider, TuiCheckbox, tuiCheckboxOptionsProvider, TuiInput, tuiTextfieldOptionsProvider, } from '@taiga-ui/core'; @Component({ selector: 'tui-wrapper-example-1', imports: [FormsModule, TuiButton, TuiCheckbox, TuiInput], templateUrl: './index.html', styleUrl: './index.less', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [ tuiButtonOptionsProvider({appearance: 'material-button', size: 's'}), tuiCheckboxOptionsProvider({appearance: () => 'material-checkbox'}), tuiTextfieldOptionsProvider({ appearance: signal('material-textfield'), cleaner: signal(false), }), ], }) export class TuiWrapperExample1 { protected value = ''; protected checkbox = false; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; tui-wrapper-example-1 { --tui-background-accent-1: #6200ee; --tui-background-accent-1-hover: #6e14ef; --tui-background-accent-1-pressed: #6e14ef; --tui-text-primary-on-accent-1: #fff; } [tuiAppearance][data-appearance='material-textfield'] { .transition(~'background, box-shadow'); background: #f5f5f5; outline: none; color: rgba(0, 0, 0, 0.87); border-radius: 0.25rem 0.25rem 0 0; box-shadow: inset 0 -1px #8e8e8e; .appearance-hover({ background: #ececec; box-shadow: inset 0 -1px #1f1f1f; }); .appearance-focus({ background: #dcdcdc; box-shadow: inset 0 -2px var(--tui-background-accent-1) !important; ~ label { --tui-text-primary: var(--tui-background-accent-1); } }); &:invalid { --tui-background-accent-1: #b00020; box-shadow: inset 0 -1px var(--tui-background-accent-1) !important; ~ label { --tui-text-primary: #b00020 !important; } } } [tuiButton][tuiAppearance][data-appearance='material-button'] { .transition(all); border-radius: 0.25rem; background: var(--tui-background-accent-1); color: var(--tui-text-primary-on-accent-1); outline: none; text-transform: uppercase; font-weight: bold; box-shadow: 0 0.1875rem 0.0625rem -0.125rem rgba(0, 0, 0, 0.2), 0 0.125rem 0.125rem 0 rgba(0, 0, 0, 0.0014), 0 0.0625rem 0.3125rem 0 rgba(0, 0, 0, 0.12); .appearance-hover({ background: var(--tui-background-accent-1-hover); box-shadow: 0 0.125rem 0.25rem -0.0625rem rgba(0, 0, 0, 0.2), 0rem 0.25rem 0.3125rem 0rem rgba(0, 0, 0, 0.14), 0rem 0.0625rem 0.625rem 0rem rgba(0, 0, 0, 0.12); }); .appearance-active({ background: var(--tui-background-accent-1-pressed); box-shadow: 0 0.3125rem 0.3125rem -0.1875rem rgba(0, 0, 0, 0.2), 0rem 0.5rem 0.625rem 0.0625rem rgba(0, 0, 0, 0.14), 0rem 0.1875rem 0.875rem 0.125rem rgba(0, 0, 0, 0.12); }); .appearance-focus({ background: #883df2; }); } [tuiAppearance][data-appearance='material-checkbox'] { color: var(--tui-text-primary-on-accent-1); border-radius: 0.125rem; border: 0.125rem solid rgba(0, 0, 0, 0.54); outline: none; &::after { .transition(all); content: ''; position: absolute; inset: -0.625rem; border-radius: 100%; background: #000; opacity: 0; transform: scale(0); } .appearance-hover({ &:after { opacity: 0.05; transform: none; } }); .appearance-active({ &:after { opacity: 0.1; transform: none; } }); .appearance-focus({ &:after { opacity: 0.1; transform: none; } }); &:checked { background: var(--tui-background-accent-1); border-color: transparent; } } ``` ### TypeScript ```ts import {ClipboardModule} from '@angular/cdk/clipboard'; import {Component, ViewEncapsulation} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiLink} from '@taiga-ui/core'; import {TuiWrapperExample1} from './examples/1'; @Component({ imports: [ClipboardModule, TuiDemo, TuiLink, TuiWrapperExample1], templateUrl: './index.html', encapsulation: ViewEncapsulation.None, changeDetection, }) export default class Page { protected readonly example1 = { HTML: import('./examples/1/index.html'), LESS: import('./examples/1/index.less'), TypeScript: import('./examples/1/index.ts?raw', {with: {loader: 'text'}}), }; protected readonly mixins = [ '.appearance-hover(@ruleset)', '.appearance-active(@ruleset)', '.appearance-disabled(@ruleset)', '.appearance-focus(@ruleset)', ]; protected readonly routes = DemoRoute; } ``` --- # customization/Internationalization (i18n) - **Package**: `null` - **Type**: null ### Usage Examples #### Language Switcher **Template:** ```html ``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiButton, TuiDropdown} from '@taiga-ui/core'; import {TuiLanguageSwitcherService} from '@taiga-ui/i18n'; import {TuiButtonSelect, TuiChevron, TuiDataListWrapper} from '@taiga-ui/kit'; @Component({ selector: 'tui-i18n-example-1', imports: [ FormsModule, TuiButton, TuiButtonSelect, TuiChevron, TuiDataListWrapper, TuiDropdown, ], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export class TuiI18nExample1 { protected readonly switcher = inject(TuiLanguageSwitcherService); protected language = this.switcher.language; protected readonly languages = [ 'arabic', 'belarusian', 'chinese', 'dutch', 'english', 'french', 'german', 'hebrew', 'italian', 'japanese', 'kazakh', 'korean', 'lithuanian', 'malay', 'polish', 'portuguese', 'russian', 'spanish', 'turkish', 'ukrainian', 'vietnamese', ]; } ``` **LESS:** ```less [tuiButton], tui-data-list-wrapper { text-transform: capitalize; } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiTablePagination} from '@taiga-ui/addon-table'; import {TuiLink} from '@taiga-ui/core'; import {TuiAccordion} from '@taiga-ui/kit'; import {TuiI18nExample1} from './examples/1'; @Component({ imports: [TuiAccordion, TuiDemo, TuiI18nExample1, TuiLink, TuiTablePagination], templateUrl: './index.html', changeDetection, }) export default class Page { protected example = { base: import('./base.md'), dynamic: import('./dynamic.md'), esbuild: import('./esbuild.md'), custom: import('./custom.md'), }; protected readonly example1 = { TypeScript: import('./examples/1/index.ts?raw', {with: {loader: 'text'}}), HTML: import('./examples/1/index.html'), LESS: import('./examples/1/index.less'), }; } ``` --- # customization/Portals - **Package**: `null` - **Type**: null You can easily create your custom portals by extending our abstract classes and put your own portal-host on any layer ### Usage Examples #### Custom portals **Template:** ```html
Hello Taiga UI
``` **TypeScript:** ```ts import {Component, type EmbeddedViewRef, inject, type TemplateRef} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiButton, TuiIcon} from '@taiga-ui/core'; import {CustomPortalService} from './service'; @Component({ selector: 'tui-portals-example-1', imports: [TuiButton, TuiIcon], templateUrl: './index.html', styleUrl: './index.less', changeDetection, }) export class TuiPortalsExample1 { private readonly customPortalService = inject(CustomPortalService); protected templates: Array> = []; protected addTemplate(template: TemplateRef): void { this.templates.push(this.customPortalService.add(template)); } protected removeTemplate(): void { this.templates.pop()?.destroy(); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .wrapper { display: flex; align-items: center; } .template { box-shadow: var(--tui-shadow-small); padding: 0.5rem; margin: 0.5rem; border-radius: 0.25rem; animation: tuiFadeIn var(--tui-duration) var(--tui-duration); animation-fill-mode: backwards; background: var(--tui-chart-categorical-01); font: var(--tui-typography-body-m); } .icon { color: var(--tui-chart-categorical-10); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {TuiPortalsExample1} from './examples/1'; @Component({ imports: [TuiDemo, TuiPortalsExample1], templateUrl: './index.html', changeDetection, }) export default class Page { protected host = import('./examples/setup/create-host.md'); protected service = import('./examples/setup/create-service.md'); protected insert = import('./examples/setup/insert-host.md'); protected readonly example1 = { TypeScript: import('./examples/1/index.ts?raw', {with: {loader: 'text'}}), HTML: import('./examples/1/index.html'), LESS: import('./examples/1/index.less'), 'portal.ts': import('./examples/1/portal.ts?raw', {with: {loader: 'text'}}), 'service.ts': import('./examples/1/service.ts?raw', {with: {loader: 'text'}}), }; } ``` --- # customization/Variables - **Package**: `null` - **Type**: null ### Usage Examples #### Override example **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 {TuiCheckbox, TuiInput, TuiLabel} from '@taiga-ui/core'; import {TuiCardLarge} from '@taiga-ui/layout'; @Component({ selector: 'tui-variables-example-1', imports: [FormsModule, TuiCardLarge, TuiCheckbox, TuiInput, TuiLabel], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export class TuiVariablesExample1 { protected value = ''; protected checkbox = true; } ``` **LESS:** ```less :host { --tui-typography-family-text: 'Comic Sans MS', cursive; --tui-typography-body-m: bold 1rem/1.5rem var(--tui-typography-family-text); --tui-typography-body-s: normal 0.5rem/1.25rem var(--tui-typography-family-text); --tui-background-accent-1: #c86dd7; --tui-background-accent-1-hover: #a456b1; --tui-background-accent-1-pressed: #7f3b8a; --tui-text-primary-on-accent-1: #fff; --tui-radius-s: 0; --tui-radius-m: 0.25rem; --tui-height-l: 4.375rem; } ``` ### TypeScript ```ts import {ClipboardModule} from '@angular/cdk/clipboard'; import {KeyValuePipe} from '@angular/common'; import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiLink} from '@taiga-ui/core'; import {TuiVariablesExample1} from './examples/1'; @Component({ imports: [ClipboardModule, KeyValuePipe, TuiDemo, TuiLink, TuiVariablesExample1], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly example1 = { HTML: import('./examples/1/index.html'), LESS: import('./examples/1/index.less'), }; protected readonly vars: Record = { '--tui-typography-family-display': 'font for headings', '--tui-typography-family-text': 'font for text', '--tui-radius-xs': 'border radius for smallest items (i.e. small checkbox)', '--tui-radius-s': 'border radius for small elements (i.e. tags)', '--tui-radius-m': 'default border radius', '--tui-radius-l': 'border radius for containers (i.e. island, accordion)', '--tui-height-xs': 'smallest elements height (i.e. small button, badges)', '--tui-height-s': 'small elements height (i.e. small inputs)', '--tui-height-m': 'default elements height (i.e. inputs, buttons)', '--tui-height-l': 'large elements height (i.e. inputs, buttons)', '--tui-padding-s': 'padding for inputs with size "s"', '--tui-padding-m': 'padding for inputs with size "m"', '--tui-padding-l': 'padding for inputs with size "l"', '--tui-disabled-opacity': 'amount of transparency for disabled elements', }; protected readonly routes = DemoRoute; } ``` --- # customization/Viewport - **Package**: `CORE` - **Type**: customization TUI_VIEWPORT - define the area relative to which the position constraints will be calculated. Also you can use tuiAsViewport helper instead of token. ### Usage Examples #### Dropdown **Template:** ```html ``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {tuiInjectElement} from '@taiga-ui/cdk'; import {tuiAsViewport, TuiDropdown, TuiRectAccessor} from '@taiga-ui/core'; @Component({ imports: [TuiDropdown], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [tuiAsViewport(Example)], }) export default class Example extends TuiRectAccessor { private readonly el = tuiInjectElement(); public readonly type = 'viewport'; public getClientRect(): DOMRect { return this.el.getBoundingClientRect(); } } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; .dropdowns { position: relative; display: block; block-size: 18.75rem; inline-size: 50%; resize: both; overflow: hidden; outline: 0.125rem dotted var(--tui-border-normal); @media @tui-tablet { inline-size: 100%; } } .t1, .t2, .t3, .t4 { position: absolute; inline-size: 3.125rem; block-size: 3.125rem; background: var(--tui-background-accent-1); } .t1 { inset-block-start: 0.625rem; inset-inline-start: 0.625rem; } .t2 { inset-block-start: 0.625rem; inset-inline-end: 0.625rem; } .t3 { inset-inline-end: 0.625rem; inset-block-end: 0.625rem; } .t4 { inset-inline-start: 0.625rem; inset-block-end: 0.625rem; } .t-centered-axis-xy { .center-all(); } .t-direction { inline-size: max-content; margin-block-end: 1rem; } ``` #### Dropdown and custom portal **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.

``` **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) { } 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
  1. {{ 10728.9 | tuiAmount }}
  2. {{ 10728.9 | tuiAmount: 'RUB' }}
  3. {{ 10728.9 | tuiAmount: 'EUR' }}
  4. {{ 10728.9 | tuiAmount: 'USD' }}
  5. {{ 10728.9 | tuiAmount: 'GBP' }}
  6. {{ -12345.1 | tuiAmount: 'USD' : 'start' }}
  7. {{ 100 | tuiAmount: '£' : 'start' }}
  8. {{ 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
{{ 120.59 | tuiAmount }} {{ 120.59 | tuiDecimal }}
{{ 120.59 | tuiAmount: 'EUR' }} {{ 120.59 | tuiDecimal }}
{{ 120.59 | tuiAmount: 'USD' }} {{ 120.59 | tuiDecimal }}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import { tuiAmountOptionsProvider, TuiAmountPipe, TuiDecimalPipe, } from '@taiga-ui/addon-commerce'; import {TuiNumberFormat} from '@taiga-ui/core'; @Component({ imports: [TuiAmountPipe, TuiDecimalPipe, TuiNumberFormat], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, providers: [tuiAmountOptionsProvider({currencyAlign: 'start'})], }) export default class Example {} ``` **LESS:** ```less :host { font: var(--tui-typography-body-l); } ``` ### 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 examples = ['base', 'format', 'options', 'separate decimal part']; protected readonly routes = DemoRoute; } ``` --- # pipes/Currency - **Package**: `ADDON-COMMERCE` - **Type**: pipes Pipe for transforming number into money. It is usually used with InputNumber ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | currency | `TuiCurrencyVariants` | Currency symbol | ### Usage Examples #### Basic **Template:** ```html

100 {{ 'RUB' | tuiCurrency }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiCurrencyPipe} from '@taiga-ui/addon-commerce'; @Component({ imports: [TuiCurrencyPipe], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### With Textfield **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 {TuiCurrencyPipe} from '@taiga-ui/addon-commerce'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ReactiveFormsModule, TuiCurrencyPipe, TuiInputNumber, TuiTextfield], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly form = new FormGroup({value: new FormControl(100)}); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiCurrencyPipe} from '@taiga-ui/addon-commerce'; import {TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiCurrencyPipe, TuiDemo, TuiInputNumber, TuiTextfield, ], templateUrl: './index.html', changeDetection, }) export default class Page { protected readonly currencyVariants = [null, 826, 840, 'EUR', 'RUB', 'UGX', 'USD']; protected currency = this.currencyVariants[0]; protected readonly control = new FormControl(6432, Validators.required); protected readonly routes = DemoRoute; protected readonly examples = ['Basic', 'With Textfield']; } ``` --- # pipes/Emails - **Package**: `KIT` - **Type**: pipes Pipe for creating autocomplete when entering email addresses ### Usage Examples #### Example **Template:** ```html

@if (default | tuiEmails; as emails) { @if (emails.length) { } }

@if (custom | tuiEmails: emails; as emails) { @if (emails.length) { } }

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiInput, TuiLabel} from '@taiga-ui/core'; import {TuiDataListWrapper, TuiEmailsPipe} from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, ReactiveFormsModule, TuiDataListWrapper, TuiEmailsPipe, TuiInput, TuiLabel, ], templateUrl: './index.html', changeDetection, }) export default class Example { protected default = ''; protected custom = ''; protected readonly emails = ['google.com', 'github.com', 'taiga-ui.dev']; } ``` ### 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 {} ``` ### LESS ```less .cell { font-size: 1rem; padding: 1rem; } .row:nth-child(odd) { background: var(--tui-background-neutral-1); } .table { inline-size: 31.25rem; border: 1px solid var(--tui-border-normal); } ``` --- # pipes/Filter - **Package**: `CDK` - **Type**: pipes Pipe for filtering an array ### Usage Examples #### Usage **Template:** ```html @for (item of items | tuiFilter: matcher : 300; track item) { }
Name Sum, $
{{ item.name }} {{ item.price }}
``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiTable} from '@taiga-ui/addon-table'; import {TuiFilterPipe} from '@taiga-ui/cdk'; export interface Item { readonly name: string; readonly price: number; } @Component({ imports: [TuiFilterPipe, TuiTable], templateUrl: './index.html', styleUrl: './index.less', encapsulation, changeDetection, }) export default class Example { protected readonly items: readonly Item[] = [ { name: 'Sword', price: 1000, }, { name: 'Axe', price: 100, }, { name: 'Spear', price: 500, }, ]; protected readonly matcher = (item: Item, search: number): boolean => item.price > search; } ``` **LESS:** ```less @import '@taiga-ui/styles/utils'; [tuiTh], [tuiTd] { border: none; } [tuiTd] { font: var(--tui-typography-body-m) !important; } ``` ### 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 {} ``` --- # pipes/FilterByInput - **Package**: `KIT` - **Type**: pipes Pipe for filtering an array by value entered in a textfield ### Usage Examples #### Basic **Template:** ```html
@if (items | tuiFilterByInput; as filtered) { @if (input.value) { } }
``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiFilterByInputPipe, TuiInput, TuiLabel} from '@taiga-ui/core'; import {TuiDataListWrapper} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiDataListWrapper, TuiFilterByInputPipe, TuiInput, TuiLabel, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items = inject('Pythons' as any); protected readonly form = new FormGroup({user: new FormControl('')}); } ``` #### Custom matcher **Template:** ```html
``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {FormControl, FormGroup, 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 {type TuiFilterByInputOptions, TuiFilterByInputPipe} from '@taiga-ui/core'; import { TuiChevron, TuiComboBox, TuiDataListWrapper, TuiStringifyContentPipe, } from '@taiga-ui/kit'; interface User { readonly id: number; readonly name: string; } @Component({ imports: [ ReactiveFormsModule, TuiChevron, TuiComboBox, TuiDataListWrapper, TuiFilterByInputPipe, TuiStringifyContentPipe, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items = inject('Pythons' as any); protected readonly users = [ {id: 42, name: 'John Cleese'}, {id: 0, name: 'Eric Idle'}, {id: 444, name: 'Graham Chapman'}, {id: 222, name: 'Michael Palin'}, {id: 404, name: 'Terry Gilliam'}, ] as unknown as readonly T[]; protected readonly form = new FormGroup({ user: new FormControl(null), user2: new FormControl(null), }); protected readonly stringify = ({name}: T): string => name; protected readonly bySurname: TuiFilterByInputOptions['filter'] = ( items, search, ) => items.filter((name) => name.split(' ').pop()?.toLowerCase().startsWith(search.toLowerCase()), ); protected readonly byId: TuiFilterByInputOptions['filter'] = (users, search) => users.find((x) => String(x.id) === search) ? users : users.filter( (user) => String(user.id).includes(search) || TUI_DEFAULT_MATCHER(user, search, this.stringify), ); } ``` #### Multiselect **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, TuiTextfield} from '@taiga-ui/core'; import { TuiChevron, TuiDataListWrapper, TuiInputChip, TuiMultiSelect, } from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiChevron, TuiDataListWrapper, TuiFilterByInputPipe, TuiInputChip, TuiMultiSelect, TuiTextfield, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items = [ 'Luke Skywalker and a long time ago in a galaxy far, far away..', 'Leia Organa Solo', 'Darth Vader', 'Han Solo', 'Obi-Wan Kenobi', 'Yoda', ]; protected readonly control = new FormControl([this.items[0]]); } ``` #### Async **Template:** ```html
@if (items$ | async | tuiFilterByInput; as filtered) { @if (input.value) { } }
``` **TypeScript:** ```ts import {AsyncPipe} from '@angular/common'; import {Component, inject} from '@angular/core'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiFilterByInputPipe, TuiInput} from '@taiga-ui/core'; import {TuiDataListWrapper} from '@taiga-ui/kit'; import {delay, of, startWith} from 'rxjs'; @Component({ selector: 'example-4', imports: [ AsyncPipe, ReactiveFormsModule, TuiDataListWrapper, TuiFilterByInputPipe, TuiInput, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly items$ = of(inject('Pythons' as any)).pipe( startWith([]), delay(1000), ); protected readonly form = new FormGroup({user: new FormControl('')}); } ``` ### 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 matcher', 'Multiselect', 'Async']; } ``` --- # pipes/Flag - **Package**: `KIT` - **Type**: pipes Pipe for getting source path to image with flag ### Example ```html ``` ### API - Inputs | Property | Type | Description | |----------|-----|----------| | Country code | `TuiCountryIsoCode` | | ### Usage Examples #### Basic **Template:** ```html ``` **TypeScript:** ```ts import {Component, inject} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiCountryIsoCode} from '@taiga-ui/i18n'; import {TUI_COUNTRIES, TuiFlagPipe} from '@taiga-ui/kit'; @Component({ imports: [TuiFlagPipe], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly countryIsoCode: TuiCountryIsoCode = 'AE'; protected readonly countriesNames = inject(TUI_COUNTRIES); } ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {TuiDemo} from '@demo/utils'; import {type TuiCountryIsoCode} from '@taiga-ui/i18n'; import {TuiFlagPipe} from '@taiga-ui/kit'; import {getCountries} from 'libphonenumber-js'; @Component({ imports: [TuiDemo, TuiFlagPipe], templateUrl: './index.html', changeDetection, }) export default class Page { protected countryIsoCodeVariants = getCountries(); protected countryIsoCode: TuiCountryIsoCode = 'FR'; protected get apiCodeDemo(): string { return ``; } } ``` --- # pipes/FormatNumber - **Package**: `CORE` - **Type**: pipes Pipe to format number values to separate thousands Number formatting can be customized by using TUI_NUMBER_FORMAT ### Usage Examples #### Basic **Template:** ```html

Formatted number by default: {{ 10500.33 | tuiFormatNumber }}

Formatted number with custom params: {{ 10500.33 | tuiFormatNumber: {precision: 4, decimalSeparator: '.'} }}

Formatted number with rounding: {{ 10500.334 | tuiFormatNumber: {precision: 2, decimalSeparator: '.', rounding: 'ceil'} }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiFormatNumberPipe} from '@taiga-ui/core'; @Component({ imports: [TuiFormatNumberPipe], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {TuiDocNumberFormat} from '@demo/components/number-format'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {TuiFormatNumberPipe, TuiTextfield} from '@taiga-ui/core'; import {TuiInputNumber} from '@taiga-ui/kit'; @Component({ imports: [ ReactiveFormsModule, TuiDemo, TuiDocNumberFormat, TuiFormatNumberPipe, TuiInputNumber, TuiTextfield, ], templateUrl: './index.html', changeDetection, }) export default class PageComponent { protected readonly routes = DemoRoute; public readonly control = new FormControl(100); } ``` --- # pipes/Mapper - **Package**: `CDK` - **Type**: pipes Pipe to transform a value with a function ### Usage Examples #### Basic **Template:** ```html

Transform 10 into {{ 10 | tuiMapper: mapper : '₽' }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiMapperPipe} from '@taiga-ui/cdk'; @Component({ imports: [TuiMapperPipe], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected readonly mapper = (amount: number, currencySymbol: string): string => `Total: ${amount} ${currencySymbol}`; } ``` #### With array **Template:** ```html

Transform {{ numbers }} into {{ numbers | tuiMapper: mapper : 3 }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {type TuiMapper, TuiMapperPipe} from '@taiga-ui/cdk'; @Component({ imports: [TuiMapperPipe], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected numbers = [1, 2, 3, 4, 5] as const; protected readonly mapper: TuiMapper<[readonly number[], number], number[]> = ( numbers, multiplier, ) => numbers.map((number) => number * multiplier); } ``` ### 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', 'With array']; } ``` --- # pipes/Obfuscate - **Package**: `CDK` - **Type**: pipes Pipe for obfuscating sensitive data ### Usage Examples #### Basic **Template:** ```html

Obfuscate "+7(900)500-40-20" by default:
{{ '+7(900)500-40-20' | tuiObfuscate }}

Obfuscate "2200 4400" with "#":
{{ '2200 4400' | tuiObfuscate: '#' }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {TuiObfuscatePipe} from '@taiga-ui/cdk'; @Component({ imports: [TuiObfuscatePipe], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example {} ``` #### Recipes **Template:** ```html

Obfuscate "Moscow" by recipe 'city':
{{ 'Moscow' | tuiObfuscate: 'city' }}

Obfuscate "+7(900)500-40-20" by recipe 'phone':
{{ '+7(900)500-40-20' | tuiObfuscate: 'phone' }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {tuiObfuscateOptionsProvider, TuiObfuscatePipe} from '@taiga-ui/cdk'; @Component({ imports: [TuiObfuscatePipe], templateUrl: './index.html', encapsulation, changeDetection, providers: [ tuiObfuscateOptionsProvider({ recipes: { city: ({length}) => 'x'.repeat(length), phone: ({length}) => '*'.repeat(length), }, }), ], }) export default class Example {} ``` #### Custom default **Template:** ```html

Obfuscate "+7(900)500-40-20" by default:
{{ '+7(900)500-40-20' | tuiObfuscate }}

Obfuscate "2200 4400" with "x":
{{ '2200 4400' | tuiObfuscate: 'x' }}

``` **TypeScript:** ```ts import {Component} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; import {tuiObfuscateOptionsProvider, TuiObfuscatePipe} from '@taiga-ui/cdk'; @Component({ selector: 'example-3', imports: [TuiObfuscatePipe], templateUrl: './index.html', encapsulation, changeDetection, providers: [ tuiObfuscateOptionsProvider({ default: ({length}, symbol = '#') => symbol.repeat(length), }), ], }) export default class Example {} ``` ### TypeScript ```ts import {Component} from '@angular/core'; import {ReactiveFormsModule} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {DemoRoute} from '@demo/routes'; import {TuiDemo} from '@demo/utils'; import {tuiObfuscateOptionsProvider} from '@taiga-ui/cdk'; import {TuiTextfield} from '@taiga-ui/core'; @Component({ imports: [ReactiveFormsModule, TuiDemo, TuiTextfield], templateUrl: './index.html', changeDetection, providers: [ tuiObfuscateOptionsProvider({recipes: {city: ({length}) => 'x'.repeat(length)}}), ], }) export default class PageComponent { protected readonly routes = DemoRoute; protected readonly examples = ['Basic', 'Recipes', 'Custom default']; } ``` --- # pipes/Stringify - **Package**: `KIT` - **Type**: pipes Pipe that creates TuiStringHandler by given key. ### 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, TuiStringifyContentPipe, TuiStringifyPipe, } from '@taiga-ui/kit'; @Component({ imports: [ FormsModule, TuiChevron, TuiComboBox, TuiDataListWrapper, TuiFilterByInputPipe, TuiStringifyContentPipe, TuiStringifyPipe, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value = null; protected readonly items = [ { name: 'John Cleese', role: 'Black Knight', }, { name: 'Eric Idle', role: 'Dead collector', }, ] as const; } ``` ### 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; } ``` --- # pipes/StringifyContent - **Package**: `KIT` - **Type**: pipes Pipe that turns TuiStringHandler into content that works with $implicit . ### 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, TuiStringifyContentPipe, } from '@taiga-ui/kit'; interface User { readonly name: string; readonly surname: string; } @Component({ imports: [ FormsModule, TuiChevron, TuiComboBox, TuiDataListWrapper, TuiFilterByInputPipe, TuiStringifyContentPipe, ], templateUrl: './index.html', encapsulation, changeDetection, }) export default class Example { protected value = null; protected readonly items = [ { name: 'John', surname: 'Cleese', }, { name: 'Eric', surname: 'Idle', }, ]; protected readonly stringify = ({name, surname}: User): string => `${name} ${surname}`; } ``` ### 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; } ``` ---