# Icon

- Entry point: `@koobiq/components/icon`
- NgModule: `KbqIconModule`
- Page: https://koobiq.io/en/components/icon/overview

## Overview

### Font icons (default)

Uses `@koobiq/icons` CSS font. No providers needed — works out of the box after adding the stylesheet.

Install the dependency and configure `angular.json`:

```bash
npm install @koobiq/icons
```

```json
"styles": [
  "node_modules/@koobiq/icons/fonts/kbq-icons.css"
]
```

```ts
import { KbqIconModule } from '@koobiq/components/icon';

@Component({
    imports: [KbqIconModule],
    template: `
        <i kbq-icon="kbq-plus_16"></i>
    `
})
export class AppComponent {}
```

Available icons: [Icons](https://koobiq.io/en/main/icons)

---

#### SVG icons

SVG icons render inline and support CSS color theming via `currentColor`. Choose one of the approaches below depending on your needs.

All three fetch their icons over HTTP through `HttpClient`, which Angular injects without any setup: `provideHttpClient()` in the examples below is only needed to configure it. An icon whose request fails keeps its font class. To register icons with no request at all, add them as inline literals through `KbqIconRegistry.addSvgIconLiteral()`.

##### Sprite file

Best when you have a pre-built SVG sprite and want all icons in a single HTTP request.

```ts
import { provideHttpClient } from '@angular/common/http';
import { kbqIconsProvider } from '@koobiq/components/icon';

bootstrapApplication(AppComponent, {
    providers: [
        provideHttpClient(),
        kbqIconsProvider(
            { spriteUrl: '/assets/icons/sprite.symbol.svg' },
            { spriteUrl: '/assets/brand/sprite.symbol.svg', namespace: 'brand' }
        )
    ]
});

@Component({
    imports: [KbqIconModule],
    template: `
        <i kbq-icon="plus_16"></i>
        <i kbq-icon="brand:logo_24"></i>
    `
})
export class AppComponent {}
```

##### URL resolver

Best when icons live at predictable URLs and you want them fetched on demand (no sprite required).

```ts
import { provideHttpClient } from '@angular/common/http';
import { kbqIconsResolverProvider } from '@koobiq/components/icon';

bootstrapApplication(AppComponent, {
    providers: [
        provideHttpClient(),
        kbqIconsResolverProvider((name) => `/assets/icons/${name}.svg`)
    ]
});

@Component({
    imports: [KbqIconModule],
    template: `
        <i kbq-icon="plus_16"></i>
    `
})
export class AppComponent {}
```

##### Dictionary

`kbqIconsDictProvider` is the resolver above with the mapping written out: a name→URL record, looked up by icon name. A name the dictionary does not hold falls through to the next resolver, and then to the font class.

```ts
import { provideHttpClient } from '@angular/common/http';
import { kbqIconsDictProvider } from '@koobiq/components/icon';

bootstrapApplication(AppComponent, {
    providers: [
        provideHttpClient(),
        kbqIconsDictProvider({
            plus_16: '/assets/icons/plus_16.svg',
            logo_24: '/assets/brand/logo_24.svg'
        })
    ]
});
```

##### Inline literals

The only setup that issues no request. The markup is passed through Angular's HTML sanitizer, which drops `<svg>` wholesale, so it has to be handed over as `SafeHtml` the application vouches for:

```ts
import { inject, provideAppInitializer } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { KbqIconRegistry } from '@koobiq/components/icon';

bootstrapApplication(AppComponent, {
    providers: [
        provideAppInitializer(() => {
            const sanitizer = inject(DomSanitizer);

            inject(KbqIconRegistry).addSvgIconLiteral('plus_16', sanitizer.bypassSecurityTrustHtml(PLUS_16_SVG));
        })
    ]
});
```

---

#### Accessibility

An icon carries no text, so `<i kbq-icon>` is `aria-hidden="true"` by default and assistive technology walks past it. That is right nearly everywhere: the icon repeats a label that is already in the button, the link or the row beside it.

An icon that is the only carrier of its meaning has to opt out and name itself:

```html
<i kbq-icon="kbq-triangle-exclamation_16" role="img" aria-hidden="false" aria-label="Error"></i>
```

`kbq-icon-button` is not hidden by default — it is interactive, and it needs a name of its own. Give it an `aria-label` (or an `aria-labelledby`); in dev mode it logs a warning when it has neither. On a host that is not a native `<button>` it supplies `role="button"` and `aria-disabled` itself, but not keyboard activation: bind `(keydown.enter)` and `(keydown.space)` next to `(click)`.

---

#### Two-tone icons

Some SVG icons (e.g. `folder-dot_16`, `envelope-dot_24`) include an extra accent element that can be recolored independently of the icon's main color. The accent color is controlled by the `--icon-accent-color` CSS variable, which defaults to `currentColor`.

```css
.my-scope {
    --icon-accent-color: var(--kbq-foreground-error);
}
```

The variable only applies to SVG icons — font icons are always single-color.

See the full list of two-tone icons on the [Icons](https://koobiq.io/en/main/icons) page with the "Two-color only" filter enabled.

## API

### Components

#### KbqIcon (component)

Renders an icon on the element it is applied to: a `@koobiq/icons` font glyph by default, or an inline
`<svg>` when the name resolves through a configured `KbqIconRegistry`. A name that does not resolve
falls back to the font class, and every later name is resolved again.

The host is `aria-hidden` by default: an icon carries no text of its own, and in nearly every place the
library and its consumers use one it repeats a label that is already there. A meaningful icon opts out
with `aria-hidden="false"`, and then has to supply its own `role="img"` and `aria-label`.

```ts
@Component({ selector: '[kbq-icon]' })
class KbqIcon extends KbqColorDirective {
    readonly ariaHidden = input<boolean | undefined>(undefined, {
        alias: 'aria-hidden'
    });
    readonly iconSize = input<number | undefined>();
    autoColor = input<boolean | undefined>();
    readonly color = input<T | null | undefined>();
    hasError: boolean;
    iconName: string | undefined;
    get colorClassName(): string;
    getHostElement(): HTMLElement;
    setDefaultColor(color: T): void;
}
```

- `[aria-hidden]`: `boolean | undefined` — Whether the icon is hidden from assistive technology. Left unbound, it falls back to
  `defaultAriaHidden`, which is `true` for a decorative icon and `false` for an interactive one.
- `[iconSize]`: `number | undefined` — Rendered size of the icon in pixels. Left unset, the size is read from the `_<px>` suffix of the
  icon name (`kbq-plus_16` → 16), which is the `@koobiq/icons` convention; a name without one is
  rendered at whatever size the surrounding styles give it.
- `[autoColor]`: `boolean | undefined` — Whether the icon follows the error state of the host it sits in (a form field, for one), painting
  itself red while that host reports an error.
- `[color]`: `T | null | undefined` — Color of the component. A value set in code holds until the input changes, and the default color is used
  while both are unset or falsy.
- `hasError`: `boolean` — Whether the icon takes the error color whatever its `color` is. Set by a host that tints the icons
  it holds, such as `KbqEmptyState`.
- `iconName`: `string | undefined` — Name of an icon within a `@koobiq/icons`. Accepts "namespace:name" syntax.
- `colorClassName`: `string` — current class name of color
- `setDefaultColor()`: `void` — this color will be used as a default value. For example [color]="'' | false | undefined | null".

#### KbqIconButton (component)

An icon that acts as a button. Applied to a native `<button>` it only adds the styling; applied to any
other element — `<i>` is what the library itself mostly uses — it also supplies the semantics the
element does not have: `role="button"` and `aria-disabled`. It does not synthesize Enter/Space
activation itself — attach a `(keydown.enter)`/`(keydown.space)` listener where it is used.

It has no text of its own, so give it an `aria-label` (or an `aria-labelledby`); in dev mode it warns
when it has neither.

It extends `KbqIcon` for the rendering, but deliberately does not register under that DI token: a
`contentChild(KbqIcon)` is looking for a decorative icon to position or tint, and an interactive
control is not one. See the note on `KbqIconItem`.

```ts
@Component({ selector: '[kbq-icon-button]' })
class KbqIconButton extends KbqIcon {
    readonly size = input<KbqIconButtonSize>('normal');
    tabindex = input<number | null | undefined>();
    disabled = input<boolean | undefined>();
    readonly ariaHidden = input<boolean | undefined>(undefined, {
        alias: 'aria-hidden'
    });
    readonly iconSize = input<number | undefined>();
    autoColor = input<boolean | undefined>();
    readonly color = input<T | null | undefined>();
    hasError: boolean;
    iconName: string | undefined;
    get colorClassName(): string;
    getHostElement(): HTMLElement;
    setDefaultColor(color: T): void;
}
```

- `[size]`: `KbqIconButtonSize` — Size of the icon button.
- `[tabindex]`: `number | null | undefined` — Position of the button in the tab order. A disabled button is taken out of it entirely.
- `[disabled]`: `boolean | undefined` — Whether the button is disabled.
- `[aria-hidden]`: `boolean | undefined` — Whether the icon is hidden from assistive technology. Left unbound, it falls back to
  `defaultAriaHidden`, which is `true` for a decorative icon and `false` for an interactive one.
- `[iconSize]`: `number | undefined` — Rendered size of the icon in pixels. Left unset, the size is read from the `_<px>` suffix of the
  icon name (`kbq-plus_16` → 16), which is the `@koobiq/icons` convention; a name without one is
  rendered at whatever size the surrounding styles give it.
- `[autoColor]`: `boolean | undefined` — Whether the icon follows the error state of the host it sits in (a form field, for one), painting
  itself red while that host reports an error.
- `[color]`: `T | null | undefined` — Color of the component. A value set in code holds until the input changes, and the default color is used
  while both are unset or falsy.
- `hasError`: `boolean` — Whether the icon takes the error color whatever its `color` is. Set by a host that tints the icons
  it holds, such as `KbqEmptyState`.
- `iconName`: `string | undefined` — Name of an icon within a `@koobiq/icons`. Accepts "namespace:name" syntax.
- `colorClassName`: `string` — current class name of color
- `setDefaultColor()`: `void` — this color will be used as a default value. For example [color]="'' | false | undefined | null".

#### KbqIconItem (component)

An icon on a filled round background — the status badge of an alert or an empty state. Decorative like
`KbqIcon`, and hidden from assistive technology by the same default.

Host metadata is inherited from `KbqIcon` through `ɵɵInheritDefinitionFeature`; DI tokens are not, so a
`contentChild(KbqIcon)` does not match an icon item. That is deliberate — a host that wants both asks
for both, the way `KbqAlert` does, and telling them apart is what lets it report two projected icons.

```ts
@Component({ selector: '[kbq-icon-item]' })
class KbqIconItem extends KbqIcon {
    readonly fade = input<boolean>(false);
    readonly big = input<boolean>(false);
    readonly ariaHidden = input<boolean | undefined>(undefined, {
        alias: 'aria-hidden'
    });
    readonly iconSize = input<number | undefined>();
    autoColor = input<boolean | undefined>();
    readonly color = input<T | null | undefined>();
    hasError: boolean;
    iconName: string | undefined;
    get colorClassName(): string;
    getHostElement(): HTMLElement;
    setDefaultColor(color: T): void;
}
```

- `[fade]`: `boolean` — Whether the background is the translucent "fade" variant of the color rather than the solid one.
- `[big]`: `boolean` — Whether the item is rendered at the larger of its two sizes.
- `[aria-hidden]`: `boolean | undefined` — Whether the icon is hidden from assistive technology. Left unbound, it falls back to
  `defaultAriaHidden`, which is `true` for a decorative icon and `false` for an interactive one.
- `[iconSize]`: `number | undefined` — Rendered size of the icon in pixels. Left unset, the size is read from the `_<px>` suffix of the
  icon name (`kbq-plus_16` → 16), which is the `@koobiq/icons` convention; a name without one is
  rendered at whatever size the surrounding styles give it.
- `[autoColor]`: `boolean | undefined` — Whether the icon follows the error state of the host it sits in (a form field, for one), painting
  itself red while that host reports an error.
- `[color]`: `T | null | undefined` — Color of the component. A value set in code holds until the input changes, and the default color is used
  while both are unset or falsy.
- `hasError`: `boolean` — Whether the icon takes the error color whatever its `color` is. Set by a host that tints the icons
  it holds, such as `KbqEmptyState`.
- `iconName`: `string | undefined` — Name of an icon within a `@koobiq/icons`. Accepts "namespace:name" syntax.
- `colorClassName`: `string` — current class name of color
- `setDefaultColor()`: `void` — this color will be used as a default value. For example [color]="'' | false | undefined | null".

### Services

#### KbqIconRegistry (service)

```ts
@Injectable({ providedIn: 'root' })
class KbqIconRegistry {
    addSvgIcon(
        name: string,
        url: SafeResourceUrl,
        options?: KbqIconOptions
    ): void;
    addSvgIconLiteral(
        name: string,
        literal: SafeHtml,
        options?: KbqIconOptions
    ): void;
    addSvgIconInNamespace(
        namespace: string,
        name: string,
        url: SafeResourceUrl,
        options?: KbqIconOptions
    ): void;
    addSvgIconLiteralInNamespace(
        namespace: string,
        name: string,
        literal: SafeHtml,
        options?: KbqIconOptions
    ): void;
    addSvgIconSet(url: SafeResourceUrl, options?: KbqIconOptions): void;
    addSvgIconSetInNamespace(
        namespace: string,
        url: SafeResourceUrl,
        options?: KbqIconOptions
    ): void;
    getNamedSvgIcon(
        name: string,
        namespace?: string
    ): Observable<SVGElement>;
}
```

- `addSvgIcon()`: `void` — Registers an SVG icon by URL in the default namespace.
- `addSvgIconLiteral()`: `void` — Registers an SVG icon from an inline HTML string in the default namespace.
- `addSvgIconInNamespace()`: `void` — Registers an SVG icon by URL under the given namespace.
- `addSvgIconLiteralInNamespace()`: `void` — Registers an SVG icon from an inline HTML string under the given namespace.
- `addSvgIconSet()`: `void` — Registers an SVG sprite file in the default namespace. Icons are looked up by their `symbol id`.
- `addSvgIconSetInNamespace()`: `void` — Registers an SVG sprite file under the given namespace. Duplicate URLs are silently ignored.
- `getNamedSvgIcon()`: `Observable<SVGElement>` — Emits a cloned SVGElement for the given name.
  Accepts "namespace:name" syntax.

### Functions

#### kbqIconsDictProvider (function)

Registers a name→URL dictionary as an icon resolver. Values are fetched at runtime through `HttpClient`.

A value starting with `<` is taken as inline markup and passed through Angular's HTML sanitizer,
which drops `<svg>` wholesale — register inline icons with
`KbqIconRegistry.addSvgIconLiteral(name, sanitizer.bypassSecurityTrustHtml(svg))` instead, where the
markup is a `SafeHtml` the application has explicitly vouched for.

```ts
kbqIconsDictProvider({ 'chevron-down-s_16': '/assets/icons/chevron-down-s_16.svg' })
```

```ts
function kbqIconsDictProvider(dict: Record<string, string>): Provider;
```

#### kbqIconsProvider (function)

Configures the icon registry to load icons from one or more SVG sprite assets.

```ts
function kbqIconsProvider(...sprites: KbqIconsConfig[]): Provider;
```

#### kbqIconsResolverProvider (function)

Registers a resolver function that maps icon names to SVG strings or URLs.
Multiple resolvers can be registered; they are tried in registration order.

```ts
function kbqIconsResolverProvider(resolver: KbqIconResolver): Provider;
```

### Types

#### KbqIconButtonSize (type)

Size options for `KbqIconButton`.
- `compact`: 16px icon with padding, 24×24px container.
- `normal`: 24px icon, no padding.

```ts
type KbqIconButtonSize = 'compact' | 'normal';
```

#### KbqIconOptions (interface)

```ts
interface KbqIconOptions {
    viewBox?: string;
    withCredentials?: boolean;
}
```

#### KbqIconResolver (type)

A function that resolves an icon name to either an inline SVG string or a URL string.
Return `null` to indicate this resolver does not handle the given name.

```ts
type KbqIconResolver = (name: string) => string | null;
```

#### KbqIconsConfig (interface)

```ts
interface KbqIconsConfig {
    spriteUrl: string;
    namespace?: string;
}
```

- `spriteUrl`: `string` — URL to the SVG sprite file. Must be served as a static asset.
- `namespace?`: `string` — Optional namespace to register the sprite under.

### Constants

#### KBQ_ICON_RESOLVER (const)

Multi-valued injection token for icon resolver functions.
Resolvers are tried in registration order; the first non-null result wins.

```ts
const KBQ_ICON_RESOLVER: InjectionToken<KbqIconResolver[]>;
```

#### KBQ_ICONS_CONFIG (const)

```ts
const KBQ_ICONS_CONFIG: InjectionToken<KbqIconsConfig[]>;
```
