# Koobiq Angular: full documentation > Koobiq is an open-source design system for designers and developers, focused on designing products related to **information security**. `@koobiq/components` 21.x for Angular 21. This file holds every item of https://koobiq.io/llms.txt in English, one after another. Rules for code that uses Koobiq: - Import from the secondary entry points only, for example `import { KbqButtonModule } from '@koobiq/components/button'`: the root of the package exports nothing. Shared providers, tokens and utilities come from `@koobiq/components/core`. - Start from the closest example of an item instead of guessing its inputs. The API of its entry point lists every input and output; where the text of a page disagrees with it, the API is right. - Put text controls, such as `input[kbqInput]`, `textarea[kbqTextarea]` and `kbq-select`, inside ``; their hints and errors go inside it too. - Style with the `--kbq-*` design tokens and the `kbq-*` typography classes; never hard-code colors, sizes or fonts. - Icons come from `@koobiq/icons` through ``; an icon-only button needs an accessible label. - Do not use anything marked as deprecated in new code. ## Installation - Page: https://koobiq.io/en/main/installation/overview This guide describes how to set up an Angular project to use `@koobiq/components`. ### Installing dependencies Installation using [Angular CLI](https://angular.dev/cli/add) — recommended, because it installs the Angular packages at the version your application is already on: ```bash ng add @koobiq/components ``` `ng add` also adds the styles to `angular.json`, the theme classes to `index.html` and the theme provider to the application. `--theme` picks `light`, `dark` or `auto`, the default, which follows the system color scheme. The locale and the fonts are yours to set, as described below. Manual installation. First install `@angular/cdk` at the version your `package.json` already has for `@angular/core`: ```bash npm install @angular/cdk@^21.2.0 ``` Then install the library and the rest of its dependencies: ```bash npm 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 ``` `overlayscrollbars` is pinned to an exact version rather than a range: the scrollbar is built against that specific release, and this is the version `ng add` installs too. `@koobiq/design-tokens` is held to major version 4, the one this version of the library is built against. `@koobiq/angular-luxon-adapter` (or `@koobiq/angular-moment-adapter`) is only needed if you use the date components — [datepicker](https://koobiq.io/en/components/datepicker.md), [timepicker](https://koobiq.io/en/components/timepicker.md) or [filter-bar](https://koobiq.io/en/components/filter-bar.md). Either one is a wrapper around a base adapter that has to be installed next to it — `@koobiq/luxon-date-adapter` or `@koobiq/moment-date-adapter`. npm adds that base package on its own, Yarn and pnpm do not, so install it explicitly. The adapter is provided next to the locale, see below. Install `marked` if you use [markdown](https://koobiq.io/en/components/markdown.md), `highlight.js` if you use [code-block](https://koobiq.io/en/components/code-block.md), and `@angular/router` if you use [breadcrumbs](https://koobiq.io/en/components/breadcrumbs.md). ### Change detection The components do not need zone.js. They render in a zoneless application, the default since Angular 21, and keep working in an application that loads zone.js and provides `provideZoneChangeDetection()`. Nothing has to be configured for them. To make an existing application zoneless, remove `provideZoneChangeDetection()` from its providers (`ng update` to Angular 21 adds it to a bootstrap that has no change detection provider) and `zone.js` from its `polyfills`. `provideZonelessChangeDetection()` only states the default: next to `provideZoneChangeDetection()` it logs the NG0408 warning, and the order of the providers decides which one applies. ```typescript import { provideZonelessChangeDetection } from '@angular/core'; bootstrapApplication(AppComponent, { // No provideZoneChangeDetection(); this provider is optional. providers: [provideZonelessChangeDetection()] }); ``` ### Animations The components animate with CSS and need no setup: neither `@angular/animations` nor `provideAnimations()`. The motion is off when the operating system asks for reduced motion (`prefers-reduced-motion: reduce`). To turn it off in the whole application, for example in an end-to-end suite, provide `KBQ_ANIMATIONS_CONFIG`: ```typescript import { KBQ_ANIMATIONS_CONFIG } from '@koobiq/components/core'; bootstrapApplication(AppComponent, { providers: [{ provide: KBQ_ANIMATIONS_CONFIG, useValue: { animationsDisabled: true } }] }); ``` ### Setting up styles After installation, you need to include the library styles. Add the following files to the `styles` array in your `angular.json` file: ```json "styles": [ "node_modules/@koobiq/icons/fonts/kbq-icons.css", "node_modules/@koobiq/design-tokens/web/css-tokens.css", "node_modules/@koobiq/design-tokens/web/css-tokens-light.css", "node_modules/@koobiq/components/prebuilt-themes/theme.css", "src/styles.css" ] ``` `@koobiq/design-tokens` ships code-block, scrollbar and skeleton tokens as opt-in files. With the default setup you need none of them: their values are already compiled into `prebuilt-themes/theme.css`. Load the files yourself only if you build the library styles from SCSS sources: ```scss @use '@koobiq/design-tokens/web/component-tokens'; @use '@koobiq/design-tokens/web/component-tokens-light'; @use '@koobiq/design-tokens/web/component-tokens-dark'; ``` To change the default values, don't load the files — set the CSS variables in your own stylesheet, see the [theming guide](https://koobiq.io/en/main/theming.md). ### Setting up theme Add the theme classes to the `` element in your `index.html` file: `kbq-app-background` paints the page background and text, `kbq-light` picks the theme. ```html ``` For the dark theme, or to switch themes, also add `css-tokens-dark.css` to the styles; see the [theming](https://koobiq.io/en/main/theming.md) section. ### Setting the locale The components render some strings of their own: placeholders, tooltips, the accessible names of icon buttons. Until the application provides a locale, they use the `ru-RU` strings, and switching the locale at runtime does nothing. Provide the locale of your application: ```typescript import { kbqLocaleIDProvider, kbqLocaleServiceProvider } from '@koobiq/components/core'; bootstrapApplication(AppComponent, { providers: [kbqLocaleIDProvider('en-US'), kbqLocaleServiceProvider()] }); ``` With the date components, provide the date adapter in the same `providers`: one locale service then serves the components and the adapter, and a locale switched at runtime reaches both. ```typescript import { importProvidersFrom } from '@angular/core'; import { KbqLuxonDateModule } from '@koobiq/angular-luxon-adapter/adapter'; bootstrapApplication(AppComponent, { providers: [kbqLocaleIDProvider('en-US'), kbqLocaleServiceProvider(), importProvidersFrom(KbqLuxonDateModule)] }); ``` The library ships `en-US`, `es-LA`, `pt-BR`, `ru-RU` and `tk-TM`. To switch the locale at runtime or override the strings, see the [localization](https://koobiq.io/en/main/localization.md) section. ### Setting up typography For proper component rendering, it is recommended to include the [Inter](https://github.com/rsms/inter) and [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono) fonts. For more information, see the [typography](https://koobiq.io/en/main/typography.md) section. ### Using a component Add a component to your application to verify that everything works correctly. ```typescript import { KbqButtonModule } from '@koobiq/components/button'; import { KbqIconModule } from '@koobiq/components/icon'; @Component({ imports: [KbqButtonModule, KbqIconModule], template: ` ` }) export class AppComponent {} ``` ## AI agents - Page: https://koobiq.io/en/main/ai-agents/overview AI coding agents, such as Claude Code, Codex, Cursor or GitHub Copilot, build better UI with Koobiq when they read its documentation instead of guessing. Agent Skills teach them how to work with Koobiq and where to read, the MCP server answers from the packages your project has installed, and every page of this documentation is also Markdown they can read. The skills hold no copy of the documentation: they read the site of the version your project uses, so they stay current without updates. Where the site disagrees with the typings of the version you have installed, the typings are right. ### Agent Skills A skill is a folder of instructions in the open [Agent Skills](https://agentskills.io) format. An agent reads the short description of every skill it has and loads a skill when a task matches it. Koobiq has three skills, for Koobiq 21 and later: | Skill | Use it for | Example request | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `koobiq-angular-setup` | Adding Koobiq to an Angular project: `ng add`, then the styles, theme, locale and fonts it needs | "Add Koobiq to this project" | | `koobiq-angular` | Building UI with `@koobiq/components`: the component for a task, its imports, inputs and examples, forms, theming with design tokens, and icons | "Add a form to create a user: a name, a role from a list, a switch to send an invitation" | | `koobiq-angular-upgrade` | Upgrading a project to a new version of Koobiq: `ng update`, then the warnings and the manual steps it leaves | "Upgrade Koobiq to the latest version and fix what breaks" | `koobiq-angular` does not pick a component by its name. It finds the task in [which component when](https://koobiq.io/which-component-when.md), which names the component to use, the neighbor it is often confused with, and why, then reads the page of the component, the example closest to the task and the API of its entry point. #### How the skills read the documentation A skill reads the version of `@koobiq/components` in your project, then the site of that version: `koobiq.io` for the latest major version, `v.koobiq.io` for an older one. It downloads the files it needs with `curl` where it can run commands, because a fetch tool that summarizes pages may rewrite the code in them. When the site cannot be reached, the skill says so and works from the typings in `node_modules`. #### Installing The [skills CLI](https://github.com/vercel-labs/skills) installs the skills into the folders of the agents you choose, such as `.claude/skills` or `.agents/skills`: ```bash npx skills add koobiq/angular-components/packages/skills ``` Add `--skill koobiq-angular` to install only one of them. The [GitHub CLI](https://cli.github.com) installs them as well: ```bash gh skill install koobiq/angular-components angular/koobiq-angular ``` The skills change only when the way of reading the documentation does; `npx skills update` or `gh skill update` brings them up to date. Whatever release they come from, they read the site of the version your project uses; to install the skills of a given release, add its tag: `npx skills add koobiq/angular-components/packages/skills#` or `gh skill install koobiq/angular-components angular/koobiq-angular@`. #### Network access The skills need to reach `koobiq.io` and its subdomains. In Claude Code, allow them in `.claude/settings.json`: ```json { "permissions": { "allow": ["WebFetch(domain:koobiq.io)", "WebFetch(domain:*.koobiq.io)"] } } ``` With the sandbox on, these rules also let commands such as `curl` reach the site; without it, Claude Code asks before each `curl` the skills run unless you allow it. Codex cloud and the Copilot coding agent block the internet by default: add `koobiq.io` and the `v.koobiq.io` you use to the allowlist of the Codex environment or of the agent firewall. #### Using the skills An agent loads a skill on its own when the request matches the description of the skill. To make sure it does, name the skill in the request: "Use the koobiq-angular skill to add a filter bar to the table". The skills are instructions only: they run no scripts of their own and read nothing but the Koobiq site. Install them from `koobiq/angular-components`, and review what an agent changes as you would review the code of a colleague. ### MCP server The MCP server `@koobiq/mcp` answers an agent from the packages your project has installed: the API of a component, icons, design tokens, examples, guides, and which component fits a task. Install it in the project: ```bash npm install -D @koobiq/mcp ``` Then register it with the agent, for Claude Code in `.mcp.json` at the root of the project: ```json { "mcpServers": { "koobiq": { "command": "npx", "args": ["koobiq-mcp"] } } } ``` The server starts inside the project and reads its `node_modules`, so its answers follow the versions you have installed. It works alongside the skills. | Tool | What it answers | | ---------------------- | --------------------------------------------------------------------------------------------- | | `search` | Finds entry points, symbols, examples, icons and design tokens by words in English or Russian | | `get_component` | The API of an entry point: inputs, outputs, selectors and `exportAs` | | `get_example` | The source of an example from the documentation | | `get_icon` | The name and markup of an icon | | `get_token` | The CSS variable of a design token and its value in each theme | | `get_guide` | A guide or a section of it: installation, theming, localization, migration and others | | `migrate` | What changed between two versions and which entry points lost API | | `which_component_when` | Which component fits a task and which neighbor does not, with the reason | | `list_components` | Every entry point of `@koobiq/components`, as ids the other tools accept | ### Documentation for LLMs Every page of this documentation is also available as Markdown, short enough for an agent that reads the web to read whole: | File | What it holds | | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | [llms.txt](https://koobiq.io/llms.txt) | The index: the rules for code that uses Koobiq, and a link to the Markdown of every page with what the page is about | | [which-component-when.md](https://koobiq.io/which-component-when.md) | The words of a request mapped to the component that does the task and the neighbor it is confused with | | The address of a page with `.md` at the end, such as [select.md](https://koobiq.io/en/components/select.md) | The text of the page, with a link to every example, to the API of its entry point and to the tokens of its component | | `/examples/.md`, such as [select-multiple.md](https://koobiq.io/examples/select-multiple.md) | One example with every file of it | | `/api/.md`, such as [api/select.md](https://koobiq.io/api/select.md) | The API of an entry point, as its API tab shows it | | [llms-full.txt](https://koobiq.io/llms-full.txt) | All the pages with their examples and API in one file of about 4 MB: for tools that index it, not for a chat | Give an agent the address of `llms.txt` and let it open the files it needs. The links in every file lead to the same site, so the files of `next.koobiq.io` link those of `next.koobiq.io`. The Markdown is in English under every language of the site: a model reads it just as well and answers in the language you ask in. ### Instructions for the project Agents, Claude Code included, read the instructions of a project from `AGENTS.md` at its root; a project that also has a `CLAUDE.md` includes it there with an `@AGENTS.md` line. A few lines there make an agent use Koobiq even when a request does not mention it: ```markdown ## UI This project uses the Koobiq design system, `@koobiq/components`. - Build UI with the `koobiq-angular` skill; upgrade Koobiq with the `koobiq-angular-upgrade` skill. - Without the skills, read https://koobiq.io/llms.txt and the Markdown of a component before writing code with it. - Import from the secondary entry points only, such as `@koobiq/components/button`. - Style with the `--kbq-*` design tokens; never hard-code colors, sizes or fonts. ``` ## Versioning - Page: https://koobiq.io/en/main/versioning/overview Our releases follow the versioning described in the [Semver spec](http://semver.org/). ### Versioning **MAJOR** Contains significant new features and changes and may include breaking changes. Upgrading takes effort from developers and involves changes that are incompatible with previous versions: in the API, file paths, component behavior and/or dependencies. Components deprecated earlier are removed in a new major version. For example: - The names of exported entry points change; - Public interfaces (@Inputs/@Outputs) change or are renamed; - CSS changes that affect styles outside a component; - A peer dependency moves to a new major version; - A visual change in a component that could affect the public API. **MINOR** Includes small features and changes. Upgrading does not require any action from developers. Minor versions may deprecate components. Full backward compatibility is kept. For example: - Something is added to a component's template. This includes elements, attributes and classes. **PATCH** Contains bug fixes. Upgrading takes no effort from developers. For example: - package dependencies are updated; - the project structure changes; - improvements and fixes. **No release** - Dev dependencies are updated - Tests or examples are added - Examples are updated - Internal documentation is updated ### Compatibility | Koobiq | Angular | TypeScript | Node.js | | ------ | ------- | ---------- | ----------------------- | | 21.x | 21.x | 5.9 | 20.19, 22.12 or 24+ | | 20.x | 20.x | 5.8–5.9 | 20.19, 22.12 or 24+ | | 19.x | 19.x | 5.5–5.8 | 18.19.1, 20.11.1 or 22+ | | 18.x | 18.x | 5.4–5.5 | 18.19.1, 20.11.1 or 22+ | TypeScript and Node.js follow the requirements of the Angular release. ### Release frequency We follow a time-based release schedule that consists of: - One major release every six months - One to three minor releases for each major release - Patch releases shipped as urgent fixes when needed ### Support policy Every major release is supported for about 12 months. We distinguish two stages of support for major releases: | Status | Period | Description | | ------ | -------- | ---------------------------------------------------------------- | | Active | 6 months | Regular minor releases | | LTS | 6 months | Patch releases that fix critical issues, and compatible features | Our support policy covers every part of the design system, including the codebase, the Figma design kits and our documentation. We recommend always staying on the active version. When a version moves from the active stage to LTS (long-term support), projects should start migrating to the new active major version. That said, we are always ready to consider critical fixes for earlier versions too. ## Theming - Page: https://koobiq.io/en/main/theming/overview A theme in Koobiq is a set of CSS variables. Switching a theme swaps the values; nothing else has to change. This guide shows how to plug a theme in, how to switch it, and how to use its values in your own styles. ### How theming works There are three layers. **Global variables** describe the design system: colors, sizes, fonts. They come from the `@koobiq/design-tokens` package and are declared under the theme selector — `.kbq-light` or `.kbq-dark`. These are the values you reach for in your own styles. **Component variables** are used inside Koobiq components. Each one is named after its component (`--kbq-button-*`, `--kbq-alert-*`) and points to a global variable. The component declares them itself, so nothing extra has to be included. The exception is code-block, scrollbar and skeleton: their tokens live in `@koobiq/design-tokens`, but with the default setup nothing has to be done for them either — their values are already part of the prebuilt styles. **Your styles** use the global variables directly. No imports, no mixins, no registration in a single theme entry point. Adding a theme to the page means putting a class on ``. Everything below it — components and your own markup alike — picks up the new values automatically, because all of it reads the same variables. ### Setup - [Install the Koobiq package](https://koobiq.io/en/main/installation.md). - Include `css-tokens.css`, `css-tokens-light.css` and `css-tokens-dark.css` — these hold the global values. - Include the prebuilt styles in your main stylesheet. Components and overlays (popups, dropdowns) need this to render correctly. It also carries the token values of code-block, scrollbar and skeleton, so no separate files are needed for them: ```scss @use '@koobiq/components/prebuilt-themes/theme.css'; ``` - Add the theme class to ``: ```html ``` `kbq-app-background` paints the page itself — background and text color. - Import a component and use it. 🚀 ### Switching themes Use `KbqThemeService`. It puts the right class on ``, remembers the choice, and in `'auto'` mode follows the system color scheme. There are three modes: `'light'`, `'dark'` and `'auto'`. **`'auto'` is the default.** The system color scheme is supported out of the box: no `matchMedia` subscription of your own. The service exposes its current state as signals, so you can read it straight from a template: ```ts protected readonly themeService = inject(KbqThemeService); ``` ```html ``` If you have more than two themes and need a list rather than a light/dark switch, you can pin one by name. See [Core](https://koobiq.io/en/components/core.md) for the details and a live example. #### Theme selectors | Theme | Selector | | ----- | ---------- | | Light | .kbq-light | | Dark | .kbq-dark | #### Settings Pass the settings when the app starts: ```ts import { kbqThemeProvider } from '@koobiq/components/core'; bootstrapApplication(AppComponent, { providers: [kbqThemeProvider({ mode: 'auto', storageKey: 'my-app-theme' })] }); ``` The 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. ### Using theme values in your own styles Write the variable where you need the value: ```css .my-component-text { color: var(--kbq-foreground-contrast-secondary); } ``` Nothing 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. **Example: Theme values in your own styles** (`theme-css-variables`) ```ts theme-css-variables-example.ts import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { KbqButtonModule } from '@koobiq/components/button'; import { KbqThemeService } from '@koobiq/components/core'; @Component({ selector: 'theme-css-variables-example', imports: [KbqButtonModule], template: `

Current theme: {{ themeService.colorScheme() }}

