diff --git a/packages/mcp/README.md b/packages/mcp/README.md index 51f6d11eb3..f3eaa67328 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -10,17 +10,17 @@ from a checkout of `koobiq/angular-components`. ## Tools -| Tool | Answers | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| `search` | one ranked index over entry points, symbols, members, examples, icons, tokens and guides, in English and Russian | -| `get_component` | inputs under the names a template writes, outputs, selectors, `exportAs` | -| `get_example` | source of one live example from the documentation site | -| `get_icon` | usable attribute value and markup for an icon | -| `get_token` | CSS custom property, value per theme, deprecation status | -| `get_guide` | installation, theming, localization, validation, schematics, smart search, migration — narrowed in three steps, in either language | -| `migrate` | what changed between two versions, which entry points lost API, where the written instructions are | -| `which_component_when` | which component answers a task, and which neighbour would be the wrong choice — with the reason | -| `list_components` | the complete list of documented areas, as ids the other tools accept | +| Tool | Answers | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `search` | one ranked index over entry points, symbols, members, examples, icons, tokens and guides, in English and Russian | +| `get_component` | inputs under the names a template writes, outputs, selectors, `exportAs` | +| `get_example` | source of one live example from the documentation site | +| `get_icon` | usable attribute value and markup for an icon | +| `get_token` | CSS custom property, value per theme, deprecation status | +| `get_guide` | installation, theming, localization, validation, schematics, smart search, migration — narrowed in three steps | +| `migrate` | what changed between two versions, which entry points lost API, where the written instructions are | +| `which_component_when` | which component answers a task, and which neighbour would be the wrong choice — with the reason | +| `list_components` | the complete list of documented areas, as ids the other tools accept | ## Using it on a project diff --git a/packages/mcp/data/guides.json b/packages/mcp/data/guides.json index 49f7de24e4..04b11777e7 100644 --- a/packages/mcp/data/guides.json +++ b/packages/mcp/data/guides.json @@ -1 +1 @@ -[{"id":"angular-20-breaking-changes","title":"Angular 20 breaking changes","lang":"en","since":"21.0.0","lede":"These changes are part of **Koobiq v20.0.0** (2026-05-13) — the move to Angular 20. The step-by-step upgrade scenario is described in the [migration guide](/en/main/migration); below is the full list of breaking changes.","sections":[{"heading":"Angular 20","depth":3,"body":"**Angular 20**. The library has been updated to Angular 20: `requiredAngularVersion` is now `^20.0.0`, and all `peerDependencies` of the published packages target `^20.0.0`. Consumers must upgrade to Angular 20+. The minimum Node.js version is **24.16** and `zone.js` is aligned with Angular 20's peer requirement.\n\n**Code block**. The deprecated `canLoad` input has been renamed to `canDownload`, and `codeFiles` to `files`. The old names still work (marked `@deprecated`) but will be removed in the next major release. The `v20-upgrade` schematic rewrites the template bindings (`[canLoad]` → `[canDownload]`, `[codeFiles]` → `[files]`) automatically and warns about programmatic `.canLoad` / `.codeFiles` access that must be updated by hand.\n\n**Dropdown**. Code that subclassed `KbqDropdownContent` no longer receives `ComponentFactoryResolver` in its constructor (removed in Angular 20).","examples":[]},{"heading":"Tooling","depth":3,"body":"Tooling dependencies have been updated:\n\n| Package | Version |\n| ------------------------- | --------- |\n| ng-packagr | ^20.3.2 |\n| @angular-builders/jest | 20.0.0 |\n| @angular-eslint/\\* | ^20.7.0 |\n| @typescript-eslint/\\* | ^8.60.1 |\n| ESLint | ^9.0.0 |\n| @schematics/angular | 20.3.27 |\n| @angular-devkit/architect | 0.2003.27 |\n\nSeparate **tsconfig.spec.json** files have been added at the root of each library/application, extending the config from the workspace root. The **test.options.tsConfig** paths in angular.json are now resolved relative to the project root (required by the v20 schematic migrations).\n\nThe schematics unit tests have been updated for the **@schematics/angular:application** output from v20 (file names changed from app.component.html → app.html).","examples":[]},{"heading":"Removal of deprecated APIs","depth":3,"body":"Long-deprecated symbols have been removed. For a simplified migration, use `ng update @koobiq/components@20` (invokes the `v20-upgrade` schematic).","examples":[]},{"heading":"Packages","depth":4,"body":"**Navbar IC**. The @koobiq/components/navbar-ic package has been removed entirely — switch to @koobiq/components/navbar (KbqNavbar, KbqNavbarItem, KbqNavbarModule).\n\n**Risk level**. The @koobiq/components/risk-level package has been removed entirely — use @koobiq/components/badge (KbqBadge with `[outline]` and `[badgeColor]`); note that Badge's default density and color enum differ — verify the visual result.\n\n**Form field (experimental)**. The @koobiq/components-experimental/form-field subpackage has been removed — switch to @koobiq/components/form-field; the experimental package was a transitional fork and has now been merged back.\n\n**CDK (Core)**. The @koobiq/cdk package has been removed entirely; its a11y, keycodes, and testing utilities now live in @koobiq/components/core. Remove @koobiq/cdk from package.json and import these symbols from @koobiq/components/core (the v20-upgrade schematic remaps the imports automatically).","examples":[]},{"heading":"Symbols from Core","depth":4,"body":"**Enums and interfaces**. The `AnimationCurves` enum has been removed (use KbqAnimationCurves), the `MeasurementSystem` enum has been removed (use KbqMeasurementSystem), and the `SizeUnitsConfig` interface has been removed (use KbqSizeUnitsLocaleConfiguration). The KbqCommonModule, KBQ_SANITY_CHECKS, and mcSanityChecksFactory symbols are no longer used and have been removed.\n\n**Conversion functions**. The toBoolean() function has been replaced with booleanAttribute from @angular/core. The formatDataSize() function has been replaced with getFormattedSizeParts(); the three-argument overload (value, precision, system) has been replaced with a two-argument one (value, system).\n\n**States and components**. The RdxAccordionItemState symbol has been replaced with KbqAccordionItemState, and KbqCodeFile with KbqCodeBlockFile.\n\n**Tokens and configuration**. The KBQ_SIDEPANEL_WITH_SHADOW token has been removed, and the KbqSidepanelConfig.requiredBackdrop field has also been removed (a single shared backdrop is used).\n\n**Validation**. The KBQ_VALIDATION token and the KbqValidationOptions interface have been removed along with the legacy validation pipeline. The kbqDisableLegacyValidationDirectiveProvider() function — a no-op stub left after KbqValidateDirective was removed — has also been removed in v20.0.0; run `ng update @koobiq/components@20` to automatically strip the calls and import; the schematic also flags the resulting empty `providers: []` arrays for manual cleanup.","examples":[]},{"heading":"Component methods and inputs","depth":4,"body":"**Autocomplete trigger**. The KbqAutocompleteTrigger.openPanel() method has been replaced with open().\n\n**Clamped text**. The KbqClampedText.toggleIsCollapsed() method has been replaced with `toggle()`.\n\n**Divider**. The `KbqDivider.inset` input has been removed.\n\n**Tag list**. All unused fields and methods have been removed: multiple, compareWith, emitOnTagChanges, orientation, selectionModel, tagChanges, setSelectionByValue().\n\n**Tag input**. countOfSymbolsForUpdateWidth and updateInputWidth() have been removed.\n\n**Form field**. The KbqFormField.canShowStepper property has been replaced with `hasStepper` (the stepper is always visible when set).\n\n**App switcher**. On the trigger, KbqAppSwitcherTrigger.apps has been replaced with `sites` taking a single-element array.","examples":[]},{"heading":"Validation","depth":4,"body":"The **KbqValidateDirective** directive has been removed entirely. The new behavior relies solely on **ErrorStateMatcher**. Consumers who relied on the legacy \"show errors only after blur/submit\" pattern should explicitly wire up **ShowOnSubmitErrorStateMatcher** (or a similar one) via the **errorStateMatcher** inputs or the **ErrorStateMatcher** provider. The \"lazy validation\" behavior is no longer available (not showing required until submit).","examples":[]},{"heading":"File upload validation API","depth":4,"body":"- The KbqInputFile and KbqInputFileLabel interfaces have been removed, and the KbqFileValidatorFn type has also been removed.\n- The isCorrectExtension() function has been replaced with FileValidators.isCorrectExtension (a ValidatorFn).\n- `KbqMultipleFileUploadComponent`: `errors`, `customValidation`, and `hasErrors` have been removed — use `FormControl.errors` and `FormControl` validators.\n- KbqSingleFileUploadComponent: errors and customValidation have been removed — same as above.","examples":[]},{"heading":"Modal API","depth":4,"body":"**ModalOptions.kbqComponentParams** is replaced with the **data** field plus **inject(KBQ_MODAL_DATA)** in the child component. The @Input KbqModalComponent.kbqComponentParams has been removed.","examples":[]},{"heading":"Filter bar API","depth":4,"body":"**Filter bar**. The KbqFilters.onSaveAsNew output has been replaced with `onSave` carrying `status === 'newFilter'`. The KbqFilterBarSearch component `` has been removed — use ``; note that kbq-search-expandable requires a FormControl/NgModel binding.","examples":[]},{"heading":"Form field directives","depth":4,"body":"The KbqDatepickerToggle component (``) has been removed — use `` (KbqDatepickerToggleIconComponent).\n\nThe KbqFormFieldWithoutBorders directive (``) has been removed — use the noBorders input on KbqFormField: ``.","examples":[]},{"heading":"Tooltip modifier triggers","depth":4,"body":"The KbqWarningTooltipTrigger (`[kbqWarningTooltip]`) and KbqExtendedTooltipTrigger (`[kbqExtendedTooltip]`) directives have been removed. Use the base `[kbqTooltip]` directive with the new public `kbqTooltipModifier` input:\n\n```html\n\n
\n\n
\n```\n\nFor the extended variant, `[kbqTooltipHeader]` is now also available on the base trigger:\n\n```html\n\n\n\n\n```\n\n**Datepicker, Timepicker**. The KbqDatepickerInput.kbqValidationTooltip and KbqTimepicker.kbqValidationTooltip setters now accept KbqTooltipTrigger (the base class) instead of KbqWarningTooltipTrigger.","examples":[]},{"heading":"Migration","depth":3,"body":"**V20 upgrade**. Consumers can run the automatic migration with the `ng update @koobiq/components@20` command. It invokes the `v20-upgrade` schematic, which rewrites your code in place.\n\n**Imports and packages**. Imports from @koobiq/components/navbar-ic, risk-level, and components-experimental/form-field are remapped to the surviving packages (navbar, badge, components/form-field).\n\n**Identifiers in .ts files**. The following identifiers have been renamed:\n\n| Old name | New name |\n| ------------------------- | -------------------------------- |\n| KbqNavbarIc\\* | KbqNavbar\\* |\n| KbqRiskLevel\\* | KbqBadge\\* |\n| KbqWarningTooltipTrigger | KbqTooltipTrigger |\n| KbqExtendedTooltipTrigger | KbqTooltipTrigger |\n| KbqDatepickerToggle | KbqDatepickerToggleIconComponent |\n| KbqFilterBarSearch | KbqSearchExpandable |\n| RdxAccordionItemState | KbqAccordionItemState |\n| KbqCodeFile | KbqCodeBlockFile |\n| AnimationCurves | KbqAnimationCurves |\n| MeasurementSystem | KbqMeasurementSystem |\n| SizeUnitsConfig | KbqSizeUnitsLocaleConfiguration |\n| KbqFormFieldRef | KbqFormField |\n| DropdownPositionX/Y | KbqDropdownPositionX/Y |\n\n**Tokens and functions**. Updated: toBoolean( → booleanAttribute(, isCorrectExtension( → FileValidators.isCorrectExtension(, formatDataSize( → getFormattedSizeParts(, kbqComponentParams: → data:;\n\nRemoved tokens are stripped from imports: KBQ_VALIDATION, KBQ_SANITY_CHECKS, KBQ_SIDEPANEL_WITH_SHADOW\n\n**Instance methods**. Updated: .openPanel( → .open(, .toggleIsCollapsed( → .toggle(, .focusViaKeyboard( → .focus(.\n\n**Selectors in templates**. Updated: `` → ``, `` → ``, `` → ``, `` → ``.\n\n**Attributes in templates**. Updated: `kbqFormFieldWithoutBorders` → `noBorders`, `[kbqWarningTooltip]` → `kbqTooltipModifier=\"warning\" [kbqTooltip]`, `[kbqExtendedTooltip]` → `kbqTooltipModifier=\"extended\" [kbqTooltip]`, the template ref `=\"kbqWarningTooltip\"` → `=\"kbqTooltip\"`.\n\n**CSS classes in SCSS**. Updated: `.kbq-risk-level` → `.kbq-badge`, `.kbq-navbar-ic` → `.kbq-navbar`, etc.\n\n**Manual fixes**. The schematic emits warnings for structural changes that cannot be fixed automatically and safely: `(onSaveAsNew)` listeners on `` must be moved to `(onSave)` with branching on `$event.status === 'newFilter'`; the `[customValidation]` / `[errors]` attributes on file-upload components must be replaced with `FormControl` validators; the `[apps]` attribute on `\n\n\n```\n\n**Datepicker, Timepicker**. Сеттеры KbqDatepickerInput.kbqValidationTooltip и KbqTimepicker.kbqValidationTooltip теперь принимают KbqTooltipTrigger (базовый класс) вместо KbqWarningTooltipTrigger.","examples":[]},{"heading":"Миграция","depth":3,"body":"**V20 upgrade**. Потребители могут запустить автоматическую миграцию с помощью команды `ng update @koobiq/components@20`. Она вызывает схематик `v20-upgrade`, который переписывает ваш код на месте.\n\n**Импорты и пакеты**. Импорты из @koobiq/components/navbar-ic, risk-level и components-experimental/form-field переназначаются на сохранившиеся пакеты (navbar, badge, components/form-field).\n\n**Идентификаторы в .ts файлах**. Переименованы идентификаторы:\n\n| Старое имя | Новое имя |\n| ------------------------- | -------------------------------- |\n| KbqNavbarIc\\* | KbqNavbar\\* |\n| KbqRiskLevel\\* | KbqBadge\\* |\n| KbqWarningTooltipTrigger | KbqTooltipTrigger |\n| KbqExtendedTooltipTrigger | KbqTooltipTrigger |\n| KbqDatepickerToggle | KbqDatepickerToggleIconComponent |\n| KbqFilterBarSearch | KbqSearchExpandable |\n| RdxAccordionItemState | KbqAccordionItemState |\n| KbqCodeFile | KbqCodeBlockFile |\n| AnimationCurves | KbqAnimationCurves |\n| MeasurementSystem | KbqMeasurementSystem |\n| SizeUnitsConfig | KbqSizeUnitsLocaleConfiguration |\n| KbqFormFieldRef | KbqFormField |\n| DropdownPositionX/Y | KbqDropdownPositionX/Y |\n\n**Токены и функции**. Обновляются: toBoolean( → booleanAttribute(, isCorrectExtension( → FileValidators.isCorrectExtension(, formatDataSize( → getFormattedSizeParts(, kbqComponentParams: → data:;\n\nУбираются из импортов удаленные токены: KBQ_VALIDATION, KBQ_SANITY_CHECKS, KBQ_SIDEPANEL_WITH_SHADOW\n\n**Методы инстансов**. Обновляются: .openPanel( → .open(, .toggleIsCollapsed( → .toggle(, .focusViaKeyboard( → .focus(.\n\n**Селекторы в шаблонах**. Обновляются: `` → ``, `` → ``, `` → ``, `` → ``.\n\n**Атрибуты в шаблонах**. Обновляются: `kbqFormFieldWithoutBorders` → `noBorders`, `[kbqWarningTooltip]` → `kbqTooltipModifier=\"warning\" [kbqTooltip]`, `[kbqExtendedTooltip]` → `kbqTooltipModifier=\"extended\" [kbqTooltip]`, template-ref `=\"kbqWarningTooltip\"` → `=\"kbqTooltip\"`.\n\n**CSS классы в SCSS**. Обновляются: `.kbq-risk-level` → `.kbq-badge`, `.kbq-navbar-ic` → `.kbq-navbar` и т. д.\n\n**Ручные исправления**. Схематик выводит предупреждения для структурных изменений, которые нельзя безопасно исправить автоматически: слушатели `(onSaveAsNew)` у `` нужно перевести на `(onSave)` с ветвлением по `$event.status === 'newFilter'`; атрибуты `[customValidation]` / `[errors]` у file-upload компонентов нужно заменить валидаторами `FormControl`; атрибут `[apps]` у `\n `\n})\nexport class AppComponent {}\n```","examples":[]}]},{"id":"installation","title":"Installation","lang":"ru","since":"21.0.0","lede":"В этом руководстве описана настройка Angular-проекта для использования `@koobiq/components`.","sections":[{"heading":"Установка зависимостей","depth":3,"body":"Установка с помощью [Angular CLI](https://angular.dev/cli/add) — рекомендуемый способ, так как\nAngular-пакеты будут установлены той версии, на которой уже находится приложение:\n\n```bash\nng add @koobiq/components\n```\n\nРучная установка. Сначала установите `@angular/cdk` той версии, которая уже стоит в `package.json`\nдля `@angular/core`:\n\n```bash\nnpm install @angular/cdk@^21.2.0\n```\n\nЗатем установите библиотеку и остальные её зависимости:\n\n```bash\nnpm install @koobiq/components overlayscrollbars@2.7.3 @koobiq/icons @koobiq/design-tokens@^4 @koobiq/angular-luxon-adapter @koobiq/luxon-date-adapter @koobiq/date-adapter @koobiq/date-formatter luxon\n```\n\nВерсия `overlayscrollbars` зафиксирована, а не задана кареткой: скроллбар рассчитан на этот\nконкретный релиз, и ту же версию устанавливает `ng add`. `@koobiq/design-tokens` ограничен 4-й мажорной\nверсией: под неё собрана эта версия библиотеки.\n\n`@koobiq/angular-luxon-adapter` (или `@koobiq/angular-moment-adapter`) нужен только при использовании\nкомпонентов для работы с датами — [datepicker](/ru/components/datepicker),\n[timepicker](/ru/components/timepicker) или [filter-bar](/ru/components/filter-bar). Каждый из них —\nобёртка над базовым адаптером, который нужно установить рядом: `@koobiq/luxon-date-adapter` или\n`@koobiq/moment-date-adapter`. npm добавляет базовый пакет сам, Yarn и pnpm — нет, поэтому указывайте\nего явно. Установите `marked`, если используете [markdown](/ru/components/markdown), `highlight.js`,\nесли используете [code-block](/ru/components/code-block), и `@angular/router`, если используете\n[breadcrumbs](/ru/components/breadcrumbs).","examples":[]},{"heading":"Обнаружение изменений","depth":3,"body":"Компонентам не нужен zone.js. Они работают в приложении без zone.js, как принято по умолчанию с\nAngular 21, и продолжают работать в приложении, которое загружает zone.js и предоставляет\n`provideZoneChangeDetection()`. Настраивать для них ничего не нужно. Чтобы перевести существующее\nприложение на работу без zone.js, уберите `provideZoneChangeDetection()` из его провайдеров\n(`ng update` до Angular 21 добавляет его в запуск, где нет провайдера обнаружения изменений) и\n`zone.js` из его `polyfills`. `provideZonelessChangeDetection()` лишь явно задает поведение по\nумолчанию: рядом с `provideZoneChangeDetection()` он выводит предупреждение NG0408, а какой из них\nприменится, решает порядок провайдеров.\n\n```typescript\n\nbootstrapApplication(AppComponent, {\n // Без provideZoneChangeDetection(); этот провайдер необязателен.\n providers: [provideZonelessChangeDetection()]\n});\n```","examples":[]},{"heading":"Анимации","depth":3,"body":"Анимации компонентов работают на CSS и не требуют настройки: не нужны ни `@angular/animations`, ни\n`provideAnimations()`. Движение отключается, когда операционная система просит сократить анимацию\n(`prefers-reduced-motion: reduce`). Чтобы отключить его во всем приложении, например в end-to-end\nтестах, предоставьте `KBQ_ANIMATIONS_CONFIG`:\n\n```typescript\n\nbootstrapApplication(AppComponent, {\n providers: [{ provide: KBQ_ANIMATIONS_CONFIG, useValue: { animationsDisabled: true } }]\n});\n```","examples":[]},{"heading":"Настройка стилей","depth":3,"body":"После установки необходимо подключить стили библиотеки. Добавьте следующие файлы в массив `styles` вашего файла `angular.json`:\n\n```json\n\"styles\": [\n \"node_modules/@koobiq/icons/fonts/kbq-icons.css\",\n \"node_modules/@koobiq/design-tokens/web/css-tokens.css\",\n \"node_modules/@koobiq/design-tokens/web/css-tokens-light.css\",\n \"node_modules/@koobiq/components/prebuilt-themes/theme.css\",\n \"src/styles.css\"\n]\n```\n\nТокены code-block, scrollbar и skeleton поставляются отдельными файлами, подключаемыми по желанию.\nПри обычном подключении делать ничего не нужно: их значения уже входят в `prebuilt-themes/theme.css`,\nфайл собирается вместе с библиотекой. Отдельно эти файлы нужны только если вы собираете стили\nбиблиотеки сами из SCSS-исходников:\n\n```scss\n@use '@koobiq/design-tokens/web/component-tokens';\n@use '@koobiq/design-tokens/web/component-tokens-light';\n@use '@koobiq/design-tokens/web/component-tokens-dark';\n```\n\nЧтобы поменять значения по умолчанию, подключать файлы не нужно — просто задайте нужные\nCSS-переменные в своих стилях, см. [руководство по темам](/ru/main/theming/overview).","examples":[]},{"heading":"Настройка темы","depth":3,"body":"Добавьте класс темы к элементу `` в файле `index.html`:\n\n```html\n\n \n\n```\n\nПодробнее о настройке и переключении темы читайте в разделе [темизация](/ru/main/theming).","examples":[]},{"heading":"Настройка типографики","depth":3,"body":"Для корректного отображения компонентов рекомендуется подключить шрифт [Inter](https://github.com/rsms/inter).\n\nПодробнее читайте в разделе [типографика](/ru/main/typography).","examples":[]},{"heading":"Использование компонента","depth":3,"body":"Добавьте компонент в ваше приложение, чтобы убедиться, что всё работает корректно.\n\n```typescript\n\n@Component({\n imports: [KbqButtonModule, KbqIconModule],\n template: `\n \n `\n})\nexport class AppComponent {}\n```","examples":[]}]},{"id":"localization","title":"Localization","lang":"en","since":"21.0.0","lede":"Koobiq components render some strings of their own — the filters menu of the filter bar, the tooltips of\nthe code block, the accessible names of every icon-only button, the placeholder of a datepicker, and so on.\nAll of them come from one place: `KbqLocaleService`.\n\nThe library ships five locales: `en-US`, `es-LA`, `pt-BR`, `ru-RU` and `tk-TM`.\n\nThe application's data is never translated. Option labels, filter values, table cells and everything else\nthe application passes in is rendered exactly as given.\n\nTranslating those strings is the application's own job: Koobiq neither requires nor ships a library for it.\nWhen one of them has to contain a Koobiq component — a link, a button, a dropdown — embed it with\n[dynamic-translation](/en/components/dynamic-translation).","sections":[{"heading":"Setting the locale","depth":3,"body":"`KbqLocaleService` is `providedIn: 'root'`, but the components read it through the `KBQ_LOCALE_SERVICE`\ntoken, which has no factory. Nothing is localized until it is provided:\n\n```ts\n\nbootstrapApplication(AppComponent, {\n providers: [kbqLocaleServiceProvider()]\n});\n```\n\nWithout that provider every component falls back to its own built-in `ru-RU` defaults, and switching the\nlocale at runtime does nothing. The placeholder of the datepicker is the exception: it names the format its input\nparses — the shipped one of the date adapter's locale (`KBQ_DATE_LOCALE`, Angular's `LOCALE_ID` unless\nprovided), or the format itself, such as `yyyy/mm/dd`, for a locale the library ships no strings for and for a\nformat set through `KBQ_DATE_FORMATS`.\n\nUse `kbqLocaleServiceProvider()` rather than `{ provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }`:\n`useClass` builds a **second** instance, independent of the `providedIn: 'root'` one, so a `setLocale()`\ncalled on the instance that `inject(KbqLocaleService)` hands out moves nothing that is rendered. The helper\nprovides the service next to the token, and both references resolve to one instance.\n\nThere are three ways to control which locale is active:\n\n- **`KBQ_DEFAULT_LOCALE_ID`** is the fallback, `ru-RU`. It is a plain exported constant, not an injection\n token — it cannot be provided, only read.\n- **`KBQ_LOCALE_ID`**, set through `kbqLocaleIDProvider(id)`, fixes the locale once, when `KbqLocaleService`\n is constructed. It must sit in the **same `providers` array** as the service itself, because the service\n reads the token from the injector that created it.\n- **`setLocale(id)`** changes the locale at runtime.\n\n```ts\nproviders: [kbqLocaleIDProvider('en-US'), kbqLocaleServiceProvider()];\n```\n\nThe same pair on a component's `providers` scopes the locale to that component's subtree, while the rest of\nthe application keeps the locale it was given.\n\nReading the active locale:\n\n```ts\nreadonly localeService = kbqInjectLocaleService();\n\nreadonly currentLocale = this.localeService.localeId; // Signal\nreadonly localeData = this.localeService.data; // Signal\nreadonly available = this.localeService.items; // Signal, for a locale picker\n```\n\n`kbqInjectLocaleService()` resolves `KBQ_LOCALE_SERVICE`, never the class. Pass `{ optional: true }` for code\nthat has to keep working in an application that provided no locale service.\n\n`changes` is a `BehaviorSubject` of the active locale id, and it remains the way for code outside a template\nto observe a locale change. The `id` and `current` getters, on the other hand, are deprecated in favour of\nthe `localeId` and `data` signals. A signal read from a template registers on the reading view, so a runtime\n`setLocale()` reaches `OnPush` children that an observable subscribed in the parent could not.","examples":[]},{"heading":"What a locale is made of","depth":3,"body":"A locale is a set of sections, one per area of strings. The section is the unit of overriding: its name\nappears in the locale data, in `getParams()` and in `[localeOverrides]` alike.\n\n| Section | What it translates | Package |\n| -------------------- | ---------------------------------------------------------------------------------- | --------------------- |\n| `a11y` | accessible names of the icon-only buttons and regions the library renders itself | `core` |\n| `select` | the \"select all\" label and the counter of values that did not fit | `core` |\n| `popoverConfirm` | the question and the button of the confirmation popover | `core` |\n| `formatters` | separators and abbreviations of `kbqNumber`, `kbqTableNumber` and `kbqRoundNumber` | `core` |\n| `sizeUnits` | the size units of `kbqDataSize` | `core` |\n| `input` | the separators of the number input | `input` |\n| `datepicker` | the placeholder and the format of the date field | `datepicker` |\n| `timepicker` | the placeholders of the time field | `timepicker` |\n| `timezone` | the search placeholder of the timezone select | `timezone` |\n| `timeRange` | the title and the editor of the time range | `time-range` |\n| `codeBlock` | the tooltips and labels of the code block buttons | `code-block` |\n| `fileUpload` | the text of the single and multiple upload areas | `file-upload` |\n| `filterBar` | search, reset and the saved filters menu | `filter-bar` |\n| `actionsPanel` | the close tooltip of the actions panel | `actions-panel` |\n| `clampedText` | the expand and collapse labels of clamped text | `clamped-text` |\n| `navbar` | the tooltips of the vertical navbar toggle | `navbar` |\n| `searchExpandable` | the tooltip and the placeholder of the expandable search | `search-expandable` |\n| `appSwitcher` | the search and the header of the app switcher | `app-switcher` |\n| `notificationCenter` | the labels of the notification center | `notification-center` |\n\nThe full contract of a section is its `KbqLocaleConfiguration` type, where every key is documented.\nIts token and its provider are named mechanically: `KBQ__LOCALE_CONFIGURATION` and\n`kbqLocaleConfigurationProvider()`.","examples":[]},{"heading":"Overriding the strings of one component","depth":3,"body":"A section provider overrides only the keys it is given — everything else keeps its default:\n\n```ts\n\nproviders: [kbqCodeBlockLocaleConfigurationProvider({ copyTooltip: 'Copy the snippet' })];\n```\n\nBecause these providers are element-injector friendly, providing one on a component scopes the override to\nthat component's subtree.\n\n\n\nEach helper ships from its own component's package. The exception is `kbqSelectLocaleConfigurationProvider`,\nwhich ships from `@koobiq/components/core`: the `select` section is rendered by four packages that do not\ndepend on one another — `kbq-select`, `kbq-tree-select`, `kbq-tree-selection` and `kbq-list-selection`.\n\nAn override is applied on top of whatever is active — the strings of the active locale when the application\nprovides a service, the token's defaults otherwise. So the keys it names stay pinned across a runtime\n`setLocale()`, while every key it leaves out follows the locale. Override a whole section to stop it\nfollowing the locale entirely; register your own locale (see below) to make the override switch along with\nthe others.","examples":["code-block-with-custom-locale-configuration"]},{"heading":"Overriding the strings of one instance","depth":3,"body":"A provider can only be attached to an injector, so scoping one to a single component means owning a\ncomponent boundary. Every localized component also takes its strings as a template binding:\n\n```html\n\n```\n\nThe value is keyed by locale section — the same shape `addLocale()` and `KBQ_LOCALE_DATA` accept — so one\nbinding can also cover the accessible names the component renders through its own children:\n\n```html\n\n```\n\n\n\nTo scope an override to a whole region rather than one component, put `KbqLocaleOverridesDirective` on any\nelement of the application. Everything rendered inside — including a panel that opens in the overlay\ncontainer — resolves against that element:\n\n```ts\n\n```\n\n```html\n
\n \n \n
\n```\n\nThe two names are not interchangeable. `[localeOverrides]` is the input a Koobiq component declares;\n`[kbqLocaleOverrides]` is the directive's own selector, for the application's elements. Writing the selector\non a component that already carries the directive matches it twice on one element, which Angular rejects\nwith `NG0309`.\n\nSources are applied from the most general to the most local:\n\n1. the configuration token's defaults;\n2. the active locale;\n3. `kbqLocaleConfigurationProvider()`, outermost injector first;\n4. `KbqLocaleOverridesDirective`, outermost element first;\n5. the component's own `[localeOverrides]`.\n\nSteps 3 to 5 only override the keys they mention. Step 2 is the exception: the active locale replaces the\ntoken's defaults outright rather than merging over them, so a value provided for a configuration token is\ndropped as soon as a locale service exists. Override the section instead of providing the token.\n\nA pipe is not an element, so `kbqNumber`, `kbqRoundNumber` and `kbqDataSize` have no `[localeOverrides]` of\ntheir own. A carrier on an ancestor does reach them: a pipe resolves its section from the same injector any\nother reader would.","examples":["select-locale-configuration"]},{"heading":"Registering your own locale","depth":3,"body":"`addLocale()` accepts partial data — every section, and every key within a section, is optional. Whatever is\nleft out is completed from the shipped locale of the same id, or from `KBQ_DEFAULT_LOCALE_ID` when the id is\nnew. `getParams()` therefore always returns a complete section, whatever was registered:\n\n```ts\nlocaleService.addLocale('en-GB', {\n select: { selectAll: 'Select everything' },\n a11y: { close: 'Dismiss' }\n});\n```\n\nThe parameter is typed as `KbqPartialLocaleData`, so a misspelled section or key is a compile error rather\nthan a string that silently never appears.\n\nThe same data can be provided up front through `KBQ_LOCALE_DATA`. The token is read once, by the\n`KbqLocaleService` constructor, so its provider belongs in the same `providers` array as the service:\n\n```ts\nconst localeData = {\n 'en-GB': { select: { selectAll: 'Select everything' } }\n} satisfies KbqLocaleDataInput;\n\nproviders: [{ provide: KBQ_LOCALE_DATA, useValue: localeData }, kbqLocaleServiceProvider()];\n```\n\nThe `satisfies` is load-bearing: `useValue` is declared as `any`, and without it the same typo goes through\nsilently.\n\nA locale added this way is registered, but it is not added to `items`, which is the list a locale picker\nreads. Spell `items` out in full, shipped locales included, if the new locale has to be selectable.","examples":[]},{"heading":"Reading a section yourself","depth":3,"body":"```ts\nconst { selectAll } = localeService.getParams('select'); // KbqSelectLocaleConfiguration\nconst select = localeService.params('select'); // Signal\n```\n\nThe section name is checked against `KbqLocaleSection`, and the return type follows from it.\n\nBoth of those read the locale alone. A component that should honour the overrides above as well — the\nprovider helpers and `[localeOverrides]` — carries the directive and reads through it:\n\n```ts\n\n@Component({\n selector: 'my-widget',\n hostDirectives: [\n { directive: KbqLocaleOverridesDirective, inputs: ['kbqLocaleOverrides: localeOverrides'] }\n ]\n})\nexport class MyWidget {\n protected readonly localeConfiguration = inject(KbqLocaleOverridesDirective, { self: true }).read(\n 'select',\n KBQ_SELECT_LOCALE_CONFIGURATION\n );\n}\n```\n\n`{ self: true }` is required here: with `{ host: true }` Angular walks up to the enclosing component's host\nelement and returns **its** carrier, so the component reads someone else's overrides instead of its own. The\n`hostDirectives` entry has to be spelled out in every component — extracted into a const it fails with\n`NG1010` when used from another entry point.\n\nName the field `localeConfiguration` — the library-wide name for the resolved strings a component reads,\nkept apart from `[localeOverrides]`, which writes a partial. A component that reads a second section\nqualifies it, the way `a11yLocaleConfiguration` does.\n\n`read()` returns a signal, so `setLocale()` reaches the template on its own, and it merges every source in\nthe order listed above. Reading through the carrier is what makes `[localeOverrides]` work on the component:\nthe two cannot come apart. Where there is no element to carry the directive — in a pipe, or in content\ncreated programmatically — `kbqInjectLocaleConfiguration(section, token)` resolves the same sources from the\ninjector instead.","examples":[]},{"heading":"Dates and numbers","depth":3,"body":"Date adapters and the number pipes follow the same service, but they need their own providers.\n`KbqLocaleServiceModule` — pulled in by the date adapter modules — provides it itself.\n\n\n\nTo scope a locale to a subtree that contains dates, provide the adapter and the formatter in the `providers`\nof the same component as `kbqLocaleIDProvider`. Importing the module will not do: `imports: [KbqLuxonDateModule]`\nputs the adapter in the environment injector, where it resolves the root locale service, and month names\nstay in the language of the whole application rather than that of the subtree.","examples":["number-formatter-locale"]}]},{"id":"localization","title":"Localization","lang":"ru","since":"21.0.0","lede":"Компоненты Koobiq выводят собственные строки — меню фильтров в filter bar, подсказки в code block,\nдоступные имена всех кнопок-иконок, плейсхолдер поля даты и так далее. Все они приходят из одного места:\n`KbqLocaleService`.\n\nДоступные идентификаторы локали: `en-US`, `es-LA`, `pt-BR`, `ru-RU` и `tk-TM`.\n\nДанные приложения не переводятся. Названия опций, значения фильтров, ячейки таблиц и всё, что приложение\nпередаёт в компонент, выводится без изменений.\n\nПеревод собственных строк — задача приложения: Koobiq не требует и не поставляет для этого библиотеку. Если\nпереведённая строка должна содержать компонент Koobiq — ссылку, кнопку, выпадающий список, — встройте его\nчерез [dynamic-translation](/ru/components/dynamic-translation).","sections":[{"heading":"Подключение локали","depth":3,"body":"`KbqLocaleService` объявлен как `providedIn: 'root'`, но компоненты читают его через токен\n`KBQ_LOCALE_SERVICE`, у которого нет фабрики. Пока он не предоставлен, локализация не работает:\n\n```ts\n\nbootstrapApplication(AppComponent, {\n providers: [kbqLocaleServiceProvider()]\n});\n```\n\nБез этого провайдера каждый компонент использует собственные значения по умолчанию (`ru-RU`), а смена\nлокали во время работы приложения ничего не меняет. Исключение — плейсхолдер поля даты: он называет формат, который\nразбирает поле, — поставляемый для локали адаптера дат (`KBQ_DATE_LOCALE`, если токен не предоставлен —\n`LOCALE_ID` Angular) или сам формат, например `yyyy/mm/dd`, для локали, строк которой библиотека не\nпоставляет, и для формата, заданного через `KBQ_DATE_FORMATS`.\n\nИспользуйте `kbqLocaleServiceProvider()`, а не `{ provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }`:\n`useClass` создаёт **второй** экземпляр, независимый от `providedIn: 'root'`, поэтому `setLocale()`, вызванный\nна экземпляре из `inject(KbqLocaleService)`, не меняет ничего из уже отрисованного. Эта утилита объявляет\nсервис рядом с токеном, и обе ссылки указывают на один экземпляр.\n\nУправлять активной локалью можно тремя способами:\n\n- **`KBQ_DEFAULT_LOCALE_ID`** — значение по умолчанию, `ru-RU`. Это обычная экспортируемая константа, а не\n injection token: её нельзя предоставить, только прочитать.\n- **`KBQ_LOCALE_ID`**, который задаётся через `kbqLocaleIDProvider(id)`, фиксирует локаль один раз, в момент\n создания `KbqLocaleService`. Провайдер должен лежать в **том же массиве `providers`**, что и сам сервис,\n потому что сервис читает токен из создавшего инжектора.\n- **`setLocale(id)`** меняет локаль во время работы приложения.\n\n```ts\nproviders: [kbqLocaleIDProvider('en-US'), kbqLocaleServiceProvider()];\n```\n\nТа же пара в `providers` компонента ограничивает локаль его поддеревом, а остальное приложение сохраняет\nсвою локаль.\n\nЧтение активной локали:\n\n```ts\nreadonly localeService = kbqInjectLocaleService();\n\nreadonly currentLocale = this.localeService.localeId; // Signal\nreadonly localeData = this.localeService.data; // Signal\nreadonly available = this.localeService.items; // Signal, для выбора локали\n```\n\n`kbqInjectLocaleService()` читает токен `KBQ_LOCALE_SERVICE`, а не класс. Передайте `{ optional: true }` в коде,\nкоторый должен работать и в приложении без сервиса локали.\n\n`changes` — `BehaviorSubject` с идентификатором активной локали; через него о смене локали узнаёт код за\nпределами шаблона. Геттеры `id` и `current`, напротив, устарели в пользу сигналов `localeId` и `data`.\nСигнал, прочитанный в шаблоне, регистрируется на читающем представлении, поэтому `setLocale()` во время\nработы приложения доходит до `OnPush`-потомков, чего подписка в родителе сделать не может.","examples":[]},{"heading":"Из чего состоит локаль","depth":3,"body":"Локаль — это набор секций, по одной на область строк. Секция и есть единица переопределения: её имя стоит\nи в данных локали, и в `getParams()`, и в `[localeOverrides]`.\n\n| Секция | Что переводит | Пакет |\n| -------------------- | ------------------------------------------------------------------------- | --------------------- |\n| `a11y` | доступные имена кнопок-иконок и областей, которые библиотека рисует сама | `core` |\n| `select` | подпись «Выбрать все» и счётчик скрытых значений | `core` |\n| `popoverConfirm` | вопрос и кнопку подтверждающего поповера | `core` |\n| `formatters` | разделители и сокращения `kbqNumber`, `kbqTableNumber` и `kbqRoundNumber` | `core` |\n| `sizeUnits` | единицы размера `kbqDataSize` | `core` |\n| `input` | разделители числового поля ввода | `input` |\n| `datepicker` | плейсхолдер и формат поля даты | `datepicker` |\n| `timepicker` | плейсхолдеры поля времени | `timepicker` |\n| `timezone` | плейсхолдер поиска часового пояса | `timezone` |\n| `timeRange` | заголовок и редактор диапазона времени | `time-range` |\n| `codeBlock` | подсказки и подписи кнопок блока кода | `code-block` |\n| `fileUpload` | тексты областей загрузки одного файла и нескольких | `file-upload` |\n| `filterBar` | поиск, сброс и меню сохранённых фильтров | `filter-bar` |\n| `actionsPanel` | подсказку закрытия панели действий | `actions-panel` |\n| `clampedText` | подписи «Развернуть» и «Свернуть» усечённого текста | `clamped-text` |\n| `navbar` | подсказки переключателя вертикального navbar | `navbar` |\n| `searchExpandable` | подсказку и плейсхолдер раскрывающегося поиска | `search-expandable` |\n| `appSwitcher` | поиск и заголовок переключателя приложений | `app-switcher` |\n| `notificationCenter` | подписи центра уведомлений | `notification-center` |\n\nПолный контракт секции описан её типом `Kbq<Секция>LocaleConfiguration`, где задокументирован каждый ключ.\nТокен секции и её провайдер называются механически: `KBQ_<СЕКЦИЯ>_LOCALE_CONFIGURATION` и\n`kbq<Секция>LocaleConfigurationProvider()`.","examples":[]},{"heading":"Переопределение строк одного компонента","depth":3,"body":"Провайдер секции переопределяет только переданные ключи — остальные сохраняют значения по умолчанию:\n\n```ts\n\nproviders: [kbqCodeBlockLocaleConfigurationProvider({ copyTooltip: 'Скопировать фрагмент' })];\n```\n\nЭти провайдеры работают и в element injector, поэтому провайдер на компоненте ограничивает переопределение\nего поддеревом.\n\n\n\nКаждая такая функция поставляется из пакета своего компонента. Исключение —\n`kbqSelectLocaleConfigurationProvider`, который живёт в `@koobiq/components/core`: секцию `select` выводят\nчетыре независимых друг от друга пакета — `kbq-select`, `kbq-tree-select`, `kbq-tree-selection` и\n`kbq-list-selection`.\n\nПереопределение накладывается поверх того, что активно: поверх строк активной локали, если приложение\nпредоставляет сервис, иначе поверх значений по умолчанию из токена. Поэтому переданные ключи остаются\nзакреплёнными при вызове `setLocale()` во время работы, а все остальные следуют за локалью. Переопределите\nсекцию целиком, если она вообще не должна следовать за локалью, либо зарегистрируйте собственную локаль\n(см. ниже), если переопределение должно переключаться вместе с остальными строками.","examples":["code-block-with-custom-locale-configuration"]},{"heading":"Переопределение строк одного экземпляра","depth":3,"body":"Провайдер можно повесить только на инжектор, поэтому ограничить его одним компонентом — значит завести\nотдельную компонентную границу. Все локализованные компоненты принимают строки ещё и как привязку в шаблоне:\n\n```html\n\n```\n\nЗначение задаётся по секциям — в том же виде, в каком его принимают `addLocale()` и `KBQ_LOCALE_DATA`, —\nпоэтому одной привязкой можно покрыть и доступные имена, которые компонент выводит через своих потомков:\n\n```html\n\n```\n\n\n\nЧтобы ограничить переопределение целой областью, а не одним компонентом, повесьте\n`KbqLocaleOverridesDirective` на любой элемент приложения. Всё, что отображается внутри, — в том числе\nпанель, которая открывается в overlay-контейнере, — разрешается относительно этого элемента:\n\n```ts\n\n```\n\n```html\n
\n \n \n
\n```\n\nЭти два имени не взаимозаменяемы. `[localeOverrides]` — инпут, который объявляет компонент Koobiq;\n`[kbqLocaleOverrides]` — собственный селектор директивы, для элементов приложения. Селектор, написанный на\nкомпоненте, который уже несёт директиву, сопоставляет её на одном элементе дважды, и Angular отклоняет это\nошибкой `NG0309`.\n\nИсточники применяются от самого общего к самому локальному:\n\n1. значения по умолчанию из токена конфигурации;\n2. активная локаль;\n3. `kbqLocaleConfigurationProvider()`, начиная с самого внешнего инжектора;\n4. `KbqLocaleOverridesDirective`, начиная с самого внешнего элемента;\n5. собственный `[localeOverrides]` компонента.\n\nШаги с 3 по 5 переопределяют только те ключи, которые называют. Шаг 2 — исключение: активная локаль\nзаменяет значения токена целиком, а не накладывается на них, поэтому значение, переданное в токен\nконфигурации, отбрасывается, как только появляется сервис локали. Переопределяйте секцию, а не токен.\n\nПайп — не элемент, поэтому своего `[localeOverrides]` у `kbqNumber`, `kbqRoundNumber` и `kbqDataSize` нет.\nЗато до них доходит носитель на предке: пайп разрешает секцию по тому же инжектору, что и обычный читатель.","examples":["select-locale-configuration"]},{"heading":"Регистрация собственной локали","depth":3,"body":"`addLocale()` принимает частичные данные — каждая секция и каждый ключ внутри секции необязательны. Всё,\nчто не указано, дополняется из поставляемой локали с тем же идентификатором, а для нового идентификатора —\nиз `KBQ_DEFAULT_LOCALE_ID`. Поэтому `getParams()` всегда возвращает полную секцию, что бы ни было\nзарегистрировано:\n\n```ts\nlocaleService.addLocale('en-GB', {\n select: { selectAll: 'Select everything' },\n a11y: { close: 'Dismiss' }\n});\n```\n\nПараметр типизирован как `KbqPartialLocaleData`, поэтому опечатка в названии секции или ключа — это ошибка\nкомпиляции, а не строка, которая молча никогда не появится.\n\nТе же данные можно передать заранее через `KBQ_LOCALE_DATA`. Токен читается один раз, конструктором\n`KbqLocaleService`, поэтому провайдер должен лежать в том же массиве `providers`, что и сам сервис:\n\n```ts\nconst localeData = {\n 'en-GB': { select: { selectAll: 'Select everything' } }\n} satisfies KbqLocaleDataInput;\n\nproviders: [{ provide: KBQ_LOCALE_DATA, useValue: localeData }, kbqLocaleServiceProvider()];\n```\n\n`satisfies` здесь не для красоты: `useValue` объявлен как `any`, и без него та же опечатка пройдёт молча.\n\nЛокаль, добавленная таким образом, регистрируется, но в список `items` не попадает — а переключатель локали\nстроится именно по этому списку. Если новая локаль должна быть доступна для выбора, перечислите `items`\nцеликом, вместе с поставляемыми локалями.","examples":[]},{"heading":"Чтение секции напрямую","depth":3,"body":"```ts\nconst { selectAll } = localeService.getParams('select'); // KbqSelectLocaleConfiguration\nconst select = localeService.params('select'); // Signal\n```\n\nНазвание секции проверяется по `KbqLocaleSection`, а тип результата выводится из него.\n\nОба варианта читают только локаль. Компоненту, которому важны и переопределения выше — провайдеры и\n`[localeOverrides]`, — придётся объявить директиву и читать через неё:\n\n```ts\n\n@Component({\n selector: 'my-widget',\n hostDirectives: [\n { directive: KbqLocaleOverridesDirective, inputs: ['kbqLocaleOverrides: localeOverrides'] }\n ]\n})\nexport class MyWidget {\n protected readonly localeConfiguration = inject(KbqLocaleOverridesDirective, { self: true }).read(\n 'select',\n KBQ_SELECT_LOCALE_CONFIGURATION\n );\n}\n```\n\n`{ self: true }` здесь обязательно: с `{ host: true }` Angular поднимется до хост-элемента охватывающего\nкомпонента и вернёт **его** носитель, так что компонент прочитает чужие переопределения вместо своих.\nЗапись `hostDirectives` приходится повторять литералом в каждом компоненте: вынесенная в константу, она\nпадает с `NG1010` при использовании из другой точки входа.\n\nНазывайте поле `localeConfiguration` — это принятое в библиотеке имя итоговых строк, которые читает\nкомпонент; оно отделено от `[localeOverrides]`, который задаёт частичное переопределение. Компонент,\nчитающий вторую секцию, уточняет имя — как это делает `a11yLocaleConfiguration`.\n\n`read()` возвращает сигнал, поэтому `setLocale()` доходит до шаблона сам, а источники сливаются в\nперечисленном выше порядке. Именно чтение через носитель и заставляет `[localeOverrides]` работать на\nкомпоненте: одно без другого не бывает. Там, где носитель поставить некуда — в пайпе или в содержимом,\nкоторое создаётся программно, — те же источники читает `kbqInjectLocaleConfiguration(section, token)`.","examples":[]},{"heading":"Даты и числа","depth":3,"body":"Адаптеры дат и числовые пайпы используют тот же сервис, но им нужны собственные провайдеры.\n`KbqLocaleServiceModule` — его подключают модули адаптеров дат — объявляет сервис сам.\n\n\n\nЧтобы ограничить локаль поддеревом, в котором есть даты, объявите адаптер и форматтер в `providers` того же\nкомпонента, что и `kbqLocaleIDProvider`. Импорт модуля для этого не годится: `imports: [KbqLuxonDateModule]`\nпомещает адаптер в environment injector, где он получит корневой сервис локали, и названия месяцев\nостанутся на языке всего приложения, а не этого поддерева.","examples":["number-formatter-locale"]}]},{"id":"migration","title":"Migration","lang":"en","since":"21.0.0","lede":"","sections":[{"heading":"How to upgrade from Koobiq 17","depth":2,"body":"New versions include improvements but also contain **breaking changes**; they must be applied step by step.","examples":[]},{"heading":"Upgrade plan","depth":3,"body":"- **Up to 18.5.3**: a safe baseline with updated theming and icons.\n- **18.6**: token update.\n- **18.22**: component attribute changes.\n- **20.0.0**: the move to Angular 20: removal of deprecated APIs and package renames.\n- **20.2.0**: the move of the filter-bar API to signals.\n- **20.2.0**: one shared mechanism for dropdown panel width.\n- **21.0.0**: the move to Angular 21: zoneless change detection, CSS animations instead of `@angular/animations`, and signal inputs, outputs and queries.\n- **21.0.0**: removal of the APIs deprecated before 21.0.0.\n- **21.0.0**: removal of `ThemePalette`.\n- **21.0.0**: removal of the core API nothing in the library used, and deprecation of its legacy API.\n- **21.0.0**: removal of the component API and styles nothing in the library used.\n- **21.0.0**: the actions panel overlay container.\n- **21.0.0**: legacy splitter deprecation.\n- **21.0.0**: removal of the overlay demotion mechanism.\n- **21.0.0**: the move of the app-switcher API to signals.\n- **21.0.0**: the button review — host attributes, group ownership and styles.\n- **21.0.0**: button supported colors — a default color of its own per style.\n- **21.0.0**: the button-toggle review — ARIA semantics, keyboard navigation and signal inputs.\n- **21.0.0**: the form-field review — signals, accessibility and the removal of `mixinColor`.\n- **21.0.0**: the theme service review — signals, `auto` mode and built-in persistence.\n- **21.0.0**: explicit prefix and suffix slots for tag content.\n- **21.0.0**: deprecation of the overlayscrollbars-based Scrollbar implementation.\n- **21.0.0**: the link component instead of the directive — the `kbqLink` selector, slots, `external` and `size`.\n- **21.0.0**: the locale layer typing — a typed `getParams`, partial locale data and signals.\n- **21.0.0**: `multiple` on the selection list and tree became a real, changeable input.\n- **21.0.0**: the component review — closed internals, signal inputs and the behavior fixes it uncovered.\n- **21.0.0**: removal of the deprecated file-upload `fileQueueChanged`/`fileQueueChange` outputs.\n- **21.0.0**: the accordion state store moved into `core`, shared by every component that persists state.\n- **21.0.0**: accordion state saving is on by default, keyed on the document instead of instantiation order.\n- **21.0.0**: tree state saving is on by default, keyed on the value the tree control gives each node.\n- **21.0.0**: tabs, sidebar and content-panel remember what the user changed, on by default.\n- **21.0.0**: the filter bar remembers the selected filter and its edits, on by default.\n- **21.0.0**: the tag list cleaner removes the tags itself and leaves the disabled ones in place.\n- **21.0.0**: the move to `@koobiq/design-tokens` 4 — a new palette scale, DTCG sources and opt-in component tokens.","examples":[]},{"heading":"Upgrade to 18.5.3","depth":3,"body":"{/* migration-step-version(18.5.3) */}\n\n```bash\nnpm install @koobiq/cdk@18.5.3\nnpm install @koobiq/components@18.5.3\nnpm install @koobiq/icons@^9.0.0\nnpm install @koobiq/design-tokens@~3.7.3\nnpm install @koobiq/angular-luxon-adapter@18.5.3\nnpm install @koobiq/date-adapter@^3.1.3\nnpm install @koobiq/date-formatter@^3.1.3\nnpm install luxon\nnpm install @messageformat/core\n```","examples":[]},{"heading":"Token update (18.6.x)","depth":3,"body":"{/* migration-step-version(18.6.0) */}\n\nDeprecated color tokens were removed and typography parameter tokens were renamed.\n\nThe script will rename class and CSS-variable names to the new ones and highlight places where deprecated colors need to be removed (replaced):\n\n```bash\nng g @koobiq/angular-components:css-selectors --fix=true --project \"\"\n```\n\nFor manual review, add `--fix=false`. The script will highlight places where colors and typography names need to be removed (replaced):\n\n```bash\nng g @koobiq/angular-components:css-selectors --fix=false --project \"\"\n```","examples":[]},{"heading":"Attribute update (18.22.0)","depth":3,"body":"{/* migration-step-components(loader-overlay, empty-state) */}\n\nComponent attribute names have changed:\n\n- **KbqLoaderOverlay**: compact → size\n- **KbqEmptyState**: big → size\n\nThe schematic replaces the attributes automatically:\n\n```bash\nng g @koobiq/angular-components:loader-overlay-size-attr --project \"\"\nng g @koobiq/angular-components:empty-state-size-attr --project \"\"\n```","examples":[]},{"heading":"Upgrade to Angular 20","depth":3,"body":"{/* migration-step-version(20.0.0) */}\n\nIn version 20.0.0 the library moves to Angular 20. This is a major release: long-deprecated APIs were removed and some packages were renamed. Requirements: **Angular 20+** and **Node.js ≥ 20.19**.\n\nRemove `@koobiq/cdk` from `package.json` — the package has been merged into `@koobiq/components/core`.","examples":[]},{"heading":"Running the migration","depth":4,"body":"Most of the changes are applied by the `v20-upgrade` schematic (runs automatically):\n\n```bash\nng update @koobiq/components@20\n```\n\nOr manually. To preview without writing — `--fix=false`:\n\n```bash\nng g @koobiq/components:v20-upgrade --project \"\"\n```","examples":[]},{"heading":"Filter-bar upgrade (20.2.0)","depth":3,"body":"{/* migration-step-components(filter-bar) */}\n\nIn version 20.2.0 the public API of `KbqFilterBar` moved to signals. Template bindings (`[filter]`, `[(filter)]`, `[pipeTemplates]`) and the `(filterChange)` output keep working — only programmatic reads break: they now require a call.\n\n| Member | Before | After |\n| --------------------------------------------------------------------------- | ----------------------- | -------------------------------------------------------- |\n| `filter` | accessor | `ModelSignal` — write via `.set(...)` |\n| `pipeTemplates` | accessor | `InputSignal` |\n| `isChanged` / `isDisabled` / `isReadOnly` / `isSaved` / `isSavedAndChanged` | getter | `Signal` |\n| `onChangePipe` | `EventEmitter` | `OutputEmitterRef` |","examples":[]},{"heading":"Running the migration","depth":4,"body":"The changes are applied by the `filter-bar-signals` schematic (runs automatically):\n\n```bash\nng update @koobiq/components@20\n```\n\nOr manually — for example, if you have already upgraded to 20.2.0. To preview without writing — `--fix=false`:\n\n```bash\nng g @koobiq/components:filter-bar-signals --project \"\"\n```","examples":[]},{"heading":"Panel width unification (20.2.0)","depth":3,"body":"{/* migration-step-components(autocomplete, select, tree-select, timezone, dropdown) */}\n\nIn version 20.2.0 `autocomplete`, `select`, `tree-select`, `timezone` and `dropdown` started resolving the width of their dropdown panel through one shared mechanism. They now all expose the same three inputs — `panelWidth`, `panelMinWidth` and `panelMaxWidth` — with the same meaning:\n\n| `panelWidth` | Panel width |\n| ---------------------- | ------------------------------------------------------------------------ |\n| not set (default) | sizes to its content, never narrower than the trigger or `panelMinWidth` |\n| `'auto'` | matches the trigger, never narrower than `panelMinWidth` |\n| a number or CSS string | exactly that width; `panelMinWidth` is not applied |\n\nPanels are now also capped at **640px** through the `--kbq-panel-size-width-max` token. The cap is soft: it limits how far a panel grows with its content, but it never makes a panel narrower than its trigger and never clamps an explicit `panelWidth`. Change it globally by setting the token on `:root`, per component through the component's own token (`--kbq-dropdown-size-container-width-max` still works), or per instance through `panelMaxWidth`.","examples":[]},{"heading":"Running the migration","depth":4,"body":"The `autocomplete-panel-width-auto` schematic runs automatically:\n\n```bash\nng update @koobiq/components@20\n```\n\nOr manually:\n\n```bash\nng g @koobiq/components:autocomplete-panel-width-auto --project \"\"\n```","examples":[]},{"heading":"Upgrade to Angular 21","depth":3,"body":"{/* migration-step-version(21.0.0) */}\n\nIn version 21.0.0 the library moves to Angular 21. Requirements: **Angular 21+**, **TypeScript 5.9** and **Node.js 20.19, 22.12 or 24+**. Update Angular first, then Koobiq:\n\n```bash\nng update @angular/core@21 @angular/cli@21 @angular/cdk@21\nng update @koobiq/components@21\n```","examples":[]},{"heading":"Deprecated API removal (21.0.0)","depth":3,"body":"The APIs deprecated in 20.x are removed in 21.0.0, those whose note promised removal in the next major release and those deprecated without a date alike. A renamed member or alias is rewritten by `v21-upgrade`; for the rest it reports the file and the replacement to use.","examples":[]},{"heading":"Running the migration","depth":4,"body":"`v21-upgrade` is registered for 21.0.0, so an `ng update` to that version runs it:\n\n```bash\nng g @koobiq/components:v21-upgrade --project \"\"\n```\n\nWith `--fix false` it only reports what it would change.","examples":[]},{"heading":"ThemePalette removal (21.0.0)","depth":3,"body":"`ThemePalette` is removed from `@koobiq/components/core`: `KbqComponentColors` describes the colors the components support.\n\n| Removed | Use instead | Migration |\n| --------------------------------------------------------- | -------------------------------------------------------------------- | --------- |\n| `ThemePalette.Error`, `.Warning`, `.Success` | `KbqComponentColors.Error`, `.Warning`, `.Success` | rewritten |\n| `ThemePalette.Primary`, `.Secondary`, `.Info`, `.Default` | no color, or a member of `KbqComponentColors` the component supports | reported |\n| `ThemePalette.Empty` | an unset `color` | reported |\n| the `ThemePalette` type | `KbqComponentColors` | reported |\n\n`Error`, `Warning` and `Success` have the same values in both enums, so the rewrite changes nothing on screen. `Primary`, `Secondary` (`Default` is its alias) and `Info` set a `kbq-primary`, `kbq-secondary` or `kbq-info` class that no component styles, so such a component was drawn without its color rules; without a color it takes its default one. `Empty` is `''`, which also falls back to the default color, while `KbqComponentColors.Empty` is `'empty'`, a color of its own: do not swap one for the other.\n\n`v21-upgrade` rewrites the three members and drops the `ThemePalette` import once nothing else names it. It reports every other use in a file that imports `ThemePalette` from `@koobiq/components/core`, including a field that hands the enum to a template. A re-export of `ThemePalette` from `@koobiq/components/core` is left as is and reported: `KbqComponentColors` has other members and values, so re-export it under its own name and migrate the files that import `ThemePalette` through it.","examples":[]},{"heading":"Unused core API removal (21.0.0)","depth":3,"body":"`@koobiq/components/core` drops the undocumented exports, members and Sass that nothing in the library used. They were never deprecated: most are leftovers of removed features or implementation details that leaked into the public API.\n\n| Removed | Use instead | Migration |\n| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- |\n| `POSITION_MAP`, `EXTENDED_OVERLAY_POSITIONS`, `POSITION_PRIORITY_STRATEGY`, the twelve `*_POSITION_PRIORITY` lists | `kbqPlacement` on the trigger; copy a value you need | reported |\n| `ARROW_BOTTOM_MARGIN_AND_HALF_HEIGHT`, `SELECT_PANEL_VIEWPORT_PADDING`, `NUMBER_FORMAT_REGEXP`, `isWithin()` | a value or function of your own | reported |\n| `KBQ_SELECT_SCROLL_STRATEGY_PROVIDER` | nothing: `KBQ_SELECT_SCROLL_STRATEGY` has a root default; provide the token itself to change it | reported |\n| `KBQ_CHECKABLE_CONTROL_VALUE_ACCESSOR` | nothing: `KbqCheckable` registers itself | reported |\n| `KbqHover` | `(mouseenter)` and `(mouseleave)`, or `:hover` | reported |\n| `CanColor` | `Signal`, or `KbqColorDirective` | reported |\n| `KbqAnimationCurves`, `KbqAnimationDurations` | constants of your own: the library animates in CSS | reported |\n| `KbqRectangleItem`, `countGroupLabelsBeforeOption()` | no replacement | reported |\n| `KbqPopUp.markForCheck()` | nothing after `updateClassMap()` or a change of `visibility`, which re-render the pop-up; for another field, `changeDetectorRef.markForCheck()` in a subclass, `instance.detectChanges()` in a trigger | reported |\n| `KbqPopUp.warning`, `.handleBodyInteraction()`, the protected `closeOnInteraction` | no replacement: `warning` did nothing, nothing called `handleBodyInteraction()`, and `closeOnInteraction` only gated it | reported |\n| `KbqDecimalPipe.isSpecialFormatForRULocale()`, `KbqRoundDecimalPipe.roundingOptions` | private now: the pipe arguments and the formatters locale configuration | reported |\n| `KbqFormElement.isRow` | the `kbq-form__row` class of the element | reported |\n| `KbqLineSetter` and the `kbq-2-line`, `kbq-3-line`, `kbq-multi-line` classes of a list item | nothing: no style read them | reported |\n| `KbqLine`, `KbqListItem.lines` | nothing: only `lines` read `KbqLine`, and nothing counted the lines; the `kbq-line` attribute still places an element in the text of a list item | reported |\n| `KbqSelectSearch.hasPlaceholder()` | the placeholder of the search input | reported |\n| the `mcListIcon` attribute of a list item | `kbq-list-icon` | rewritten |\n| the `mcLine` attribute | `kbq-line` | rewritten |\n\nThe Sass that goes is reachable only through a deep import, since `@koobiq/components` forwards none of it:\n\n- the modules `core/styles/functions` (its index, `number` and `string`), `typography/typography-utils`, `common/clearfix` and `common/popup`;\n- `kbq-markdown-typography-config()`, the `typography-utils` getters (`kbq-typography-level()`, `kbq-font-size()`…), `popup-params`, `input-placeholder`, `rtl-value()` and `kbq-typography-css-variables`;\n- the timing variables of `core/styles/functions/timing` but `$ease-out-circ`, `$ease-in-out-quad` and `$ease-in-out-circ`.\n\nFor typography, use the `--kbq-typography-*` CSS variables or `kbq-typography-level-to-styles-css-variables()` of `core/styles/common/tokens`; copy anything else into your own styles. `v21-upgrade` reports each load and use. The compiled CSS also loses a few prefixes for browsers the library does not support: `-ms-text-size-adjust`, `-ms-overflow-style`, `-moz-` and `-ms-user-select`, the old flexbox `display` values and an `@-moz-document` copy of `.layout-fill`.","examples":[]},{"heading":"Unused component API removal (21.0.0)","depth":3,"body":"The component entry points drop the exports, members and styles that nothing in the library used. They were never deprecated: most are leftovers of removed features or implementation details that leaked into the public API. `v21-upgrade` reports each use.\n\n| Removed | Use instead |\n| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |\n| `normalizeSplitter()`, `isInt()`, `isDigit()` of `@koobiq/components/input` | `value.replace(/,/g, '.')`, `/^-?\\d+$/.test(value)`; `isFloat()` stays |\n| `KbqTimeRangeTranslateTypeMap` | `Record` |\n| `KbqTabBodyOriginState` | `'left' \\| 'right'` |\n| `KbqDatepickerIntl` | nothing: no view rendered its labels; the texts come from `kbqDatepickerLocaleConfigurationProvider()` |\n| `KbqDropdown.onPanelReached()` | nothing: nothing subscribed to it |\n| the protected `KbqTreeSelection.getItemHeight()` | `options.first?.getHeight() ?? 0` |\n| the protected `KbqTreeSelection.setFocusedOption()` | `this.keyManager.setActiveItem(option)` |\n| `KbqTagList.tagSelectionChanges`, `.shouldLabelFloat` | the `selectionChange` output of each tag; `!empty() \\|\\| focused()` |\n| `KbqTreeNodeOutlet.changeDetectorRef` | `ChangeDetectorRef`, injected where you need it |\n\nA subclass loses the protected members nothing read: `changeDetectorRef` of `KbqAccordion`, `KbqAccordionItem`, `KbqPasswordToggle`, `KbqIcon` and `KbqSearchExpandable`, `KbqAccordionTrigger.nativeElement`, `KbqNotificationCenterComponent.dateAdapter`, `KbqFilters.styles`, and `isHorizontalOverflown`, `isVerticalOverflown` and `child` of `KbqTitleDirective`. Inject what the subclass needs itself; `isOverflown` of the title covers both directions.\n\nThe styles lose the rules for classes nothing sets: `kbq-control_has-validate-directive` and `kbq-form-field_has-validate-directive`, left from the `KbqValidateDirective` removed in 20.0.0 (the form field, select and tree select show an error through their `ErrorStateMatcher`), and the `kbq-no-select` utility class: set `user-select: none` in your own styles.","examples":[]},{"heading":"Actions panel overlay container (21.0.0)","depth":3,"body":"{/* migration-step-components(actions-panel) */}\n\n`KbqActionsPanelConfig.overlayContainer` now names the element the panel is rendered **into**, rather than an anchor it was positioned against. The panel is pinned to the bottom center of that element and resizes with it.\n\nWhat that changes for code already setting the option:\n\n- **An element with `overflow: hidden` clips the panel**, and one that scrolls its own content scrolls the panel away with it. At `overflow: visible` the entrance animation renders the panel below the element.\n- **The element is mutated while the panel is open**: it gains one child node holding the overlay, and a `static` element is promoted to `position: relative`, since the overlay is positioned against it.\n- **`maxWidth` is no longer ignored** when `overlayContainer` is set.\n- **A globally provided `OverlayContainer` is bypassed** while the option is set.","examples":[]},{"heading":"Running the migration","depth":4,"body":"`actions-panel-overlay-container` is registered for 21.0.0, so an `ng update` to that version runs it:\n\n```bash\nng g @koobiq/components:actions-panel-overlay-container --project \"\"\n```\n\nIt only reports the call sites — there is nothing to rewrite, because the option is the same one and only its meaning changed.","examples":[]},{"heading":"Legacy splitter deprecation (21.0.0)","depth":3,"body":"{/* migration-step-components(splitter) */}\n\n`@koobiq/components/splitter` now resolves to a rewritten splitter: it lays panels out with CSS grid and adds per-panel size constraints, collapsing, snap points, full keyboard support and the WAI-ARIA `separator` pattern. The previous implementation — `KbqSplitterComponent`, `KbqSplitterAreaDirective`, `KbqGutterDirective`, `KbqGutterGhostDirective`, `KbqSplitterModule` and the `Direction` enum — moved to `@koobiq/components/splitter/deprecated` unchanged, and will be removed in 22.0.0.\n\n**Most unmigrated imports break loudly.** `KbqSplitterComponent`, `KbqSplitterAreaDirective`, `KbqGutterDirective`, `KbqGutterGhostDirective` and `Direction` live at the `/deprecated` path only, so the build stops with `TS2305: … has no exported member`.\n\n**`KbqSplitterModule` is the silent one — it is the case this migration exists for.** Both entry points export a class under that name, and the `kbq-splitter` selector exists on both sides, so an unmigrated import keeps compiling but now pulls in the rewritten splitter. That one projects `kbq-splitter-panel` children instead of `[kbq-splitter-area]` hosts and takes `orientation` rather than `direction`, so the group renders with no panels and no separators.\n\nTemplates need no changes of their own: once the import points at `/deprecated`, `` with `[kbq-splitter-area]` hosts, `direction`, `useGhost`, `hideGutters` and `gutterSize` keeps working exactly as before.","examples":[]},{"heading":"Running the migration","depth":4,"body":"`splitter-deprecated-path` is registered for 21.0.0, so an `ng update` to that version runs it:\n\n```bash\nng g @koobiq/components:splitter-deprecated-path --project \"\"\n```\n\nIt rewrites the `@koobiq/components/splitter` specifier to `@koobiq/components/splitter/deprecated` across every `.ts` file, and running it twice changes nothing. It does not move you onto the new splitter: that is separate manual work — areas become `kbq-splitter-panel`, `direction` becomes `orientation`, and sizes move from `min-width` and CSS `flex` onto the `size` and `minSize` inputs.","examples":[]},{"heading":"Overlay demotion removal (21.0.0)","depth":3,"body":"{/* migration-step-components(dropdown, select, popover, navbar, top-bar) */}\n\nUntil 21.0.0 an open `dropdown`, `select` or `popover` panel lowered the **shared, app-wide** `.cdk-overlay-container` from `z-index: 1000` to `999` by adding a `.cdk-overlay-container_dropdown` class to it. The point was to let a panel slide under a sticky `kbq-navbar` / `kbq-top-bar` while the page scrolled. `KbqDropdownTrigger.demoteOverlay` turned that off for one trigger, and the `KBQ_DROPDOWN_HOST` marker token — provided by `KbqNavbar` and `KbqTopBar` — flipped its default to `false` so a dropdown inside the chrome would not end up behind its own trigger.\n\nThe whole mechanism was removed: the input, the token, the class and the stylesheet rule. It operated on the container rather than on individual overlays, so it could not lower a panel without lowering every other overlay — modals, sidepanels, toasts and tooltips included — and it did so from whichever component happened to open first.\n\n**Panels now render above `kbq-navbar` and `kbq-top-bar` instead of sliding under them.** They open in the browser top layer (see Overlays in the top layer in Upgrade to Angular 21); with `usePopover: false` the overlay container stays at `z-index: 1000`, above the bars at `990`.","examples":[]},{"heading":"Running the migration","depth":4,"body":"The `dropdown-demote-overlay` schematic runs automatically:\n\n```bash\nng update @koobiq/components@21\n```\n\nOr manually:\n\n```bash\nng g @koobiq/components:dropdown-demote-overlay --project \"\"\n```","examples":[]},{"heading":"App-switcher upgrade (21.0.0)","depth":3,"body":"{/* migration-step-components(app-switcher) */}\n\nIn version 21.0.0 `KbqAppSwitcherTrigger` moved `selectedApp` and `selectedSite` from a plain `@Input()` (plus a matching `output()`) to `model()`, and a review of the component removed several members that never did anything. Template bindings keep working — `[selectedApp]`, `[(selectedSite)]` and `(selectedAppChange)` are unchanged — so only programmatic access and reads through a `#ref=\"kbqAppSwitcher\"` template reference variable break.\n\n| Member | Before | After |\n| ------------------------------------------------------------------------------------ | ------------------- | ------------------------------------------------------------------ |\n| `selectedApp` | `@Input()` property | `ModelSignal` — write via `.set()` |\n| `selectedSite` | accessor input | `ModelSignal` — **value changed** |\n| `selectedAppChange` / `selectedSiteChange` | `output()` | the implicit outputs of the models above |\n| `header` / `footer` | properties | removed |\n| `KbqAppSwitcherComponent.isTrapFocus` / `updateTrapFocus()` | public API | removed |\n| `KbqAppSwitcherDropdownApp.getIcon()` | public method | removed |\n| `KbqAppSwitcherComponent.activeSite` / `activeApp` | protected | removed: the context of the flyout's `kbqDropdownContent` template |\n| `KbqAppSwitcherListItem.collapsed` | property | `ModelSignal` |\n| `app` / `site` inputs of `KbqAppSwitcherListItem` / `-DropdownApp` / `-DropdownSite` | optional inputs | `input.required` signals |","examples":[]},{"heading":"Running the migration","depth":4,"body":"The changes are applied by the `app-switcher-signals` schematic (runs automatically):\n\n```bash\nng update @koobiq/components@21\n```\n\nOr manually — for example, if you have already upgraded to 21.0.0. To preview without writing — `--fix=false`:\n\n```bash\nng g @koobiq/components:app-switcher-signals --project \"\"\n```","examples":[]},{"heading":"Button review (21.0.0)","depth":3,"body":"{/* migration-step-components(button, button-group) */}\n\nThe review of `[kbq-button]` changed three unrelated things at once. Nothing here has a deprecation period — the old behaviour is simply gone — but only one of the changes stops your code from compiling.\n\n**Host attributes are now chosen by host tag.** Until 21.0.0 a disabled button rendered `disabled` _and_ `aria-disabled=\"true\"`, whatever the host element was. `disabled` is not a valid attribute on an anchor and was ignored by the browser, while `aria-disabled` on a native `\n```\n\nIf you have more than two themes and need a list rather than a light/dark switch, you can pin one by name. See [Core](/en/components/core) for the details and a live example.","examples":[]},{"heading":"Theme selectors","depth":4,"body":"| Theme | Selector |\n| ----- | ---------- |\n| Light | .kbq-light |\n| Dark | .kbq-dark |","examples":[]},{"heading":"Settings","depth":4,"body":"Pass the settings when the app starts:\n\n```ts\n\nbootstrapApplication(AppComponent, {\n providers: [kbqThemeProvider({ mode: 'auto', storageKey: 'my-app-theme' })]\n});\n```\n\nThe choice is saved between visits — in `localStorage` by default, where switching the theme in one tab is picked up by the app's other open tabs. Apps rendered on the server can store it in a cookie instead, so the server already knows which theme to render: provide `KbqThemeCookieStore` through the `KBQ_THEME_STORE` token. It reads the cookie straight off the incoming request, so nothing else has to be wired up on the server.","examples":[]},{"heading":"Using theme values in your own styles","depth":3,"body":"Write the variable where you need the value:\n\n```css\n.my-component-text {\n color: var(--kbq-foreground-contrast-secondary);\n}\n```\n\nNothing to import, no mixin to write, nothing to register in a single theme entry point. The value resolves in the browser, so it changes on its own when the theme changes.\n\n\n\nThe full list of global variables is on the [Design tokens](/en/main/design-tokens/colors) page.","examples":["theme-css-variables"]},{"heading":"Overriding a component's variables","depth":3,"body":"When a component's default look doesn't fit, change the values of its variables rather than its styles. Start by knowing where those variables are declared, because that determines how you reach them. Every component sets them on its own selector, in a file next to itself:\n\n```css\n/* button-tokens.scss */\n.kbq-button,\n.kbq-button-icon {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-contrast);\n}\n```\n\nThe button sets the variable on itself, so a blanket declaration in `:root` simply won't be seen. For an override to work, you have to hit that same element.\n\nThat leaves two approaches.\n\n**Change a single instance.** Give it a class of your own and set the variable there. The class lands on the same element as the default value and, coming later, wins:\n\n```html\n\n\n\n```\n\n**Change every component of that type in the app.** This needs a selector that outranks the component's own. The simplest one to add is the theme selector — which also lets you give light and dark different values, when one color won't do for both:\n\n```css\n.kbq-light .kbq-button {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error);\n}\n\n.kbq-dark .kbq-button {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error-fade);\n}\n```\n\nRules like these are worth keeping in one file, loaded after the design system's styles — then all of the app's overrides live in one place instead of scattering across components.","examples":[]},{"heading":"Building your own component","depth":3,"body":"Your component reads the same global variables the library reads. Give it a stylesheet and turn off style encapsulation — that is the whole setup:\n\n```ts\n@Component({\n selector: 'my-card',\n templateUrl: './my-card.html',\n styleUrl: './my-card.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class MyCard {}\n```\n\n```scss\n.my-card {\n padding: var(--kbq-size-l);\n border: var(--kbq-size-border-width) solid var(--kbq-line-contrast-less);\n border-radius: var(--kbq-size-border-radius);\n\n background: var(--kbq-background-card);\n color: var(--kbq-foreground-contrast);\n}\n```\n\nThere is **no** need to write a separate `_my-card-theme.scss` mixin and then register it in a single theme entry point.","examples":[]},{"heading":"Corner shape","depth":4,"body":"Where the browser supports `corner-shape`, components round their corners with a squircle (`corner-shape: squircle`); other browsers get a plain radius. A squircle looks smaller than an arc of the same radius, so its radius is scaled up. To give your component the same corners as the library's, include the mixin:\n\n```scss\n@use '@koobiq/components/core/styles/common/squircle' as *;\n\n.my-card {\n @include kbq-squircle(var(--kbq-size-border-radius));\n}\n```\n\nWithout SCSS, write the same rule by hand:\n\n```css\n.my-card {\n --my-card-radius: var(--kbq-size-border-radius);\n\n border-radius: var(--my-card-radius);\n}\n\n@supports (corner-shape: squircle) {\n .my-card {\n corner-shape: squircle;\n border-radius: clamp(\n var(--my-card-radius) * 1.85,\n var(--my-card-radius) * 2.3 - 3.6px,\n var(--my-card-radius) * 2\n );\n }\n}\n```\n\nTo change a library component's radius, set its variable (for example `--kbq-button-size-border-radius`) rather than `border-radius`. The scaling applies to the variable: a plain `border-radius` leaves the corner a squircle that looks too small. A component made round with `50%` also needs `corner-shape: round`.","examples":[]},{"heading":"Rebranding with a single file","depth":3,"body":"You can restyle the whole app by overriding CSS variables in one place:\n\n```css\n.kbq-light {\n --kbq-background-contrast: #1a3a6b;\n --kbq-foreground-contrast: #0d1b2a;\n}\n\n.kbq-dark {\n --kbq-background-contrast: #7ba7e8;\n --kbq-foreground-contrast: #e8eef7;\n}\n```\n\nLoad that file after the design system's own, and every component follows.\n\nOne honest limitation: this works at the global layer. Component variables are declared on the component's selector, so a single global file can't reach them — you would need a rule per component, as shown above.","examples":[]},{"heading":"Where to find the variables","depth":3,"body":"Global variables, with their values and previews: [Design tokens](/en/main/design-tokens/colors).\n\nThe variables of a component are declared next to its code, in its tokens file, such as `button-tokens.scss` for the button: open it from the **Source code** link on the page of the component. In a browser, the developer tools show them on the element of the component, as custom properties such as `--kbq-button-*`.","examples":[]},{"heading":"Where the values come from","depth":3,"body":"**Design system tokens** are the values that define how our components look. They live in the [@koobiq/design-tokens](https://github.com/koobiq/design-tokens) package.\n\n**Component variables** are the values used inside component styles. They are derived from the design tokens and live in the `@koobiq/components` repository, next to the components themselves.\n\n
\n
Note
\n
\n\nVersion 4.0.0 of `@koobiq/design-tokens` removed the component tokens, except for code-block, scrollbar and skeleton. Their tokens ship as separate files — `component-tokens.css` and its light and dark parts — and their values are already part of `@koobiq/components`' own `prebuilt-themes/theme.css`, so you don't need to load them yourself. Everything else is carried by the components themselves. If copies of the removed tokens are still in your own `css-tokens.css`, `css-tokens-light.css`, `css-tokens-dark.css` or `css-tokens-font.css`, delete them, and run `npx koobiq-tokens-codemod src` to find what else the upgrade touches.\n\n
\n
","examples":[]}]},{"id":"theming","title":"Theming","lang":"ru","since":"21.0.0","lede":"Тема в Koobiq — это набор CSS-переменных. Смена темы меняет значения переменных, всё остальное остаётся как есть. Здесь описано, как подключить тему, как её переключать и как брать её значения в своих стилях.","sections":[{"heading":"Как устроена темизация","depth":3,"body":"Есть три уровня.\n\n**Глобальные переменные** описывают дизайн-систему: цвета, размеры, шрифты. Они приходят из пакета `@koobiq/design-tokens` и объявлены под селектором темы — `.kbq-light` или `.kbq-dark`. Именно ими вы пользуетесь в своих стилях.\n\n**Переменные компонента** нужны внутри компонентов Koobiq. Имя каждой начинается с названия компонента (`--kbq-button-*`, `--kbq-alert-*`), а значение ссылается на глобальную переменную. Компонент объявляет их сам, поэтому отдельно подключать ничего не нужно. Исключение — code-block, scrollbar и skeleton: их токены лежат в пакете `@koobiq/design-tokens`, но при обычном подключении и для них ничего делать не нужно — значения уже входят в файл готовых стилей.\n\n**Ваши стили** обращаются к глобальным переменным напрямую. Ни импортов, ни миксинов, ни регистрации в едином источнике темы.\n\nПодключить тему — значит поставить класс на ``. Всё, что внутри, и компоненты, и ваша вёрстка, подхватит новые значения само, потому что читает одни и те же переменные.","examples":[]},{"heading":"Подключение","depth":3,"body":"- [Установите пакет Koobiq](/ru/main/installation).\n- Подключите файлы `css-tokens.css`, `css-tokens-light.css` и `css-tokens-dark.css` — в них лежат глобальные значения.\n- Подключите файл готовых стилей в свой основной файл. Без него компоненты и всплывающие элементы (окна, выпадающие списки) отобразятся неправильно. В нём же лежат и значения токенов code-block, scrollbar и skeleton, поэтому отдельные файлы для них подключать не нужно:\n\n```sass\n@use '@koobiq/components/prebuilt-themes/theme.css';\n```\n\n- Добавьте класс темы на ``:\n\n```html\n\n \n\n```\n\nКласс `kbq-app-background` красит саму страницу — фон и цвет текста.\n\n- Импортируйте компонент и пользуйтесь. 🚀","examples":[]},{"heading":"Переключение тем","depth":3,"body":"Для этого есть `KbqThemeService`. Он ставит нужный класс на ``, запоминает выбор и в режиме `'auto'` следует системной цветовой схеме.\n\nРежима три: `'light'`, `'dark'` и `'auto'`. **По умолчанию — `'auto'`.** Системная цветовая схема поддерживается из коробки: подписываться на `matchMedia` вручную не нужно.\n\nТекущее состояние сервис отдаёт сигналами, их можно читать прямо в шаблоне:\n\n```ts\nprotected readonly themeService = inject(KbqThemeService);\n```\n\n```html\n\n```\n\nЕсли тем больше двух и нужен список, а не переключатель «светлая/тёмная», тему можно закрепить по имени. Подробности и живой пример — на странице [Core](/ru/components/core).","examples":[]},{"heading":"Селекторы тем","depth":4,"body":"| Тема | Селектор |\n| ------- | ---------- |\n| Светлая | .kbq-light |\n| Тёмная | .kbq-dark |","examples":[]},{"heading":"Настройки","depth":4,"body":"Настройки передаются при запуске приложения:\n\n```ts\n\nbootstrapApplication(AppComponent, {\n providers: [kbqThemeProvider({ mode: 'auto', storageKey: 'my-app-theme' })]\n});\n```\n\nВыбор темы сохраняется между визитами — по умолчанию в `localStorage`, причём смена темы в одной вкладке подхватывается остальными открытыми вкладками приложения. Приложения, которые собираются на сервере, могут хранить его в куке: тогда сервер сразу знает, какую тему рисовать. Для этого подключите `KbqThemeCookieStore` через токен `KBQ_THEME_STORE` — он читает куку прямо из входящего запроса, больше на сервере ничего настраивать не нужно.","examples":[]},{"heading":"Как брать значения темы в своих стилях","depth":3,"body":"Напишите переменную там, где нужно значение:\n\n```css\n.my-component-text {\n color: var(--kbq-foreground-contrast-secondary);\n}\n```\n\nНичего не импортируется, миксин писать не нужно, регистрировать его в едином источнике темы тоже. Значение подставляется в браузере, поэтому при смене темы оно меняется само.\n\n\n\nПолный список глобальных переменных — на странице [Дизайн-токены](/ru/main/design-tokens/colors).","examples":["theme-css-variables"]},{"heading":"Переопределение переменных компонента","depth":3,"body":"Если стандартный вид компонента не подходит, менять нужно не его стили, а значения его переменных. Сначала стоит понять, где эти переменные объявлены — от этого зависит, как до них дотянуться. Каждый компонент задаёт их на своём селекторе, в файле рядом с собой:\n\n```css\n/* button-tokens.scss */\n.kbq-button,\n.kbq-button-icon {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-contrast);\n}\n```\n\nКнопка задаёт переменную на себе, поэтому общее объявление в `:root` она просто не увидит. Чтобы переопределение сработало, нужно попасть в тот же самый элемент.\n\nОтсюда два рабочих способа.\n\n**Поменять один экземпляр.** Повесьте на него свой класс и задайте переменную в нём — класс окажется на том же элементе, что и стандартное значение, и, поскольку идёт следом, победит:\n\n```html\n\n\n\n```\n\n**Поменять все компоненты этого типа в приложении.** Здесь нужен селектор потяжелее, чем у самого компонента. Проще всего добавить перед ним селектор темы: заодно можно задать разные значения для светлой и тёмной, если один цвет на обе не годится:\n\n```css\n.kbq-light .kbq-button {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error);\n}\n\n.kbq-dark .kbq-button {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error-fade);\n}\n```\n\nТакие правила удобно держать в одном файле и подключать его после стилей дизайн-системы — тогда все переопределения приложения лежат в одном месте, а не расползаются по компонентам.","examples":[]},{"heading":"Компонент на основе темы","depth":3,"body":"Кастомный компонент читает те же глобальные переменные, что и библиотека. Дайте ему файл стилей и отключите изоляцию стилей — это вся настройка:\n\n```ts\n@Component({\n selector: 'my-card',\n templateUrl: './my-card.html',\n styleUrl: './my-card.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class MyCard {}\n```\n\n```scss\n.my-card {\n padding: var(--kbq-size-l);\n border: var(--kbq-size-border-width) solid var(--kbq-line-contrast-less);\n border-radius: var(--kbq-size-border-radius);\n\n background: var(--kbq-background-card);\n color: var(--kbq-foreground-contrast);\n}\n```\n\nОтдельный миксин `_my-card-theme.scss`, который потом нужно регистрировать в едином источнике темы, писать **не надо**.","examples":[]},{"heading":"Форма углов","depth":4,"body":"Где браузер поддерживает `corner-shape`, компоненты скругляют углы сквирклом (`corner-shape: squircle`), в остальных браузерах — обычным радиусом. Сквиркл выглядит мельче дуги того же радиуса, поэтому радиус для него увеличивается. Чтобы углы вашего компонента совпадали с библиотечными, подключите миксин:\n\n```scss\n@use '@koobiq/components/core/styles/common/squircle' as *;\n\n.my-card {\n @include kbq-squircle(var(--kbq-size-border-radius));\n}\n```\n\nБез SCSS то же правило пишется вручную:\n\n```css\n.my-card {\n --my-card-radius: var(--kbq-size-border-radius);\n\n border-radius: var(--my-card-radius);\n}\n\n@supports (corner-shape: squircle) {\n .my-card {\n corner-shape: squircle;\n border-radius: clamp(\n var(--my-card-radius) * 1.85,\n var(--my-card-radius) * 2.3 - 3.6px,\n var(--my-card-radius) * 2\n );\n }\n}\n```\n\nРадиус компонента библиотеки меняйте через его переменную (например, `--kbq-button-size-border-radius`), а не через `border-radius`. Увеличивается именно переменная: с голым `border-radius` угол останется сквирклом и будет выглядеть слишком мелким. Компоненту, скруглённому в круг через `50%`, нужно ещё `corner-shape: round`.","examples":[]},{"heading":"Смена оформления одним файлом","depth":3,"body":"Оформление всего приложения меняется в одном месте:\n\n```css\n.kbq-light {\n --kbq-background-contrast: #1a3a6b;\n --kbq-foreground-contrast: #0d1b2a;\n}\n\n.kbq-dark {\n --kbq-background-contrast: #7ba7e8;\n --kbq-foreground-contrast: #e8eef7;\n}\n```\n\nПодключите этот файл после файлов дизайн-системы — и все компоненты подхватят новые значения.\n\nОдна честная оговорка: так работает глобальный уровень. Переменные компонента объявлены на его селекторе, поэтому одним общим файлом до них не дотянуться — придётся писать правило на каждый компонент, как показано выше.","examples":[]},{"heading":"Где посмотреть переменные","depth":3,"body":"Глобальные переменные, со значениями и образцами: [Дизайн-токены](/ru/main/design-tokens/colors).\n\nПеременные компонента объявлены рядом с его кодом, в файле токенов, например `button-tokens.scss` у кнопки: откройте его по ссылке **Исходный код** на странице компонента. В браузере инструменты разработчика показывают их на элементе компонента как CSS-переменные вида `--kbq-button-*`.","examples":[]},{"heading":"Откуда берутся значения","depth":3,"body":"**Токены дизайн-системы** — это значения, которые задают внешний вид наших компонентов. Они лежат в пакете [@koobiq/design-tokens](https://github.com/koobiq/design-tokens).\n\n**Переменные компонентов** — это значения, которые используются в стилях компонентов. Они получены из токенов дизайн-системы и лежат в репозитории `@koobiq/components`, рядом с самими компонентами.\n\n
\n
Обратите внимание
\n
\n\nВерсия 4.0.0 пакета `@koobiq/design-tokens` удалила токены компонентов — кроме code-block, scrollbar и skeleton. Их токены поставляются отдельными файлами (`component-tokens.css` и его светлая и тёмная части), и их значения уже входят в `prebuilt-themes/theme.css` пакета `@koobiq/components`, так что подключать их самостоятельно не нужно. Остальные значения компоненты везут сами. Если копии удалённых токенов остались в ваших `css-tokens.css`, `css-tokens-light.css`, `css-tokens-dark.css` или `css-tokens-font.css` — удалите их, а затем запустите `npx koobiq-tokens-codemod src`, чтобы найти остальное, что затронуло обновление.\n\n
\n
","examples":[]}]},{"id":"validation","title":"Validation","lang":"en","since":"21.0.0","lede":"Form-field controls display errors via [`ErrorStateMatcher`](/en/other/validation/examples) — a small policy object that decides _when_ to surface a control's existing errors.","sections":[{"heading":"Summary","depth":3,"body":"- The submit button is never disabled\n- Invalid character input is reported as it's typed\n- Error messages are shown on blur (focus loss)\n- Empty required fields don't show errors on blur\n- Fields already in error state are validated as user types","examples":[]},{"heading":"Opening the form","depth":3,"body":"Focus is placed on the first field. Unfilled fields are not highlighted in red by default. An exception is a saved draft with already-visible errors that the user resumes filling.\n\n","examples":["validation-on-open"]},{"heading":"Required fields","depth":3,"body":"Required fields are not marked as such if most of the fields are required. Optional fields can be labeled with a tag or placeholder like “Optional.” It's even better to explain why filling in this info is helpful to the user.\n\n\n\nSome fields can go unmarked as optional if their auxiliary purpose is obvious. For example, “Description.”\n\nIf most fields in the form are optional, then the few required ones should be marked. You can add a red asterisk after the label or a gray “Required” tag below the field. Don't use multiple methods for indicating required fields in the same form.\n\n","examples":["validation-optional-label","validation-required-label"]},{"heading":"When to validate for errors","depth":3,"body":"There are three main strategies for validation:\n\n1. While typing\n2. On blur (when leaving the field)\n3. After submitting the form","examples":[]},{"heading":"While typing","depth":4,"body":"Used to check for valid characters. Invalid characters are filtered out during input, and an error tooltip is shown near the text caret. The message disappears when a valid character is entered or the field loses focus.\n\n","examples":["validation-on-type"]},{"heading":"On blur","depth":4,"body":"For fields with complex requirements (like IP address or email), they shouldn't enter an error state during typing to avoid distracting the user. Validation is done on blur — the field is marked invalid and an error message appears beneath it.\n\n\n\nEmpty fields are not validated on blur.\n\nReal-time validation works for fields already marked as invalid. If the value is corrected, the error message disappears and the field is restored to normal state.\n\nInvalid fields remain styled as such if the user leaves the field without correcting the error.\n\n","examples":["validation-on-blur","validation-on-blur-filled"]},{"heading":"On form submission","depth":4,"body":"By default, errors are shown only for **invalid** (incorrectly filled) fields after the user interacts with them.\nTo display errors immediately after form submission, use `ShowOnFormSubmitErrorStateMatcher` — an implementation of [ErrorStateMatcher](https://github.com/koobiq/angular-components/blob/main/packages/components/core/error/error-state-matcher.ts).\n\nUsed for checking empty required fields and things that can't be validated client-side.\n\nThe submit button is **not disabled**, even if the form is empty or contains invalid fields. When clicked, validation and submission are triggered. The button enters a **Progress** state during submission. You can also display a [Loader Overlay](/ru/components/loader-overlay) over the form during this time.\n\nIf validation fails, focus is set to the first field with an error. If the error isn't tied to a specific field, show an alert above the form and focus on it — ensuring visibility even if the form's top section is off-screen.\n\nIf the same alert message remains valid on repeated submissions, don't hide and re-show it — instead, fade it out and back in while re-focusing.\n\n","examples":["validation-on-submit"]},{"heading":"Error messages","depth":3,"body":"","examples":[]},{"heading":"Field-specific message","depth":4,"body":"Field-level error messages are shown below the field, **before** any helper text. If the error message duplicates the helper text, you can replace one with the other.\n\n\n\nYou don't always need to display an error message just for a required field — marking it as invalid may be enough.\n\n","examples":["validation-message-for-specific-field","validation-no-message"]},{"heading":"Global message","depth":4,"body":"General form errors are shown in an alert **above the first field** and focused. The alert appears higher than any informational blocks above the form fields.\n\nIn this example, the message appears after clicking the submit button. For mouse users, scrolling to the alert is enough — no need to show a visible focus outline. If the button is activated via keyboard, show a visible focus outline on the alert.\n\n","examples":["validation-message-global"]},{"heading":"One required field from several","depth":4,"body":"Some forms require at least **one** of several fields to be filled. In this case, fields aren't marked as invalid after submission. Instead, a general error alert appears above the form. You can list the problematic fields and include pseudo-links to move focus to them.\n\n","examples":["validation-message-global-with-links"]}]},{"id":"validation","title":"Validation","lang":"ru","since":"21.0.0","lede":"Поля формы показывают ошибки через [`ErrorStateMatcher`](/ru/other/validation/examples) — небольшой объект-политику, который решает, _когда_ выводить уже существующие ошибки контрола пользователю.\n\n---","sections":[{"heading":"Кратко","depth":3,"body":"- Кнопка отправки никогда не блокируется\n- О вводе недопустимого символа сообщаем по мере ввода\n- Показываем сообщение об ошибке при смене фокуса\n- На пустые обязательные поля не ругаемся при потере фокуса\n- Поле в состоянии ошибки валидируется по мере ввода","examples":[]},{"heading":"Открытие формы","depth":3,"body":"Фокус расположен в первом поле формы. Незаполненные поля не подсвечиваются красным с ошибкой. Исключением может быть сохраненный черновик с уже показанными ошибками, который открыли, чтобы продолжить заполнение.\n\n","examples":["validation-on-open"]},{"heading":"Обязательные поля","depth":3,"body":"Обязательные поля не отмечаются, если их в форме большинство. К дополнительным для заполнения полям можно добавить пометку или плейсхолдер «Необязательно». Еще лучше объяснить, чем поможет пользователю заполнение этой информации.\n\n\n\nНекоторые поля можно не отмечать необязательными, если их дополнительная роль понятна. Например, «Описание».\n\nЕсли в форме большинство полей необязательные, то редкие обязательные нужно пометить. Можно добавить красную звездочку после названия или серую пометку «Обязательно» под поле. В одной форме не используйте разные способы обозначения\n\n","examples":["validation-optional-label","validation-required-label"]},{"heading":"Когда проверять на ошибки","depth":3,"body":"Есть три способа проверки на ошибки:\n\n1. Проверка по мере ввода\n2. При выходе из поля\n3. После отправки формы","examples":[]},{"heading":"Проверка по мере ввода","depth":4,"body":"Используется для проверки на допустимые символы. В момент ввода фильтруем недопустимый символ и показываем ошибку в тултипе около каретки ввода. Сообщение пропадает при вводе корректного символа или потере фокуса.\n\n","examples":["validation-on-type"]},{"heading":"При выходе из поля","depth":4,"body":"Если требования к значениям в поле относительно сложные (например, это IP-адрес или адрес эл. почты), то поле не должно получать состояние ошибки во время ввода, чтобы не сбивать пользователя с толку. Валидация в таких случаях выполняется по потере фокуса: поле оформляется невалидным и показывается сообщение об ошибке под ним.\n\n\n\nПустое поле при потере фокуса не валидируется.\n\nВалидация при вводе работает в уже отмеченном ошибкой поле. Поле возвращается к нормальному состоянию после изменения значения, сообщение об ошибке пропадет.\n\nНевалидное поле сохраняет оформление, если из него вышли, но ошибку не исправили.\n\n","examples":["validation-on-blur","validation-on-blur-filled"]},{"heading":"После отправки формы","depth":4,"body":"По умолчанию ошибки показываются только для **невалидных** (некорректно заполненных) полей после взаимодействия пользователя.\nЧтобы отображать ошибки сразу после отправки формы, используйте `ShowOnFormSubmitErrorStateMatcher` — реализацию [ErrorStateMatcher](https://github.com/koobiq/angular-components/blob/main/packages/components/core/error/error-state-matcher.ts).\n\nТакая валидация используется для проверки пустых обязательных полей и тех, которые нельзя проверить на клиенте.\n\nКнопка отправки не блокируется в пустой форме или с невалидными полями. По клику на кнопку происходит валидация и отправка формы: кнопка принимает состояние **Progress** в момент отправки. Также в момент отправки поверх формы можно показать [Loader Overlay](/ru/components/loader-overlay).\n\nПри неудачной валидации фокус встает в первое поле с ошибкой. Если ошибка отправки не относится к конкретному полю, то показываем такое сообщение алертом над формой и ставим на него фокус. Так пользователь точно увидит алерт, даже если верхняя часть формы оказалась вне видимой области.\n\nЕсли при повторной отправке формы ошибка не исправлена и сообщение в алерте по-прежнему актуально, то не надо скрывать и снова показывать его. Алерт должен плавно стать прозрачными и снова проявится, а фокус снова окажется на сообщении.\n\n","examples":["validation-on-submit"]},{"heading":"Сообщение об ошибке","depth":3,"body":"","examples":[]},{"heading":"Сообщение для отдельного поля","depth":4,"body":"Сообщение об ошибке, которое относится к отдельному полю показываем под ним, но располагаем его перед текстом подсказки. Если сообщение об ошибке повторяет подсказку, то можно заменить одно другим.\n\n\n\nДопустимо не писать текст ошибки о том, что необходимо заполнить обязательные поля, а лишь оформить их невалидными.\n\n","examples":["validation-message-for-specific-field","validation-no-message"]},{"heading":"Общее сообщение","depth":4,"body":"Общую для формы ошибку показываем алертом над первым полем и перемещаем фокус на сообщение. Алерт располагается выше, чем информационный блок, который часто располагается над полями.\n\nВ этом примере сообщение будет показано после нажатия по кнопку отправки. При управлении мышкой достаточно пролистать до алерта, а рамку фокуса показывать не надо. Если кнопка была активирована с клавиатуры, то показываем на алерте видимую рамку фокуса.\n\n","examples":["validation-message-global"]},{"heading":"Обязательные поля на выбор","depth":4,"body":"В некоторых формах требуется заполнить хотя бы одно из обязательных полей. В этом случае после отправки формы поля не оформляются невалидными. Сообщение об ошибке показывается алертом над формой. В нем можно перечислить невалидные поля и вставить псевдоссылки, которые будут перемещать фокус в соответствующий элемент управления.\n\n","examples":["validation-message-global-with-links"]}]},{"id":"which-component-when","title":"Which component when","lang":"en","since":"21.0.0","lede":"# which_component_when data source\n\nNot a page of koobiq.io. This file is the data the `which_component_when` tool of the Koobiq MCP server parses; it\nis `.md` rather than `.mdx` on purpose, so the docs site build skips it — see [Provenance](#provenance).\n\nMaps a **task** to a Koobiq component, and names the neighboring component it is confused with.\n\nWritten for the `which_component_when` tool of the Koobiq MCP server. An agent without this mapping picks the\ncomponent whose name looks like a word in the request — the frequent failure is not \"found nothing\" but \"found the\nneighbor\": `autocomplete` for a multi-select, `toast` for a message that must be acknowledged, `tabs` for a form\ncontrol.","sections":[{"heading":"How to read it","depth":2,"body":"- [Disambiguation](#disambiguation) — the clusters where the neighbor is picked. The useful part; read it first.\n- [Component index](#component-index) — one line per component, grouped by the job it does.\n- [Keyword index](#keyword-index) — words a request is likely to use, mapped to the cluster to read.\n\nEach disambiguation row answers three things: what to use, what not to use, and why not. \"Why not\" is the part an\nagent cannot reconstruct from component names.","examples":[]},{"heading":"Provenance","depth":2,"body":"The component docs carry the usage prose for roughly a third of the library:\n\n| Source | What it actually holds |\n| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `packages/components/*/.en.mdx` — intro paragraph | Present for every component. One sentence, says _what_ it is; _when_ only by implication |\n| `### When to use` | 7 components: `tree`, `modal`, `sidepanel`, `timepicker`, `loader-overlay`, `link`, `dynamic-translation` |\n| `### Recommendations` / `### Guidelines` | 15 components: `accordion`, `badge`, `button-group`, `inline-edit`, `list`, `popover`, `radio`, `select`, `skeleton`, `split-button`, `tabs`, `tag`, `title`, `toggle`, `tooltip` |\n| `modal.en.mdx` → `### Related components` | The richest comparison text in the repo: modal against popover, sidepanel, inline forms, accordion, toast, a new page, undo |\n| Cross-links in prose | Scattered \"use X instead\": `list` → `select`/`tree`, `title` → `tooltip`/`popover`, `tag` → `badge`, `tabs` → `navbar` |\n| `*.ru.mdx` | A faithful mirror of the English pages — same sections, same count. No extra guidance to mine |\n| `packages/docs-examples/**` `@title` | 623 titles, but they name features (\"Select multiple\"), not tasks. Usable as keywords only |\n| `apps/docs/src/llms.txt`, `llms-full.txt` | A link index and the same MDX text. No formulations of their own |\n\nEverything else below — the clusters, the \"why not\", the per-component lines for the ~50 components that document\nonly what they are — was written for this guide from the docs, the public API and the component code. Keep it here\nrather than scattering it back into the MDX pages: the comparisons cross component boundaries, and a page about one\ncomponent is the wrong place to say which other component wins.\n\nThe MCP server reads this file as an ordinary guide — `build-guides.ts` picks it up from `docs/guides` along with\nthe rest, in both languages — but it never becomes a page of koobiq.io: `tools/docs-pages/sources.ts` globs only\n`docs/guides/**/*.mdx`, and the `.md` extension here is deliberate, not an oversight. This file is the single\nsource of truth — the server holds no copy of the mapping.","examples":[]},{"heading":"Disambiguation","depth":2,"body":"","examples":[]},{"heading":"Pick a value from a set","depth":3,"body":"| Task | Use | Not | Why not |\n| ------------------------------------------------------------ | ----------------------------------------------- | --------------------- | ------------------------------------------------------------------------------- |\n| One value from a closed list, in a form | `select` | `autocomplete` | Autocomplete is a text field that suggests; it accepts what the user types |\n| Several values from a closed list | `select` + `multiple` | `autocomplete` | Autocomplete has no `multiple` input at all |\n| Several values from a long list (hundreds) | `select` + `multiple` + search + virtual scroll | `tree-select` | Tree select is for data that _is_ a hierarchy, not for data that is merely long |\n| Several values from a hierarchy | `tree-select` + `multiple` | `select` | Select flattens: the parent-child relation is lost |\n| One value, list comes from a server query as the user types | `autocomplete` | `select` | Select owns its option list; it has no text input to drive a query |\n| Several values, user may enter values outside the dictionary | `tag-autocomplete` | `select` + `multiple` | Select cannot accept a value that is not an option |\n| Several free-form values, no dictionary | `tag-input` | `tag-autocomplete` | Autocomplete's dropdown with nothing to suggest is noise |\n| Show and remove values chosen elsewhere | `tag-list` | `tag-input` | Tag input also _enters_ values; a read-only set does not need that |\n| 2–7 exclusive options, all visible | `radio` | `select` | Hiding a handful of options behind a trigger costs a click for nothing |\n| More than 7 exclusive options | `select` | `radio` | Docs cap a radio group at ~7 rows |\n| 3–5 exclusive options, short labels, room in a row | `button-toggle` | `radio`, `tabs` | Radio spends a row per option; tabs switch content and are not a form control |\n| Independent on/off options, saved with the form | `checkbox` | `toggle` | A toggle promises the change already took effect |\n| One setting that applies instantly | `toggle` | `checkbox` | A checkbox implies a later Save |\n| Partially selected group header | `checkbox` + `indeterminate` | two checkboxes | The third state is built in |\n| Select rows of page content, not a form value | `list` + `multiple=\"checkbox\"` | `select` | Select is a form control behind a trigger; a list is content |\n| Hundreds of items in a hierarchy | `tree` | `list` | Docs: without hierarchy and volume, a plain list is better |\n| A menu of commands | `dropdown` | `select` | Select holds a value; a dropdown runs actions |\n\nFor the long multi-select: project `[kbqSelectSearch]` into the panel (docs recommend search past 10 options) and\nwrap the options in `cdk-virtual-scroll-viewport`. With object values, supply `virtualOptionFactory` so the trigger\ncan label a value whose option is not rendered. The \"select all\" row is not rendered together with virtual scroll —\nit can only act on options currently rendered.","examples":[]},{"heading":"Show that something is in progress","depth":3,"body":"| Task | Use | Not | Why not |\n| ----------------------------------------------------------- | ------------------------------------ | -------------------- | ----------------------------------------------------------------------------- |\n| Share of the work is known and worth showing | `progress-bar` | `progress-spinner` | A spinner cannot show how much is left; put the remaining time beside the bar |\n| Duration unknown, short wait, little room, a single element | `progress-spinner` | `progress-bar` | A bar with `indeterminate` claims a width it does not have |\n| A whole block is busy and must not be touched | `loader-overlay` | `progress-spinner` | A bare spinner does not stop interaction with the block underneath |\n| Page or section content loading, 2–10 s | `skeleton` | `progress-spinner` | Docs: skeletons suit full-page loading and keep the layout from jumping |\n| Loading takes under 1 s | nothing | `skeleton` | Docs: no indicator is needed below a second |\n| A button's own action takes over 1–2 s | `class=\"kbq-progress\"` on the button | `loader-overlay` | Overlaying the page for one button is out of proportion |\n| A long file upload | `file-upload` (own progress) | `progress-bar` alone | The component already reports per-file state |","examples":[]},{"heading":"Tell the user something","depth":3,"body":"| Task | Use | Not | Why not |\n| ---------------------------------------------------------- | ---------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------- |\n| An error the user must acknowledge before continuing | `modal` | `toast`, `alert` | A toast is allowed to be missed; an alert sits in the page and does not block |\n| Result of an operation, no response needed, may be missed | `toast` | `modal` | Docs: avoid modality unless an answer is required |\n| A standing status or problem tied to a page region | `alert` | `toast` | A toast disappears; a reported problem has to stay (a non-closable alert) |\n| A message the user should be able to re-read later | `notification-center` | `toast` | A toast leaves no trace — pair the two when both matter |\n| \"Are you sure?\" before a destructive action | do it, offer undo | `modal` | Docs name the undo pattern as the preferred one; keep the modal for the irreversible case |\n| A short text hint | `tooltip` | `popover` | A popover takes focus and expects interaction |\n| Full text, but only when it is truncated | `kbq-title` | `tooltip` | A plain tooltip fires even when the text fits |\n| Content with controls or links beside a trigger | `popover` | `tooltip`, `dropdown` | A tooltip holds no controls; a dropdown has no Tab navigation inside |\n| Nothing to show: no data, no search results, a failed load | `empty-state` | `alert` | An alert annotates content; here there is no content |\n| Validation errors of one field | `form-field` hints + `ErrorStateMatcher` | `alert` | The matcher decides _when_ errors surface; the field renders them |","examples":[]},{"heading":"An extra layer over the page","depth":3,"body":"| Task | Use | Not | Why not |\n| ---------------------------------------------- | --------------- | ----------- | ----------------------------------------------------------------------------- |\n| Short blocking task, a small form, settings | `modal` | `sidepanel` | Narrow and tall content reads badly in a modal; wide content badly in a panel |\n| A lot of data, tall narrow content, stacking | `sidepanel` | `modal` | Docs: modals should not be stacked; non-modal sidepanels stack |\n| Preview a row while the table stays usable | `content-panel` | `sidepanel` | A content panel shifts adjacent content instead of covering it |\n| A permanent collapsible region of the layout | `sidebar` | `sidepanel` | A sidepanel is a transient window, not part of the layout |\n| Two regions the user resizes | `splitter` | `resizer` | The resizer directive resizes one element, with no neighbor logic |\n| One element resizable by drag | `resizer` | `splitter` | A splitter owns the layout of the panels inside it |\n| A small dialog by a trigger, page stays usable | `popover` | `modal` | Docs: a popover claims attention less disruptively |\n| Commands by a trigger | `dropdown` | `popover` | A popover captures focus; a command menu does not need to |\n| Bulk actions over selected rows | `actions-panel` | `top-bar` | The panel appears with the selection and counts it |","examples":[]},{"heading":"Text that does not fit","depth":3,"body":"| Task | Use | Not | Why not |\n| ---------------------------------------------------- | ----------------- | ---------------- | -------------------------------------------------------------- |\n| One line cut at the end, full text on hover when cut | `kbq-title` | `tooltip` | Only `kbq-title` keeps quiet while the text fits |\n| One line where the start and the end both matter | `ellipsis-center` | `kbq-title` | Paths, hashes and file names lose their meaning cut at the end |\n| Multi-line text collapsed to N lines, with expansion | `clamped-text` | CSS `line-clamp` | The component owns the trigger, its a11y and the locale text |\n| A list collapsed to N items, with expansion | `clamped-list` | `overflow-items` | A clamped list counts items; overflow items measure width |\n| A row of items collapsed to the container width | `overflow-items` | `clamped-list` | The limit here is pixels, and it changes as the box resizes |","examples":[]},{"heading":"Tabular and structured data","depth":3,"body":"| Task | Use | Not | Why not |\n| -------------------------------------------------- | ---------------------------------------------- | ------------------- | ------------------------------------------------------------ |\n| A static table, no sorting, no column resizing | `table` | AG Grid | A data grid is a large dependency for plain rows |\n| Sorting, column resize and reorder, virtual scroll | AG Grid (`docs/data-grid`) | `table` | Docs: the table offers only what native HTML tables do |\n| Attributes of one entity, term → value | `dl` | `table` | A two-column table has to re-implement the responsive layout |\n| Filtering a table or a list, with saved filters | `filter-bar` | `select` per column | The filter bar owns pipes, search, saving and state restore |\n| Highlight why a row matched | `kbqHighlightBackground` / `mcHighlight` pipes | manual markup | The pipes wrap matches in `` with the right class |\n| Items the user picks from | `list` | `table` | A table row is not a selection control |","examples":[]},{"heading":"Navigation","depth":3,"body":"| Task | Use | Not | Why not |\n| -------------------------------------------------- | --------------- | -------------- | --------------------------------------------------------------- |\n| Main product navigation | `navbar` | `tabs` | Docs name tabs for in-page navigation only |\n| A toolbar that stays visible: title, actions | `top-bar` | `navbar` | The navbar is product-level; the top bar belongs to the screen |\n| Where the user is in a hierarchy | `breadcrumbs` | `top-bar` text | Breadcrumbs navigate and collapse under `max` |\n| Switch between groups of content on one page | `tabs` | `accordion` | Tabs show one group at a time; an accordion can show several |\n| Switch the view of the same data (list/tile/table) | `button-toggle` | `tabs` | Docs list this as a misuse of tabs |\n| Reveal extra content in place | `accordion` | `modal` | Docs: an accordion or \"show more\" is the least intrusive option |\n| Between applications and platforms | `app-switcher` | `dropdown` | The switcher is the agreed pattern and shape for this |\n| Go to another page, or a button inside a sentence | `link` | `button` | For a button in running text, use the pseudo-link variant |","examples":[]},{"heading":"Editing existing data","depth":3,"body":"| Task | Use | Not | Why not |\n| -------------------------------------------------- | ----------------------- | ----------------------- | ------------------------------------------------------------------ |\n| Change one or two fields among many, saved at once | `inline-edit` | a form | Docs: inline edit saves on change, without a global Save |\n| Create a new entity | a form (`form-field`) | `inline-edit` | Docs rule out inline edit for creation |\n| Fields that validate each other, masks, prompts | a form | `inline-edit` | Docs rule out inline edit for dependent or complex validation |\n| Critical data, or a save that needs confirmation | `modal` with a form | `inline-edit` | Docs rule out inline edit here too |\n| Pick a date | `datepicker` | `input` | Masking, parsing and calendar come with the component |\n| Pick a date range | two `datepicker` inputs | one datepicker | There is no range widget; bind `minDate`/`maxDate` and `min`/`max` |\n| Pick a time | `timepicker` | `input` | Same reason as the datepicker |\n| Pick a period (\"last 24 hours\", or a manual range) | `time-range` | two `timepicker` fields | The menu carries the presets |\n| Pick a timezone | `timezone` | `select` | It extends select with the timezone option template |\n| Upload files | `file-upload` | `input[file]` | Drag area, size, errors and the list come with it |\n| A search field that should stay small until used | `search-expandable` | `input` | It collapses into an icon button; bind it to a form control |","examples":[]},{"heading":"Labels, marks, identity","depth":3,"body":"| Task | Use | Not | Why not |\n| ------------------------------------------------- | ------------- | ---------- | ----------------------------------------------------------- |\n| A status or count label in a table or a list | `badge` | `tag` | Docs: tags belong inside input controls, badges stand alone |\n| A token for a value chosen in a control | `tag` | `badge` | Same rule, read the other way |\n| A country | `flag` | `icon` | The flag shapes and crops whatever image you pass |\n| A decorative icon with a backing, in place of art | `icon-item` | `icon` | The backing is what draws the eye |\n| A clickable icon | `icon-button` | `icon` | The button carries the hit area, states and focus ring |\n| An internal system user | `username` | plain text | The pattern fixes which attributes identify a person |","examples":[]},{"heading":"Component index","depth":2,"body":"One line each: when to reach for it. `→` names the neighbor to prefer in the case that follows.","examples":[]},{"heading":"Choosing values","depth":3,"body":"- `select` — one or more values from a list the application owns; search past 10 options, virtual scroll for long lists. → `autocomplete` when the value is typed, `tree-select` when the data is a hierarchy.\n- `tree-select` — one or more values from hierarchical data, with the tree kept visible while searching.\n- `autocomplete` — a text field that suggests matches while typing, including server-driven suggestions; single value.\n- `tag-autocomplete` — several values from a dictionary, with custom values allowed; the dropdown is an autocomplete panel.\n- `tag-input` — several free-form values entered as tags.\n- `tag-list` — a set of tags with keyboard navigation, selection and removal; no entry of its own.\n- `tag` — one token inside an input control. → `badge` for a standalone label.\n- `list` — related items as page content, single or multiple (`multiple=\"checkbox\"` when the state must be visible at a glance); virtual scroll for long lists, but not together with dragging.\n- `tree` — hundreds of items the user navigates as a catalog. → `list` without hierarchy or volume.\n- `radio` — up to ~7 mutually exclusive options, all visible.\n- `checkbox` — independent options, and the group state via `indeterminate`.\n- `toggle` — a binary setting that applies immediately, with no Save.\n- `button-toggle` — 3–5 short exclusive options in one row, in a form or a filter.\n- `dropdown` — a menu of actions or options beside a trigger, with disabled, loading and footer states.\n- `timezone` — timezone selection; `kbq-select` with a timezone option template, no multiple selection.\n- `time-range` — a period: a preset, or boundaries entered by hand.","examples":[]},{"heading":"Data entry","depth":3,"body":"- `form-field` — the wrapper that gives a control its label, hint, error and cleaner; input, select, textarea, autocomplete, tag input, tag autocomplete, timepicker, timezone and tree select are built to sit inside it.\n- `input` — text, number and password entry with formatting, masks and validation.\n- `textarea` — multi-line text.\n- `datepicker` — a date, typed or picked from the calendar; `minDate`/`maxDate` bound the calendar, `min`/`max` on the input drive validation.\n- `timepicker` — a time, typed into a masked field.\n- `file-upload` — files, single or multiple, with a drop area, sizes, icons and per-file errors.\n- `search-expandable` — a search field that expands from an icon button; it is a form control and throws without one.\n- `inline-edit` — edit a value where it is displayed, saving at once. → a form for creation, dependent fields, complex validation or critical data.\n- `filter-bar` — filtering a table or a list: typed pipes, search inside pipes, required and locked filters, saved filters, state restore.","examples":[]},{"heading":"Actions","depth":3,"body":"- `button` — an action, on a native `
\n\n\n```\n\n**Datepicker, Timepicker**. The KbqDatepickerInput.kbqValidationTooltip and KbqTimepicker.kbqValidationTooltip setters now accept KbqTooltipTrigger (the base class) instead of KbqWarningTooltipTrigger.","examples":[]},{"heading":"Migration","depth":3,"body":"**V20 upgrade**. Consumers can run the automatic migration with the `ng update @koobiq/components@20` command. It invokes the `v20-upgrade` schematic, which rewrites your code in place.\n\n**Imports and packages**. Imports from @koobiq/components/navbar-ic, risk-level, and components-experimental/form-field are remapped to the surviving packages (navbar, badge, components/form-field).\n\n**Identifiers in .ts files**. The following identifiers have been renamed:\n\n| Old name | New name |\n| ------------------------- | -------------------------------- |\n| KbqNavbarIc\\* | KbqNavbar\\* |\n| KbqRiskLevel\\* | KbqBadge\\* |\n| KbqWarningTooltipTrigger | KbqTooltipTrigger |\n| KbqExtendedTooltipTrigger | KbqTooltipTrigger |\n| KbqDatepickerToggle | KbqDatepickerToggleIconComponent |\n| KbqFilterBarSearch | KbqSearchExpandable |\n| RdxAccordionItemState | KbqAccordionItemState |\n| KbqCodeFile | KbqCodeBlockFile |\n| AnimationCurves | KbqAnimationCurves |\n| MeasurementSystem | KbqMeasurementSystem |\n| SizeUnitsConfig | KbqSizeUnitsLocaleConfiguration |\n| KbqFormFieldRef | KbqFormField |\n| DropdownPositionX/Y | KbqDropdownPositionX/Y |\n\n**Tokens and functions**. Updated: toBoolean( → booleanAttribute(, isCorrectExtension( → FileValidators.isCorrectExtension(, formatDataSize( → getFormattedSizeParts(, kbqComponentParams: → data:;\n\nRemoved tokens are stripped from imports: KBQ_VALIDATION, KBQ_SANITY_CHECKS, KBQ_SIDEPANEL_WITH_SHADOW\n\n**Instance methods**. Updated: .openPanel( → .open(, .toggleIsCollapsed( → .toggle(, .focusViaKeyboard( → .focus(.\n\n**Selectors in templates**. Updated: `` → ``, `` → ``, `` → ``, `` → ``.\n\n**Attributes in templates**. Updated: `kbqFormFieldWithoutBorders` → `noBorders`, `[kbqWarningTooltip]` → `kbqTooltipModifier=\"warning\" [kbqTooltip]`, `[kbqExtendedTooltip]` → `kbqTooltipModifier=\"extended\" [kbqTooltip]`, the template ref `=\"kbqWarningTooltip\"` → `=\"kbqTooltip\"`.\n\n**CSS classes in SCSS**. Updated: `.kbq-risk-level` → `.kbq-badge`, `.kbq-navbar-ic` → `.kbq-navbar`, etc.\n\n**Manual fixes**. The schematic emits warnings for structural changes that cannot be fixed automatically and safely: `(onSaveAsNew)` listeners on `` must be moved to `(onSave)` with branching on `$event.status === 'newFilter'`; the `[customValidation]` / `[errors]` attributes on file-upload components must be replaced with `FormControl` validators; the `[apps]` attribute on `\n `\n})\nexport class AppComponent {}\n```","examples":[]}]},{"id":"localization","title":"Localization","lang":"en","since":"21.0.0","lede":"Koobiq components render some strings of their own — the filters menu of the filter bar, the tooltips of\nthe code block, the accessible names of every icon-only button, the placeholder of a datepicker, and so on.\nAll of them come from one place: `KbqLocaleService`.\n\nThe library ships five locales: `en-US`, `es-LA`, `pt-BR`, `ru-RU` and `tk-TM`.\n\nThe application's data is never translated. Option labels, filter values, table cells and everything else\nthe application passes in is rendered exactly as given.\n\nTranslating those strings is the application's own job: Koobiq neither requires nor ships a library for it.\nWhen one of them has to contain a Koobiq component — a link, a button, a dropdown — embed it with\n[dynamic-translation](/en/components/dynamic-translation).","sections":[{"heading":"Setting the locale","depth":3,"body":"`KbqLocaleService` is `providedIn: 'root'`, but the components read it through the `KBQ_LOCALE_SERVICE`\ntoken, which has no factory. Nothing is localized until it is provided:\n\n```ts\n\nbootstrapApplication(AppComponent, {\n providers: [kbqLocaleServiceProvider()]\n});\n```\n\nWithout that provider every component falls back to its own built-in `ru-RU` defaults, and switching the\nlocale at runtime does nothing. The placeholder of the datepicker is the exception: it names the format its input\nparses — the shipped one of the date adapter's locale (`KBQ_DATE_LOCALE`, Angular's `LOCALE_ID` unless\nprovided), or the format itself, such as `yyyy/mm/dd`, for a locale the library ships no strings for and for a\nformat set through `KBQ_DATE_FORMATS`.\n\nUse `kbqLocaleServiceProvider()` rather than `{ provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }`:\n`useClass` builds a **second** instance, independent of the `providedIn: 'root'` one, so a `setLocale()`\ncalled on the instance that `inject(KbqLocaleService)` hands out moves nothing that is rendered. The helper\nprovides the service next to the token, and both references resolve to one instance.\n\nThere are three ways to control which locale is active:\n\n- **`KBQ_DEFAULT_LOCALE_ID`** is the fallback, `ru-RU`. It is a plain exported constant, not an injection\n token — it cannot be provided, only read.\n- **`KBQ_LOCALE_ID`**, set through `kbqLocaleIDProvider(id)`, fixes the locale once, when `KbqLocaleService`\n is constructed. It must sit in the **same `providers` array** as the service itself, because the service\n reads the token from the injector that created it.\n- **`setLocale(id)`** changes the locale at runtime.\n\n```ts\nproviders: [kbqLocaleIDProvider('en-US'), kbqLocaleServiceProvider()];\n```\n\nThe same pair on a component's `providers` scopes the locale to that component's subtree, while the rest of\nthe application keeps the locale it was given.\n\nReading the active locale:\n\n```ts\nreadonly localeService = kbqInjectLocaleService();\n\nreadonly currentLocale = this.localeService.localeId; // Signal\nreadonly localeData = this.localeService.data; // Signal\nreadonly available = this.localeService.items; // Signal, for a locale picker\n```\n\n`kbqInjectLocaleService()` resolves `KBQ_LOCALE_SERVICE`, never the class. Pass `{ optional: true }` for code\nthat has to keep working in an application that provided no locale service.\n\n`changes` is a `BehaviorSubject` of the active locale id, and it remains the way for code outside a template\nto observe a locale change. The `id` and `current` getters, on the other hand, are deprecated in favour of\nthe `localeId` and `data` signals. A signal read from a template registers on the reading view, so a runtime\n`setLocale()` reaches `OnPush` children that an observable subscribed in the parent could not.","examples":[]},{"heading":"What a locale is made of","depth":3,"body":"A locale is a set of sections, one per area of strings. The section is the unit of overriding: its name\nappears in the locale data, in `getParams()` and in `[localeOverrides]` alike.\n\n| Section | What it translates | Package |\n| -------------------- | ---------------------------------------------------------------------------------- | --------------------- |\n| `a11y` | accessible names of the icon-only buttons and regions the library renders itself | `core` |\n| `select` | the \"select all\" label and the counter of values that did not fit | `core` |\n| `popoverConfirm` | the question and the button of the confirmation popover | `core` |\n| `formatters` | separators and abbreviations of `kbqNumber`, `kbqTableNumber` and `kbqRoundNumber` | `core` |\n| `sizeUnits` | the size units of `kbqDataSize` | `core` |\n| `input` | the separators of the number input | `input` |\n| `datepicker` | the placeholder and the format of the date field | `datepicker` |\n| `timepicker` | the placeholders of the time field | `timepicker` |\n| `timezone` | the search placeholder of the timezone select | `timezone` |\n| `timeRange` | the title and the editor of the time range | `time-range` |\n| `codeBlock` | the tooltips and labels of the code block buttons | `code-block` |\n| `fileUpload` | the text of the single and multiple upload areas | `file-upload` |\n| `filterBar` | search, reset and the saved filters menu | `filter-bar` |\n| `actionsPanel` | the close tooltip of the actions panel | `actions-panel` |\n| `clampedText` | the expand and collapse labels of clamped text | `clamped-text` |\n| `navbar` | the tooltips of the vertical navbar toggle | `navbar` |\n| `searchExpandable` | the tooltip and the placeholder of the expandable search | `search-expandable` |\n| `appSwitcher` | the search and the header of the app switcher | `app-switcher` |\n| `notificationCenter` | the labels of the notification center | `notification-center` |\n\nThe full contract of a section is its `KbqLocaleConfiguration` type, where every key is documented.\nIts token and its provider are named mechanically: `KBQ__LOCALE_CONFIGURATION` and\n`kbqLocaleConfigurationProvider()`.","examples":[]},{"heading":"Overriding the strings of one component","depth":3,"body":"A section provider overrides only the keys it is given — everything else keeps its default:\n\n```ts\n\nproviders: [kbqCodeBlockLocaleConfigurationProvider({ copyTooltip: 'Copy the snippet' })];\n```\n\nBecause these providers are element-injector friendly, providing one on a component scopes the override to\nthat component's subtree.\n\n\n\nEach helper ships from its own component's package. The exception is `kbqSelectLocaleConfigurationProvider`,\nwhich ships from `@koobiq/components/core`: the `select` section is rendered by four packages that do not\ndepend on one another — `kbq-select`, `kbq-tree-select`, `kbq-tree-selection` and `kbq-list-selection`.\n\nAn override is applied on top of whatever is active — the strings of the active locale when the application\nprovides a service, the token's defaults otherwise. So the keys it names stay pinned across a runtime\n`setLocale()`, while every key it leaves out follows the locale. Override a whole section to stop it\nfollowing the locale entirely; register your own locale (see below) to make the override switch along with\nthe others.","examples":["code-block-with-custom-locale-configuration"]},{"heading":"Overriding the strings of one instance","depth":3,"body":"A provider can only be attached to an injector, so scoping one to a single component means owning a\ncomponent boundary. Every localized component also takes its strings as a template binding:\n\n```html\n\n```\n\nThe value is keyed by locale section — the same shape `addLocale()` and `KBQ_LOCALE_DATA` accept — so one\nbinding can also cover the accessible names the component renders through its own children:\n\n```html\n\n```\n\n\n\nTo scope an override to a whole region rather than one component, put `KbqLocaleOverridesDirective` on any\nelement of the application. Everything rendered inside — including a panel that opens in the overlay\ncontainer — resolves against that element:\n\n```ts\n\n```\n\n```html\n
\n \n \n
\n```\n\nThe two names are not interchangeable. `[localeOverrides]` is the input a Koobiq component declares;\n`[kbqLocaleOverrides]` is the directive's own selector, for the application's elements. Writing the selector\non a component that already carries the directive matches it twice on one element, which Angular rejects\nwith `NG0309`.\n\nSources are applied from the most general to the most local:\n\n1. the configuration token's defaults;\n2. the active locale;\n3. `kbqLocaleConfigurationProvider()`, outermost injector first;\n4. `KbqLocaleOverridesDirective`, outermost element first;\n5. the component's own `[localeOverrides]`.\n\nSteps 3 to 5 only override the keys they mention. Step 2 is the exception: the active locale replaces the\ntoken's defaults outright rather than merging over them, so a value provided for a configuration token is\ndropped as soon as a locale service exists. Override the section instead of providing the token.\n\nA pipe is not an element, so `kbqNumber`, `kbqRoundNumber` and `kbqDataSize` have no `[localeOverrides]` of\ntheir own. A carrier on an ancestor does reach them: a pipe resolves its section from the same injector any\nother reader would.","examples":["select-locale-configuration"]},{"heading":"Registering your own locale","depth":3,"body":"`addLocale()` accepts partial data — every section, and every key within a section, is optional. Whatever is\nleft out is completed from the shipped locale of the same id, or from `KBQ_DEFAULT_LOCALE_ID` when the id is\nnew. `getParams()` therefore always returns a complete section, whatever was registered:\n\n```ts\nlocaleService.addLocale('en-GB', {\n select: { selectAll: 'Select everything' },\n a11y: { close: 'Dismiss' }\n});\n```\n\nThe parameter is typed as `KbqPartialLocaleData`, so a misspelled section or key is a compile error rather\nthan a string that silently never appears.\n\nThe same data can be provided up front through `KBQ_LOCALE_DATA`. The token is read once, by the\n`KbqLocaleService` constructor, so its provider belongs in the same `providers` array as the service:\n\n```ts\nconst localeData = {\n 'en-GB': { select: { selectAll: 'Select everything' } }\n} satisfies KbqLocaleDataInput;\n\nproviders: [{ provide: KBQ_LOCALE_DATA, useValue: localeData }, kbqLocaleServiceProvider()];\n```\n\nThe `satisfies` is load-bearing: `useValue` is declared as `any`, and without it the same typo goes through\nsilently.\n\nA locale added this way is registered, but it is not added to `items`, which is the list a locale picker\nreads. Spell `items` out in full, shipped locales included, if the new locale has to be selectable.","examples":[]},{"heading":"Reading a section yourself","depth":3,"body":"```ts\nconst { selectAll } = localeService.getParams('select'); // KbqSelectLocaleConfiguration\nconst select = localeService.params('select'); // Signal\n```\n\nThe section name is checked against `KbqLocaleSection`, and the return type follows from it.\n\nBoth of those read the locale alone. A component that should honour the overrides above as well — the\nprovider helpers and `[localeOverrides]` — carries the directive and reads through it:\n\n```ts\n\n@Component({\n selector: 'my-widget',\n hostDirectives: [\n { directive: KbqLocaleOverridesDirective, inputs: ['kbqLocaleOverrides: localeOverrides'] }\n ]\n})\nexport class MyWidget {\n protected readonly localeConfiguration = inject(KbqLocaleOverridesDirective, { self: true }).read(\n 'select',\n KBQ_SELECT_LOCALE_CONFIGURATION\n );\n}\n```\n\n`{ self: true }` is required here: with `{ host: true }` Angular walks up to the enclosing component's host\nelement and returns **its** carrier, so the component reads someone else's overrides instead of its own. The\n`hostDirectives` entry has to be spelled out in every component — extracted into a const it fails with\n`NG1010` when used from another entry point.\n\nName the field `localeConfiguration` — the library-wide name for the resolved strings a component reads,\nkept apart from `[localeOverrides]`, which writes a partial. A component that reads a second section\nqualifies it, the way `a11yLocaleConfiguration` does.\n\n`read()` returns a signal, so `setLocale()` reaches the template on its own, and it merges every source in\nthe order listed above. Reading through the carrier is what makes `[localeOverrides]` work on the component:\nthe two cannot come apart. Where there is no element to carry the directive — in a pipe, or in content\ncreated programmatically — `kbqInjectLocaleConfiguration(section, token)` resolves the same sources from the\ninjector instead.","examples":[]},{"heading":"Dates and numbers","depth":3,"body":"Date adapters and the number pipes follow the same service, but they need their own providers.\n`KbqLocaleServiceModule` — pulled in by the date adapter modules — provides it itself.\n\n\n\nTo scope a locale to a subtree that contains dates, provide the adapter and the formatter in the `providers`\nof the same component as `kbqLocaleIDProvider`. Importing the module will not do: `imports: [KbqLuxonDateModule]`\nputs the adapter in the environment injector, where it resolves the root locale service, and month names\nstay in the language of the whole application rather than that of the subtree.","examples":["number-formatter-locale"]}]},{"id":"migration","title":"Migration","lang":"en","since":"21.0.0","lede":"","sections":[{"heading":"How to upgrade from Koobiq 17","depth":2,"body":"New versions include improvements but also contain **breaking changes**; they must be applied step by step.","examples":[]},{"heading":"Upgrade plan","depth":3,"body":"- **Up to 18.5.3**: a safe baseline with updated theming and icons.\n- **18.6**: token update.\n- **18.22**: component attribute changes.\n- **20.0.0**: the move to Angular 20: removal of deprecated APIs and package renames.\n- **20.2.0**: the move of the filter-bar API to signals.\n- **20.2.0**: one shared mechanism for dropdown panel width.\n- **21.0.0**: the move to Angular 21: zoneless change detection, CSS animations instead of `@angular/animations`, and signal inputs, outputs and queries.\n- **21.0.0**: removal of the APIs deprecated before 21.0.0.\n- **21.0.0**: removal of `ThemePalette`.\n- **21.0.0**: removal of the core API nothing in the library used, and deprecation of its legacy API.\n- **21.0.0**: removal of the component API and styles nothing in the library used.\n- **21.0.0**: the actions panel overlay container.\n- **21.0.0**: legacy splitter deprecation.\n- **21.0.0**: removal of the overlay demotion mechanism.\n- **21.0.0**: the move of the app-switcher API to signals.\n- **21.0.0**: the button review — host attributes, group ownership and styles.\n- **21.0.0**: button supported colors — a default color of its own per style.\n- **21.0.0**: the button-toggle review — ARIA semantics, keyboard navigation and signal inputs.\n- **21.0.0**: the form-field review — signals, accessibility and the removal of `mixinColor`.\n- **21.0.0**: the theme service review — signals, `auto` mode and built-in persistence.\n- **21.0.0**: explicit prefix and suffix slots for tag content.\n- **21.0.0**: deprecation of the overlayscrollbars-based Scrollbar implementation.\n- **21.0.0**: the link component instead of the directive — the `kbqLink` selector, slots, `external` and `size`.\n- **21.0.0**: the locale layer typing — a typed `getParams`, partial locale data and signals.\n- **21.0.0**: `multiple` on the selection list and tree became a real, changeable input.\n- **21.0.0**: the component review — closed internals, signal inputs and the behavior fixes it uncovered.\n- **21.0.0**: removal of the deprecated file-upload `fileQueueChanged`/`fileQueueChange` outputs.\n- **21.0.0**: the accordion state store moved into `core`, shared by every component that persists state.\n- **21.0.0**: accordion state saving is on by default, keyed on the document instead of instantiation order.\n- **21.0.0**: tree state saving is on by default, keyed on the value the tree control gives each node.\n- **21.0.0**: tabs, sidebar and content-panel remember what the user changed, on by default.\n- **21.0.0**: the filter bar remembers the selected filter and its edits, on by default.\n- **21.0.0**: the tag list cleaner removes the tags itself and leaves the disabled ones in place.\n- **21.0.0**: the move to `@koobiq/design-tokens` 4 — a new palette scale, DTCG sources and opt-in component tokens.","examples":[]},{"heading":"Upgrade to 18.5.3","depth":3,"body":"{/* migration-step-version(18.5.3) */}\n\n```bash\nnpm install @koobiq/cdk@18.5.3\nnpm install @koobiq/components@18.5.3\nnpm install @koobiq/icons@^9.0.0\nnpm install @koobiq/design-tokens@~3.7.3\nnpm install @koobiq/angular-luxon-adapter@18.5.3\nnpm install @koobiq/date-adapter@^3.1.3\nnpm install @koobiq/date-formatter@^3.1.3\nnpm install luxon\nnpm install @messageformat/core\n```","examples":[]},{"heading":"Token update (18.6.x)","depth":3,"body":"{/* migration-step-version(18.6.0) */}\n\nDeprecated color tokens were removed and typography parameter tokens were renamed.\n\nThe script will rename class and CSS-variable names to the new ones and highlight places where deprecated colors need to be removed (replaced):\n\n```bash\nng g @koobiq/angular-components:css-selectors --fix=true --project \"\"\n```\n\nFor manual review, add `--fix=false`. The script will highlight places where colors and typography names need to be removed (replaced):\n\n```bash\nng g @koobiq/angular-components:css-selectors --fix=false --project \"\"\n```","examples":[]},{"heading":"Attribute update (18.22.0)","depth":3,"body":"{/* migration-step-components(loader-overlay, empty-state) */}\n\nComponent attribute names have changed:\n\n- **KbqLoaderOverlay**: compact → size\n- **KbqEmptyState**: big → size\n\nThe schematic replaces the attributes automatically:\n\n```bash\nng g @koobiq/angular-components:loader-overlay-size-attr --project \"\"\nng g @koobiq/angular-components:empty-state-size-attr --project \"\"\n```","examples":[]},{"heading":"Upgrade to Angular 20","depth":3,"body":"{/* migration-step-version(20.0.0) */}\n\nIn version 20.0.0 the library moves to Angular 20. This is a major release: long-deprecated APIs were removed and some packages were renamed. Requirements: **Angular 20+** and **Node.js ≥ 20.19**.\n\nRemove `@koobiq/cdk` from `package.json` — the package has been merged into `@koobiq/components/core`.","examples":[]},{"heading":"Running the migration","depth":4,"body":"Most of the changes are applied by the `v20-upgrade` schematic (runs automatically):\n\n```bash\nng update @koobiq/components@20\n```\n\nOr manually. To preview without writing — `--fix=false`:\n\n```bash\nng g @koobiq/components:v20-upgrade --project \"\"\n```","examples":[]},{"heading":"Filter-bar upgrade (20.2.0)","depth":3,"body":"{/* migration-step-components(filter-bar) */}\n\nIn version 20.2.0 the public API of `KbqFilterBar` moved to signals. Template bindings (`[filter]`, `[(filter)]`, `[pipeTemplates]`) and the `(filterChange)` output keep working — only programmatic reads break: they now require a call.\n\n| Member | Before | After |\n| --------------------------------------------------------------------------- | ----------------------- | -------------------------------------------------------- |\n| `filter` | accessor | `ModelSignal` — write via `.set(...)` |\n| `pipeTemplates` | accessor | `InputSignal` |\n| `isChanged` / `isDisabled` / `isReadOnly` / `isSaved` / `isSavedAndChanged` | getter | `Signal` |\n| `onChangePipe` | `EventEmitter` | `OutputEmitterRef` |","examples":[]},{"heading":"Running the migration","depth":4,"body":"The changes are applied by the `filter-bar-signals` schematic (runs automatically):\n\n```bash\nng update @koobiq/components@20\n```\n\nOr manually — for example, if you have already upgraded to 20.2.0. To preview without writing — `--fix=false`:\n\n```bash\nng g @koobiq/components:filter-bar-signals --project \"\"\n```","examples":[]},{"heading":"Panel width unification (20.2.0)","depth":3,"body":"{/* migration-step-components(autocomplete, select, tree-select, timezone, dropdown) */}\n\nIn version 20.2.0 `autocomplete`, `select`, `tree-select`, `timezone` and `dropdown` started resolving the width of their dropdown panel through one shared mechanism. They now all expose the same three inputs — `panelWidth`, `panelMinWidth` and `panelMaxWidth` — with the same meaning:\n\n| `panelWidth` | Panel width |\n| ---------------------- | ------------------------------------------------------------------------ |\n| not set (default) | sizes to its content, never narrower than the trigger or `panelMinWidth` |\n| `'auto'` | matches the trigger, never narrower than `panelMinWidth` |\n| a number or CSS string | exactly that width; `panelMinWidth` is not applied |\n\nPanels are now also capped at **640px** through the `--kbq-panel-size-width-max` token. The cap is soft: it limits how far a panel grows with its content, but it never makes a panel narrower than its trigger and never clamps an explicit `panelWidth`. Change it globally by setting the token on `:root`, per component through the component's own token (`--kbq-dropdown-size-container-width-max` still works), or per instance through `panelMaxWidth`.","examples":[]},{"heading":"Running the migration","depth":4,"body":"The `autocomplete-panel-width-auto` schematic runs automatically:\n\n```bash\nng update @koobiq/components@20\n```\n\nOr manually:\n\n```bash\nng g @koobiq/components:autocomplete-panel-width-auto --project \"\"\n```","examples":[]},{"heading":"Upgrade to Angular 21","depth":3,"body":"{/* migration-step-version(21.0.0) */}\n\nIn version 21.0.0 the library moves to Angular 21. Requirements: **Angular 21+**, **TypeScript 5.9** and **Node.js 20.19, 22.12 or 24+**. Update Angular first, then Koobiq:\n\n```bash\nng update @angular/core@21 @angular/cli@21 @angular/cdk@21\nng update @koobiq/components@21\n```","examples":[]},{"heading":"Deprecated API removal (21.0.0)","depth":3,"body":"The APIs deprecated in 20.x are removed in 21.0.0, those whose note promised removal in the next major release and those deprecated without a date alike. A renamed member or alias is rewritten by `v21-upgrade`; for the rest it reports the file and the replacement to use.","examples":[]},{"heading":"Running the migration","depth":4,"body":"`v21-upgrade` is registered for 21.0.0, so an `ng update` to that version runs it:\n\n```bash\nng g @koobiq/components:v21-upgrade --project \"\"\n```\n\nWith `--fix false` it only reports what it would change.","examples":[]},{"heading":"ThemePalette removal (21.0.0)","depth":3,"body":"`ThemePalette` is removed from `@koobiq/components/core`: `KbqComponentColors` describes the colors the components support.\n\n| Removed | Use instead | Migration |\n| --------------------------------------------------------- | -------------------------------------------------------------------- | --------- |\n| `ThemePalette.Error`, `.Warning`, `.Success` | `KbqComponentColors.Error`, `.Warning`, `.Success` | rewritten |\n| `ThemePalette.Primary`, `.Secondary`, `.Info`, `.Default` | no color, or a member of `KbqComponentColors` the component supports | reported |\n| `ThemePalette.Empty` | an unset `color` | reported |\n| the `ThemePalette` type | `KbqComponentColors` | reported |\n\n`Error`, `Warning` and `Success` have the same values in both enums, so the rewrite changes nothing on screen. `Primary`, `Secondary` (`Default` is its alias) and `Info` set a `kbq-primary`, `kbq-secondary` or `kbq-info` class that no component styles, so such a component was drawn without its color rules; without a color it takes its default one. `Empty` is `''`, which also falls back to the default color, while `KbqComponentColors.Empty` is `'empty'`, a color of its own: do not swap one for the other.\n\n`v21-upgrade` rewrites the three members and drops the `ThemePalette` import once nothing else names it. It reports every other use in a file that imports `ThemePalette` from `@koobiq/components/core`, including a field that hands the enum to a template. A re-export of `ThemePalette` from `@koobiq/components/core` is left as is and reported: `KbqComponentColors` has other members and values, so re-export it under its own name and migrate the files that import `ThemePalette` through it.","examples":[]},{"heading":"Unused core API removal (21.0.0)","depth":3,"body":"`@koobiq/components/core` drops the undocumented exports, members and Sass that nothing in the library used. They were never deprecated: most are leftovers of removed features or implementation details that leaked into the public API.\n\n| Removed | Use instead | Migration |\n| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- |\n| `POSITION_MAP`, `EXTENDED_OVERLAY_POSITIONS`, `POSITION_PRIORITY_STRATEGY`, the twelve `*_POSITION_PRIORITY` lists | `kbqPlacement` on the trigger; copy a value you need | reported |\n| `ARROW_BOTTOM_MARGIN_AND_HALF_HEIGHT`, `SELECT_PANEL_VIEWPORT_PADDING`, `NUMBER_FORMAT_REGEXP`, `isWithin()` | a value or function of your own | reported |\n| `KBQ_SELECT_SCROLL_STRATEGY_PROVIDER` | nothing: `KBQ_SELECT_SCROLL_STRATEGY` has a root default; provide the token itself to change it | reported |\n| `KBQ_CHECKABLE_CONTROL_VALUE_ACCESSOR` | nothing: `KbqCheckable` registers itself | reported |\n| `KbqHover` | `(mouseenter)` and `(mouseleave)`, or `:hover` | reported |\n| `CanColor` | `Signal`, or `KbqColorDirective` | reported |\n| `KbqAnimationCurves`, `KbqAnimationDurations` | constants of your own: the library animates in CSS | reported |\n| `KbqRectangleItem`, `countGroupLabelsBeforeOption()` | no replacement | reported |\n| `KbqPopUp.markForCheck()` | nothing after `updateClassMap()` or a change of `visibility`, which re-render the pop-up; for another field, `changeDetectorRef.markForCheck()` in a subclass, `instance.detectChanges()` in a trigger | reported |\n| `KbqPopUp.warning`, `.handleBodyInteraction()`, the protected `closeOnInteraction` | no replacement: `warning` did nothing, nothing called `handleBodyInteraction()`, and `closeOnInteraction` only gated it | reported |\n| `KbqDecimalPipe.isSpecialFormatForRULocale()`, `KbqRoundDecimalPipe.roundingOptions` | private now: the pipe arguments and the formatters locale configuration | reported |\n| `KbqFormElement.isRow` | the `kbq-form__row` class of the element | reported |\n| `KbqLineSetter` and the `kbq-2-line`, `kbq-3-line`, `kbq-multi-line` classes of a list item | nothing: no style read them | reported |\n| `KbqLine`, `KbqListItem.lines` | nothing: only `lines` read `KbqLine`, and nothing counted the lines; the `kbq-line` attribute still places an element in the text of a list item | reported |\n| `KbqSelectSearch.hasPlaceholder()` | the placeholder of the search input | reported |\n| the `mcListIcon` attribute of a list item | `kbq-list-icon` | rewritten |\n| the `mcLine` attribute | `kbq-line` | rewritten |\n\nThe Sass that goes is reachable only through a deep import, since `@koobiq/components` forwards none of it:\n\n- the modules `core/styles/functions` (its index, `number` and `string`), `typography/typography-utils`, `common/clearfix` and `common/popup`;\n- `kbq-markdown-typography-config()`, the `typography-utils` getters (`kbq-typography-level()`, `kbq-font-size()`…), `popup-params`, `input-placeholder`, `rtl-value()` and `kbq-typography-css-variables`;\n- the timing variables of `core/styles/functions/timing` but `$ease-out-circ`, `$ease-in-out-quad` and `$ease-in-out-circ`.\n\nFor typography, use the `--kbq-typography-*` CSS variables or `kbq-typography-level-to-styles-css-variables()` of `core/styles/common/tokens`; copy anything else into your own styles. `v21-upgrade` reports each load and use. The compiled CSS also loses a few prefixes for browsers the library does not support: `-ms-text-size-adjust`, `-ms-overflow-style`, `-moz-` and `-ms-user-select`, the old flexbox `display` values and an `@-moz-document` copy of `.layout-fill`.","examples":[]},{"heading":"Unused component API removal (21.0.0)","depth":3,"body":"The component entry points drop the exports, members and styles that nothing in the library used. They were never deprecated: most are leftovers of removed features or implementation details that leaked into the public API. `v21-upgrade` reports each use.\n\n| Removed | Use instead |\n| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |\n| `normalizeSplitter()`, `isInt()`, `isDigit()` of `@koobiq/components/input` | `value.replace(/,/g, '.')`, `/^-?\\d+$/.test(value)`; `isFloat()` stays |\n| `KbqTimeRangeTranslateTypeMap` | `Record` |\n| `KbqTabBodyOriginState` | `'left' \\| 'right'` |\n| `KbqDatepickerIntl` | nothing: no view rendered its labels; the texts come from `kbqDatepickerLocaleConfigurationProvider()` |\n| `KbqDropdown.onPanelReached()` | nothing: nothing subscribed to it |\n| the protected `KbqTreeSelection.getItemHeight()` | `options.first?.getHeight() ?? 0` |\n| the protected `KbqTreeSelection.setFocusedOption()` | `this.keyManager.setActiveItem(option)` |\n| `KbqTagList.tagSelectionChanges`, `.shouldLabelFloat` | the `selectionChange` output of each tag; `!empty() \\|\\| focused()` |\n| `KbqTreeNodeOutlet.changeDetectorRef` | `ChangeDetectorRef`, injected where you need it |\n\nA subclass loses the protected members nothing read: `changeDetectorRef` of `KbqAccordion`, `KbqAccordionItem`, `KbqPasswordToggle`, `KbqIcon` and `KbqSearchExpandable`, `KbqAccordionTrigger.nativeElement`, `KbqNotificationCenterComponent.dateAdapter`, `KbqFilters.styles`, and `isHorizontalOverflown`, `isVerticalOverflown` and `child` of `KbqTitleDirective`. Inject what the subclass needs itself; `isOverflown` of the title covers both directions.\n\nThe styles lose the rules for classes nothing sets: `kbq-control_has-validate-directive` and `kbq-form-field_has-validate-directive`, left from the `KbqValidateDirective` removed in 20.0.0 (the form field, select and tree select show an error through their `ErrorStateMatcher`), and the `kbq-no-select` utility class: set `user-select: none` in your own styles.","examples":[]},{"heading":"Actions panel overlay container (21.0.0)","depth":3,"body":"{/* migration-step-components(actions-panel) */}\n\n`KbqActionsPanelConfig.overlayContainer` now names the element the panel is rendered **into**, rather than an anchor it was positioned against. The panel is pinned to the bottom center of that element and resizes with it.\n\nWhat that changes for code already setting the option:\n\n- **An element with `overflow: hidden` clips the panel**, and one that scrolls its own content scrolls the panel away with it. At `overflow: visible` the entrance animation renders the panel below the element.\n- **The element is mutated while the panel is open**: it gains one child node holding the overlay, and a `static` element is promoted to `position: relative`, since the overlay is positioned against it.\n- **`maxWidth` is no longer ignored** when `overlayContainer` is set.\n- **A globally provided `OverlayContainer` is bypassed** while the option is set.","examples":[]},{"heading":"Running the migration","depth":4,"body":"`actions-panel-overlay-container` is registered for 21.0.0, so an `ng update` to that version runs it:\n\n```bash\nng g @koobiq/components:actions-panel-overlay-container --project \"\"\n```\n\nIt only reports the call sites — there is nothing to rewrite, because the option is the same one and only its meaning changed.","examples":[]},{"heading":"Legacy splitter deprecation (21.0.0)","depth":3,"body":"{/* migration-step-components(splitter) */}\n\n`@koobiq/components/splitter` now resolves to a rewritten splitter: it lays panels out with CSS grid and adds per-panel size constraints, collapsing, snap points, full keyboard support and the WAI-ARIA `separator` pattern. The previous implementation — `KbqSplitterComponent`, `KbqSplitterAreaDirective`, `KbqGutterDirective`, `KbqGutterGhostDirective`, `KbqSplitterModule` and the `Direction` enum — moved to `@koobiq/components/splitter/deprecated` unchanged, and will be removed in 22.0.0.\n\n**Most unmigrated imports break loudly.** `KbqSplitterComponent`, `KbqSplitterAreaDirective`, `KbqGutterDirective`, `KbqGutterGhostDirective` and `Direction` live at the `/deprecated` path only, so the build stops with `TS2305: … has no exported member`.\n\n**`KbqSplitterModule` is the silent one — it is the case this migration exists for.** Both entry points export a class under that name, and the `kbq-splitter` selector exists on both sides, so an unmigrated import keeps compiling but now pulls in the rewritten splitter. That one projects `kbq-splitter-panel` children instead of `[kbq-splitter-area]` hosts and takes `orientation` rather than `direction`, so the group renders with no panels and no separators.\n\nTemplates need no changes of their own: once the import points at `/deprecated`, `` with `[kbq-splitter-area]` hosts, `direction`, `useGhost`, `hideGutters` and `gutterSize` keeps working exactly as before.","examples":[]},{"heading":"Running the migration","depth":4,"body":"`splitter-deprecated-path` is registered for 21.0.0, so an `ng update` to that version runs it:\n\n```bash\nng g @koobiq/components:splitter-deprecated-path --project \"\"\n```\n\nIt rewrites the `@koobiq/components/splitter` specifier to `@koobiq/components/splitter/deprecated` across every `.ts` file, and running it twice changes nothing. It does not move you onto the new splitter: that is separate manual work — areas become `kbq-splitter-panel`, `direction` becomes `orientation`, and sizes move from `min-width` and CSS `flex` onto the `size` and `minSize` inputs.","examples":[]},{"heading":"Overlay demotion removal (21.0.0)","depth":3,"body":"{/* migration-step-components(dropdown, select, popover, navbar, top-bar) */}\n\nUntil 21.0.0 an open `dropdown`, `select` or `popover` panel lowered the **shared, app-wide** `.cdk-overlay-container` from `z-index: 1000` to `999` by adding a `.cdk-overlay-container_dropdown` class to it. The point was to let a panel slide under a sticky `kbq-navbar` / `kbq-top-bar` while the page scrolled. `KbqDropdownTrigger.demoteOverlay` turned that off for one trigger, and the `KBQ_DROPDOWN_HOST` marker token — provided by `KbqNavbar` and `KbqTopBar` — flipped its default to `false` so a dropdown inside the chrome would not end up behind its own trigger.\n\nThe whole mechanism was removed: the input, the token, the class and the stylesheet rule. It operated on the container rather than on individual overlays, so it could not lower a panel without lowering every other overlay — modals, sidepanels, toasts and tooltips included — and it did so from whichever component happened to open first.\n\n**Panels now render above `kbq-navbar` and `kbq-top-bar` instead of sliding under them.** They open in the browser top layer (see Overlays in the top layer in Upgrade to Angular 21); with `usePopover: false` the overlay container stays at `z-index: 1000`, above the bars at `990`.","examples":[]},{"heading":"Running the migration","depth":4,"body":"The `dropdown-demote-overlay` schematic runs automatically:\n\n```bash\nng update @koobiq/components@21\n```\n\nOr manually:\n\n```bash\nng g @koobiq/components:dropdown-demote-overlay --project \"\"\n```","examples":[]},{"heading":"App-switcher upgrade (21.0.0)","depth":3,"body":"{/* migration-step-components(app-switcher) */}\n\nIn version 21.0.0 `KbqAppSwitcherTrigger` moved `selectedApp` and `selectedSite` from a plain `@Input()` (plus a matching `output()`) to `model()`, and a review of the component removed several members that never did anything. Template bindings keep working — `[selectedApp]`, `[(selectedSite)]` and `(selectedAppChange)` are unchanged — so only programmatic access and reads through a `#ref=\"kbqAppSwitcher\"` template reference variable break.\n\n| Member | Before | After |\n| ------------------------------------------------------------------------------------ | ------------------- | ------------------------------------------------------------------ |\n| `selectedApp` | `@Input()` property | `ModelSignal` — write via `.set()` |\n| `selectedSite` | accessor input | `ModelSignal` — **value changed** |\n| `selectedAppChange` / `selectedSiteChange` | `output()` | the implicit outputs of the models above |\n| `header` / `footer` | properties | removed |\n| `KbqAppSwitcherComponent.isTrapFocus` / `updateTrapFocus()` | public API | removed |\n| `KbqAppSwitcherDropdownApp.getIcon()` | public method | removed |\n| `KbqAppSwitcherComponent.activeSite` / `activeApp` | protected | removed: the context of the flyout's `kbqDropdownContent` template |\n| `KbqAppSwitcherListItem.collapsed` | property | `ModelSignal` |\n| `app` / `site` inputs of `KbqAppSwitcherListItem` / `-DropdownApp` / `-DropdownSite` | optional inputs | `input.required` signals |","examples":[]},{"heading":"Running the migration","depth":4,"body":"The changes are applied by the `app-switcher-signals` schematic (runs automatically):\n\n```bash\nng update @koobiq/components@21\n```\n\nOr manually — for example, if you have already upgraded to 21.0.0. To preview without writing — `--fix=false`:\n\n```bash\nng g @koobiq/components:app-switcher-signals --project \"\"\n```","examples":[]},{"heading":"Button review (21.0.0)","depth":3,"body":"{/* migration-step-components(button, button-group) */}\n\nThe review of `[kbq-button]` changed three unrelated things at once. Nothing here has a deprecation period — the old behaviour is simply gone — but only one of the changes stops your code from compiling.\n\n**Host attributes are now chosen by host tag.** Until 21.0.0 a disabled button rendered `disabled` _and_ `aria-disabled=\"true\"`, whatever the host element was. `disabled` is not a valid attribute on an anchor and was ignored by the browser, while `aria-disabled` on a native `
\n```\n\nIf you have more than two themes and need a list rather than a light/dark switch, you can pin one by name. See [Core](/en/components/core) for the details and a live example.","examples":[]},{"heading":"Theme selectors","depth":4,"body":"| Theme | Selector |\n| ----- | ---------- |\n| Light | .kbq-light |\n| Dark | .kbq-dark |","examples":[]},{"heading":"Settings","depth":4,"body":"Pass the settings when the app starts:\n\n```ts\n\nbootstrapApplication(AppComponent, {\n providers: [kbqThemeProvider({ mode: 'auto', storageKey: 'my-app-theme' })]\n});\n```\n\nThe choice is saved between visits — in `localStorage` by default, where switching the theme in one tab is picked up by the app's other open tabs. Apps rendered on the server can store it in a cookie instead, so the server already knows which theme to render: provide `KbqThemeCookieStore` through the `KBQ_THEME_STORE` token. It reads the cookie straight off the incoming request, so nothing else has to be wired up on the server.","examples":[]},{"heading":"Using theme values in your own styles","depth":3,"body":"Write the variable where you need the value:\n\n```css\n.my-component-text {\n color: var(--kbq-foreground-contrast-secondary);\n}\n```\n\nNothing to import, no mixin to write, nothing to register in a single theme entry point. The value resolves in the browser, so it changes on its own when the theme changes.\n\n\n\nThe full list of global variables is on the [Design tokens](/en/main/design-tokens/colors) page.","examples":["theme-css-variables"]},{"heading":"Overriding a component's variables","depth":3,"body":"When a component's default look doesn't fit, change the values of its variables rather than its styles. Start by knowing where those variables are declared, because that determines how you reach them. Every component sets them on its own selector, in a file next to itself:\n\n```css\n/* button-tokens.scss */\n.kbq-button,\n.kbq-button-icon {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-contrast);\n}\n```\n\nThe button sets the variable on itself, so a blanket declaration in `:root` simply won't be seen. For an override to work, you have to hit that same element.\n\nThat leaves two approaches.\n\n**Change a single instance.** Give it a class of your own and set the variable there. The class lands on the same element as the default value and, coming later, wins:\n\n```html\n\n\n\n```\n\n**Change every component of that type in the app.** This needs a selector that outranks the component's own. The simplest one to add is the theme selector — which also lets you give light and dark different values, when one color won't do for both:\n\n```css\n.kbq-light .kbq-button {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error);\n}\n\n.kbq-dark .kbq-button {\n --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error-fade);\n}\n```\n\nRules like these are worth keeping in one file, loaded after the design system's styles — then all of the app's overrides live in one place instead of scattering across components.","examples":[]},{"heading":"Building your own component","depth":3,"body":"Your component reads the same global variables the library reads. Give it a stylesheet and turn off style encapsulation — that is the whole setup:\n\n```ts\n@Component({\n selector: 'my-card',\n templateUrl: './my-card.html',\n styleUrl: './my-card.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class MyCard {}\n```\n\n```scss\n.my-card {\n padding: var(--kbq-size-l);\n border: var(--kbq-size-border-width) solid var(--kbq-line-contrast-less);\n border-radius: var(--kbq-size-border-radius);\n\n background: var(--kbq-background-card);\n color: var(--kbq-foreground-contrast);\n}\n```\n\nThere is **no** need to write a separate `_my-card-theme.scss` mixin and then register it in a single theme entry point.","examples":[]},{"heading":"Corner shape","depth":4,"body":"Where the browser supports `corner-shape`, components round their corners with a squircle (`corner-shape: squircle`); other browsers get a plain radius. A squircle looks smaller than an arc of the same radius, so its radius is scaled up. To give your component the same corners as the library's, include the mixin:\n\n```scss\n@use '@koobiq/components/core/styles/common/squircle' as *;\n\n.my-card {\n @include kbq-squircle(var(--kbq-size-border-radius));\n}\n```\n\nWithout SCSS, write the same rule by hand:\n\n```css\n.my-card {\n --my-card-radius: var(--kbq-size-border-radius);\n\n border-radius: var(--my-card-radius);\n}\n\n@supports (corner-shape: squircle) {\n .my-card {\n corner-shape: squircle;\n border-radius: clamp(\n var(--my-card-radius) * 1.85,\n var(--my-card-radius) * 2.3 - 3.6px,\n var(--my-card-radius) * 2\n );\n }\n}\n```\n\nTo change a library component's radius, set its variable (for example `--kbq-button-size-border-radius`) rather than `border-radius`. The scaling applies to the variable: a plain `border-radius` leaves the corner a squircle that looks too small. A component made round with `50%` also needs `corner-shape: round`.","examples":[]},{"heading":"Rebranding with a single file","depth":3,"body":"You can restyle the whole app by overriding CSS variables in one place:\n\n```css\n.kbq-light {\n --kbq-background-contrast: #1a3a6b;\n --kbq-foreground-contrast: #0d1b2a;\n}\n\n.kbq-dark {\n --kbq-background-contrast: #7ba7e8;\n --kbq-foreground-contrast: #e8eef7;\n}\n```\n\nLoad that file after the design system's own, and every component follows.\n\nOne honest limitation: this works at the global layer. Component variables are declared on the component's selector, so a single global file can't reach them — you would need a rule per component, as shown above.","examples":[]},{"heading":"Where to find the variables","depth":3,"body":"Global variables, with their values and previews: [Design tokens](/en/main/design-tokens/colors).\n\nThe variables of a component are declared next to its code, in its tokens file, such as `button-tokens.scss` for the button: open it from the **Source code** link on the page of the component. In a browser, the developer tools show them on the element of the component, as custom properties such as `--kbq-button-*`.","examples":[]},{"heading":"Where the values come from","depth":3,"body":"**Design system tokens** are the values that define how our components look. They live in the [@koobiq/design-tokens](https://github.com/koobiq/design-tokens) package.\n\n**Component variables** are the values used inside component styles. They are derived from the design tokens and live in the `@koobiq/components` repository, next to the components themselves.\n\n
\n
Note
\n
\n\nVersion 4.0.0 of `@koobiq/design-tokens` removed the component tokens, except for code-block, scrollbar and skeleton. Their tokens ship as separate files — `component-tokens.css` and its light and dark parts — and their values are already part of `@koobiq/components`' own `prebuilt-themes/theme.css`, so you don't need to load them yourself. Everything else is carried by the components themselves. If copies of the removed tokens are still in your own `css-tokens.css`, `css-tokens-light.css`, `css-tokens-dark.css` or `css-tokens-font.css`, delete them, and run `npx koobiq-tokens-codemod src` to find what else the upgrade touches.\n\n
\n
","examples":[]}]},{"id":"which-component-when","title":"Which component when","lang":"en","since":"21.0.0","lede":"# which_component_when data source\n\nNot a page of koobiq.io. This file is the data the `which_component_when` tool of the Koobiq MCP server parses; it\nis `.md` rather than `.mdx` on purpose, so the docs site build skips it — see [Provenance](#provenance).\n\nMaps a **task** to a Koobiq component, and names the neighboring component it is confused with.\n\nWritten for the `which_component_when` tool of the Koobiq MCP server. An agent without this mapping picks the\ncomponent whose name looks like a word in the request — the frequent failure is not \"found nothing\" but \"found the\nneighbor\": `autocomplete` for a multi-select, `toast` for a message that must be acknowledged, `tabs` for a form\ncontrol.","sections":[{"heading":"How to read it","depth":2,"body":"- [Disambiguation](#disambiguation) — the clusters where the neighbor is picked. The useful part; read it first.\n- [Component index](#component-index) — one line per component, grouped by the job it does.\n- [Keyword index](#keyword-index) — words a request is likely to use, mapped to the cluster to read.\n\nEach disambiguation row answers three things: what to use, what not to use, and why not. \"Why not\" is the part an\nagent cannot reconstruct from component names.","examples":[]},{"heading":"Provenance","depth":2,"body":"The component docs carry the usage prose for roughly a third of the library:\n\n| Source | What it actually holds |\n| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `packages/components/*/.en.mdx` — intro paragraph | Present for every component. One sentence, says _what_ it is; _when_ only by implication |\n| `### When to use` | 7 components: `tree`, `modal`, `sidepanel`, `timepicker`, `loader-overlay`, `link`, `dynamic-translation` |\n| `### Recommendations` / `### Guidelines` | 15 components: `accordion`, `badge`, `button-group`, `inline-edit`, `list`, `popover`, `radio`, `select`, `skeleton`, `split-button`, `tabs`, `tag`, `title`, `toggle`, `tooltip` |\n| `modal.en.mdx` → `### Related components` | The richest comparison text in the repo: modal against popover, sidepanel, inline forms, accordion, toast, a new page, undo |\n| Cross-links in prose | Scattered \"use X instead\": `list` → `select`/`tree`, `title` → `tooltip`/`popover`, `tag` → `badge`, `tabs` → `navbar` |\n| `*.ru.mdx` | A faithful mirror of the English pages — same sections, same count. No extra guidance to mine |\n| `packages/docs-examples/**` `@title` | 623 titles, but they name features (\"Select multiple\"), not tasks. Usable as keywords only |\n| `apps/docs/src/llms.txt`, `llms-full.txt` | A link index and the same MDX text. No formulations of their own |\n\nEverything else below — the clusters, the \"why not\", the per-component lines for the ~50 components that document\nonly what they are — was written for this guide from the docs, the public API and the component code. Keep it here\nrather than scattering it back into the MDX pages: the comparisons cross component boundaries, and a page about one\ncomponent is the wrong place to say which other component wins.\n\nThe MCP server reads this file as an ordinary guide — `build-guides.ts` picks it up from `docs/guides` along with\nthe rest, in both languages — but it never becomes a page of koobiq.io: `tools/docs-pages/sources.ts` globs only\n`docs/guides/**/*.mdx`, and the `.md` extension here is deliberate, not an oversight. This file is the single\nsource of truth — the server holds no copy of the mapping.","examples":[]},{"heading":"Disambiguation","depth":2,"body":"","examples":[]},{"heading":"Pick a value from a set","depth":3,"body":"| Task | Use | Not | Why not |\n| ------------------------------------------------------------ | ----------------------------------------------- | --------------------- | ------------------------------------------------------------------------------- |\n| One value from a closed list, in a form | `select` | `autocomplete` | Autocomplete is a text field that suggests; it accepts what the user types |\n| Several values from a closed list | `select` + `multiple` | `autocomplete` | Autocomplete has no `multiple` input at all |\n| Several values from a long list (hundreds) | `select` + `multiple` + search + virtual scroll | `tree-select` | Tree select is for data that _is_ a hierarchy, not for data that is merely long |\n| Several values from a hierarchy | `tree-select` + `multiple` | `select` | Select flattens: the parent-child relation is lost |\n| One value, list comes from a server query as the user types | `autocomplete` | `select` | Select owns its option list; it has no text input to drive a query |\n| Several values, user may enter values outside the dictionary | `tag-autocomplete` | `select` + `multiple` | Select cannot accept a value that is not an option |\n| Several free-form values, no dictionary | `tag-input` | `tag-autocomplete` | Autocomplete's dropdown with nothing to suggest is noise |\n| Show and remove values chosen elsewhere | `tag-list` | `tag-input` | Tag input also _enters_ values; a read-only set does not need that |\n| 2–7 exclusive options, all visible | `radio` | `select` | Hiding a handful of options behind a trigger costs a click for nothing |\n| More than 7 exclusive options | `select` | `radio` | Docs cap a radio group at ~7 rows |\n| 3–5 exclusive options, short labels, room in a row | `button-toggle` | `radio`, `tabs` | Radio spends a row per option; tabs switch content and are not a form control |\n| Independent on/off options, saved with the form | `checkbox` | `toggle` | A toggle promises the change already took effect |\n| One setting that applies instantly | `toggle` | `checkbox` | A checkbox implies a later Save |\n| Partially selected group header | `checkbox` + `indeterminate` | two checkboxes | The third state is built in |\n| Select rows of page content, not a form value | `list` + `multiple=\"checkbox\"` | `select` | Select is a form control behind a trigger; a list is content |\n| Hundreds of items in a hierarchy | `tree` | `list` | Docs: without hierarchy and volume, a plain list is better |\n| A menu of commands | `dropdown` | `select` | Select holds a value; a dropdown runs actions |\n\nFor the long multi-select: project `[kbqSelectSearch]` into the panel (docs recommend search past 10 options) and\nwrap the options in `cdk-virtual-scroll-viewport`. With object values, supply `virtualOptionFactory` so the trigger\ncan label a value whose option is not rendered. The \"select all\" row is not rendered together with virtual scroll —\nit can only act on options currently rendered.","examples":[]},{"heading":"Show that something is in progress","depth":3,"body":"| Task | Use | Not | Why not |\n| ----------------------------------------------------------- | ------------------------------------ | -------------------- | ----------------------------------------------------------------------------- |\n| Share of the work is known and worth showing | `progress-bar` | `progress-spinner` | A spinner cannot show how much is left; put the remaining time beside the bar |\n| Duration unknown, short wait, little room, a single element | `progress-spinner` | `progress-bar` | A bar with `indeterminate` claims a width it does not have |\n| A whole block is busy and must not be touched | `loader-overlay` | `progress-spinner` | A bare spinner does not stop interaction with the block underneath |\n| Page or section content loading, 2–10 s | `skeleton` | `progress-spinner` | Docs: skeletons suit full-page loading and keep the layout from jumping |\n| Loading takes under 1 s | nothing | `skeleton` | Docs: no indicator is needed below a second |\n| A button's own action takes over 1–2 s | `class=\"kbq-progress\"` on the button | `loader-overlay` | Overlaying the page for one button is out of proportion |\n| A long file upload | `file-upload` (own progress) | `progress-bar` alone | The component already reports per-file state |","examples":[]},{"heading":"Tell the user something","depth":3,"body":"| Task | Use | Not | Why not |\n| ---------------------------------------------------------- | ---------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------- |\n| An error the user must acknowledge before continuing | `modal` | `toast`, `alert` | A toast is allowed to be missed; an alert sits in the page and does not block |\n| Result of an operation, no response needed, may be missed | `toast` | `modal` | Docs: avoid modality unless an answer is required |\n| A standing status or problem tied to a page region | `alert` | `toast` | A toast disappears; a reported problem has to stay (a non-closable alert) |\n| A message the user should be able to re-read later | `notification-center` | `toast` | A toast leaves no trace — pair the two when both matter |\n| \"Are you sure?\" before a destructive action | do it, offer undo | `modal` | Docs name the undo pattern as the preferred one; keep the modal for the irreversible case |\n| A short text hint | `tooltip` | `popover` | A popover takes focus and expects interaction |\n| Full text, but only when it is truncated | `kbq-title` | `tooltip` | A plain tooltip fires even when the text fits |\n| Content with controls or links beside a trigger | `popover` | `tooltip`, `dropdown` | A tooltip holds no controls; a dropdown has no Tab navigation inside |\n| Nothing to show: no data, no search results, a failed load | `empty-state` | `alert` | An alert annotates content; here there is no content |\n| Validation errors of one field | `form-field` hints + `ErrorStateMatcher` | `alert` | The matcher decides _when_ errors surface; the field renders them |","examples":[]},{"heading":"An extra layer over the page","depth":3,"body":"| Task | Use | Not | Why not |\n| ---------------------------------------------- | --------------- | ----------- | ----------------------------------------------------------------------------- |\n| Short blocking task, a small form, settings | `modal` | `sidepanel` | Narrow and tall content reads badly in a modal; wide content badly in a panel |\n| A lot of data, tall narrow content, stacking | `sidepanel` | `modal` | Docs: modals should not be stacked; non-modal sidepanels stack |\n| Preview a row while the table stays usable | `content-panel` | `sidepanel` | A content panel shifts adjacent content instead of covering it |\n| A permanent collapsible region of the layout | `sidebar` | `sidepanel` | A sidepanel is a transient window, not part of the layout |\n| Two regions the user resizes | `splitter` | `resizer` | The resizer directive resizes one element, with no neighbor logic |\n| One element resizable by drag | `resizer` | `splitter` | A splitter owns the layout of the panels inside it |\n| A small dialog by a trigger, page stays usable | `popover` | `modal` | Docs: a popover claims attention less disruptively |\n| Commands by a trigger | `dropdown` | `popover` | A popover captures focus; a command menu does not need to |\n| Bulk actions over selected rows | `actions-panel` | `top-bar` | The panel appears with the selection and counts it |","examples":[]},{"heading":"Text that does not fit","depth":3,"body":"| Task | Use | Not | Why not |\n| ---------------------------------------------------- | ----------------- | ---------------- | -------------------------------------------------------------- |\n| One line cut at the end, full text on hover when cut | `kbq-title` | `tooltip` | Only `kbq-title` keeps quiet while the text fits |\n| One line where the start and the end both matter | `ellipsis-center` | `kbq-title` | Paths, hashes and file names lose their meaning cut at the end |\n| Multi-line text collapsed to N lines, with expansion | `clamped-text` | CSS `line-clamp` | The component owns the trigger, its a11y and the locale text |\n| A list collapsed to N items, with expansion | `clamped-list` | `overflow-items` | A clamped list counts items; overflow items measure width |\n| A row of items collapsed to the container width | `overflow-items` | `clamped-list` | The limit here is pixels, and it changes as the box resizes |","examples":[]},{"heading":"Tabular and structured data","depth":3,"body":"| Task | Use | Not | Why not |\n| -------------------------------------------------- | ---------------------------------------------- | ------------------- | ------------------------------------------------------------ |\n| A static table, no sorting, no column resizing | `table` | AG Grid | A data grid is a large dependency for plain rows |\n| Sorting, column resize and reorder, virtual scroll | AG Grid (`docs/data-grid`) | `table` | Docs: the table offers only what native HTML tables do |\n| Attributes of one entity, term → value | `dl` | `table` | A two-column table has to re-implement the responsive layout |\n| Filtering a table or a list, with saved filters | `filter-bar` | `select` per column | The filter bar owns pipes, search, saving and state restore |\n| Highlight why a row matched | `kbqHighlightBackground` / `mcHighlight` pipes | manual markup | The pipes wrap matches in `` with the right class |\n| Items the user picks from | `list` | `table` | A table row is not a selection control |","examples":[]},{"heading":"Navigation","depth":3,"body":"| Task | Use | Not | Why not |\n| -------------------------------------------------- | --------------- | -------------- | --------------------------------------------------------------- |\n| Main product navigation | `navbar` | `tabs` | Docs name tabs for in-page navigation only |\n| A toolbar that stays visible: title, actions | `top-bar` | `navbar` | The navbar is product-level; the top bar belongs to the screen |\n| Where the user is in a hierarchy | `breadcrumbs` | `top-bar` text | Breadcrumbs navigate and collapse under `max` |\n| Switch between groups of content on one page | `tabs` | `accordion` | Tabs show one group at a time; an accordion can show several |\n| Switch the view of the same data (list/tile/table) | `button-toggle` | `tabs` | Docs list this as a misuse of tabs |\n| Reveal extra content in place | `accordion` | `modal` | Docs: an accordion or \"show more\" is the least intrusive option |\n| Between applications and platforms | `app-switcher` | `dropdown` | The switcher is the agreed pattern and shape for this |\n| Go to another page, or a button inside a sentence | `link` | `button` | For a button in running text, use the pseudo-link variant |","examples":[]},{"heading":"Editing existing data","depth":3,"body":"| Task | Use | Not | Why not |\n| -------------------------------------------------- | ----------------------- | ----------------------- | ------------------------------------------------------------------ |\n| Change one or two fields among many, saved at once | `inline-edit` | a form | Docs: inline edit saves on change, without a global Save |\n| Create a new entity | a form (`form-field`) | `inline-edit` | Docs rule out inline edit for creation |\n| Fields that validate each other, masks, prompts | a form | `inline-edit` | Docs rule out inline edit for dependent or complex validation |\n| Critical data, or a save that needs confirmation | `modal` with a form | `inline-edit` | Docs rule out inline edit here too |\n| Pick a date | `datepicker` | `input` | Masking, parsing and calendar come with the component |\n| Pick a date range | two `datepicker` inputs | one datepicker | There is no range widget; bind `minDate`/`maxDate` and `min`/`max` |\n| Pick a time | `timepicker` | `input` | Same reason as the datepicker |\n| Pick a period (\"last 24 hours\", or a manual range) | `time-range` | two `timepicker` fields | The menu carries the presets |\n| Pick a timezone | `timezone` | `select` | It extends select with the timezone option template |\n| Upload files | `file-upload` | `input[file]` | Drag area, size, errors and the list come with it |\n| A search field that should stay small until used | `search-expandable` | `input` | It collapses into an icon button; bind it to a form control |","examples":[]},{"heading":"Labels, marks, identity","depth":3,"body":"| Task | Use | Not | Why not |\n| ------------------------------------------------- | ------------- | ---------- | ----------------------------------------------------------- |\n| A status or count label in a table or a list | `badge` | `tag` | Docs: tags belong inside input controls, badges stand alone |\n| A token for a value chosen in a control | `tag` | `badge` | Same rule, read the other way |\n| A country | `flag` | `icon` | The flag shapes and crops whatever image you pass |\n| A decorative icon with a backing, in place of art | `icon-item` | `icon` | The backing is what draws the eye |\n| A clickable icon | `icon-button` | `icon` | The button carries the hit area, states and focus ring |\n| An internal system user | `username` | plain text | The pattern fixes which attributes identify a person |","examples":[]},{"heading":"Component index","depth":2,"body":"One line each: when to reach for it. `→` names the neighbor to prefer in the case that follows.","examples":[]},{"heading":"Choosing values","depth":3,"body":"- `select` — one or more values from a list the application owns; search past 10 options, virtual scroll for long lists. → `autocomplete` when the value is typed, `tree-select` when the data is a hierarchy.\n- `tree-select` — one or more values from hierarchical data, with the tree kept visible while searching.\n- `autocomplete` — a text field that suggests matches while typing, including server-driven suggestions; single value.\n- `tag-autocomplete` — several values from a dictionary, with custom values allowed; the dropdown is an autocomplete panel.\n- `tag-input` — several free-form values entered as tags.\n- `tag-list` — a set of tags with keyboard navigation, selection and removal; no entry of its own.\n- `tag` — one token inside an input control. → `badge` for a standalone label.\n- `list` — related items as page content, single or multiple (`multiple=\"checkbox\"` when the state must be visible at a glance); virtual scroll for long lists, but not together with dragging.\n- `tree` — hundreds of items the user navigates as a catalog. → `list` without hierarchy or volume.\n- `radio` — up to ~7 mutually exclusive options, all visible.\n- `checkbox` — independent options, and the group state via `indeterminate`.\n- `toggle` — a binary setting that applies immediately, with no Save.\n- `button-toggle` — 3–5 short exclusive options in one row, in a form or a filter.\n- `dropdown` — a menu of actions or options beside a trigger, with disabled, loading and footer states.\n- `timezone` — timezone selection; `kbq-select` with a timezone option template, no multiple selection.\n- `time-range` — a period: a preset, or boundaries entered by hand.","examples":[]},{"heading":"Data entry","depth":3,"body":"- `form-field` — the wrapper that gives a control its label, hint, error and cleaner; input, select, textarea, autocomplete, tag input, tag autocomplete, timepicker, timezone and tree select are built to sit inside it.\n- `input` — text, number and password entry with formatting, masks and validation.\n- `textarea` — multi-line text.\n- `datepicker` — a date, typed or picked from the calendar; `minDate`/`maxDate` bound the calendar, `min`/`max` on the input drive validation.\n- `timepicker` — a time, typed into a masked field.\n- `file-upload` — files, single or multiple, with a drop area, sizes, icons and per-file errors.\n- `search-expandable` — a search field that expands from an icon button; it is a form control and throws without one.\n- `inline-edit` — edit a value where it is displayed, saving at once. → a form for creation, dependent fields, complex validation or critical data.\n- `filter-bar` — filtering a table or a list: typed pipes, search inside pipes, required and locked filters, saved filters, state restore.","examples":[]},{"heading":"Actions","depth":3,"body":"- `button` — an action, on a native `