`, styles: ` .theme-css-variables-example__card { padding: var(--kbq-size-l); border: 1px solid var(--kbq-line-contrast-less); border-radius: var(--kbq-size-border-radius); background: var(--kbq-background-card); color: var(--kbq-foreground-contrast); } .theme-css-variables-example__caption { margin: 0 0 var(--kbq-size-m); color: var(--kbq-foreground-contrast-secondary); } `, changeDetection: ChangeDetectionStrategy.OnPush }) export class ThemeCssVariablesExample { // The app's shared service, so the switch flips the whole page — that is the point here: nothing // in the styles above reacts to it explicitly, the variables simply resolve to the other theme. protected readonly themeService = inject(KbqThemeService); } ``` The full list of global variables is on the [Design tokens](https://koobiq.io/en/main/design-tokens.md) page. ### Overriding a component's variables 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: ```css /* button-tokens.scss */ .kbq-button, .kbq-button-icon { --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-contrast); } ``` The 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. That leaves two approaches. **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: ```html ``` **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: ```css .kbq-light .kbq-button { --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error); } .kbq-dark .kbq-button { --kbq-button-filled-contrast-fade-off-background: var(--kbq-background-error-fade); } ``` Rules 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. ### Building your own component Your component reads the same global variables the library reads. Give it a stylesheet and turn off style encapsulation — that is the whole setup: ```ts @Component({ selector: 'my-card', templateUrl: './my-card.html', styleUrl: './my-card.scss', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) export class MyCard {} ``` ```scss .my-card { padding: var(--kbq-size-l); border: var(--kbq-size-border-width) solid var(--kbq-line-contrast-less); border-radius: var(--kbq-size-border-radius); background: var(--kbq-background-card); color: var(--kbq-foreground-contrast); } ``` There is **no** need to write a separate `_my-card-theme.scss` mixin and then register it in a single theme entry point. #### Corner shape 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: ```scss @use '@koobiq/components/core/styles/common/squircle' as *; .my-card { @include kbq-squircle(var(--kbq-size-border-radius)); } ``` Without SCSS, write the same rule by hand: ```css .my-card { --my-card-radius: var(--kbq-size-border-radius); border-radius: var(--my-card-radius); } @supports (corner-shape: squircle) { .my-card { corner-shape: squircle; border-radius: clamp( var(--my-card-radius) * 1.85, var(--my-card-radius) * 2.3 - 3.6px, var(--my-card-radius) * 2 ); } } ``` To 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`. ### Rebranding with a single file You can restyle the whole app by overriding CSS variables in one place: ```css .kbq-light { --kbq-background-contrast: #1a3a6b; --kbq-foreground-contrast: #0d1b2a; } .kbq-dark { --kbq-background-contrast: #7ba7e8; --kbq-foreground-contrast: #e8eef7; } ``` Load that file after the design system's own, and every component follows. One 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. ### Where to find the variables Global variables, with their values and previews: [Design tokens](https://koobiq.io/en/main/design-tokens.md). The 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-*`. ### Where the values come from **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. **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. Note Version 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. ## Localization - Page: https://koobiq.io/en/main/localization/overview Koobiq components render some strings of their own — the filters menu of the filter bar, the tooltips of the code block, the accessible names of every icon-only button, the placeholder of a datepicker, and so on. All of them come from one place: `KbqLocaleService`. The library ships five locales: `en-US`, `es-LA`, `pt-BR`, `ru-RU` and `tk-TM`. The application's data is never translated. Option labels, filter values, table cells and everything else the application passes in is rendered exactly as given. Translating those strings is the application's own job: Koobiq neither requires nor ships a library for it. When one of them has to contain a Koobiq component — a link, a button, a dropdown — embed it with [dynamic-translation](https://koobiq.io/en/components/dynamic-translation.md). ### Setting the locale `KbqLocaleService` is `providedIn: 'root'`, but the components read it through the `KBQ_LOCALE_SERVICE` token, which has no factory. Nothing is localized until it is provided: ```ts import { kbqLocaleServiceProvider } from '@koobiq/components/core'; bootstrapApplication(AppComponent, { providers: [kbqLocaleServiceProvider()] }); ``` Without that provider every component falls back to its own built-in `ru-RU` defaults, and switching the locale at runtime does nothing. The placeholder of the datepicker is the exception: it names the format its input parses — the shipped one of the date adapter's locale (`KBQ_DATE_LOCALE`, Angular's `LOCALE_ID` unless provided), or the format itself, such as `yyyy/mm/dd`, for a locale the library ships no strings for and for a format set through `KBQ_DATE_FORMATS`. Use `kbqLocaleServiceProvider()` rather than `{ provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }`: `useClass` builds a **second** instance, independent of the `providedIn: 'root'` one, so a `setLocale()` called on the instance that `inject(KbqLocaleService)` hands out moves nothing that is rendered. The helper provides the service next to the token, and both references resolve to one instance. There are three ways to control which locale is active: - **`KBQ_DEFAULT_LOCALE_ID`** is the fallback, `ru-RU`. It is a plain exported constant, not an injection token — it cannot be provided, only read. - **`KBQ_LOCALE_ID`**, set through `kbqLocaleIDProvider(id)`, fixes the locale once, when `KbqLocaleService` is constructed. It must sit in the **same `providers` array** as the service itself, because the service reads the token from the injector that created it. - **`setLocale(id)`** changes the locale at runtime. ```ts providers: [kbqLocaleIDProvider('en-US'), kbqLocaleServiceProvider()]; ``` The same pair on a component's `providers` scopes the locale to that component's subtree, while the rest of the application keeps the locale it was given. Reading the active locale: ```ts readonly localeService = kbqInjectLocaleService(); readonly currentLocale = this.localeService.localeId; // Signal readonly localeData = this.localeService.data; // Signal readonly available = this.localeService.items; // Signal, for a locale picker ``` `kbqInjectLocaleService()` resolves `KBQ_LOCALE_SERVICE`, never the class. Pass `{ optional: true }` for code that has to keep working in an application that provided no locale service. `changes` is a `BehaviorSubject` of the active locale id, and it remains the way for code outside a template to observe a locale change. The `id` and `current` getters, on the other hand, are deprecated in favour of the `localeId` and `data` signals. A signal read from a template registers on the reading view, so a runtime `setLocale()` reaches `OnPush` children that an observable subscribed in the parent could not. ### What a locale is made of A locale is a set of sections, one per area of strings. The section is the unit of overriding: its name appears in the locale data, in `getParams()` and in `[localeOverrides]` alike. | Section | What it translates | Package | | -------------------- | ---------------------------------------------------------------------------------- | --------------------- | | `a11y` | accessible names of the icon-only buttons and regions the library renders itself | `core` | | `select` | the "select all" label and the counter of values that did not fit | `core` | | `popoverConfirm` | the question and the button of the confirmation popover | `core` | | `formatters` | separators and abbreviations of `kbqNumber`, `kbqTableNumber` and `kbqRoundNumber` | `core` | | `sizeUnits` | the size units of `kbqDataSize` | `core` | | `input` | the separators of the number input | `input` | | `datepicker` | the placeholder and the format of the date field | `datepicker` | | `timepicker` | the placeholders of the time field | `timepicker` | | `timezone` | the search placeholder of the timezone select | `timezone` | | `timeRange` | the title and the editor of the time range | `time-range` | | `codeBlock` | the tooltips and labels of the code block buttons | `code-block` | | `fileUpload` | the text of the single and multiple upload areas | `file-upload` | | `filterBar` | search, reset and the saved filters menu | `filter-bar` | | `actionsPanel` | the close tooltip of the actions panel | `actions-panel` | | `clampedText` | the expand and collapse labels of clamped text | `clamped-text` | | `navbar` | the tooltips of the vertical navbar toggle | `navbar` | | `searchExpandable` | the tooltip and the placeholder of the expandable search | `search-expandable` | | `appSwitcher` | the search and the header of the app switcher | `app-switcher` | | `notificationCenter` | the labels of the notification center | `notification-center` | The full contract of a section is its `Kbq
LocaleConfiguration` type, where every key is documented. Its token and its provider are named mechanically: `KBQ_
_LOCALE_CONFIGURATION` and `kbq
LocaleConfigurationProvider()`. ### Overriding the strings of one component A section provider overrides only the keys it is given — everything else keeps its default: ```ts import { kbqCodeBlockLocaleConfigurationProvider } from '@koobiq/components/code-block'; providers: [kbqCodeBlockLocaleConfigurationProvider({ copyTooltip: 'Copy the snippet' })]; ``` Because these providers are element-injector friendly, providing one on a component scopes the override to that component's subtree. **Example: Code-block with custom locale configuration, without KBQ\_LOCALE\_SERVICE** (`code-block-with-custom-locale-configuration`) ```ts code-block-with-custom-locale-configuration-example.ts import { ChangeDetectionStrategy, Component } from '@angular/core'; import { KbqCodeBlockFile, kbqCodeBlockHighlightJsConfigProvider, kbqCodeBlockLocaleConfigurationProvider, KbqCodeBlockModule } from '@koobiq/components/code-block'; import { KBQ_LOCALE_SERVICE } from '@koobiq/components/core'; @Component({ selector: 'code-block-with-custom-locale-configuration-example', imports: [KbqCodeBlockModule], template: ` `, providers: [ kbqCodeBlockHighlightJsConfigProvider({ languages: { xml: () => import('highlight.js/lib/languages/xml') } }), // Disable global KBQ_LOCALE_SERVICE for locale configuration overriding { provide: KBQ_LOCALE_SERVICE, useValue: null }, kbqCodeBlockLocaleConfigurationProvider({ softWrapOnTooltip: '*CUSTOM* Enable word wrap', softWrapOffTooltip: '*CUSTOM* Disable word wrap', downloadTooltip: '*CUSTOM* Download', copiedTooltip: '*CUSTOM* ✓ Copied', copyTooltip: '*CUSTOM* Copy', viewAllText: '*CUSTOM* Show all', viewLessText: '*CUSTOM* Show less', openExternalSystemTooltip: '*CUSTOM* Open in the external system' }) ], changeDetection: ChangeDetectionStrategy.OnPush }) export class CodeBlockWithCustomLocaleConfigurationExample { readonly files: KbqCodeBlockFile[] = [ { content: `\n\n\t\n\t\tCross-site scripting\n\t\tXSS\n\t\tCross-site scripting (XSS) is a type of security vulnerability that can be found in some web applications. XSS attacks enable attackers to inject client-side scripts into web pages viewed by other users. A cross-site scripting vulnerability may be used by attackers to bypass access controls such as the same-origin policy. During the second half of 2007, XSSed documented 11,253 site-specific cross-site vulnerabilities, compared to 2,134 "traditional" vulnerabilities documented by Symantec. XSS effects vary in range from petty nuisance to significant security risk, depending on the sensitivity of the data handled by the vulnerable site and the nature of any security mitigation implemented by the site's owner network.\n\t\n\t\n\t\tDenial-of-service attack\n\t\tDoS\n\t\tIn computing, a denial-of-service attack (DoS attack) is a cyber-attack in which the perpetrator seeks to make a machine or network resource unavailable to its intended users by temporarily or indefinitely disrupting services of a host connected to a network. Denial of service is typically accomplished by flooding the targeted machine or resource with superfluous requests in an attempt to overload systems and prevent some or all legitimate requests from being fulfilled. The range of attacks varies widely, spanning from inundating a server with millions of requests to slow its performance, overwhelming a server with a substantial amount of invalid data, to submitting requests with an illegitimate IP address.\n\t\n`, language: 'xml', link: 'https://github.com/koobiq/angular-components' } ]; } ``` Each helper ships from its own component's package. The exception is `kbqSelectLocaleConfigurationProvider`, which ships from `@koobiq/components/core`: the `select` section is rendered by four packages that do not depend on one another — `kbq-select`, `kbq-tree-select`, `kbq-tree-selection` and `kbq-list-selection`. An override is applied on top of whatever is active — the strings of the active locale when the application provides a service, the token's defaults otherwise. So the keys it names stay pinned across a runtime `setLocale()`, while every key it leaves out follows the locale. Override a whole section to stop it following the locale entirely; register your own locale (see below) to make the override switch along with the others. ### Overriding the strings of one instance A provider can only be attached to an injector, so scoping one to a single component means owning a component boundary. Every localized component also takes its strings as a template binding: ```html ``` The value is keyed by locale section — the same shape `addLocale()` and `KBQ_LOCALE_DATA` accept — so one binding can also cover the accessible names the component renders through its own children: ```html ``` **Example: Select locale configuration** (`select-locale-configuration`) ```ts select-locale-configuration-example.ts import { ChangeDetectionStrategy, Component } from '@angular/core'; import { KbqFormFieldModule } from '@koobiq/components/form-field'; import { KbqSelectModule } from '@koobiq/components/select'; @Component({ selector: 'select-locale-configuration-example', imports: [KbqFormFieldModule, KbqSelectModule], template: ` Default strings @for (option of options; track option) { {{ option }} } Overridden for this instance only @for (option of options; track option) { {{ option }} } `, styles: ` :host { display: flex; flex-direction: column; gap: var(--kbq-size-l); padding: var(--kbq-size-l); } .kbq-form-field-type-select { width: 320px; } `, changeDetection: ChangeDetectionStrategy.OnPush }) export class SelectLocaleConfigurationExample { readonly options = Array.from({ length: 4 }).map((_, i) => `Option ${i + 1}`); readonly selected = [this.options[0]]; } ``` To scope an override to a whole region rather than one component, put `KbqLocaleOverridesDirective` on any element of the application. Everything rendered inside — including a panel that opens in the overlay container — resolves against that element: ```ts import { KbqLocaleOverridesDirective } from '@koobiq/components/core'; ``` ```html
``` The two names are not interchangeable. `[localeOverrides]` is the input a Koobiq component declares; `[kbqLocaleOverrides]` is the directive's own selector, for the application's elements. Writing the selector on a component that already carries the directive matches it twice on one element, which Angular rejects with `NG0309`. Sources are applied from the most general to the most local: 1. the configuration token's defaults; 2. the active locale; 3. `kbqLocaleConfigurationProvider()`, outermost injector first; 4. `KbqLocaleOverridesDirective`, outermost element first; 5. the component's own `[localeOverrides]`. Steps 3 to 5 only override the keys they mention. Step 2 is the exception: the active locale replaces the token's defaults outright rather than merging over them, so a value provided for a configuration token is dropped as soon as a locale service exists. Override the section instead of providing the token. A pipe is not an element, so `kbqNumber`, `kbqRoundNumber` and `kbqDataSize` have no `[localeOverrides]` of their own. A carrier on an ancestor does reach them: a pipe resolves its section from the same injector any other reader would. ### Registering your own locale `addLocale()` accepts partial data — every section, and every key within a section, is optional. Whatever is left out is completed from the shipped locale of the same id, or from `KBQ_DEFAULT_LOCALE_ID` when the id is new. `getParams()` therefore always returns a complete section, whatever was registered: ```ts localeService.addLocale('en-GB', { select: { selectAll: 'Select everything' }, a11y: { close: 'Dismiss' } }); ``` The parameter is typed as `KbqPartialLocaleData`, so a misspelled section or key is a compile error rather than a string that silently never appears. The same data can be provided up front through `KBQ_LOCALE_DATA`. The token is read once, by the `KbqLocaleService` constructor, so its provider belongs in the same `providers` array as the service: ```ts const localeData = { 'en-GB': { select: { selectAll: 'Select everything' } } } satisfies KbqLocaleDataInput; providers: [{ provide: KBQ_LOCALE_DATA, useValue: localeData }, kbqLocaleServiceProvider()]; ``` The `satisfies` is load-bearing: `useValue` is declared as `any`, and without it the same typo goes through silently. A locale added this way is registered, but it is not added to `items`, which is the list a locale picker reads. Spell `items` out in full, shipped locales included, if the new locale has to be selectable. ### Reading a section yourself ```ts const { selectAll } = localeService.getParams('select'); // KbqSelectLocaleConfiguration const select = localeService.params('select'); // Signal ``` The section name is checked against `KbqLocaleSection`, and the return type follows from it. Both of those read the locale alone. A component that should honour the overrides above as well — the provider helpers and `[localeOverrides]` — carries the directive and reads through it: ```ts import { inject } from '@angular/core'; import { KBQ_SELECT_LOCALE_CONFIGURATION, KbqLocaleOverridesDirective } from '@koobiq/components/core'; @Component({ selector: 'my-widget', hostDirectives: [ { directive: KbqLocaleOverridesDirective, inputs: ['kbqLocaleOverrides: localeOverrides'] } ] }) export class MyWidget { protected readonly localeConfiguration = inject(KbqLocaleOverridesDirective, { self: true }).read( 'select', KBQ_SELECT_LOCALE_CONFIGURATION ); } ``` `{ self: true }` is required here: with `{ host: true }` Angular walks up to the enclosing component's host element and returns **its** carrier, so the component reads someone else's overrides instead of its own. The `hostDirectives` entry has to be spelled out in every component — extracted into a const it fails with `NG1010` when used from another entry point. Name the field `localeConfiguration` — the library-wide name for the resolved strings a component reads, kept apart from `[localeOverrides]`, which writes a partial. A component that reads a second section qualifies it, the way `a11yLocaleConfiguration` does. `read()` returns a signal, so `setLocale()` reaches the template on its own, and it merges every source in the order listed above. Reading through the carrier is what makes `[localeOverrides]` work on the component: the two cannot come apart. Where there is no element to carry the directive — in a pipe, or in content created programmatically — `kbqInjectLocaleConfiguration(section, token)` resolves the same sources from the injector instead. ### Dates and numbers Date adapters and the number pipes follow the same service, but they need their own providers. `KbqLocaleServiceModule` — pulled in by the date adapter modules — provides it itself. **Example: Number-formatter forced locale** (`number-formatter-locale`) ```ts number-formatter-locale-example.ts import { ChangeDetectionStrategy, Component } from '@angular/core'; import { KbqFormattersModule } from '@koobiq/components/core'; import { KbqTableModule } from '@koobiq/components/table'; @Component({ selector: 'number-formatter-locale-example', imports: [KbqFormattersModule, KbqTableModule], template: ` @for (locale of locales; track locale) { }
Locale kbqNumber kbqRoundNumber
{{ locale }} {{ value | kbqNumber: '' : locale }} {{ value | kbqRoundNumber: locale }}
`, changeDetection: ChangeDetectionStrategy.OnPush }) export class NumberFormatterLocaleExample { protected readonly value = 1234567.89; protected readonly locales = ['ru-RU', 'en-US', 'es-LA', 'pt-BR']; } ``` To scope a locale to a subtree that contains dates, provide the adapter and the formatter in the `providers` of the same component as `kbqLocaleIDProvider`. Importing the module will not do: `imports: [KbqLuxonDateModule]` puts the adapter in the environment injector, where it resolves the root locale service, and month names stay in the language of the whole application rather than that of the subtree. ## Typography - Page: https://koobiq.io/en/main/typography/overview Connecting fonts, which sources fit, the text classes and replacing the fonts. ### Connecting fonts Koobiq uses the [Inter](https://github.com/rsms/inter) and [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono) fonts by default. | Font | Weights | | -------------- | ---------------------------------------- | | Inter | 400, 500, 600, 700; italic — 400 and 600 | | JetBrains Mono | 400, 600, 700 | Fonts come from npm packages or from files the application hosts itself. Google Fonts does not fit: it serves the fonts without some of the symbols, and Inter also without the OpenType features the tokens enable — the slashed zero, for example, does not appear. #### Packages Both fonts are installed from npm: [inter-ui](https://www.npmjs.com/package/inter-ui) and [@fontsource/jetbrains-mono](https://www.npmjs.com/package/@fontsource/jetbrains-mono). ```bash npm install inter-ui @fontsource/jetbrains-mono ``` From **inter-ui**, `inter.css` is the one that fits: it has the whole font — Cyrillic, the special symbols and the OpenType features. `inter-latin.css` has neither Cyrillic nor some of the symbols, and `inter-variable.css` declares the `InterVariable` family, which the tokens do not name. **@fontsource/jetbrains-mono** is built from the Google Fonts files and has no box-drawing characters or arrows (`├ ─ →`): the browser takes them from the next font of `--kbq-font-family-mono`. If the interface shows them, JetBrains Mono is self-hosted. The stylesheets go to the `styles` array of `angular.json`: ```json "styles": [ "node_modules/inter-ui/inter.css", "node_modules/@fontsource/jetbrains-mono/400.css", "node_modules/@fontsource/jetbrains-mono/600.css", "node_modules/@fontsource/jetbrains-mono/700.css", "src/styles.css" ] ``` #### Self-hosting Inter is downloaded [from rsms.me](https://rsms.me/inter/download/): the `web` folder of the archive holds the files and a ready-made `inter.css`. The folder is copied into the project, for example to `src/fonts/inter`. JetBrains Mono is downloaded [from the font release](https://github.com/JetBrains/JetBrainsMono/releases/latest): the `fonts/webfonts` folder of the archive has the files but no stylesheet. The folder is copied into the project, for example to `src/fonts/jetbrains-mono`, and `jetbrains-mono.css` next to the files is written by hand — one `@font-face` rule per weight, with the family name the tokens use: ```css @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('JetBrainsMono-Regular.woff2') format('woff2'); } ``` The 600 and 700 weights take `JetBrainsMono-SemiBold.woff2` and `JetBrainsMono-Bold.woff2`. Both stylesheets go to `styles`: ```json "styles": [ "src/fonts/inter/inter.css", "src/fonts/jetbrains-mono/jetbrains-mono.css", "src/styles.css" ] ``` #### Checking the files Inter and JetBrains Mono from any other source — a package, a build or a CDN — are checked before connecting: the files must contain the characters the interface shows, and Inter also the OpenType features the tokens enable: `calt`, `case`, `kern`, `liga`, `ss01`, `ss04`, `tnum` and `zero`. A subset may lack both, depending on how it was built. The **unicode-range** descriptor only tells the browser which characters to request the file for, not which characters the file contains. ### Text styles Text is styled with the ready-made classes, such as `kbq-headline` and `kbq-text-normal`; every class is listed [in the design tokens](https://koobiq.io/en/main/design-tokens.md). A class sets the family, size, line height, letter spacing, weight, italic, text transform and OpenType features from the variables of its level, such as `--kbq-typography-text-normal-font-size`. The variables are for adjusting a single property, not for assembling a text style of your own. The classes come [with the prebuilt theme](https://koobiq.io/en/main/theming.md); a theme built from the SCSS mixins adds them with `koobiq-typography()`. ```html

Heading

Interface text

``` ### Changing the fonts The families are set by three global variables: `--kbq-font-family-base` for interface text, `--kbq-font-family-accent` for headings and display styles, `--kbq-font-family-mono` for code. Overriding them changes the fonts across the interface. The override is declared after the token stylesheets — for example in `src/styles.css`, which comes after them in `styles` — otherwise the token values apply. ```css :root { --kbq-font-family-base: 'MySans', sans-serif; --kbq-font-family-accent: 'MyDisplay', 'MySans', sans-serif; --kbq-font-family-mono: 'MyMono', monospace; } ``` The OpenType features of the text styles stay on with another font. `ss01` and `ss04` are Inter's stylistic sets and switch on other glyphs in another font, so they are checked, or the `--kbq-typography-*-font-feature-settings` variables are overridden as well. ## Design tokens - Page: https://koobiq.io/en/main/design-tokens/colors The global design tokens of Koobiq: CSS custom properties that the light and the dark theme define. Style with them instead of hard-coded values, for example `color: var(--kbq-foreground-contrast)`. Colors lists the themed colors; the two palettes list the color scales behind them. ### Colors #### Background `--kbq-background-bg`, `--kbq-background-bg-secondary`, `--kbq-background-bg-tertiary`, `--kbq-background-night`, `--kbq-background-card`, `--kbq-background-theme`, `--kbq-background-theme-fade`, `--kbq-background-theme-less`, `--kbq-background-contrast`, `--kbq-background-contrast-fade`, `--kbq-background-contrast-less`, `--kbq-background-error`, `--kbq-background-error-fade`, `--kbq-background-error-less`, `--kbq-background-success`, `--kbq-background-success-fade`, `--kbq-background-success-less`, `--kbq-background-warning`, `--kbq-background-warning-fade`, `--kbq-background-warning-less`, `--kbq-background-transparent`, `--kbq-background-overlay`, `--kbq-background-overlay-inverse`, `--kbq-background-overlay-base-theme`, `--kbq-background-overlay-base-error`, `--kbq-background-highlight` #### Foreground `--kbq-foreground-contrast`, `--kbq-foreground-contrast-secondary`, `--kbq-foreground-contrast-tertiary`, `--kbq-foreground-theme`, `--kbq-foreground-theme-secondary`, `--kbq-foreground-error`, `--kbq-foreground-error-secondary`, `--kbq-foreground-error-tertiary`, `--kbq-foreground-success`, `--kbq-foreground-success-secondary`, `--kbq-foreground-success-tertiary`, `--kbq-foreground-warning`, `--kbq-foreground-warning-secondary`, `--kbq-foreground-visited`, `--kbq-foreground-on-contrast`, `--kbq-foreground-on-contrast-secondary`, `--kbq-foreground-white`, `--kbq-foreground-white-secondary`, `--kbq-foreground-white-tertiary`, `--kbq-foreground-night`, `--kbq-foreground-night-secondary`, `--kbq-foreground-night-tertiary` #### Icon `--kbq-icon-contrast`, `--kbq-icon-contrast-fade`, `--kbq-icon-theme`, `--kbq-icon-theme-fade`, `--kbq-icon-error`, `--kbq-icon-error-fade`, `--kbq-icon-success`, `--kbq-icon-success-fade`, `--kbq-icon-warning`, `--kbq-icon-warning-fade`, `--kbq-icon-visited`, `--kbq-icon-on-contrast`, `--kbq-icon-on-contrast-fade`, `--kbq-icon-white`, `--kbq-icon-white-fade`, `--kbq-icon-night`, `--kbq-icon-night-fade` #### Line `--kbq-line-contrast`, `--kbq-line-contrast-fade`, `--kbq-line-contrast-less`, `--kbq-line-theme`, `--kbq-line-theme-fade`, `--kbq-line-theme-less`, `--kbq-line-error`, `--kbq-line-error-fade`, `--kbq-line-success`, `--kbq-line-success-fade`, `--kbq-line-warning`, `--kbq-line-warning-fade`, `--kbq-line-visited`, `--kbq-line-visited-fade`, `--kbq-line-on-contrast`, `--kbq-line-on-contrast-fade`, `--kbq-line-on-contrast-less`, `--kbq-line-white`, `--kbq-line-white-fade`, `--kbq-line-white-less`, `--kbq-line-night`, `--kbq-line-night-fade`, `--kbq-line-night-less` #### States ##### Background `--kbq-states-background-disabled`, `--kbq-states-background-contrast-hover`, `--kbq-states-background-contrast-active`, `--kbq-states-background-contrast-fade-hover`, `--kbq-states-background-contrast-fade-active`, `--kbq-states-background-contrast-less-hover`, `--kbq-states-background-contrast-less-active`, `--kbq-states-background-transparent-hover`, `--kbq-states-background-transparent-active`, `--kbq-states-background-theme-hover`, `--kbq-states-background-theme-active`, `--kbq-states-background-theme-fade-hover`, `--kbq-states-background-theme-fade-active`, `--kbq-states-background-theme-less-hover`, `--kbq-states-background-theme-less-active`, `--kbq-states-background-error-hover`, `--kbq-states-background-error-active`, `--kbq-states-background-error-fade-hover`, `--kbq-states-background-error-fade-active`, `--kbq-states-background-error-less-hover`, `--kbq-states-background-error-less-active`, `--kbq-states-background-warning-hover`, `--kbq-states-background-warning-active`, `--kbq-states-background-warning-fade-hover`, `--kbq-states-background-warning-fade-active`, `--kbq-states-background-warning-less-hover`, `--kbq-states-background-warning-less-active`, `--kbq-states-background-success-hover`, `--kbq-states-background-success-active`, `--kbq-states-background-success-fade-hover`, `--kbq-states-background-success-fade-active`, `--kbq-states-background-success-less-hover`, `--kbq-states-background-success-less-active`, `--kbq-states-background-highlight-current` ##### Foreground `--kbq-states-foreground-disabled`, `--kbq-states-foreground-theme-hover`, `--kbq-states-foreground-theme-active`, `--kbq-states-foreground-visited-hover`, `--kbq-states-foreground-visited-active` ##### Icon `--kbq-states-icon-disabled`, `--kbq-states-icon-contrast-hover`, `--kbq-states-icon-contrast-active`, `--kbq-states-icon-contrast-fade-hover`, `--kbq-states-icon-contrast-fade-active`, `--kbq-states-icon-theme-hover`, `--kbq-states-icon-theme-active`, `--kbq-states-icon-error-hover`, `--kbq-states-icon-error-active`, `--kbq-states-icon-success-hover`, `--kbq-states-icon-success-active`, `--kbq-states-icon-warning-hover`, `--kbq-states-icon-warning-active`, `--kbq-states-icon-visited-hover`, `--kbq-states-icon-visited-active` ##### Line `--kbq-states-line-disabled`, `--kbq-states-line-focus`, `--kbq-states-line-focus-theme`, `--kbq-states-line-focus-error` #### Shadow `--kbq-shadow-outline`, `--kbq-shadow-key`, `--kbq-shadow-ambient` #### Opacity `--kbq-opacity-disabled`, `--kbq-opacity-overlay` ### Typography CSS classes of the text styles, for example `

`: `kbq-display-big`, `kbq-display-big-strong`, `kbq-display-normal`, `kbq-display-normal-strong`, `kbq-display-compact`, `kbq-display-compact-strong`, `kbq-headline`, `kbq-title`, `kbq-subheading`, `kbq-navbar-title`, `kbq-text-big`, `kbq-text-big-medium`, `kbq-text-big-strong`, `kbq-caps-big`, `kbq-caps-big-strong`, `kbq-mono-big`, `kbq-mono-big-strong`, `kbq-tabular-big`, `kbq-tabular-big-strong`, `kbq-italic-big`, `kbq-italic-big-strong`, `kbq-text-normal`, `kbq-text-normal-medium`, `kbq-text-normal-strong`, `kbq-caps-normal`, `kbq-caps-normal-strong`, `kbq-mono-normal`, `kbq-mono-normal-medium`, `kbq-mono-normal-strong`, `kbq-mono-codeblock`, `kbq-tabular-normal`, `kbq-tabular-normal-strong`, `kbq-italic-normal`, `kbq-italic-normal-strong`, `kbq-text-compact`, `kbq-text-compact-medium`, `kbq-text-compact-strong`, `kbq-caps-compact`, `kbq-caps-compact-strong`, `kbq-mono-compact`, `kbq-mono-compact-strong`, `kbq-tabular-compact`, `kbq-tabular-compact-strong`, `kbq-italic-compact`, `kbq-italic-compact-strong` ### Shadows `--kbq-shadow-overflow-compact-top`, `--kbq-shadow-overflow-compact-right`, `--kbq-shadow-overflow-compact-bottom`, `--kbq-shadow-overflow-compact-left`, `--kbq-shadow-overflow-normal-top`, `--kbq-shadow-overflow-normal-right`, `--kbq-shadow-overflow-normal-left`, `--kbq-shadow-overflow-normal-bottom`, `--kbq-shadow-card`, `--kbq-shadow-popup`, `--kbq-shadow-overlay` ### Border radius `--kbq-size-border-radius` ### Sizes `--kbq-size-3xs`, `--kbq-size-xxs`, `--kbq-size-xs`, `--kbq-size-s`, `--kbq-size-m`, `--kbq-size-l`, `--kbq-size-xl`, `--kbq-size-xxl`, `--kbq-size-3xl`, `--kbq-size-4xl`, `--kbq-size-5xl`, `--kbq-size-6xl`, `--kbq-size-7xl`, `--kbq-size-border-width` ### Engineer palette #### Blue `--kbq-plt-blue-1`, `--kbq-plt-blue-2`, `--kbq-plt-blue-3`, `--kbq-plt-blue-4`, `--kbq-plt-blue-5`, `--kbq-plt-blue-6`, `--kbq-plt-blue-7`, `--kbq-plt-blue-8`, `--kbq-plt-blue-9`, `--kbq-plt-blue-10`, `--kbq-plt-blue-11`, `--kbq-plt-blue-12`, `--kbq-plt-blue-13`, `--kbq-plt-blue-14`, `--kbq-plt-blue-15`, `--kbq-plt-blue-16`, `--kbq-plt-blue-17`, `--kbq-plt-blue-18`, `--kbq-plt-blue-19`, `--kbq-plt-blue-20` #### Slate `--kbq-plt-slate-1`, `--kbq-plt-slate-2`, `--kbq-plt-slate-3`, `--kbq-plt-slate-4`, `--kbq-plt-slate-5`, `--kbq-plt-slate-6`, `--kbq-plt-slate-7`, `--kbq-plt-slate-8`, `--kbq-plt-slate-9`, `--kbq-plt-slate-10`, `--kbq-plt-slate-11`, `--kbq-plt-slate-12`, `--kbq-plt-slate-13`, `--kbq-plt-slate-14`, `--kbq-plt-slate-15`, `--kbq-plt-slate-16`, `--kbq-plt-slate-17`, `--kbq-plt-slate-18`, `--kbq-plt-slate-19`, `--kbq-plt-slate-20` #### Red `--kbq-plt-red-1`, `--kbq-plt-red-2`, `--kbq-plt-red-3`, `--kbq-plt-red-4`, `--kbq-plt-red-5`, `--kbq-plt-red-6`, `--kbq-plt-red-7`, `--kbq-plt-red-8`, `--kbq-plt-red-9`, `--kbq-plt-red-10`, `--kbq-plt-red-11`, `--kbq-plt-red-12`, `--kbq-plt-red-13`, `--kbq-plt-red-14`, `--kbq-plt-red-15`, `--kbq-plt-red-16`, `--kbq-plt-red-17`, `--kbq-plt-red-18`, `--kbq-plt-red-19`, `--kbq-plt-red-20` #### Orange `--kbq-plt-orange-1`, `--kbq-plt-orange-2`, `--kbq-plt-orange-3`, `--kbq-plt-orange-4`, `--kbq-plt-orange-5`, `--kbq-plt-orange-6`, `--kbq-plt-orange-7`, `--kbq-plt-orange-8`, `--kbq-plt-orange-9`, `--kbq-plt-orange-10`, `--kbq-plt-orange-11`, `--kbq-plt-orange-12`, `--kbq-plt-orange-13`, `--kbq-plt-orange-14`, `--kbq-plt-orange-15`, `--kbq-plt-orange-16`, `--kbq-plt-orange-17`, `--kbq-plt-orange-18`, `--kbq-plt-orange-19`, `--kbq-plt-orange-20` #### Yellow `--kbq-plt-yellow-1`, `--kbq-plt-yellow-2`, `--kbq-plt-yellow-3`, `--kbq-plt-yellow-4`, `--kbq-plt-yellow-5`, `--kbq-plt-yellow-6`, `--kbq-plt-yellow-7`, `--kbq-plt-yellow-8`, `--kbq-plt-yellow-9`, `--kbq-plt-yellow-10`, `--kbq-plt-yellow-11`, `--kbq-plt-yellow-12`, `--kbq-plt-yellow-13`, `--kbq-plt-yellow-14`, `--kbq-plt-yellow-15`, `--kbq-plt-yellow-16`, `--kbq-plt-yellow-17`, `--kbq-plt-yellow-18`, `--kbq-plt-yellow-19`, `--kbq-plt-yellow-20` #### Green `--kbq-plt-green-1`, `--kbq-plt-green-2`, `--kbq-plt-green-3`, `--kbq-plt-green-4`, `--kbq-plt-green-5`, `--kbq-plt-green-6`, `--kbq-plt-green-7`, `--kbq-plt-green-8`, `--kbq-plt-green-9`, `--kbq-plt-green-10`, `--kbq-plt-green-11`, `--kbq-plt-green-12`, `--kbq-plt-green-13`, `--kbq-plt-green-14`, `--kbq-plt-green-15`, `--kbq-plt-green-16`, `--kbq-plt-green-17`, `--kbq-plt-green-18`, `--kbq-plt-green-19`, `--kbq-plt-green-20` #### Teal `--kbq-plt-teal-1`, `--kbq-plt-teal-2`, `--kbq-plt-teal-3`, `--kbq-plt-teal-4`, `--kbq-plt-teal-5`, `--kbq-plt-teal-6`, `--kbq-plt-teal-7`, `--kbq-plt-teal-8`, `--kbq-plt-teal-9`, `--kbq-plt-teal-10`, `--kbq-plt-teal-11`, `--kbq-plt-teal-12`, `--kbq-plt-teal-13`, `--kbq-plt-teal-14`, `--kbq-plt-teal-15`, `--kbq-plt-teal-16`, `--kbq-plt-teal-17`, `--kbq-plt-teal-18`, `--kbq-plt-teal-19`, `--kbq-plt-teal-20` #### Purple `--kbq-plt-purple-1`, `--kbq-plt-purple-2`, `--kbq-plt-purple-3`, `--kbq-plt-purple-4`, `--kbq-plt-purple-5`, `--kbq-plt-purple-6`, `--kbq-plt-purple-7`, `--kbq-plt-purple-8`, `--kbq-plt-purple-9`, `--kbq-plt-purple-10`, `--kbq-plt-purple-11`, `--kbq-plt-purple-12`, `--kbq-plt-purple-13`, `--kbq-plt-purple-14`, `--kbq-plt-purple-15`, `--kbq-plt-purple-16`, `--kbq-plt-purple-17`, `--kbq-plt-purple-18`, `--kbq-plt-purple-19`, `--kbq-plt-purple-20` #### DarkBlue `--kbq-plt-dark-blue-1`, `--kbq-plt-dark-blue-2`, `--kbq-plt-dark-blue-3`, `--kbq-plt-dark-blue-4`, `--kbq-plt-dark-blue-5`, `--kbq-plt-dark-blue-6`, `--kbq-plt-dark-blue-7`, `--kbq-plt-dark-blue-8`, `--kbq-plt-dark-blue-9`, `--kbq-plt-dark-blue-10`, `--kbq-plt-dark-blue-11`, `--kbq-plt-dark-blue-12`, `--kbq-plt-dark-blue-13`, `--kbq-plt-dark-blue-14`, `--kbq-plt-dark-blue-15`, `--kbq-plt-dark-blue-16`, `--kbq-plt-dark-blue-17`, `--kbq-plt-dark-blue-18`, `--kbq-plt-dark-blue-19`, `--kbq-plt-dark-blue-20` #### DarkSlate `--kbq-plt-dark-slate-1`, `--kbq-plt-dark-slate-2`, `--kbq-plt-dark-slate-3`, `--kbq-plt-dark-slate-4`, `--kbq-plt-dark-slate-5`, `--kbq-plt-dark-slate-6`, `--kbq-plt-dark-slate-7`, `--kbq-plt-dark-slate-8`, `--kbq-plt-dark-slate-9`, `--kbq-plt-dark-slate-10`, `--kbq-plt-dark-slate-11`, `--kbq-plt-dark-slate-12`, `--kbq-plt-dark-slate-13`, `--kbq-plt-dark-slate-14`, `--kbq-plt-dark-slate-15`, `--kbq-plt-dark-slate-16`, `--kbq-plt-dark-slate-17`, `--kbq-plt-dark-slate-18`, `--kbq-plt-dark-slate-19`, `--kbq-plt-dark-slate-20` #### DarkRed `--kbq-plt-dark-red-1`, `--kbq-plt-dark-red-2`, `--kbq-plt-dark-red-3`, `--kbq-plt-dark-red-4`, `--kbq-plt-dark-red-5`, `--kbq-plt-dark-red-6`, `--kbq-plt-dark-red-7`, `--kbq-plt-dark-red-8`, `--kbq-plt-dark-red-9`, `--kbq-plt-dark-red-10`, `--kbq-plt-dark-red-11`, `--kbq-plt-dark-red-12`, `--kbq-plt-dark-red-13`, `--kbq-plt-dark-red-14`, `--kbq-plt-dark-red-15`, `--kbq-plt-dark-red-16`, `--kbq-plt-dark-red-17`, `--kbq-plt-dark-red-18`, `--kbq-plt-dark-red-19`, `--kbq-plt-dark-red-20` #### DarkOrange `--kbq-plt-dark-orange-1`, `--kbq-plt-dark-orange-2`, `--kbq-plt-dark-orange-3`, `--kbq-plt-dark-orange-4`, `--kbq-plt-dark-orange-5`, `--kbq-plt-dark-orange-6`, `--kbq-plt-dark-orange-7`, `--kbq-plt-dark-orange-8`, `--kbq-plt-dark-orange-9`, `--kbq-plt-dark-orange-10`, `--kbq-plt-dark-orange-11`, `--kbq-plt-dark-orange-12`, `--kbq-plt-dark-orange-13`, `--kbq-plt-dark-orange-14`, `--kbq-plt-dark-orange-15`, `--kbq-plt-dark-orange-16`, `--kbq-plt-dark-orange-17`, `--kbq-plt-dark-orange-18`, `--kbq-plt-dark-orange-19`, `--kbq-plt-dark-orange-20` #### DarkYellow `--kbq-plt-dark-yellow-1`, `--kbq-plt-dark-yellow-2`, `--kbq-plt-dark-yellow-3`, `--kbq-plt-dark-yellow-4`, `--kbq-plt-dark-yellow-5`, `--kbq-plt-dark-yellow-6`, `--kbq-plt-dark-yellow-7`, `--kbq-plt-dark-yellow-8`, `--kbq-plt-dark-yellow-9`, `--kbq-plt-dark-yellow-10`, `--kbq-plt-dark-yellow-11`, `--kbq-plt-dark-yellow-12`, `--kbq-plt-dark-yellow-13`, `--kbq-plt-dark-yellow-14`, `--kbq-plt-dark-yellow-15`, `--kbq-plt-dark-yellow-16`, `--kbq-plt-dark-yellow-17`, `--kbq-plt-dark-yellow-18`, `--kbq-plt-dark-yellow-19`, `--kbq-plt-dark-yellow-20` #### DarkGreen `--kbq-plt-dark-green-1`, `--kbq-plt-dark-green-2`, `--kbq-plt-dark-green-3`, `--kbq-plt-dark-green-4`, `--kbq-plt-dark-green-5`, `--kbq-plt-dark-green-6`, `--kbq-plt-dark-green-7`, `--kbq-plt-dark-green-8`, `--kbq-plt-dark-green-9`, `--kbq-plt-dark-green-10`, `--kbq-plt-dark-green-11`, `--kbq-plt-dark-green-12`, `--kbq-plt-dark-green-13`, `--kbq-plt-dark-green-14`, `--kbq-plt-dark-green-15`, `--kbq-plt-dark-green-16`, `--kbq-plt-dark-green-17`, `--kbq-plt-dark-green-18`, `--kbq-plt-dark-green-19`, `--kbq-plt-dark-green-20` #### DarkTeal `--kbq-plt-dark-teal-1`, `--kbq-plt-dark-teal-2`, `--kbq-plt-dark-teal-3`, `--kbq-plt-dark-teal-4`, `--kbq-plt-dark-teal-5`, `--kbq-plt-dark-teal-6`, `--kbq-plt-dark-teal-7`, `--kbq-plt-dark-teal-8`, `--kbq-plt-dark-teal-9`, `--kbq-plt-dark-teal-10`, `--kbq-plt-dark-teal-11`, `--kbq-plt-dark-teal-12`, `--kbq-plt-dark-teal-13`, `--kbq-plt-dark-teal-14`, `--kbq-plt-dark-teal-15`, `--kbq-plt-dark-teal-16`, `--kbq-plt-dark-teal-17`, `--kbq-plt-dark-teal-18`, `--kbq-plt-dark-teal-19`, `--kbq-plt-dark-teal-20` #### DarkPurple `--kbq-plt-dark-purple-1`, `--kbq-plt-dark-purple-2`, `--kbq-plt-dark-purple-3`, `--kbq-plt-dark-purple-4`, `--kbq-plt-dark-purple-5`, `--kbq-plt-dark-purple-6`, `--kbq-plt-dark-purple-7`, `--kbq-plt-dark-purple-8`, `--kbq-plt-dark-purple-9`, `--kbq-plt-dark-purple-10`, `--kbq-plt-dark-purple-11`, `--kbq-plt-dark-purple-12`, `--kbq-plt-dark-purple-13`, `--kbq-plt-dark-purple-14`, `--kbq-plt-dark-purple-15`, `--kbq-plt-dark-purple-16`, `--kbq-plt-dark-purple-17`, `--kbq-plt-dark-purple-18`, `--kbq-plt-dark-purple-19`, `--kbq-plt-dark-purple-20` #### Grey `--kbq-plt-grey-1`, `--kbq-plt-grey-2`, `--kbq-plt-grey-3`, `--kbq-plt-grey-4`, `--kbq-plt-grey-5`, `--kbq-plt-grey-6`, `--kbq-plt-grey-7`, `--kbq-plt-grey-8`, `--kbq-plt-grey-9`, `--kbq-plt-grey-10`, `--kbq-plt-grey-11`, `--kbq-plt-grey-12`, `--kbq-plt-grey-13`, `--kbq-plt-grey-14`, `--kbq-plt-grey-15`, `--kbq-plt-grey-16`, `--kbq-plt-grey-17`, `--kbq-plt-grey-18`, `--kbq-plt-grey-19`, `--kbq-plt-grey-20` #### DarkGrey `--kbq-plt-dark-grey-1`, `--kbq-plt-dark-grey-2`, `--kbq-plt-dark-grey-3`, `--kbq-plt-dark-grey-4`, `--kbq-plt-dark-grey-5`, `--kbq-plt-dark-grey-6`, `--kbq-plt-dark-grey-7`, `--kbq-plt-dark-grey-8`, `--kbq-plt-dark-grey-9`, `--kbq-plt-dark-grey-10`, `--kbq-plt-dark-grey-11`, `--kbq-plt-dark-grey-12`, `--kbq-plt-dark-grey-13`, `--kbq-plt-dark-grey-14`, `--kbq-plt-dark-grey-15`, `--kbq-plt-dark-grey-16`, `--kbq-plt-dark-grey-17`, `--kbq-plt-dark-grey-18`, `--kbq-plt-dark-grey-19`, `--kbq-plt-dark-grey-20` #### GreyA `--kbq-plt-grey-a1`, `--kbq-plt-grey-a2`, `--kbq-plt-grey-a3`, `--kbq-plt-grey-a4`, `--kbq-plt-grey-a5`, `--kbq-plt-grey-a6`, `--kbq-plt-grey-a7`, `--kbq-plt-grey-a8`, `--kbq-plt-grey-a9`, `--kbq-plt-grey-a10`, `--kbq-plt-grey-a11`, `--kbq-plt-grey-a12`, `--kbq-plt-grey-a13`, `--kbq-plt-grey-a14`, `--kbq-plt-grey-a15`, `--kbq-plt-grey-a16`, `--kbq-plt-grey-a17`, `--kbq-plt-grey-a18`, `--kbq-plt-grey-a19`, `--kbq-plt-grey-a20` #### DarkGreyA `--kbq-plt-dark-grey-a1`, `--kbq-plt-dark-grey-a2`, `--kbq-plt-dark-grey-a3`, `--kbq-plt-dark-grey-a4`, `--kbq-plt-dark-grey-a5`, `--kbq-plt-dark-grey-a6`, `--kbq-plt-dark-grey-a7`, `--kbq-plt-dark-grey-a8`, `--kbq-plt-dark-grey-a9`, `--kbq-plt-dark-grey-a10`, `--kbq-plt-dark-grey-a11`, `--kbq-plt-dark-grey-a12`, `--kbq-plt-dark-grey-a13`, `--kbq-plt-dark-grey-a14`, `--kbq-plt-dark-grey-a15`, `--kbq-plt-dark-grey-a16`, `--kbq-plt-dark-grey-a17`, `--kbq-plt-dark-grey-a18`, `--kbq-plt-dark-grey-a19`, `--kbq-plt-dark-grey-a20` #### WhiteA `--kbq-plt-white-a1`, `--kbq-plt-white-a2`, `--kbq-plt-white-a3`, `--kbq-plt-white-a4`, `--kbq-plt-white-a5`, `--kbq-plt-white-a6`, `--kbq-plt-white-a7`, `--kbq-plt-white-a8`, `--kbq-plt-white-a9`, `--kbq-plt-white-a10`, `--kbq-plt-white-a11`, `--kbq-plt-white-a12`, `--kbq-plt-white-a13`, `--kbq-plt-white-a14`, `--kbq-plt-white-a15`, `--kbq-plt-white-a16`, `--kbq-plt-white-a17`, `--kbq-plt-white-a18`, `--kbq-plt-white-a19`, `--kbq-plt-white-a20` #### BlackA `--kbq-plt-black-a1`, `--kbq-plt-black-a2`, `--kbq-plt-black-a3`, `--kbq-plt-black-a4`, `--kbq-plt-black-a5`, `--kbq-plt-black-a6`, `--kbq-plt-black-a7`, `--kbq-plt-black-a8`, `--kbq-plt-black-a9`, `--kbq-plt-black-a10`, `--kbq-plt-black-a11`, `--kbq-plt-black-a12`, `--kbq-plt-black-a13`, `--kbq-plt-black-a14`, `--kbq-plt-black-a15`, `--kbq-plt-black-a16`, `--kbq-plt-black-a17`, `--kbq-plt-black-a18`, `--kbq-plt-black-a19`, `--kbq-plt-black-a20` #### BlueA `--kbq-plt-blue-a1`, `--kbq-plt-blue-a2`, `--kbq-plt-blue-a3`, `--kbq-plt-blue-a4`, `--kbq-plt-blue-a5`, `--kbq-plt-blue-a6`, `--kbq-plt-blue-a7`, `--kbq-plt-blue-a8`, `--kbq-plt-blue-a9`, `--kbq-plt-blue-a10`, `--kbq-plt-blue-a11`, `--kbq-plt-blue-a12`, `--kbq-plt-blue-a13`, `--kbq-plt-blue-a14`, `--kbq-plt-blue-a15`, `--kbq-plt-blue-a16`, `--kbq-plt-blue-a17`, `--kbq-plt-blue-a18`, `--kbq-plt-blue-a19`, `--kbq-plt-blue-a20` #### SlateA `--kbq-plt-slate-a1`, `--kbq-plt-slate-a2`, `--kbq-plt-slate-a3`, `--kbq-plt-slate-a4`, `--kbq-plt-slate-a5`, `--kbq-plt-slate-a6`, `--kbq-plt-slate-a7`, `--kbq-plt-slate-a8`, `--kbq-plt-slate-a9`, `--kbq-plt-slate-a10`, `--kbq-plt-slate-a11`, `--kbq-plt-slate-a12`, `--kbq-plt-slate-a13`, `--kbq-plt-slate-a14`, `--kbq-plt-slate-a15`, `--kbq-plt-slate-a16`, `--kbq-plt-slate-a17`, `--kbq-plt-slate-a18`, `--kbq-plt-slate-a19`, `--kbq-plt-slate-a20` #### RedA `--kbq-plt-red-a1`, `--kbq-plt-red-a2`, `--kbq-plt-red-a3`, `--kbq-plt-red-a4`, `--kbq-plt-red-a5`, `--kbq-plt-red-a6`, `--kbq-plt-red-a7`, `--kbq-plt-red-a8`, `--kbq-plt-red-a9`, `--kbq-plt-red-a10`, `--kbq-plt-red-a11`, `--kbq-plt-red-a12`, `--kbq-plt-red-a13`, `--kbq-plt-red-a14`, `--kbq-plt-red-a15`, `--kbq-plt-red-a16`, `--kbq-plt-red-a17`, `--kbq-plt-red-a18`, `--kbq-plt-red-a19`, `--kbq-plt-red-a20` #### OrangeA `--kbq-plt-orange-a1`, `--kbq-plt-orange-a2`, `--kbq-plt-orange-a3`, `--kbq-plt-orange-a4`, `--kbq-plt-orange-a5`, `--kbq-plt-orange-a6`, `--kbq-plt-orange-a7`, `--kbq-plt-orange-a8`, `--kbq-plt-orange-a9`, `--kbq-plt-orange-a10`, `--kbq-plt-orange-a11`, `--kbq-plt-orange-a12`, `--kbq-plt-orange-a13`, `--kbq-plt-orange-a14`, `--kbq-plt-orange-a15`, `--kbq-plt-orange-a16`, `--kbq-plt-orange-a17`, `--kbq-plt-orange-a18`, `--kbq-plt-orange-a19`, `--kbq-plt-orange-a20` #### YellowA `--kbq-plt-yellow-a1`, `--kbq-plt-yellow-a2`, `--kbq-plt-yellow-a3`, `--kbq-plt-yellow-a4`, `--kbq-plt-yellow-a5`, `--kbq-plt-yellow-a6`, `--kbq-plt-yellow-a7`, `--kbq-plt-yellow-a8`, `--kbq-plt-yellow-a9`, `--kbq-plt-yellow-a10`, `--kbq-plt-yellow-a11`, `--kbq-plt-yellow-a12`, `--kbq-plt-yellow-a13`, `--kbq-plt-yellow-a14`, `--kbq-plt-yellow-a15`, `--kbq-plt-yellow-a16`, `--kbq-plt-yellow-a17`, `--kbq-plt-yellow-a18`, `--kbq-plt-yellow-a19`, `--kbq-plt-yellow-a20` #### GreenA `--kbq-plt-green-a1`, `--kbq-plt-green-a2`, `--kbq-plt-green-a3`, `--kbq-plt-green-a4`, `--kbq-plt-green-a5`, `--kbq-plt-green-a6`, `--kbq-plt-green-a7`, `--kbq-plt-green-a8`, `--kbq-plt-green-a9`, `--kbq-plt-green-a10`, `--kbq-plt-green-a11`, `--kbq-plt-green-a12`, `--kbq-plt-green-a13`, `--kbq-plt-green-a14`, `--kbq-plt-green-a15`, `--kbq-plt-green-a16`, `--kbq-plt-green-a17`, `--kbq-plt-green-a18`, `--kbq-plt-green-a19`, `--kbq-plt-green-a20` #### TealA `--kbq-plt-teal-a1`, `--kbq-plt-teal-a2`, `--kbq-plt-teal-a3`, `--kbq-plt-teal-a4`, `--kbq-plt-teal-a5`, `--kbq-plt-teal-a6`, `--kbq-plt-teal-a7`, `--kbq-plt-teal-a8`, `--kbq-plt-teal-a9`, `--kbq-plt-teal-a10`, `--kbq-plt-teal-a11`, `--kbq-plt-teal-a12`, `--kbq-plt-teal-a13`, `--kbq-plt-teal-a14`, `--kbq-plt-teal-a15`, `--kbq-plt-teal-a16`, `--kbq-plt-teal-a17`, `--kbq-plt-teal-a18`, `--kbq-plt-teal-a19`, `--kbq-plt-teal-a20` #### PurpleA `--kbq-plt-purple-a1`, `--kbq-plt-purple-a2`, `--kbq-plt-purple-a3`, `--kbq-plt-purple-a4`, `--kbq-plt-purple-a5`, `--kbq-plt-purple-a6`, `--kbq-plt-purple-a7`, `--kbq-plt-purple-a8`, `--kbq-plt-purple-a9`, `--kbq-plt-purple-a10`, `--kbq-plt-purple-a11`, `--kbq-plt-purple-a12`, `--kbq-plt-purple-a13`, `--kbq-plt-purple-a14`, `--kbq-plt-purple-a15`, `--kbq-plt-purple-a16`, `--kbq-plt-purple-a17`, `--kbq-plt-purple-a18`, `--kbq-plt-purple-a19`, `--kbq-plt-purple-a20` #### DarkBlueA `--kbq-plt-dark-blue-a1`, `--kbq-plt-dark-blue-a2`, `--kbq-plt-dark-blue-a3`, `--kbq-plt-dark-blue-a4`, `--kbq-plt-dark-blue-a5`, `--kbq-plt-dark-blue-a6`, `--kbq-plt-dark-blue-a7`, `--kbq-plt-dark-blue-a8`, `--kbq-plt-dark-blue-a9`, `--kbq-plt-dark-blue-a10`, `--kbq-plt-dark-blue-a11`, `--kbq-plt-dark-blue-a12`, `--kbq-plt-dark-blue-a13`, `--kbq-plt-dark-blue-a14`, `--kbq-plt-dark-blue-a15`, `--kbq-plt-dark-blue-a16`, `--kbq-plt-dark-blue-a17`, `--kbq-plt-dark-blue-a18`, `--kbq-plt-dark-blue-a19`, `--kbq-plt-dark-blue-a20` #### DarkSlateA `--kbq-plt-dark-slate-a1`, `--kbq-plt-dark-slate-a2`, `--kbq-plt-dark-slate-a3`, `--kbq-plt-dark-slate-a4`, `--kbq-plt-dark-slate-a5`, `--kbq-plt-dark-slate-a6`, `--kbq-plt-dark-slate-a7`, `--kbq-plt-dark-slate-a8`, `--kbq-plt-dark-slate-a9`, `--kbq-plt-dark-slate-a10`, `--kbq-plt-dark-slate-a11`, `--kbq-plt-dark-slate-a12`, `--kbq-plt-dark-slate-a13`, `--kbq-plt-dark-slate-a14`, `--kbq-plt-dark-slate-a15`, `--kbq-plt-dark-slate-a16`, `--kbq-plt-dark-slate-a17`, `--kbq-plt-dark-slate-a18`, `--kbq-plt-dark-slate-a19`, `--kbq-plt-dark-slate-a20` #### DarkRedA `--kbq-plt-dark-red-a1`, `--kbq-plt-dark-red-a2`, `--kbq-plt-dark-red-a3`, `--kbq-plt-dark-red-a4`, `--kbq-plt-dark-red-a5`, `--kbq-plt-dark-red-a6`, `--kbq-plt-dark-red-a7`, `--kbq-plt-dark-red-a8`, `--kbq-plt-dark-red-a9`, `--kbq-plt-dark-red-a10`, `--kbq-plt-dark-red-a11`, `--kbq-plt-dark-red-a12`, `--kbq-plt-dark-red-a13`, `--kbq-plt-dark-red-a14`, `--kbq-plt-dark-red-a15`, `--kbq-plt-dark-red-a16`, `--kbq-plt-dark-red-a17`, `--kbq-plt-dark-red-a18`, `--kbq-plt-dark-red-a19`, `--kbq-plt-dark-red-a20` #### DarkOrangeA `--kbq-plt-dark-orange-a1`, `--kbq-plt-dark-orange-a2`, `--kbq-plt-dark-orange-a3`, `--kbq-plt-dark-orange-a4`, `--kbq-plt-dark-orange-a5`, `--kbq-plt-dark-orange-a6`, `--kbq-plt-dark-orange-a7`, `--kbq-plt-dark-orange-a8`, `--kbq-plt-dark-orange-a9`, `--kbq-plt-dark-orange-a10`, `--kbq-plt-dark-orange-a11`, `--kbq-plt-dark-orange-a12`, `--kbq-plt-dark-orange-a13`, `--kbq-plt-dark-orange-a14`, `--kbq-plt-dark-orange-a15`, `--kbq-plt-dark-orange-a16`, `--kbq-plt-dark-orange-a17`, `--kbq-plt-dark-orange-a18`, `--kbq-plt-dark-orange-a19`, `--kbq-plt-dark-orange-a20` #### DarkYellowA `--kbq-plt-dark-yellow-a1`, `--kbq-plt-dark-yellow-a2`, `--kbq-plt-dark-yellow-a3`, `--kbq-plt-dark-yellow-a4`, `--kbq-plt-dark-yellow-a5`, `--kbq-plt-dark-yellow-a6`, `--kbq-plt-dark-yellow-a7`, `--kbq-plt-dark-yellow-a8`, `--kbq-plt-dark-yellow-a9`, `--kbq-plt-dark-yellow-a10`, `--kbq-plt-dark-yellow-a11`, `--kbq-plt-dark-yellow-a12`, `--kbq-plt-dark-yellow-a13`, `--kbq-plt-dark-yellow-a14`, `--kbq-plt-dark-yellow-a15`, `--kbq-plt-dark-yellow-a16`, `--kbq-plt-dark-yellow-a17`, `--kbq-plt-dark-yellow-a18`, `--kbq-plt-dark-yellow-a19`, `--kbq-plt-dark-yellow-a20` #### DarkGreenA `--kbq-plt-dark-green-a1`, `--kbq-plt-dark-green-a2`, `--kbq-plt-dark-green-a3`, `--kbq-plt-dark-green-a4`, `--kbq-plt-dark-green-a5`, `--kbq-plt-dark-green-a6`, `--kbq-plt-dark-green-a7`, `--kbq-plt-dark-green-a8`, `--kbq-plt-dark-green-a9`, `--kbq-plt-dark-green-a10`, `--kbq-plt-dark-green-a11`, `--kbq-plt-dark-green-a12`, `--kbq-plt-dark-green-a13`, `--kbq-plt-dark-green-a14`, `--kbq-plt-dark-green-a15`, `--kbq-plt-dark-green-a16`, `--kbq-plt-dark-green-a17`, `--kbq-plt-dark-green-a18`, `--kbq-plt-dark-green-a19`, `--kbq-plt-dark-green-a20` #### DarkTealA `--kbq-plt-dark-teal-a1`, `--kbq-plt-dark-teal-a2`, `--kbq-plt-dark-teal-a3`, `--kbq-plt-dark-teal-a4`, `--kbq-plt-dark-teal-a5`, `--kbq-plt-dark-teal-a6`, `--kbq-plt-dark-teal-a7`, `--kbq-plt-dark-teal-a8`, `--kbq-plt-dark-teal-a9`, `--kbq-plt-dark-teal-a10`, `--kbq-plt-dark-teal-a11`, `--kbq-plt-dark-teal-a12`, `--kbq-plt-dark-teal-a13`, `--kbq-plt-dark-teal-a14`, `--kbq-plt-dark-teal-a15`, `--kbq-plt-dark-teal-a16`, `--kbq-plt-dark-teal-a17`, `--kbq-plt-dark-teal-a18`, `--kbq-plt-dark-teal-a19`, `--kbq-plt-dark-teal-a20` #### DarkPurpleA `--kbq-plt-dark-purple-a1`, `--kbq-plt-dark-purple-a2`, `--kbq-plt-dark-purple-a3`, `--kbq-plt-dark-purple-a4`, `--kbq-plt-dark-purple-a5`, `--kbq-plt-dark-purple-a6`, `--kbq-plt-dark-purple-a7`, `--kbq-plt-dark-purple-a8`, `--kbq-plt-dark-purple-a9`, `--kbq-plt-dark-purple-a10`, `--kbq-plt-dark-purple-a11`, `--kbq-plt-dark-purple-a12`, `--kbq-plt-dark-purple-a13`, `--kbq-plt-dark-purple-a14`, `--kbq-plt-dark-purple-a15`, `--kbq-plt-dark-purple-a16`, `--kbq-plt-dark-purple-a17`, `--kbq-plt-dark-purple-a18`, `--kbq-plt-dark-purple-a19`, `--kbq-plt-dark-purple-a20` #### YellowFixed `--kbq-plt-yellow-fixed-1`, `--kbq-plt-yellow-fixed-2`, `--kbq-plt-yellow-fixed-3`, `--kbq-plt-yellow-fixed-4`, `--kbq-plt-yellow-fixed-5`, `--kbq-plt-yellow-fixed-6`, `--kbq-plt-yellow-fixed-7`, `--kbq-plt-yellow-fixed-8`, `--kbq-plt-yellow-fixed-9`, `--kbq-plt-yellow-fixed-10`, `--kbq-plt-yellow-fixed-11`, `--kbq-plt-yellow-fixed-12`, `--kbq-plt-yellow-fixed-13`, `--kbq-plt-yellow-fixed-14`, `--kbq-plt-yellow-fixed-15`, `--kbq-plt-yellow-fixed-16`, `--kbq-plt-yellow-fixed-17`, `--kbq-plt-yellow-fixed-18`, `--kbq-plt-yellow-fixed-19`, `--kbq-plt-yellow-fixed-20` #### OrangeFixed `--kbq-plt-orange-fixed-1`, `--kbq-plt-orange-fixed-2`, `--kbq-plt-orange-fixed-3`, `--kbq-plt-orange-fixed-4`, `--kbq-plt-orange-fixed-5`, `--kbq-plt-orange-fixed-6`, `--kbq-plt-orange-fixed-7`, `--kbq-plt-orange-fixed-8`, `--kbq-plt-orange-fixed-9`, `--kbq-plt-orange-fixed-10`, `--kbq-plt-orange-fixed-11`, `--kbq-plt-orange-fixed-12`, `--kbq-plt-orange-fixed-13`, `--kbq-plt-orange-fixed-14`, `--kbq-plt-orange-fixed-15`, `--kbq-plt-orange-fixed-16`, `--kbq-plt-orange-fixed-17`, `--kbq-plt-orange-fixed-18`, `--kbq-plt-orange-fixed-19`, `--kbq-plt-orange-fixed-20` #### DarkYellowFixed `--kbq-plt-dark-yellow-fixed-1`, `--kbq-plt-dark-yellow-fixed-2`, `--kbq-plt-dark-yellow-fixed-3`, `--kbq-plt-dark-yellow-fixed-4`, `--kbq-plt-dark-yellow-fixed-5`, `--kbq-plt-dark-yellow-fixed-6`, `--kbq-plt-dark-yellow-fixed-7`, `--kbq-plt-dark-yellow-fixed-8`, `--kbq-plt-dark-yellow-fixed-9`, `--kbq-plt-dark-yellow-fixed-10`, `--kbq-plt-dark-yellow-fixed-11`, `--kbq-plt-dark-yellow-fixed-12`, `--kbq-plt-dark-yellow-fixed-13`, `--kbq-plt-dark-yellow-fixed-14`, `--kbq-plt-dark-yellow-fixed-15`, `--kbq-plt-dark-yellow-fixed-16`, `--kbq-plt-dark-yellow-fixed-17`, `--kbq-plt-dark-yellow-fixed-18`, `--kbq-plt-dark-yellow-fixed-19`, `--kbq-plt-dark-yellow-fixed-20` #### DarkOrangeFixed `--kbq-plt-dark-orange-fixed-1`, `--kbq-plt-dark-orange-fixed-2`, `--kbq-plt-dark-orange-fixed-3`, `--kbq-plt-dark-orange-fixed-4`, `--kbq-plt-dark-orange-fixed-5`, `--kbq-plt-dark-orange-fixed-6`, `--kbq-plt-dark-orange-fixed-7`, `--kbq-plt-dark-orange-fixed-8`, `--kbq-plt-dark-orange-fixed-9`, `--kbq-plt-dark-orange-fixed-10`, `--kbq-plt-dark-orange-fixed-11`, `--kbq-plt-dark-orange-fixed-12`, `--kbq-plt-dark-orange-fixed-13`, `--kbq-plt-dark-orange-fixed-14`, `--kbq-plt-dark-orange-fixed-15`, `--kbq-plt-dark-orange-fixed-16`, `--kbq-plt-dark-orange-fixed-17`, `--kbq-plt-dark-orange-fixed-18`, `--kbq-plt-dark-orange-fixed-19`, `--kbq-plt-dark-orange-fixed-20` #### YellowFixedA `--kbq-plt-yellow-fixed-a1`, `--kbq-plt-yellow-fixed-a2`, `--kbq-plt-yellow-fixed-a3`, `--kbq-plt-yellow-fixed-a4`, `--kbq-plt-yellow-fixed-a5`, `--kbq-plt-yellow-fixed-a6`, `--kbq-plt-yellow-fixed-a7`, `--kbq-plt-yellow-fixed-a8`, `--kbq-plt-yellow-fixed-a9`, `--kbq-plt-yellow-fixed-a10`, `--kbq-plt-yellow-fixed-a11`, `--kbq-plt-yellow-fixed-a12`, `--kbq-plt-yellow-fixed-a13`, `--kbq-plt-yellow-fixed-a14`, `--kbq-plt-yellow-fixed-a15`, `--kbq-plt-yellow-fixed-a16`, `--kbq-plt-yellow-fixed-a17`, `--kbq-plt-yellow-fixed-a18`, `--kbq-plt-yellow-fixed-a19`, `--kbq-plt-yellow-fixed-a20` #### OrangeFixedA `--kbq-plt-orange-fixed-a1`, `--kbq-plt-orange-fixed-a2`, `--kbq-plt-orange-fixed-a3`, `--kbq-plt-orange-fixed-a4`, `--kbq-plt-orange-fixed-a5`, `--kbq-plt-orange-fixed-a6`, `--kbq-plt-orange-fixed-a7`, `--kbq-plt-orange-fixed-a8`, `--kbq-plt-orange-fixed-a9`, `--kbq-plt-orange-fixed-a10`, `--kbq-plt-orange-fixed-a11`, `--kbq-plt-orange-fixed-a12`, `--kbq-plt-orange-fixed-a13`, `--kbq-plt-orange-fixed-a14`, `--kbq-plt-orange-fixed-a15`, `--kbq-plt-orange-fixed-a16`, `--kbq-plt-orange-fixed-a17`, `--kbq-plt-orange-fixed-a18`, `--kbq-plt-orange-fixed-a19`, `--kbq-plt-orange-fixed-a20` #### DarkYellowFixedA `--kbq-plt-dark-yellow-fixed-a1`, `--kbq-plt-dark-yellow-fixed-a2`, `--kbq-plt-dark-yellow-fixed-a3`, `--kbq-plt-dark-yellow-fixed-a4`, `--kbq-plt-dark-yellow-fixed-a5`, `--kbq-plt-dark-yellow-fixed-a6`, `--kbq-plt-dark-yellow-fixed-a7`, `--kbq-plt-dark-yellow-fixed-a8`, `--kbq-plt-dark-yellow-fixed-a9`, `--kbq-plt-dark-yellow-fixed-a10`, `--kbq-plt-dark-yellow-fixed-a11`, `--kbq-plt-dark-yellow-fixed-a12`, `--kbq-plt-dark-yellow-fixed-a13`, `--kbq-plt-dark-yellow-fixed-a14`, `--kbq-plt-dark-yellow-fixed-a15`, `--kbq-plt-dark-yellow-fixed-a16`, `--kbq-plt-dark-yellow-fixed-a17`, `--kbq-plt-dark-yellow-fixed-a18`, `--kbq-plt-dark-yellow-fixed-a19`, `--kbq-plt-dark-yellow-fixed-a20` #### DarkOrangeFixedA `--kbq-plt-dark-orange-fixed-a1`, `--kbq-plt-dark-orange-fixed-a2`, `--kbq-plt-dark-orange-fixed-a3`, `--kbq-plt-dark-orange-fixed-a4`, `--kbq-plt-dark-orange-fixed-a5`, `--kbq-plt-dark-orange-fixed-a6`, `--kbq-plt-dark-orange-fixed-a7`, `--kbq-plt-dark-orange-fixed-a8`, `--kbq-plt-dark-orange-fixed-a9`, `--kbq-plt-dark-orange-fixed-a10`, `--kbq-plt-dark-orange-fixed-a11`, `--kbq-plt-dark-orange-fixed-a12`, `--kbq-plt-dark-orange-fixed-a13`, `--kbq-plt-dark-orange-fixed-a14`, `--kbq-plt-dark-orange-fixed-a15`, `--kbq-plt-dark-orange-fixed-a16`, `--kbq-plt-dark-orange-fixed-a17`, `--kbq-plt-dark-orange-fixed-a18`, `--kbq-plt-dark-orange-fixed-a19`, `--kbq-plt-dark-orange-fixed-a20` #### White `--kbq-plt-white` #### Black `--kbq-plt-black` ### Semantic palette #### Contrast `--kbq-semantic-contrast-1`, `--kbq-semantic-contrast-2`, `--kbq-semantic-contrast-3`, `--kbq-semantic-contrast-4`, `--kbq-semantic-contrast-5`, `--kbq-semantic-contrast-6`, `--kbq-semantic-contrast-7`, `--kbq-semantic-contrast-8`, `--kbq-semantic-contrast-9`, `--kbq-semantic-contrast-10`, `--kbq-semantic-contrast-11`, `--kbq-semantic-contrast-12`, `--kbq-semantic-contrast-13`, `--kbq-semantic-contrast-14`, `--kbq-semantic-contrast-15`, `--kbq-semantic-contrast-16`, `--kbq-semantic-contrast-17`, `--kbq-semantic-contrast-18`, `--kbq-semantic-contrast-19`, `--kbq-semantic-contrast-20` #### ContrastA `--kbq-semantic-contrast-a1`, `--kbq-semantic-contrast-a2`, `--kbq-semantic-contrast-a3`, `--kbq-semantic-contrast-a4`, `--kbq-semantic-contrast-a5`, `--kbq-semantic-contrast-a6`, `--kbq-semantic-contrast-a7`, `--kbq-semantic-contrast-a8`, `--kbq-semantic-contrast-a9`, `--kbq-semantic-contrast-a10`, `--kbq-semantic-contrast-a11`, `--kbq-semantic-contrast-a12`, `--kbq-semantic-contrast-a13`, `--kbq-semantic-contrast-a14`, `--kbq-semantic-contrast-a15`, `--kbq-semantic-contrast-a16`, `--kbq-semantic-contrast-a17`, `--kbq-semantic-contrast-a18`, `--kbq-semantic-contrast-a19`, `--kbq-semantic-contrast-a20` #### Theme `--kbq-semantic-theme-1`, `--kbq-semantic-theme-2`, `--kbq-semantic-theme-3`, `--kbq-semantic-theme-4`, `--kbq-semantic-theme-5`, `--kbq-semantic-theme-6`, `--kbq-semantic-theme-7`, `--kbq-semantic-theme-8`, `--kbq-semantic-theme-9`, `--kbq-semantic-theme-10`, `--kbq-semantic-theme-11`, `--kbq-semantic-theme-12`, `--kbq-semantic-theme-13`, `--kbq-semantic-theme-14`, `--kbq-semantic-theme-15`, `--kbq-semantic-theme-16`, `--kbq-semantic-theme-17`, `--kbq-semantic-theme-18`, `--kbq-semantic-theme-19`, `--kbq-semantic-theme-20` #### ThemeA `--kbq-semantic-theme-a1`, `--kbq-semantic-theme-a2`, `--kbq-semantic-theme-a3`, `--kbq-semantic-theme-a4`, `--kbq-semantic-theme-a5`, `--kbq-semantic-theme-a6`, `--kbq-semantic-theme-a7`, `--kbq-semantic-theme-a8`, `--kbq-semantic-theme-a9`, `--kbq-semantic-theme-a10`, `--kbq-semantic-theme-a11`, `--kbq-semantic-theme-a12`, `--kbq-semantic-theme-a13`, `--kbq-semantic-theme-a14`, `--kbq-semantic-theme-a15`, `--kbq-semantic-theme-a16`, `--kbq-semantic-theme-a17`, `--kbq-semantic-theme-a18`, `--kbq-semantic-theme-a19`, `--kbq-semantic-theme-a20` #### Error `--kbq-semantic-error-1`, `--kbq-semantic-error-2`, `--kbq-semantic-error-3`, `--kbq-semantic-error-4`, `--kbq-semantic-error-5`, `--kbq-semantic-error-6`, `--kbq-semantic-error-7`, `--kbq-semantic-error-8`, `--kbq-semantic-error-9`, `--kbq-semantic-error-10`, `--kbq-semantic-error-11`, `--kbq-semantic-error-12`, `--kbq-semantic-error-13`, `--kbq-semantic-error-14`, `--kbq-semantic-error-15`, `--kbq-semantic-error-16`, `--kbq-semantic-error-17`, `--kbq-semantic-error-18`, `--kbq-semantic-error-19`, `--kbq-semantic-error-20` #### ErrorA `--kbq-semantic-error-a1`, `--kbq-semantic-error-a2`, `--kbq-semantic-error-a3`, `--kbq-semantic-error-a4`, `--kbq-semantic-error-a5`, `--kbq-semantic-error-a6`, `--kbq-semantic-error-a7`, `--kbq-semantic-error-a8`, `--kbq-semantic-error-a9`, `--kbq-semantic-error-a10`, `--kbq-semantic-error-a11`, `--kbq-semantic-error-a12`, `--kbq-semantic-error-a13`, `--kbq-semantic-error-a14`, `--kbq-semantic-error-a15`, `--kbq-semantic-error-a16`, `--kbq-semantic-error-a17`, `--kbq-semantic-error-a18`, `--kbq-semantic-error-a19`, `--kbq-semantic-error-a20` #### Warning `--kbq-semantic-warning-1`, `--kbq-semantic-warning-2`, `--kbq-semantic-warning-3`, `--kbq-semantic-warning-4`, `--kbq-semantic-warning-5`, `--kbq-semantic-warning-6`, `--kbq-semantic-warning-7`, `--kbq-semantic-warning-8`, `--kbq-semantic-warning-9`, `--kbq-semantic-warning-10`, `--kbq-semantic-warning-11`, `--kbq-semantic-warning-12`, `--kbq-semantic-warning-13`, `--kbq-semantic-warning-14`, `--kbq-semantic-warning-15`, `--kbq-semantic-warning-16`, `--kbq-semantic-warning-17`, `--kbq-semantic-warning-18`, `--kbq-semantic-warning-19`, `--kbq-semantic-warning-20` #### WarningA `--kbq-semantic-warning-a1`, `--kbq-semantic-warning-a2`, `--kbq-semantic-warning-a3`, `--kbq-semantic-warning-a4`, `--kbq-semantic-warning-a5`, `--kbq-semantic-warning-a6`, `--kbq-semantic-warning-a7`, `--kbq-semantic-warning-a8`, `--kbq-semantic-warning-a9`, `--kbq-semantic-warning-a10`, `--kbq-semantic-warning-a11`, `--kbq-semantic-warning-a12`, `--kbq-semantic-warning-a13`, `--kbq-semantic-warning-a14`, `--kbq-semantic-warning-a15`, `--kbq-semantic-warning-a16`, `--kbq-semantic-warning-a17`, `--kbq-semantic-warning-a18`, `--kbq-semantic-warning-a19`, `--kbq-semantic-warning-a20` #### WarningFixed `--kbq-semantic-warning-fixed-1`, `--kbq-semantic-warning-fixed-2`, `--kbq-semantic-warning-fixed-3`, `--kbq-semantic-warning-fixed-4`, `--kbq-semantic-warning-fixed-5`, `--kbq-semantic-warning-fixed-6`, `--kbq-semantic-warning-fixed-7`, `--kbq-semantic-warning-fixed-8`, `--kbq-semantic-warning-fixed-9`, `--kbq-semantic-warning-fixed-10`, `--kbq-semantic-warning-fixed-11`, `--kbq-semantic-warning-fixed-12`, `--kbq-semantic-warning-fixed-13`, `--kbq-semantic-warning-fixed-14`, `--kbq-semantic-warning-fixed-15`, `--kbq-semantic-warning-fixed-16`, `--kbq-semantic-warning-fixed-17`, `--kbq-semantic-warning-fixed-18`, `--kbq-semantic-warning-fixed-19`, `--kbq-semantic-warning-fixed-20` #### WarningFixedA `--kbq-semantic-warning-fixed-a1`, `--kbq-semantic-warning-fixed-a2`, `--kbq-semantic-warning-fixed-a3`, `--kbq-semantic-warning-fixed-a4`, `--kbq-semantic-warning-fixed-a5`, `--kbq-semantic-warning-fixed-a6`, `--kbq-semantic-warning-fixed-a7`, `--kbq-semantic-warning-fixed-a8`, `--kbq-semantic-warning-fixed-a9`, `--kbq-semantic-warning-fixed-a10`, `--kbq-semantic-warning-fixed-a11`, `--kbq-semantic-warning-fixed-a12`, `--kbq-semantic-warning-fixed-a13`, `--kbq-semantic-warning-fixed-a14`, `--kbq-semantic-warning-fixed-a15`, `--kbq-semantic-warning-fixed-a16`, `--kbq-semantic-warning-fixed-a17`, `--kbq-semantic-warning-fixed-a18`, `--kbq-semantic-warning-fixed-a19`, `--kbq-semantic-warning-fixed-a20` #### Success `--kbq-semantic-success-1`, `--kbq-semantic-success-2`, `--kbq-semantic-success-3`, `--kbq-semantic-success-4`, `--kbq-semantic-success-5`, `--kbq-semantic-success-6`, `--kbq-semantic-success-7`, `--kbq-semantic-success-8`, `--kbq-semantic-success-9`, `--kbq-semantic-success-10`, `--kbq-semantic-success-11`, `--kbq-semantic-success-12`, `--kbq-semantic-success-13`, `--kbq-semantic-success-14`, `--kbq-semantic-success-15`, `--kbq-semantic-success-16`, `--kbq-semantic-success-17`, `--kbq-semantic-success-18`, `--kbq-semantic-success-19`, `--kbq-semantic-success-20` #### SuccessA `--kbq-semantic-success-a1`, `--kbq-semantic-success-a2`, `--kbq-semantic-success-a3`, `--kbq-semantic-success-a4`, `--kbq-semantic-success-a5`, `--kbq-semantic-success-a6`, `--kbq-semantic-success-a7`, `--kbq-semantic-success-a8`, `--kbq-semantic-success-a9`, `--kbq-semantic-success-a10`, `--kbq-semantic-success-a11`, `--kbq-semantic-success-a12`, `--kbq-semantic-success-a13`, `--kbq-semantic-success-a14`, `--kbq-semantic-success-a15`, `--kbq-semantic-success-a16`, `--kbq-semantic-success-a17`, `--kbq-semantic-success-a18`, `--kbq-semantic-success-a19`, `--kbq-semantic-success-a20` #### Visited `--kbq-semantic-visited-1`, `--kbq-semantic-visited-2`, `--kbq-semantic-visited-3`, `--kbq-semantic-visited-4`, `--kbq-semantic-visited-5`, `--kbq-semantic-visited-6`, `--kbq-semantic-visited-7`, `--kbq-semantic-visited-8`, `--kbq-semantic-visited-9`, `--kbq-semantic-visited-10`, `--kbq-semantic-visited-11`, `--kbq-semantic-visited-12`, `--kbq-semantic-visited-13`, `--kbq-semantic-visited-14`, `--kbq-semantic-visited-15`, `--kbq-semantic-visited-16`, `--kbq-semantic-visited-17`, `--kbq-semantic-visited-18`, `--kbq-semantic-visited-19`, `--kbq-semantic-visited-20` #### VisitedA `--kbq-semantic-visited-a1`, `--kbq-semantic-visited-a2`, `--kbq-semantic-visited-a3`, `--kbq-semantic-visited-a4`, `--kbq-semantic-visited-a5`, `--kbq-semantic-visited-a6`, `--kbq-semantic-visited-a7`, `--kbq-semantic-visited-a8`, `--kbq-semantic-visited-a9`, `--kbq-semantic-visited-a10`, `--kbq-semantic-visited-a11`, `--kbq-semantic-visited-a12`, `--kbq-semantic-visited-a13`, `--kbq-semantic-visited-a14`, `--kbq-semantic-visited-a15`, `--kbq-semantic-visited-a16`, `--kbq-semantic-visited-a17`, `--kbq-semantic-visited-a18`, `--kbq-semantic-visited-a19`, `--kbq-semantic-visited-a20` #### DarkContrast `--kbq-semantic-dark-contrast-1`, `--kbq-semantic-dark-contrast-2`, `--kbq-semantic-dark-contrast-3`, `--kbq-semantic-dark-contrast-4`, `--kbq-semantic-dark-contrast-5`, `--kbq-semantic-dark-contrast-6`, `--kbq-semantic-dark-contrast-7`, `--kbq-semantic-dark-contrast-8`, `--kbq-semantic-dark-contrast-9`, `--kbq-semantic-dark-contrast-10`, `--kbq-semantic-dark-contrast-11`, `--kbq-semantic-dark-contrast-12`, `--kbq-semantic-dark-contrast-13`, `--kbq-semantic-dark-contrast-14`, `--kbq-semantic-dark-contrast-15`, `--kbq-semantic-dark-contrast-16`, `--kbq-semantic-dark-contrast-17`, `--kbq-semantic-dark-contrast-18`, `--kbq-semantic-dark-contrast-19`, `--kbq-semantic-dark-contrast-20` #### DarkContrastA `--kbq-semantic-dark-contrast-a1`, `--kbq-semantic-dark-contrast-a2`, `--kbq-semantic-dark-contrast-a3`, `--kbq-semantic-dark-contrast-a4`, `--kbq-semantic-dark-contrast-a5`, `--kbq-semantic-dark-contrast-a6`, `--kbq-semantic-dark-contrast-a7`, `--kbq-semantic-dark-contrast-a8`, `--kbq-semantic-dark-contrast-a9`, `--kbq-semantic-dark-contrast-a10`, `--kbq-semantic-dark-contrast-a11`, `--kbq-semantic-dark-contrast-a12`, `--kbq-semantic-dark-contrast-a13`, `--kbq-semantic-dark-contrast-a14`, `--kbq-semantic-dark-contrast-a15`, `--kbq-semantic-dark-contrast-a16`, `--kbq-semantic-dark-contrast-a17`, `--kbq-semantic-dark-contrast-a18`, `--kbq-semantic-dark-contrast-a19`, `--kbq-semantic-dark-contrast-a20` #### DarkTheme `--kbq-semantic-dark-theme-1`, `--kbq-semantic-dark-theme-2`, `--kbq-semantic-dark-theme-3`, `--kbq-semantic-dark-theme-4`, `--kbq-semantic-dark-theme-5`, `--kbq-semantic-dark-theme-6`, `--kbq-semantic-dark-theme-7`, `--kbq-semantic-dark-theme-8`, `--kbq-semantic-dark-theme-9`, `--kbq-semantic-dark-theme-10`, `--kbq-semantic-dark-theme-11`, `--kbq-semantic-dark-theme-12`, `--kbq-semantic-dark-theme-13`, `--kbq-semantic-dark-theme-14`, `--kbq-semantic-dark-theme-15`, `--kbq-semantic-dark-theme-16`, `--kbq-semantic-dark-theme-17`, `--kbq-semantic-dark-theme-18`, `--kbq-semantic-dark-theme-19`, `--kbq-semantic-dark-theme-20` #### DarkThemeA `--kbq-semantic-dark-theme-a1`, `--kbq-semantic-dark-theme-a2`, `--kbq-semantic-dark-theme-a3`, `--kbq-semantic-dark-theme-a4`, `--kbq-semantic-dark-theme-a5`, `--kbq-semantic-dark-theme-a6`, `--kbq-semantic-dark-theme-a7`, `--kbq-semantic-dark-theme-a8`, `--kbq-semantic-dark-theme-a9`, `--kbq-semantic-dark-theme-a10`, `--kbq-semantic-dark-theme-a11`, `--kbq-semantic-dark-theme-a12`, `--kbq-semantic-dark-theme-a13`, `--kbq-semantic-dark-theme-a14`, `--kbq-semantic-dark-theme-a15`, `--kbq-semantic-dark-theme-a16`, `--kbq-semantic-dark-theme-a17`, `--kbq-semantic-dark-theme-a18`, `--kbq-semantic-dark-theme-a19`, `--kbq-semantic-dark-theme-a20` #### DarkError `--kbq-semantic-dark-error-1`, `--kbq-semantic-dark-error-2`, `--kbq-semantic-dark-error-3`, `--kbq-semantic-dark-error-4`, `--kbq-semantic-dark-error-5`, `--kbq-semantic-dark-error-6`, `--kbq-semantic-dark-error-7`, `--kbq-semantic-dark-error-8`, `--kbq-semantic-dark-error-9`, `--kbq-semantic-dark-error-10`, `--kbq-semantic-dark-error-11`, `--kbq-semantic-dark-error-12`, `--kbq-semantic-dark-error-13`, `--kbq-semantic-dark-error-14`, `--kbq-semantic-dark-error-15`, `--kbq-semantic-dark-error-16`, `--kbq-semantic-dark-error-17`, `--kbq-semantic-dark-error-18`, `--kbq-semantic-dark-error-19`, `--kbq-semantic-dark-error-20` #### DarkErrorA `--kbq-semantic-dark-error-a1`, `--kbq-semantic-dark-error-a2`, `--kbq-semantic-dark-error-a3`, `--kbq-semantic-dark-error-a4`, `--kbq-semantic-dark-error-a5`, `--kbq-semantic-dark-error-a6`, `--kbq-semantic-dark-error-a7`, `--kbq-semantic-dark-error-a8`, `--kbq-semantic-dark-error-a9`, `--kbq-semantic-dark-error-a10`, `--kbq-semantic-dark-error-a11`, `--kbq-semantic-dark-error-a12`, `--kbq-semantic-dark-error-a13`, `--kbq-semantic-dark-error-a14`, `--kbq-semantic-dark-error-a15`, `--kbq-semantic-dark-error-a16`, `--kbq-semantic-dark-error-a17`, `--kbq-semantic-dark-error-a18`, `--kbq-semantic-dark-error-a19`, `--kbq-semantic-dark-error-a20` #### DarkWarning `--kbq-semantic-dark-warning-1`, `--kbq-semantic-dark-warning-2`, `--kbq-semantic-dark-warning-3`, `--kbq-semantic-dark-warning-4`, `--kbq-semantic-dark-warning-5`, `--kbq-semantic-dark-warning-6`, `--kbq-semantic-dark-warning-7`, `--kbq-semantic-dark-warning-8`, `--kbq-semantic-dark-warning-9`, `--kbq-semantic-dark-warning-10`, `--kbq-semantic-dark-warning-11`, `--kbq-semantic-dark-warning-12`, `--kbq-semantic-dark-warning-13`, `--kbq-semantic-dark-warning-14`, `--kbq-semantic-dark-warning-15`, `--kbq-semantic-dark-warning-16`, `--kbq-semantic-dark-warning-17`, `--kbq-semantic-dark-warning-18`, `--kbq-semantic-dark-warning-19`, `--kbq-semantic-dark-warning-20` #### DarkWarningA `--kbq-semantic-dark-warning-a1`, `--kbq-semantic-dark-warning-a2`, `--kbq-semantic-dark-warning-a3`, `--kbq-semantic-dark-warning-a4`, `--kbq-semantic-dark-warning-a5`, `--kbq-semantic-dark-warning-a6`, `--kbq-semantic-dark-warning-a7`, `--kbq-semantic-dark-warning-a8`, `--kbq-semantic-dark-warning-a9`, `--kbq-semantic-dark-warning-a10`, `--kbq-semantic-dark-warning-a11`, `--kbq-semantic-dark-warning-a12`, `--kbq-semantic-dark-warning-a13`, `--kbq-semantic-dark-warning-a14`, `--kbq-semantic-dark-warning-a15`, `--kbq-semantic-dark-warning-a16`, `--kbq-semantic-dark-warning-a17`, `--kbq-semantic-dark-warning-a18`, `--kbq-semantic-dark-warning-a19`, `--kbq-semantic-dark-warning-a20` #### DarkWarningFixed `--kbq-semantic-dark-warning-fixed-1`, `--kbq-semantic-dark-warning-fixed-2`, `--kbq-semantic-dark-warning-fixed-3`, `--kbq-semantic-dark-warning-fixed-4`, `--kbq-semantic-dark-warning-fixed-5`, `--kbq-semantic-dark-warning-fixed-6`, `--kbq-semantic-dark-warning-fixed-7`, `--kbq-semantic-dark-warning-fixed-8`, `--kbq-semantic-dark-warning-fixed-9`, `--kbq-semantic-dark-warning-fixed-10`, `--kbq-semantic-dark-warning-fixed-11`, `--kbq-semantic-dark-warning-fixed-12`, `--kbq-semantic-dark-warning-fixed-13`, `--kbq-semantic-dark-warning-fixed-14`, `--kbq-semantic-dark-warning-fixed-15`, `--kbq-semantic-dark-warning-fixed-16`, `--kbq-semantic-dark-warning-fixed-17`, `--kbq-semantic-dark-warning-fixed-18`, `--kbq-semantic-dark-warning-fixed-19`, `--kbq-semantic-dark-warning-fixed-20` #### DarkWarningFixedA `--kbq-semantic-dark-warning-fixed-a1`, `--kbq-semantic-dark-warning-fixed-a2`, `--kbq-semantic-dark-warning-fixed-a3`, `--kbq-semantic-dark-warning-fixed-a4`, `--kbq-semantic-dark-warning-fixed-a5`, `--kbq-semantic-dark-warning-fixed-a6`, `--kbq-semantic-dark-warning-fixed-a7`, `--kbq-semantic-dark-warning-fixed-a8`, `--kbq-semantic-dark-warning-fixed-a9`, `--kbq-semantic-dark-warning-fixed-a10`, `--kbq-semantic-dark-warning-fixed-a11`, `--kbq-semantic-dark-warning-fixed-a12`, `--kbq-semantic-dark-warning-fixed-a13`, `--kbq-semantic-dark-warning-fixed-a14`, `--kbq-semantic-dark-warning-fixed-a15`, `--kbq-semantic-dark-warning-fixed-a16`, `--kbq-semantic-dark-warning-fixed-a17`, `--kbq-semantic-dark-warning-fixed-a18`, `--kbq-semantic-dark-warning-fixed-a19`, `--kbq-semantic-dark-warning-fixed-a20` #### DarkSuccess `--kbq-semantic-dark-success-1`, `--kbq-semantic-dark-success-2`, `--kbq-semantic-dark-success-3`, `--kbq-semantic-dark-success-4`, `--kbq-semantic-dark-success-5`, `--kbq-semantic-dark-success-6`, `--kbq-semantic-dark-success-7`, `--kbq-semantic-dark-success-8`, `--kbq-semantic-dark-success-9`, `--kbq-semantic-dark-success-10`, `--kbq-semantic-dark-success-11`, `--kbq-semantic-dark-success-12`, `--kbq-semantic-dark-success-13`, `--kbq-semantic-dark-success-14`, `--kbq-semantic-dark-success-15`, `--kbq-semantic-dark-success-16`, `--kbq-semantic-dark-success-17`, `--kbq-semantic-dark-success-18`, `--kbq-semantic-dark-success-19`, `--kbq-semantic-dark-success-20` #### DarkSuccessA `--kbq-semantic-dark-success-a1`, `--kbq-semantic-dark-success-a2`, `--kbq-semantic-dark-success-a3`, `--kbq-semantic-dark-success-a4`, `--kbq-semantic-dark-success-a5`, `--kbq-semantic-dark-success-a6`, `--kbq-semantic-dark-success-a7`, `--kbq-semantic-dark-success-a8`, `--kbq-semantic-dark-success-a9`, `--kbq-semantic-dark-success-a10`, `--kbq-semantic-dark-success-a11`, `--kbq-semantic-dark-success-a12`, `--kbq-semantic-dark-success-a13`, `--kbq-semantic-dark-success-a14`, `--kbq-semantic-dark-success-a15`, `--kbq-semantic-dark-success-a16`, `--kbq-semantic-dark-success-a17`, `--kbq-semantic-dark-success-a18`, `--kbq-semantic-dark-success-a19`, `--kbq-semantic-dark-success-a20` #### DarkVisited `--kbq-semantic-dark-visited-1`, `--kbq-semantic-dark-visited-2`, `--kbq-semantic-dark-visited-3`, `--kbq-semantic-dark-visited-4`, `--kbq-semantic-dark-visited-5`, `--kbq-semantic-dark-visited-6`, `--kbq-semantic-dark-visited-7`, `--kbq-semantic-dark-visited-8`, `--kbq-semantic-dark-visited-9`, `--kbq-semantic-dark-visited-10`, `--kbq-semantic-dark-visited-11`, `--kbq-semantic-dark-visited-12`, `--kbq-semantic-dark-visited-13`, `--kbq-semantic-dark-visited-14`, `--kbq-semantic-dark-visited-15`, `--kbq-semantic-dark-visited-16`, `--kbq-semantic-dark-visited-17`, `--kbq-semantic-dark-visited-18`, `--kbq-semantic-dark-visited-19`, `--kbq-semantic-dark-visited-20` #### DarkVisitedA `--kbq-semantic-dark-visited-a1`, `--kbq-semantic-dark-visited-a2`, `--kbq-semantic-dark-visited-a3`, `--kbq-semantic-dark-visited-a4`, `--kbq-semantic-dark-visited-a5`, `--kbq-semantic-dark-visited-a6`, `--kbq-semantic-dark-visited-a7`, `--kbq-semantic-dark-visited-a8`, `--kbq-semantic-dark-visited-a9`, `--kbq-semantic-dark-visited-a10`, `--kbq-semantic-dark-visited-a11`, `--kbq-semantic-dark-visited-a12`, `--kbq-semantic-dark-visited-a13`, `--kbq-semantic-dark-visited-a14`, `--kbq-semantic-dark-visited-a15`, `--kbq-semantic-dark-visited-a16`, `--kbq-semantic-dark-visited-a17`, `--kbq-semantic-dark-visited-a18`, `--kbq-semantic-dark-visited-a19`, `--kbq-semantic-dark-visited-a20` ## Schematics - Page: https://koobiq.io/en/main/schematics/overview As part of the **Koobiq** design system, a set of **schematics** is provided — CLI tools designed to automate library installation, perform migrations, and update components in Angular projects. --- ### Available Schematics #### ng-add Installs the [`@koobiq/components`](https://github.com/koobiq/angular-components) library and adds all necessary dependencies. **When to use:** When adding the library to a project for the first time. ```bash ng add @koobiq/components ``` #### new-icons-pack Updates icon prefixes and names required to migrate from version `7.2.x` and below to version `8.0.2` and above. **When to use:** After upgrading the [`@koobiq/icons`](https://github.com/koobiq/icons) library to version `8.0.2` or later. ```bash ng generate @koobiq/components:new-icons-pack ``` #### css-selectors Updates deprecated CSS selectors (typography and color naming) according to the new design system conventions. **When to use:** After upgrading the [`@koobiq/components`](https://github.com/koobiq/angular-components) library to version `18.6.0` or later. ```bash ng generate @koobiq/components:css-selectors ``` #### deprecated-icons Moves icons off the deprecated `pt-icons` font to `kbq` and renames the deprecated icons. **When to use:** while the project still has `pt-icons` classes (`class="pt-icons pt-icons-"`). ```bash ng generate @koobiq/components:deprecated-icons ``` #### empty-state-size-attr Updates size attribute names and values for the [`Empty state`](https://koobiq.io/en/components/empty-state.md) component in both HTML and TypeScript templates. **When to use:** After upgrading the [`@koobiq/components`](https://github.com/koobiq/angular-components) library to version `18.22.0` or later. ```bash ng generate @koobiq/components:empty-state-size-attr ``` #### loader-overlay-size-attr Updates size attribute names and values for the [`Overlay`](https://koobiq.io/en/components/loader-overlay.md) component in both HTML and TypeScript templates. **When to use:** After upgrading the [`@koobiq/components`](https://github.com/koobiq/angular-components) library to version `18.22.0` or later. ```bash ng generate @koobiq/components:loader-overlay-size-attr ``` #### icons-replacement Replaces icon names with new ones. **When to use:** after updating the [`@koobiq/icons`](https://github.com/koobiq/icons) library version to `11.0.0` and higher. ```bash ng generate @koobiq/components:icons-replacement ``` #### Icon migrations: which ones and in what order The three icon schematics are not run by `ng update`; run the ones that match the project yourself, in this order: `icons-replacement` renames the names the other two produce, so it goes last. | Order | Schematic | Run it when the project has | | ----- | ------------------- | ------------------------------------------------------------------------------------ | | 1 | `new-icons-pack` | `@koobiq/icons` below `8.0.2`: the `mc` prefix (`mc-`, `kbq-icon="mc-"`) | | 2 | `deprecated-icons` | `pt-icons` classes (`class="pt-icons pt-icons-"`) | | 3 | `icons-replacement` | `@koobiq/icons` below `11.0.0` | Each of them only reports what it finds until it is run with `--fix=true`, and each logs which icon mapping it uses. Common options: - `--project` — a project name or a folder such as `./libs`; without it the whole workspace is migrated. `node_modules` and `dist` are always skipped. - `--custom-data-path` (`new-icons-pack`, `icons-replacement`) — a JSON file with your own icon mapping, resolved from the workspace root: `[{ "replace": "kbq-add-to-list_16", "replaceWith": "kbq-file-plus-o_16" }]`. Without it the built-in mapping is used. ```bash ng generate @koobiq/components:new-icons-pack --project ./libs ng generate @koobiq/components:new-icons-pack --project ./libs --fix=true ``` ### How to run a schematic - Make sure Angular CLI is installed: ```bash ng version ``` - Run the desired schematic using the `ng generate` command: ```bash ng generate "@koobiq/components:" ``` - Some schematics support additional options. See the `schema.json` file inside the corresponding schematic folder. ## Upgrade guide - Page: https://koobiq.io/en/main/migration/overview ### How to upgrade from Koobiq 17 New versions include improvements but also contain **breaking changes**; they must be applied step by step. #### Upgrade plan - **Up to 18.5.3**: a safe baseline with updated theming and icons. - **18.6**: token update. - **18.22**: component attribute changes. - **20.0.0**: the move to Angular 20: removal of deprecated APIs and package renames. - **20.2.0**: the move of the filter-bar API to signals. - **20.2.0**: one shared mechanism for dropdown panel width. - **21.0.0**: the move to Angular 21: zoneless change detection, CSS animations instead of `@angular/animations`, and signal inputs, outputs and queries. - **21.0.0**: removal of the APIs deprecated before 21.0.0. - **21.0.0**: removal of `ThemePalette`. - **21.0.0**: removal of the core API nothing in the library used, and deprecation of its legacy API. - **21.0.0**: removal of the component API and styles nothing in the library used. - **21.0.0**: the actions panel overlay container. - **21.0.0**: legacy splitter deprecation. - **21.0.0**: removal of the overlay demotion mechanism. - **21.0.0**: the move of the app-switcher API to signals. - **21.0.0**: the button review — host attributes, group ownership and styles. - **21.0.0**: button supported colors — a default color of its own per style. - **21.0.0**: the button-toggle review — ARIA semantics, keyboard navigation and signal inputs. - **21.0.0**: the form-field review — signals, accessibility and the removal of `mixinColor`. - **21.0.0**: the theme service review — signals, `auto` mode and built-in persistence. - **21.0.0**: explicit prefix and suffix slots for tag content. - **21.0.0**: deprecation of the overlayscrollbars-based Scrollbar implementation. - **21.0.0**: the link component instead of the directive — the `kbqLink` selector, slots, `external` and `size`. - **21.0.0**: the locale layer typing — a typed `getParams`, partial locale data and signals. - **21.0.0**: `multiple` on the selection list and tree became a real, changeable input. - **21.0.0**: the component review — closed internals, signal inputs and the behavior fixes it uncovered. - **21.0.0**: removal of the deprecated file-upload `fileQueueChanged`/`fileQueueChange` outputs. - **21.0.0**: the accordion state store moved into `core`, shared by every component that persists state. - **21.0.0**: accordion state saving is on by default, keyed on the document instead of instantiation order. - **21.0.0**: tree state saving is on by default, keyed on the value the tree control gives each node. - **21.0.0**: tabs, sidebar and content-panel remember what the user changed, on by default. - **21.0.0**: the filter bar remembers the selected filter and its edits, on by default. - **21.0.0**: the tag list cleaner removes the tags itself and leaves the disabled ones in place. - **21.0.0**: the move to `@koobiq/design-tokens` 4 — a new palette scale, DTCG sources and opt-in component tokens. - **21.1.0**: the code block loads the highlight.js core instead of the full bundle by default. #### Upgrade to 18.5.3 ```bash npm install @koobiq/cdk@18.5.3 npm install @koobiq/components@18.5.3 npm install @koobiq/icons@^9.0.0 npm install @koobiq/design-tokens@~3.7.3 npm install @koobiq/angular-luxon-adapter@18.5.3 npm install @koobiq/date-adapter@^3.1.3 npm install @koobiq/date-formatter@^3.1.3 npm install luxon npm install @messageformat/core ``` ##### New theming Theming is now simpler and built on CSS variables. [Theming. How to use](https://koobiq.io/en/main/theming.md). Examples: - [apps/docs/src/main.scss](https://github.com/koobiq/angular-components/blob/main/apps/docs/src/main.scss) - [apps/docs/src/styles/\_theme-kbq.scss](https://github.com/koobiq/angular-components/blob/main/apps/docs/src/styles/_theme-kbq.scss) ##### Icon package update Install the new icon version: ```bash npm install @koobiq/icons@9.1.0 ``` To update icon names in templates, use the update tool (schematic): ```bash ng g @koobiq/angular-components:new-icons-pack --project "" ``` #### Token update (18.6.x) Deprecated color tokens were removed and typography parameter tokens were renamed. The script will rename class and CSS-variable names to the new ones and highlight places where deprecated colors need to be removed (replaced): ```bash ng g @koobiq/angular-components:css-selectors --fix=true --project "" ``` For manual review, add `--fix=false`. The script will highlight places where colors and typography names need to be removed (replaced): ```bash ng g @koobiq/angular-components:css-selectors --fix=false --project "" ``` #### Attribute update (18.22.0) Component attribute names have changed: - **KbqLoaderOverlay**: compact → size - **KbqEmptyState**: big → size The schematic replaces the attributes automatically: ```bash ng g @koobiq/angular-components:loader-overlay-size-attr --project "" ng g @koobiq/angular-components:empty-state-size-attr --project "" ``` #### Upgrade to Angular 20 In 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**. Remove `@koobiq/cdk` from `package.json` — the package has been merged into `@koobiq/components/core`. ##### Running the migration Most of the changes are applied by the `v20-upgrade` schematic (runs automatically): ```bash ng update @koobiq/components@20 ``` Or manually. To preview without writing — `--fix=false`: ```bash ng g @koobiq/components:v20-upgrade --project "" ``` ##### What is fixed automatically **Package moves:** - @koobiq/components/navbar-ic → navbar - risk-level → badge - @koobiq/components-experimental/form-field → @koobiq/components/form-field - @koobiq/cdk/{a11y,keycodes,testing} → @koobiq/components/core **Classes, tokens, functions:** - KbqNavbarIc\* → Kbq\*, - KbqRiskLevel\* → KbqBadge\*, - toBoolean → booleanAttribute, - formatDataSize → getFormattedSizeParts **Instance methods:** - KbqAutocompleteTrigger.openPanel() → .open(), - .toggleIsCollapsed() → .toggle(), - .focusViaKeyboard() → .focus(). **Templates:** - kbq-filter-search → kbq-search-expandable, - kbq-datepicker-toggle → kbq-datepicker-toggle-icon, - kbqFormFieldWithoutBorders → noBorders, - \[kbqWarningTooltip] → kbqTooltipModifier="warning" \[kbqTooltip]. **SCSS:** - .kbq-risk-level → .kbq-badge, - .kbq-navbar-ic → .kbq-navbar, etc. ##### What you need to fix manually The schematic emits warnings for what cannot be rewritten safely: **(onSaveAsNew) on kbq-filters**: listen to `(onSave)` and check `$event.status === 'newFilter'`. **File upload**. The `[customValidation]` and `[errors]` attributes → `FormControl` validators / `FormControl.errors`. **App switcher**. `[apps]` → `[sites]="[{ id, name, apps }]"`. **Validation.** **KbqValidateDirective** and **kbqDisableLegacyValidationDirectiveProvider()** were removed → use **ErrorStateMatcher** (e.g. `ShowOnSubmitErrorStateMatcher`). **Modals**: ModalOptions.kbqComponentParams → the data field + inject(KBQ\_MODAL\_DATA). **Code block**: the deprecated `canLoad` / `codeFiles` inputs are renamed to `canDownload` / `files`. Template bindings are migrated automatically, except on a `` that binds the replacement too: it is reported, merge each pair into one binding. Programmatic access (`.canLoad`, `.codeFiles`) must be updated by hand. #### Filter-bar upgrade (20.2.0) In 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. | Member | Before | After | | --------------------------------------------------------------------------- | ----------------------- | -------------------------------------------------------- | | `filter` | accessor | `ModelSignal` — write via `.set(...)` | | `pipeTemplates` | accessor | `InputSignal` | | `isChanged` / `isDisabled` / `isReadOnly` / `isSaved` / `isSavedAndChanged` | getter | `Signal` | | `onChangePipe` | `EventEmitter` | `OutputEmitterRef` | ##### Running the migration The changes are applied by the `filter-bar-signals` schematic (runs automatically): ```bash ng update @koobiq/components@20 ``` Or manually — for example, if you have already upgraded to 20.2.0. To preview without writing — `--fix=false`: ```bash ng g @koobiq/components:filter-bar-signals --project "" ``` ##### What is fixed automatically **Reads and writes in TypeScript** (for receivers annotated `KbqFilterBar` / `KbqFilterBarHost`): - filterBar.filter → filterBar.filter(), - filterBar.filter = next → filterBar.filter.set(next), - filterBar.filter?.name → filterBar.filter()?.name, - this.filterBar.isChanged → this.filterBar.isChanged() **Reads through a template reference variable** (`#ref` on ``, in external `.html` files and inline templates): - ref.isChanged → ref.isChanged() **Renames:** - KbqFilterBarRefresher → KbqFilterRefresher (the old name stayed an alias until 21.0.0) All replacements are idempotent — running the schematic twice does not double the call. ##### What you need to fix manually The schematic emits warnings for what cannot be rewritten safely: **KbqFilterBar.changes**: deprecated, no longer emits, removed in 21.0.0 → read `filterBar.filter()` inside an `effect(...)`, or listen to `(filterChange)`. **KbqFilters.preparePopover()**: removed → `openSaveAsNewFilterPopover()` / `openChangeFilterNamePopover()`. **viewChild(KbqFilterBar) queries**: return the component instance, so a read becomes a double call — `this.filterBar().filter()`. **KBQ\_FILTER\_BAR\_PIPES**: now typed `Map>` (was an array of tuples) → wrap the entries in `new Map([...])`. The schematic does not cover the following changes — check them yourself: **\[filters] on kbq-filters**: the input became required. **KbqPipeState.state**: accessor → `InputSignal` (relevant for custom pipes). **KbqPipeTreeSelectComponent**: `template` and `filteredOptions` were removed. On **KbqFilters** the `popoverOffset` and `popoverSize` fields became `protected`. The schematic matches receivers by explicit type annotation only, so aliases (`const fb = this.filterBar; fb.filter`) are left untouched — fix them by hand. #### Panel width unification (20.2.0) In 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: | `panelWidth` | Panel width | | ---------------------- | ------------------------------------------------------------------------ | | not set (default) | sizes to its content, never narrower than the trigger or `panelMinWidth` | | `'auto'` | matches the trigger, never narrower than `panelMinWidth` | | a number or CSS string | exactly that width; `panelMinWidth` is not applied | Panels 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`. ##### Running the migration The `autocomplete-panel-width-auto` schematic runs automatically: ```bash ng update @koobiq/components@20 ``` Or manually: ```bash ng g @koobiq/components:autocomplete-panel-width-auto --project "" ``` ##### What is fixed automatically **`panelWidth="auto"` on ``** → `panelWidth="fit-content"`. On autocomplete, `auto` used to be passed to CSS verbatim, so the panel shrank to fit its content. It now means "match the host width", as it already did on `kbq-select`. `fit-content` preserves the old behaviour. Both the static (`panelWidth="auto"`) and bound (`[panelWidth]="'auto'"`) forms are rewritten; a dynamic value (`[panelWidth]="expr"`) is skipped with a warning. `auto` still type-checks and still renders, the panel is just laid out differently — it isn't the only silent change in this release though, see the `panelWidth={{0}}` note below. ##### What you need to fix manually **`panelWidth`, `panelMinWidth` and `panelMaxWidth` are signal inputs.** Reads now require a call, and writes are no longer possible — this is the one change in this release without an automated path, because there is no runtime equivalent of writing to a signal input from outside. ```ts // Before @ViewChild(KbqSelect) select: KbqSelect; this.select.panelWidth = 'auto'; const w = this.select.panelWidth; // After — bind from the template instead // panelWidth: KbqPanelWidth = 'auto'; const w = this.select.panelWidth(); ``` `kbq-tree-select` already exposed these as signals, so it is unaffected. `KbqDropdownPanel.panelWidth` / `panelMinWidth` / `panelMaxWidth` are typed `Signal<...>` for the same reason. **Panels no longer grow past 640px with their content.** `kbq-select`, `kbq-tree-select` and `kbq-autocomplete` previously had no cap at all, so a panel with long option text could grow arbitrarily wide; it now stops at 640px. Panels whose width comes from the trigger or from an explicit `panelWidth` are unaffected. To restore the old behaviour set `--kbq-panel-size-width-max: none` on `:root`, or raise the cap for one instance with `panelMaxWidth`. **`panelWidth="auto"` on `kbq-select` and `kbq-tree-select`** no longer goes below `panelMinWidth` (200 by default). A trigger narrower than 200px used to produce a panel of exactly the trigger's width; it now produces a 200px panel. If you relied on that, set `panelMinWidth="0"`. This cannot be migrated automatically — whether it affects you depends on the rendered width of the trigger. **`panelWidth={{0}}` on ``** is now an explicit width instead of being treated as unset — the panel renders at literally `0px` instead of sizing to content. `getOverlaySize()` used to treat `panelWidth` as falsy-checked, so `0` fell back to content-sizing; `select`/`tree-select` already treated `0` as an explicit width before this release, and autocomplete is now consistent with them. This only matters if `panelWidth` is bound to an expression that can evaluate to `0` (a literal `panelWidth="0"` has no legitimate use); it can't be schematic-migrated since it depends on a runtime value, not a static template attribute. **`kbq-timezone-select` no longer has width defaults of its own.** It used to declare `panelWidth: 'auto'` and `panelMinWidth: 640`; both are gone, so it now inherits the select's defaults — the menu sizes to its content, never narrower than the field or 200px, and stops at 640px. In practice the panel used to match the field exactly (the `640` minimum never reached the DOM between 20.0.0 and 20.1.0), so the visible change is that the menu now grows with long timezone names. Set `panelWidth="auto"` to get the old field-matching behaviour back. **`[panelMinWidth]="null"`** now keeps the trigger-width floor. It previously produced an invalid `NaNpx`, which browsers dropped, removing every minimum. **`KbqDropdown.triggerWidth`** is deprecated and has no effect (it has been unread since 20.0.0). To make a dropdown panel match an element other than its trigger, set `KbqDropdownTrigger.widthOrigin`. `kbq-split-button`'s `panelAutoWidth` does this for you and now works — previously it wrote to `triggerWidth` and did nothing. **`kbq-dropdown`'s minimum width is now measured with `getBoundingClientRect()`** (the trigger's full border-box) instead of `getComputedStyle().width` minus its borders (the old, incorrectly-computed content-box). A trigger with padding or a border renders a wider panel than before by that amount; a trigger with neither is unaffected. #### Upgrade to Angular 21 In 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: ```bash ng update @angular/core@21 @angular/cli@21 @angular/cdk@21 ng update @koobiq/components@21 ``` ##### Zoneless change detection Koobiq components no longer depend on zone.js: they render in an application bootstrapped with `provideZonelessChangeDetection()` and keep working with `provideZoneChangeDetection()`. What used to wait for `NgZone.onStable` now runs after the next render. - **`MockNgZone` is removed** from `@koobiq/components/core`. It fired `onStable` on demand, and nothing in the library listens to it any more: drop the `NgZone` override, and replace `simulateZoneExit()` with `fixture.detectChanges()` or `await fixture.whenStable()`. - **A spec without zone.js renders only when told to.** A check made right after an event needs `fixture.detectChanges()` or `await fixture.whenStable()` first, and `whenStable()` no longer waits for timers the component started. - **The actions panel container is `OnPush`**, like the sidepanel container. In an application with zone.js, a component opened in the panel that writes its fields from a timer or a subscription calls `markForCheck()` or keeps that state in a signal. `zoneless-change-detection` reports every use of `MockNgZone` and `simulateZoneExit()`: ```bash ng g @koobiq/components:zoneless-change-detection --project "" ``` ##### Animations without @angular/animations Koobiq components no longer use `@angular/animations`, which Angular deprecated: they animate with CSS and wait for the end of an animation themselves. `@angular/animations` is no longer a peer dependency, and the components no longer need `provideAnimations()`: remove it, and the package, unless the components of your application declare `animations: [...]` themselves. - **The motion is off under `prefers-reduced-motion: reduce`.** To turn it off in the whole application, for example in an end-to-end suite, provide `{ provide: KBQ_ANIMATIONS_CONFIG, useValue: { animationsDisabled: true } }` from `@koobiq/components/core`. `provideNoopAnimations()` still turns it off, but it belongs to the deprecated package. - **The exported triggers are removed**: `fadeAnimation` and `kbqSelectAnimations` from `core`, `kbqDropdownAnimations`, `fadeInItems` and `transformDropdown` from `dropdown`, `kbqTabsAnimations` from `tabs`, `kbqPopoverAnimations` from `popover`, `kbqDatepickerAnimations` from `datepicker`, `KbqNotificationCenterAnimations` from `notification-center` and `kbqAppSwitcherAnimations` from `app-switcher`. `KbqSidepanelAnimationState` is no longer exported from `sidepanel`, nor `MODAL_ANIMATE_DURATION` from `modal`: instead of a timer of that length, act on `afterOpen` / `afterClose` of the `KbqModalRef`. - **The members that carried an `AnimationEvent` are removed.** `KbqPopUp.animationStart()` and `animationDone()`: a subclass overrides the protected `afterShowAnimation()`, which runs once each time the pop-up appears, not again when the pointer returns to an open one. `KbqDropdown.animationDone`, `isAnimating`, `panelAnimationState`, `startAnimation()`, `resetAnimation()`, `onAnimationStart()` and `onAnimationDone()`: a custom trigger calls `setOpened()`. `KbqSidebar.animationState`, `onAnimationStart()` and `onAnimationDone()`: listen to `stateChanged`. `KbqTabBody.onTranslateTabStarted()` and `onTranslateTabComplete()`, `KbqToastService.animation`, `KbqToastComponent.animationState` and `onAnimation()`, the `animationStateChanged` of the sidepanel and actions panel containers, and `animationState`, `animationTransform` and `setAnimationState()` of the sidepanel container: follow a panel through its ref — `afterOpened()`, `beforeClosed()`, `afterClosed()`. The protected `KbqContentPanelContainer.contentAnimationState` fed the removed `@contentAnimation` trigger: a subclass reads `isOpened()`. - **The dropdown panel no longer fades out**: it is closed, and its lazy content detached, as soon as the trigger reports it. - **In a spec, what waited for an animation callback now waits for the next render**: `fixture.detectChanges()` or `await fixture.whenStable()`. jsdom has no Web Animations API, so nothing animates there. `angular-animations-removal` reports every use of the removed triggers and members: ```bash ng g @koobiq/components:angular-animations-removal --project "" ``` ##### Overlays in the top layer The CDK 21 opens overlays as popovers in the browser top layer, and the components keep that default: a panel is drawn above the whole page, whatever stacking context surrounds its trigger. The `z-index` of `.cdk-overlay-container` no longer orders panels against the application's own fixed and sticky elements, so an override of it has no effect. An application that relied on it opts out with `OVERLAY_DEFAULT_CONFIG` from `@angular/cdk/overlay`: ```typescript import { OVERLAY_DEFAULT_CONFIG } from '@angular/cdk/overlay'; bootstrapApplication(AppComponent, { providers: [{ provide: OVERLAY_DEFAULT_CONFIG, useValue: { usePopover: false } }] }); ``` ##### Color as a signal `color` of `KbqColorDirective` is a writable signal, and so is the `color` of every component built on it: the buttons, checkbox, toggle, radio, icons, form field, hints, cleaner, progress bar and spinner, and tag. The `[color]` binding is unchanged. - **A read is a call, a write is `.set()`**: `icon.color()`, `button.color.set(KbqComponentColors.Theme)`. A color set in code holds until the `[color]` binding changes, as an assigned one did. - **An unset or falsy `[color]` renders the default color of the component**, `kbq-empty` where it has none, instead of no color class. - **A subclass can no longer override the `color` accessor.** Set its default with `setDefaultColor()` instead of assigning `color` in the constructor. `_color` is removed, `defaultColor` is a signal, and `KbqColorDirective` takes the type of its color as a parameter: `KbqColorDirective` for the buttons. `CanColor` is removed: type the member `Signal`, or use `KbqColorDirective`. `color-signals` rewrites the reads and writes, and reports subclasses: ```bash ng g @koobiq/components:color-signals --project "" ``` ##### Removed mixins `mixinDisabled` and `mixinTabIndex` are removed from `@koobiq/components/core` with `CanDisable`, `CanDisableCtor`, `HasTabIndex` and `HasTabIndexCtor`. They were deprecated and unused inside the library: declare `disabled` and `tabIndex` as inputs of the class instead. `core-mixins-removal` reports every use: ```bash ng g @koobiq/components:core-mixins-removal --project "" ``` ##### Options `KbqOption`, `KbqOptgroup`, the option action and `KbqTimezoneOption` keep their state in signal inputs. The state of an option is still read as a property — `event.option.value`, `select.selected.disabled`, `option.viewValue` in a `kbqSelectTagContent` template — so those reads are unchanged. - **Programmatic writes are gone**: `option.value`, `viewValue`, `disabled`, `showCheckbox` and `KbqTimezoneOption.timezone` are bound in the template. - **`KbqOptgroup.disabled` and `KbqOptionActionComponent.disabled` are signals**: `group.disabled()`. - **`KbqOption.stateChanges` no longer emits for `disabled`**, and `textElement` is typed `ElementRef | undefined`. `option-signals` rewrites the reads of the group and the action, and reports the writes: ```bash ng g @koobiq/components:option-signals --project "" ``` ##### Number input and input type `KbqNumberInput.value`, `KbqNumberInput.disabled` and `KbqInput.type` are signal inputs read through a getter: a read is unchanged, a programmatic write no longer compiles. Bind `[value]`, `[disabled]` and `[type]`, or write through the form control. `input-signals` reports every write: ```bash ng g @koobiq/components:input-signals --project "" ``` ##### List The inputs of `KbqListSelection` and `KbqListOption` are signal inputs read through a getter: a read is unchanged, a programmatic write no longer compiles. Bind them in the template, and disable the list through its form control. `KbqListOption.selected` and `KbqListSelection.multipleMode` stay writable. `list-signals` reports every write: ```bash ng g @koobiq/components:list-signals --project "" ``` ##### Checkbox, toggle, radio, button toggle, buttons, icons, search and pop-ups Their inputs are signal inputs named `Input`, handed to the unchanged member from `ngOnChanges`: reading and writing `checkbox.checked`, `button.disabled`, `icon.iconName` or `tooltip.content` works as before. The same goes for the tooltip, popover, popover confirm, app switcher and notification center triggers, the password toggle and the ellipsis center, and for the tags, file uploads, accordion item, navbar item, notification item and sidebar. The exception is a member another migration moved to a signal, such as `color` (see Color as a signal) or the tag members and the form-field state of the tag list (see Tags). - **A subclass** that implements `ngOnChanges` calls `super.ngOnChanges(changes)`, and one that redeclared an `@Input()` overrides the `Input` signal input instead, e.g. `iconNameInput`. - **A boolean input** accepts `boolean | string | null | undefined` in a template, rather than anything. - **`KbqPopUp.elementRef` is a protected getter.** A panel that renders an element of its own overrides it over a `viewChild()` instead of re-declaring the field with `@ViewChild`. `signal-inputs-subclasses` reports every subclass: ```bash ng g @koobiq/components:signal-inputs-subclasses --project "" ``` ##### Datepicker The inputs of `KbqDatepicker`, `KbqDatepickerInput`, `KbqDatepickerToggleIconComponent`, `KbqCalendar`, `KbqCalendarHeader` and `KbqMonthView` are signal inputs named `Input`, handed to the unchanged member from `ngOnChanges`: reading and writing `datepicker.opened`, `datepicker.startAt`, `input.min` or `calendar.minDate` works as before. The exception is the form-field state of `KbqDatepickerInput`: `value`, `id`, `placeholder`, `focused`, `empty`, `required`, `disabled` and `errorState` are signals, migrated by `form-field-signals` (see Form field review (21.0.0)). - **A subclass** that implements `ngOnChanges` marks it `override` and calls `super.ngOnChanges(changes)`, and one that redeclared an `@Input()` overrides the `Input` signal input instead, e.g. `minInput`. - **A `SimpleChanges` passed to `ngOnChanges` by hand** is keyed by the `Input` names: `minDateInput`, not `minDate`. - **A boolean input** — `hasBackdrop`, `disabled` and `opened` — accepts `boolean | string | null | undefined` in a template. `datepicker-signals` reports every subclass and every direct call of `ngOnChanges`: ```bash ng g @koobiq/components:datepicker-signals --project "" ``` **Without a locale service, the placeholder names the format the input takes.** It used to be the `ru-RU` one whatever locale the date adapter parsed in. It now follows the date adapter of the input — `KBQ_DATE_LOCALE`, Angular's `LOCALE_ID` unless provided, on a component too — so with the default `en-US` it reads `yyyy-mm-dd`; for a locale the library ships no strings for, or a format set through `KBQ_DATE_FORMATS`, it is the format itself, such as `yyyy/mm/dd`. To keep the former one, bind `[placeholder]` or pass it to `kbqDatepickerLocaleConfigurationProvider()`. With a locale service nothing changes. ##### Tabs The inputs of `KbqTabGroup`, `KbqTab`, `KbqTabNavBar`, `KbqTabLink` and of the header, labels and bodies inside the tab group are signal inputs named `Input`, handed to the unchanged member from `ngOnChanges`: reading and writing `tabGroup.selectedIndex`, `tabGroup.headerPosition`, `tab.disabled` or `tabLink.active` works as before. Their queries are signal queries behind getters: `KbqTabGroup.tabs` and `KbqTabNavBar.items` are still `QueryList`s whose `changes` emit as before. - **A subclass** that implements `ngOnChanges` marks it `override` and calls `super.ngOnChanges(changes)`, and one that redeclared an `@Input()` overrides the `Input` signal input instead, e.g. `headerPositionInput`. A query it redeclared with `@ContentChildren` or `@ViewChild` — `tabs`, `items`, `templateLabel` — is overridden as a getter. - **A `SimpleChanges` passed to `ngOnChanges` by hand** is keyed by the `Input` names: `disabledInput`, not `disabled`. - **A boolean input** — `disabled`, `active`, `vertical`, `disablePagination` — accepts `boolean | string | null | undefined` in a template. - **A bound `undefined`** resets the member to its default rather than leaving it as it was: `headerPosition` becomes `'above'`, `animationDuration` the duration of `KBQ_TABS_CONFIG`, `tooltipTitle` `''` and a boolean input `false`, `selectedIndex` selects the first enabled tab, and a `KbqTabLink` gets `tabIndex` `0` and its generated `id`. - **`KbqTab.templateLabel` follows the `kbqTabLabel` in the content of the tab**: once that is removed, the tab shows its text label rather than the removed template. `tabs-signals-and-aria` reports every subclass and every direct call of `ngOnChanges`: ```bash ng g @koobiq/components:tabs-signals-and-aria --project "" ``` ##### Query lists The `options` of `KbqSelect`, `KbqAutocomplete` and `KbqListSelection`, the `tags` of `KbqSelect` and `KbqTagList`, `KbqRadioGroup.radios`, `KbqTreeSelection.unorderedOptions`, the `focusableItems` of `KbqNavbar` and `KbqVerticalNavbar`, and the `textElement` of `KbqTag` and `KbqDropdownItem` are getters over signal queries. They keep their types — a `QueryList` whose `changes` emit as before, or an `ElementRef` — so reading them works as before. Before the content is initialized a `QueryList` is empty rather than `undefined`. - **A subclass** that redeclared one of them, with a query decorator or as a field, removes the redeclaration or overrides the getter. - **A write** to one of them no longer compiles: render the items in the template. - **`KbqCodeBlock.tabLinkTemplate`**, which is protected, is a signal: a subclass reads `this.tabLinkTemplate()`. `query-list-signals` reports every redeclaration and write: ```bash ng g @koobiq/components:query-list-signals --project "" ``` ##### State as signals The state that components and services expose for reading is a signal, where it used to be a `BehaviorSubject`, an `Observable` or a plain field. The streams that report events stay observables. | Member | Was | Now | | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------------------------------------------------- | | `silentMode`, `loadingMode`, `errorMode`, `loadingMore`, `loadMoreErrorMode`, `hasMore` of `KbqNotificationCenterService` | `BehaviorSubject` | `Signal`, written by the `set…()` methods | | `unreadItemsCounter` of `KbqNotificationCenterService` and `KbqNotificationCenterTrigger` | `Observable` | `Signal` | | `KbqNotificationCenterService.groupedItems` | `Observable` | `Signal` | | `hovered`, `focused` of `KbqToastService` and `KbqToastComponent` | `BehaviorSubject` | `Signal` | | `filteredOptions` of `KbqFilters`, `KbqPipeSelectComponent` and `KbqPipeMultiSelectComponent` | `Observable` | a read-only `Signal` | | `KbqSelect.hiddenItems` | `number` | `Signal`, as in `KbqTreeSelect` | | `KbqSearchExpandable.value` | `BehaviorSubject` | `WritableSignal` | | `hovered` of `KbqPopUp` and `KbqPopUpTrigger`: tooltips, popovers, the notification center and the app switcher | `BehaviorSubject` | `Signal` | - **Read** them with a call — `service.silentMode()`, `trigger.unreadItemsCounter()` — instead of `.value`, `.getValue()` or `| async`. Where a stream is still needed, wrap the signal in `toObservable()` from `@angular/core/rxjs-interop`. - **Write** the notification center flags with their `set…()` methods and the search value with `value.set()`; the other signals are read-only. - **`KbqNotificationCenterService.changes`** no longer emits on subscription: a handler that relied on that runs once on its own before subscribing. - **`KbqFilters.filteredOptions`** now follows a change of the `filters` input too. - **A pop-up trigger no longer marks its host view for check** when it opens or closes: an OnPush view that renders a field of its own, written from a `visibleChange` subscription made in code, calls `markForCheck()` itself or keeps the field in a signal. An output bound in the template needs nothing. - **A getter backed by a signal is a tracked read.** `isOpen` of the pop-up triggers, `opened` of `KbqDropdownTrigger`, `KbqDatepicker` and `KbqSidebar`, `panelOpen` of `KbqSelect`, `KbqTreeSelect` and `KbqAutocompleteTrigger`, `expanded` of `KbqAccordionItem`, `isOpened` of `KbqSearchExpandable`, `selected` of the options, `selectedIndex` of `KbqTabGroup` and `isActive` of `KbqTab` keep their type, but reading one inside an `effect()` or a `computed()` makes it follow that state: an effect that opens or closes a component depending on one reads it with `untracked()`. The commands of the components — `open()`, `close()`, `toggle()`, `select()`, `writeValue()` and the like — read nothing an effect calling them would follow. - **A number input bound to `undefined` or `null`** falls back to its default instead of becoming `NaN`: `tabIndex` of `kbq-checkbox`, `kbq-toggle`, `kbq-radio-button`, `kbq-search-expandable` and `kbqTabLink` reads `0` (a tab link no longer renders `tabindex="NaN"`), `selectedIndex` of `kbq-tab-group` selects the first enabled tab, `offset` of `kbqAppSwitcher` takes the default offset, and `tabIndex` of `kbq-list-selection` and `kbq-tree-selection` gives back the default tab stop (`0`, or `-1` without options). A value that is not a finite number counts the same. - **Tabbing out of a list, tag list or tree leaves `tabIndex` alone.** `kbq-list-selection`, `kbq-tag-list` and `kbq-tree-selection` take themselves out of the tab order in the DOM only, so `tabIndex` reads the same value while the focus leaves, and then put back the tab stop they render. A tag list with a tag input renders no `tabindex` again instead of keeping `-1`, and a tree keeps a bound `[tabIndex]` when its rendered nodes change. - **A subclass** of `KbqToastComponent`, `KbqPopUp` or `KbqPopUpTrigger` sets its hover state with the protected `setHovered()`; one of `KbqPipeSelectComponent` drops its `super.ngOnInit()` call, and one of `KbqDatepickerToggleIconComponent` its `super.ngAfterContentInit()` and `super.ngOnDestroy()` calls, since the classes no longer implement them. The protected `isListMode`, `showStartCalendar` and `showEndCalendar` of the date pipes are signals, `returnButton` may be `undefined` (the back button renders only when the pipe template has `values`), and a date pipe template binds `(keydown.tab)` and `(keydown.shift.tab)` of its date inputs to `onDateInputTab()`, which hides the calendars before the browser moves the focus. - **`KbqFileItem`** keeps its `loading` and `progress` subjects, which the application updates. `v21-upgrade` reports these reads, writes and `super` calls: ```bash ng g @koobiq/components:v21-upgrade --project "" ``` #### Deprecated API removal (21.0.0) 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. ##### Accordion | Removed | Use instead | Migration | | ----------------------------------------- | --------------------------------------------------------------------------------------- | --------- | | the `kbq-accordion-trigger__action` class | a `kbq-accordion-header__actions` element next to the trigger in `kbq-accordion-header` | reported | An action inside the trigger is a focusable element inside a `role="button"`, so the actions move out of the trigger: see [Interactive elements](https://koobiq.io/en/components/accordion.md#interactive-elements). `v21-upgrade` reports the class in templates, style sheets and code. ##### Code block and icon button | Removed | Use instead | Migration | | ----------------------------------------------- | --------------------------------- | --------- | | `canLoad` on `` | `canDownload` | rewritten | | `codeFiles` on `` | `files` | rewritten | | `KbqCodeBlock.canLoadInput` / `.codeFilesInput` | `canDownload()` / `files()` | reported | | `KbqCodeBlock.scrollableCodeContent` | `scrollTo(options)` | reported | | `small` on an icon button | `size="compact"` | rewritten | | the `kbq-icon-button_small` class | `kbq-icon-button_compact` | rewritten | | `KbqIconButton.small` | `size` (`'compact'` / `'normal'`) | reported | The aliases no longer fill in for their replacements: a `` that binds both `canLoad` and `canDownload`, or both `codeFiles` and `files`, is reported and has to merge each pair into one binding. So is one that binds `canDownload` or `files` twice, which is what the 20.0.0 migration made of such a pair: only the last binding takes effect. A bound `[small]="expr"` becomes `[size]="(expr) ? 'compact' : 'normal'"`. A valueless `small` never took effect, because the input had no boolean transform; it is reported, as is a static `small` with any other value than `"true"`, which made the button compact. ##### Core | Removed | Use instead | Migration | | -------------------------------------- | -------------------------------------------------------------------------- | ------------------- | | `KbqThemeSelector.Default` | `KbqThemeSelector.Light` | rewritten | | `KbqThemeNames.Default` | `KbqThemeNames.Light` | rewritten | | `KbqDefaultThemes` | `KBQ_DEFAULT_THEMES` | rewritten | | `KbqTheme` | `KbqThemeConfig`: `colorScheme` is required, `selected` is gone | rewritten, reported | | `ThemeService` | `KbqThemeService` | reported | | `ThemeService.current` / `.getTheme()` | `currentTheme()`, a signal | reported | | `ThemeService.themes` | `themes()` / `setThemes()` | reported | | `ThemeService.setTheme(theme)` | `setMode(theme.colorScheme)`, or `selectTheme(theme.name)` to pin a theme | reported | | `getOptionScrollPosition()` | `KbqOption.focus()` or `kbqFocusAndReveal(element)`: both scroll into view | reported | `v21-upgrade` reports `ThemeService` and `getOptionScrollPosition` where they are imported from `@koobiq/components/core`, and every file that names `KbqTheme`. Give each custom theme its `colorScheme` and compare a theme with `currentTheme()` instead of reading `selected`. Unlike `ThemeService`, `KbqThemeService` applies a theme as soon as it is injected and starts in `'auto'` mode: an app that called `setTheme()` at startup to force a theme sets `kbqThemeProvider({ mode: 'light' })` instead. The rest of the service is described in the theme service review below. ##### Dropdown and select | Removed | Use instead | Migration | | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | --------- | | `KbqDropdown.triggerWidth`, `KbqDropdownPanel.triggerWidth` | `KbqDropdownTrigger.widthOrigin`, to match the panel to an element other than the trigger | reported | | `offsetY` of `KbqSelect`, `KbqTreeSelect`, `KbqTimezoneSelect` | the `--kbq-connected-overlay-gap` CSS variable, set on `:root` or on the overlay pane | reported | | `AUTOCOMPLETE_PANEL_HEIGHT` | a constant of your own: the panel is capped by `--kbq-autocomplete-size-panel-max-height` | reported | | `DropdownPositionX` / `DropdownPositionY` | `KbqDropdownPositionX` / `KbqDropdownPositionY` | rewritten | | `KbqSelectPanelWidth`, `KbqTreeSelectPanelWidth` | `KbqPanelWidth` from `@koobiq/components/core` | rewritten | | `clearValue()` of `KbqSelect`, `KbqTreeSelect`, `KbqTimezoneSelect` | a projected ``; from code, reset the bound form control | reported | | the `kbq-dropdown__group-header_small` class | ``, `KbqOptgroup` from `@koobiq/components/core` | reported | None of the removed members was an input, so no template binds them, and of `triggerWidth`, `offsetY` and `AUTOCOMPLETE_PANEL_HEIGHT` only `offsetY` of `kbq-timezone-select` still had an effect: it moved the panel. `v21-upgrade` reports `.triggerWidth` in a file that uses the dropdown, `triggerWidth` in a `KbqDropdownPanel` implementation, `.offsetY` in a file that uses one of the selects and an `offsetY` field of their subclass; the `offsetY` input of `KbqDropdownTrigger` stays. It renames the position types in a file that names `@koobiq/components/dropdown` and moves the panel width types to the core import; the position types elsewhere, an aliased import and a re-export are reported. `.clearValue` is reported in a file that uses one of the selects, except on `this` and on a form field, whose `clearValue()` stays; like the cleaner, it spared the options `clearPredicate` keeps, while a reset control loses them all. `` renders the subheading of the removed modifier inside a dropdown; `kbq-dropdown__group-header` itself stays. ##### Filter bar | Removed | Use instead | Migration | | ------------------------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------- | | `KbqFilterBarRefresher` | `KbqFilterRefresher` | rewritten | | `KbqFilterBar.changes` | `filter()` read in an `effect()`, or the `(filterChange)` output: `changes` never emitted | reported | | `KbqPipeMinWidth` (`kbqPipeMinWidth`) | nothing: a pipe lays its name and value out as shrinkable grid tracks, which keeps a short part intact | removed; other uses reported | | `KbqPipe.required` | `cleanable: false` and `removable: false`: the filter bar never read `required` | reported | `v21-upgrade` reports `.changes` in a file that names `KbqFilterBar`. It removes `KbqPipeMinWidth` from the `@koobiq/components/filter-bar` import and from `imports` arrays and the `kbqPipeMinWidth` attribute from templates, and reports any other reference to the directive. It reports a read of `data.required` in a custom pipe, and a `required` key only in an object literal that also has `cleanable`, `removable` or `type: KbqPipeTypes.…`: the `required` of a form config or a validator in the same file is left alone. ##### Form field | Removed | Use instead | Migration | | -------------------------------------------------------------- | ----------------------------------------------------------------------------------- | --------- | | `KbqPasswordHint` (``) | `KbqReactivePasswordHint` (``) | reported | | `PasswordRules` | `PasswordValidators` from `@koobiq/components/core`, or a `ValidatorFn` of your own | reported | | `regExpPasswordValidator` | `Validators.pattern` or a `ValidatorFn` of your own | reported | | `hasPasswordStrengthError()` | the errors of the form control | reported | | the `passwordStrength` error the form field set on the control | the errors of the password validators | reported | | `KbqFormField.passwordHints` / `.hasPasswordHint` | `viewChildren(KbqReactivePasswordHint)` | reported | | `KbqInputPassword.checkRules()` / `.checkRule` | `control.updateValueAndValidity()` after changing the validators | reported | | `KbqFormField.canCleanerClearByEsc` | nothing: it was unused | reported | | `KbqFormField.onKeyDown()` | nothing: it was a no-op | reported | | `KbqStepper.connectTo()` | nothing: the stepper finds the number input of its form field by itself | reported | `KbqReactivePasswordHint` shows the state it is given: put each rule on the form control as a validator and bind `[hasError]` to its error, for example `control.hasError('minLength')`. It has no counterpart of `[rule]`, `[min]`, `[max]`, `[regex]`, `[checkRule]` or `[viewFormField]`, so place it inside the `kbq-form-field`, which links it to the control through `aria-describedby`. `v21-upgrade` reports the first four where they are imported from `@koobiq/components/form-field`, `` and its classes in templates and style sheets, a check of the `passwordStrength` error, `.passwordHints`, `.hasPasswordHint` and `.onKeyDown()` on a form field, every `canCleanerClearByEsc`, `checkRules()` in a file that uses `KbqInputPassword`, and `connectTo()` in a file that uses `KbqStepper`. ##### Checkbox and toggle `KbqCheckbox` and `KbqToggleComponent` no longer implement `ControlValueAccessor`. The `KbqCheckable` host directive has been the value accessor of both, so forms never called their own members; `[(ngModel)]` and `[formControl]` work as before. | Removed | Use instead | Migration | | --------------------------------------------------------- | --------------------------------------------- | --------- | | `KbqCheckbox` / `KbqToggleComponent.writeValue()` | `checked = !!value` | reported | | `.setDisabledState()` of both | `disabled = isDisabled` | reported | | `.registerOnChange()` of both | the `(change)` output | reported | | `.registerOnTouched()` of both | the `touched` state of the bound form control | reported | | `KbqCheckbox.onTouched` | nothing: it was never called | reported | | `KBQ_CHECKBOX_CONTROL_VALUE_ACCESSOR` | nothing: `KbqCheckable` registers itself | reported | | `TransitionCheckState` from `@koobiq/components/checkbox` | the same enum from `@koobiq/components/core` | rewritten | `v21-upgrade` reports these uses in a file that names either class or its tag, except calls on `this`. A call that has to stay can go to the host directive: `viewChild(KbqCheckbox, { read: KbqCheckable })`, with `KbqCheckable` from `@koobiq/components/core`. It moves `TransitionCheckState` from the checkbox import into the core one, and reports it imported under an alias, re-exported or read through a namespace import of the checkbox. `KbqToggleComponent.onLabelTextChange()` does nothing now and is deprecated for removal in 22.0.0: delete the call. `v21-upgrade` reports it. The protected `KbqToggleComponent.currentCheckState` is a `Signal` shared with the host `KbqCheckable`: a subclass reads it as `currentCheckState()` and changes it through the `checked` and `indeterminate` setters. `v21-upgrade` reports a read without the call in a subclass. ##### Navbar, ellipsis center and description list | Removed | Use instead | Migration | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------- | | `longTitle` on `kbq-navbar-brand` | nothing: the brand switches to the two-line title when the title does not fit on one line | removed, reported | | `KbqNavbarBrand.longTitle` in code | nothing | reported | | `KbqNavbarItem.getTitleWidth()` | `getOuterElementWidth()` of the item's `KbqNavbarTitle`; the navbar itself collapses by `getCollapsibleWidth()` | reported | | `KbqNavbarTitle.outerElementWidth` | `getOuterElementWidth()` | reported | | `KbqEllipsisCenterDirective.resizeStream` | nothing: the shared `ResizeObserver` re-measures the host | reported | | `minWidth` on `` | `verticalBreakpoint` | rewritten | | `KbqDlComponent.minWidth` in code | `verticalBreakpoint()` | reported | `[longTitle]="true"` forced the compact title on a name that fits and `false` kept the default one for a name that does not; neither has a replacement. `outerElementWidth` held the width measured once on init and only fed `getTitleWidth()`. `minWidth` took precedence over `verticalBreakpoint`, so a `` that sets both keeps its `minWidth` and is reported: move the value into `verticalBreakpoint`. A `[minWidth]` that resolved to `null` or `undefined` left the decision to `verticalBreakpoint`, while `[verticalBreakpoint]` given such a value never switches to the vertical layout, so bind a number. `v21-upgrade` reports each renamed `minWidth` whose value is not a number literal. ##### Popover trigger The trigger inputs without the `kbqPopover` prefix are removed from `KbqPopoverTrigger` and `KbqPopoverConfirmTrigger`. The members they set — `defaultPaddings`, `container`, `hasBackdrop`, `hasCloseButton`, `closeOnScroll` and `backdropClass` — stay public, so code that reads or writes them keeps compiling. | Removed | Use instead | Migration | | ------------------------------------------------ | ----------------------------------------------------------- | --------- | | the `hideIfNotInViewPort` input | `kbqPopoverHideIfNotInViewPort` | rewritten | | the `defaultPaddings` input | `kbqPopoverDefaultPaddings` | rewritten | | the `container` input | `kbqPopoverContainer` | rewritten | | the `hasBackdrop` input | `kbqPopoverHasBackdrop` | rewritten | | the `hasCloseButton` input | `kbqPopoverHasCloseButton` | rewritten | | the `closeOnScroll` input | `kbqPopoverCloseOnScroll` | rewritten | | the `backdropClass` input | `kbqPopoverBackdropClass` | rewritten | | `KbqPopoverTrigger.hideIfNotInViewPort` (signal) | `popoverHideIfNotInViewPort()` | reported | | `getKbqPopoverInvalidPositionError()` | nothing: an invalid placement warns and falls back to `top` | reported | `v21-upgrade` rewrites bindings, static and valueless attributes only on an element that carries `kbqPopover` or `kbqPopoverConfirm`: other components, such as the dropdown and the select, have inputs of the same names. It reports a `hostDirectives` entry of `KbqPopoverTrigger` that exposes a removed input; expose the prefixed one instead, for example `'kbqPopoverHasBackdrop: hasBackdrop'`. ##### Styles and theming | Removed | Use instead | Migration | | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------- | | the arguments of `koobiq-theme()`, `koobiq-typography()` and `kbq-core-theme()` | nothing: they were ignored | removed; with nested `()` reported | | `kbq-contrast()`, `kbq-palette()`, `kbq-color()` | the CSS variables of the theme, for example `var(--kbq-foreground-contrast)` | reported | | `kbq-typography-config()` | the `--kbq-typography-*` CSS variables | reported | | `@koobiq/components/core/styles/tokens` | the modules it forwarded: `@koobiq/design-tokens/web/variables`, `palette`, `typography` and `md-typography` | reported | | `prebuilt-themes/light-theme.css`, `prebuilt-themes/dark-theme.css` | `prebuilt-themes/theme.css` | rewritten | | the `md-h1`…`md-h6`, `md-body`, `md-body-mono`, `md-caption`, `md-table-cell` and `md-table-header` classes | the `kbq-md-` class of the same name, for example `kbq-md-h1` | rewritten, reported | The light and dark files were copies of `theme.css`, which holds both themes. `v21-upgrade` rewrites them in style sheets, code, templates and the `styles` of `angular.json`, and keeps a single `theme.css` where both were loaded. A theme map built only to pass to `koobiq-theme()`, such as `kbq-light-theme($tokens)`, can go along with the argument: `kbq-light-theme()` and `kbq-dark-theme()` are deprecated and will be removed in 22.0.0, since nothing in the library reads their map. Read the CSS variables of the theme instead, for example `var(--kbq-foreground-contrast)`; the migration reports both functions. The markdown classes are rewritten in `class` attributes, `[class.…]` bindings and selectors; in any other string, such as `classList.add('md-h1')` or `[ngClass]`, they are reported. ##### Tag input Validation belongs to the `` control, so the validation members of the tag input are removed. A `[formControl]` or `[ngModel]` bound to `` keeps working: `kbqAutocomplete` drives the input through it. | Removed | Use instead | Migration | | --------------------------------- | ----------------------------------------------------------------------------- | --------- | | `KbqTagInput.ngControl` | the control you bound to the input | reported | | `KbqTagInput.triggerValidation()` | nothing: it was a no-op, and the tag list control revalidates on every change | reported | | `KbqTagTextControl.ngControl` | nothing: the library never read it | — | `v21-upgrade` reports these uses in a file that names `KbqTagInput` or binds its `exportAs`, except on `this`, and a subclass of `KbqTagInput` that mentions either member. `KbqTagTextControl` is not exported, and a control passed to `KbqTagList.registerInput()` that still declares `ngControl` keeps compiling: the member was optional. ##### Toast container | Removed | Use instead | Migration | | ------------------------------------------------ | ------------------------------------------------------------------------- | --------- | | `--kbq-toast-stack-size-margin-top` | `--kbq-toast-stack-size-margin-vertical` | rewritten | | `--kbq-toast-stack-size-margin-right` | `--kbq-toast-stack-size-margin-horizontal` | rewritten | | `KbqToastContainerComponent.dispatchScrollEvent` | nothing: the container reports its own reflow through `elementScrolled()` | reported | The removed properties already set both edges: `-top` also set the bottom margin of a stack at the bottom, and `-right` the left margin of a stack on the left. `v21-upgrade` renames them in style sheets, templates and code, and reports a file that declares both a removed and a new name: the new one used to win, after the rename the cascade decides. ##### Tree option | Removed | Use instead | Migration | | ------------------------------------------------------------ | ---------------------------------------------- | --------- | | `KbqTreeOptionChange.isUserInput` | nothing: the tree never set it, it was `false` | reported | | the second argument of the `KbqTreeOptionChange` constructor | nothing: `new KbqTreeOptionChange(option)` | reported | A check of `isUserInput` on a tree option change never passed: delete it with the branch it guarded. `v21-upgrade` reports `.isUserInput` in a file that names `KbqTreeOptionChange`, `$event.isUserInput` bound to an output of ``, and a second argument passed to the constructor or to `super()` in a subclass. The `isUserInput` of `KbqOptionSelectionChange` and of `KbqTreeSelectChange` stays. | Deprecated, removed in 22.0.0 | Use instead | Migration | | ------------------------------------------------- | --------------------------------------------------------------- | --------- | | `(onSelectAll)` on `` | `(selectAllChange)` | rewritten | | `(onCopy)` on `` | `(copyChange)` | rewritten | | `(onSelectionChange)` on `` | `(selectionChange)` | rewritten | | `KbqTreeSelection.onSelectAll`, `.onCopy` in code | `selectAllChange`, `copyChange` | reported | | `KbqTreeOption.onSelectionChange` in code | `selectionChange` | reported | | `KbqTreeNode.mostRecentTreeNode` | a query of the nodes, for example `viewChildren(KbqTreeOption)` | reported | The replacements emit the same events. The `onSelectAll` and `onCopy` outputs of `kbq-list-selection` and the `onSelectAll` output of `kbq-tree-select` are not deprecated and stay. ##### Running the migration `v21-upgrade` is registered for 21.0.0, so an `ng update` to that version runs it: ```bash ng g @koobiq/components:v21-upgrade --project "" ``` With `--fix false` it only reports what it would change. #### ThemePalette removal (21.0.0) `ThemePalette` is removed from `@koobiq/components/core`: `KbqComponentColors` describes the colors the components support. | Removed | Use instead | Migration | | --------------------------------------------------------- | -------------------------------------------------------------------- | --------- | | `ThemePalette.Error`, `.Warning`, `.Success` | `KbqComponentColors.Error`, `.Warning`, `.Success` | rewritten | | `ThemePalette.Primary`, `.Secondary`, `.Info`, `.Default` | no color, or a member of `KbqComponentColors` the component supports | reported | | `ThemePalette.Empty` | an unset `color` | reported | | the `ThemePalette` type | `KbqComponentColors` | reported | `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. `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. #### Unused core API removal (21.0.0) `@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. | Removed | Use instead | Migration | | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | | `POSITION_MAP`, `EXTENDED_OVERLAY_POSITIONS`, `POSITION_PRIORITY_STRATEGY`, the twelve `*_POSITION_PRIORITY` lists | `kbqPlacement` on the trigger; copy a value you need | reported | | `ARROW_BOTTOM_MARGIN_AND_HALF_HEIGHT`, `SELECT_PANEL_VIEWPORT_PADDING`, `NUMBER_FORMAT_REGEXP`, `isWithin()` | a value or function of your own | reported | | `KBQ_SELECT_SCROLL_STRATEGY_PROVIDER` | nothing: `KBQ_SELECT_SCROLL_STRATEGY` has a root default; provide the token itself to change it | reported | | `KBQ_CHECKABLE_CONTROL_VALUE_ACCESSOR` | nothing: `KbqCheckable` registers itself | reported | | `KbqHover` | `(mouseenter)` and `(mouseleave)`, or `:hover` | reported | | `CanColor` | `Signal`, or `KbqColorDirective` | reported | | `KbqAnimationCurves`, `KbqAnimationDurations` | constants of your own: the library animates in CSS | reported | | `KbqRectangleItem`, `countGroupLabelsBeforeOption()` | no replacement | reported | | `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 | | `KbqPopUp.warning`, `.handleBodyInteraction()`, the protected `closeOnInteraction` | no replacement: `warning` did nothing, nothing called `handleBodyInteraction()`, and `closeOnInteraction` only gated it | reported | | `KbqDecimalPipe.isSpecialFormatForRULocale()`, `KbqRoundDecimalPipe.roundingOptions` | private now: the pipe arguments and the formatters locale configuration | reported | | `KbqFormElement.isRow` | the `kbq-form__row` class of the element | reported | | `KbqLineSetter` and the `kbq-2-line`, `kbq-3-line`, `kbq-multi-line` classes of a list item | nothing: no style read them | reported | | `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 | | `KbqSelectSearch.hasPlaceholder()` | the placeholder of the search input | reported | | the `mcListIcon` attribute of a list item | `kbq-list-icon` | rewritten | | the `mcLine` attribute | `kbq-line` | rewritten | The Sass that goes is reachable only through a deep import, since `@koobiq/components` forwards none of it: - the modules `core/styles/functions` (its index, `number` and `string`), `typography/typography-utils`, `common/clearfix` and `common/popup`; - `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`; - the timing variables of `core/styles/functions/timing` but `$ease-out-circ`, `$ease-in-out-quad` and `$ease-in-out-circ`. For 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`. ##### Deprecated for removal in 22.0.0 The documented core API that has a replacement is deprecated instead; `v21-upgrade` reports each use: | Deprecated | Use instead | | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | the unprefixed date pipes (`absoluteLongDate`…`durationShort`), their `ImpurePipe` twins and `BaseFormatterPipe` | the `kbq*` pipe of the same format with the same arguments, for example `kbqAbsoluteLongDate`: it follows a locale change | | the `mcHighlight` pipe (`KbqHighlightPipe`) | `kbqHighlightBold` (`KbqHighlightBoldPipe`) | | `isDigit()` | `isNumberKey()` | | `isBoolean()` | `typeof value === 'boolean'` | | `KbqMeasureScrollbarService` | `element.offsetWidth - element.clientWidth` of the element that scrolls | | `FocusKeyManager.getFocusOrigin()` | the origin passed to `setFocusOrigin()` | | `dateInput` of the datepicker locale configuration | `KBQ_DATE_FORMATS`: nothing reads `dateInput` | #### Unused component API removal (21.0.0) 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. | Removed | Use instead | | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `normalizeSplitter()`, `isInt()`, `isDigit()` of `@koobiq/components/input` | `value.replace(/,/g, '.')`, `/^-?\d+$/.test(value)`; `isFloat()` stays | | `KbqTimeRangeTranslateTypeMap` | `Record` | | `KbqTabBodyOriginState` | `'left' \| 'right'` | | `KbqDatepickerIntl` | nothing: no view rendered its labels; the texts come from `kbqDatepickerLocaleConfigurationProvider()` | | `KbqDropdown.onPanelReached()` | nothing: nothing subscribed to it | | the protected `KbqTreeSelection.getItemHeight()` | `options.first?.getHeight() ?? 0` | | the protected `KbqTreeSelection.setFocusedOption()` | `this.keyManager.setActiveItem(option)` | | `KbqTagList.tagSelectionChanges`, `.shouldLabelFloat` | the `selectionChange` output of each tag; `!empty() \|\| focused()` | | `KbqTreeNodeOutlet.changeDetectorRef` | `ChangeDetectorRef`, injected where you need it | A 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. The 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. #### Actions panel overlay container (21.0.0) `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. What that changes for code already setting the option: - **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. - **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. - **`maxWidth` is no longer ignored** when `overlayContainer` is set. - **A globally provided `OverlayContainer` is bypassed** while the option is set. ##### Running the migration `actions-panel-overlay-container` is registered for 21.0.0, so an `ng update` to that version runs it: ```bash ng g @koobiq/components:actions-panel-overlay-container --project "" ``` It only reports the call sites — there is nothing to rewrite, because the option is the same one and only its meaning changed. #### Legacy splitter deprecation (21.0.0) `@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. **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`. **`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. Templates 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. ##### Running the migration `splitter-deprecated-path` is registered for 21.0.0, so an `ng update` to that version runs it: ```bash ng g @koobiq/components:splitter-deprecated-path --project "" ``` It 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. #### Overlay demotion removal (21.0.0) Until 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. The 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. **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`. ##### Running the migration The `dropdown-demote-overlay` schematic runs automatically: ```bash ng update @koobiq/components@21 ``` Or manually: ```bash ng g @koobiq/components:dropdown-demote-overlay --project "" ``` ##### What is fixed automatically **The `demoteOverlay` attribute is removed from templates** in all of its forms — `demoteOverlay`, `demoteOverlay="false"` and `[demoteOverlay]="expr"` — in `.html` files and in inline `template:` literals. Template rules are applied to `.ts` files only inside inline templates, so a wrapper component that forwards the input keeps its own member: ```ts // Before @Component({ template: ` ` }) export class MyTrigger { @Input() demote = false; } // After — only the binding is removed; `demote` is now dead code the compiler points at @Component({ template: ` ` }) export class MyTrigger { @Input() demote = false; } ``` **`{ provide: KBQ_DROPDOWN_HOST, … }` entries of a provider array are removed**, together with the `KBQ_DROPDOWN_HOST` import specifier they made invalid and a `providers` array the removal left empty. ##### What you need to fix manually **Programmatic `demoteOverlay` access.** A read or an assignment (`this.trigger.demoteOverlay = false`) is reported with a warning rather than rewritten — deleting a statement is not always safe, and the compiler flags it anyway. There is nothing to opt out of any more, so remove the line. **Providers the schematic could not rewrite.** A provider declared outside a provider array (`export const HOST_PROVIDER = { provide: KBQ_DROPDOWN_HOST, … };`), or an `inject(KBQ_DROPDOWN_HOST)` call, is reported as a leftover `KBQ_DROPDOWN_HOST` warning. Remove it by hand. **`.cdk-overlay-container_dropdown` rules in your stylesheets** are dead — the class is never applied now. This is reported as a warning. If the rule was an override neutralising the demotion, it is simply redundant. **Panels that used to slide under your sticky chrome now render on top of it.** To get that back, mark the content area below `kbq-navbar` / `kbq-top-bar` with `kbqOverlayLayer` from `@koobiq/components/core` — see [Panels under a sticky header](https://koobiq.io/en/components/core.md#panels-under-a-sticky-header). Unlike the demotion, it moves only the panels opened from inside that area: modals, sidepanels, toasts and tooltips stay above the bars. **`kbq-select` and popover panels inside `kbq-navbar` / `kbq-top-bar` are fixed by this release.** They applied the demotion unconditionally and had no opt-out, so they rendered behind the very chrome that contained them. No action needed — this is the bug the removal fixes. #### App-switcher upgrade (21.0.0) In 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. | Member | Before | After | | ------------------------------------------------------------------------------------ | ------------------- | ------------------------------------------------------------------ | | `selectedApp` | `@Input()` property | `ModelSignal` — write via `.set()` | | `selectedSite` | accessor input | `ModelSignal` — **value changed** | | `selectedAppChange` / `selectedSiteChange` | `output()` | the implicit outputs of the models above | | `header` / `footer` | properties | removed | | `KbqAppSwitcherComponent.isTrapFocus` / `updateTrapFocus()` | public API | removed | | `KbqAppSwitcherDropdownApp.getIcon()` | public method | removed | | `KbqAppSwitcherComponent.activeSite` / `activeApp` | protected | removed: the context of the flyout's `kbqDropdownContent` template | | `KbqAppSwitcherListItem.collapsed` | property | `ModelSignal` | | `app` / `site` inputs of `KbqAppSwitcherListItem` / `-DropdownApp` / `-DropdownSite` | optional inputs | `input.required` signals | ##### Running the migration The changes are applied by the `app-switcher-signals` schematic (runs automatically): ```bash ng update @koobiq/components@21 ``` Or manually — for example, if you have already upgraded to 21.0.0. To preview without writing — `--fix=false`: ```bash ng g @koobiq/components:app-switcher-signals --project "" ``` ##### What is fixed automatically **Reads and writes in TypeScript** (for receivers annotated `KbqAppSwitcherTrigger`): - trigger.selectedApp → trigger.selectedApp(), - trigger.selectedApp = app → trigger.selectedApp.set(app), - trigger.selectedApp.name → trigger.selectedApp().name, - trigger.selectedAppChange.subscribe(fn) → trigger.selectedApp.subscribe(fn) — `ModelSignal` implements `OutputRef`, so the callback signature is identical **Reads through a template reference variable** (`#ref="kbqAppSwitcher"`, in external `.html` files and inline templates): - switcher.selectedApp → switcher.selectedApp() All replacements are idempotent — an access already followed by `()`, `.set`, `.update`, `.asReadonly` or `.subscribe` is left alone, so running the schematic twice does not double the call. Note that `selectedApp()` is typed `KbqAppSwitcherApp | undefined`. Where the old property was read as non-nullable, the compiler will now ask for a `!` or a `?.` — that narrowing is yours to place. ##### What you need to fix manually The schematic emits warnings for what cannot be rewritten safely: **`selectedSite` is not rewritten, because its value changed.** The old getter returned the site with its applications already grouped for rendering; the model returns the value that was passed in. Read `trigger.selectedSite()` for the raw site and `trigger.parsedSelectedSite()` for the grouped one, and write with `trigger.selectedSite.set(site)`. `selectedSiteChange` is now the model's implicit output and emits the raw site as well. **`selectedAppChange.emit(app)`**: no longer an emitter → `trigger.selectedApp.set(app)`. **`header` / `footer`**: removed. The popup never rendered either, so the value was pushed into the overlay and dropped — delete the usage. **`isTrapFocus` / `updateTrapFocus()`**: removed from `KbqAppSwitcherComponent`. Its template never bound `[cdkTrapFocus]`, so neither did anything. **`activeSite` / `activeApp`** of a `KbqAppSwitcherComponent` subclass: removed. Each flyout of another site or group reads its site and app from the context of its `kbqDropdownContent` template, which `kbqDropdownTriggerData` fills. **`KbqAppSwitcherDropdownApp.getIcon()`**: removed. Inline markup is sanitized by `KbqAppSwitcherIconSanitizer` and rendered by the component itself. **Inline `icon` markup is now sanitized** against a strict SVG allow-list before it is rendered: `