+
+Use the [country-flag-icons](https://www.npmjs.com/package/country-flag-icons) package to show a small country flag in your product:
+
+```bash
+npm install country-flag-icons
+```
+
+
+
+
+- **Full ISO 3166-1 coverage**. Every existing country is identified by a two-letter code (RU, DE, FR).
+- **Flags are redrawn for small sizes**. Most sets take detailed SVGs from Wikimedia Commons, and at interface sizes (16—24 px) the small coats of arms, inscriptions and emblems on them become illegible. In country-flag-icons the details are simplified, so flags stay recognizable even at icon size.
+- Actively maintained, MIT licensed.
+
+## Aspect ratio
+
+By default flags use a 3:2 ratio. This is the primary format — use it in most scenarios: inline with text, lists, selects. Flags with a different ratio (e.g. 4:3) or from other sources are supported by overriding the `--kbq-flag-aspect-ratio` CSS variable.
+
+
+
+Square flags live in a separate subpackage and are needed where the 3:2 format does not fit: avatar selectors, compact circles, dense grids.
+
+Most 1:1 versions are a center crop of the 3:2 flag. For flags that lose meaning when cropped this way, the package ships dedicated square variants (e.g. EU and KR) — they are substituted automatically from the same subpackage.
+
+
+
+## Inset shadow: separating from the background
+
+Flags get a thin inset shadow (an inset outline) along the edge. Without it a flag blends into the background:
+
+- lots of white: the flag blends into the light page background.
+- some flags contain black or dark colors: they blend into the background in the dark theme.
+
+That is why the shadow color depends on the theme: in the light theme the shadow is dark, in the dark theme it lightens or is removed. The shadow is implemented as an inset box-shadow on a pseudo-element over the flag, and its color comes from a CSS variable that switches together with the theme.
+
+
+
+## A flag is not a language
+
+A flag denotes a country or region, not a language. Do not use a flag to pick an interface language: one language is spoken in several countries (Spanish is not only Spain), and one country may have several languages.
+
+
+
+## When the flag is missing
+
+First check availability via `hasFlag(code)` and do not render an empty gap:
+
+```ts
+import { hasFlag } from 'country-flag-icons';
+
+hasFlag('RU'); // true
+hasFlag('ZZ'); // false
+```
+
+Then it depends on the situation.
+
+**Unknown or invalid code.** Show a neutral placeholder (a gray rectangle, square, circle or a globe icon). Empty space is acceptable only if it does not break the layout.
+
+
+
+**You need a non-country flag** (organization, historical or disputed territory, federal subject). Do not add it to the country-flag-icons package. Add it to your internal set so that the way it is used does not change.
+
+**Supranational flags.** The EU flag is available as a separate 1:1 version; for the rest (ASEAN, UN, etc.) create your own images in the project.
+
+**It is a real country, but the flag really is missing.** This is unlikely — the ISO 3166-1 standard is fully covered — but first update the package version. If the flag is outdated or missing, open an issue or PR in the package repository, and temporarily use your own image in your project.
+
+**You need to show the flag at a large size.** In the package the flags are redrawn and simplified for small sizes. To show them at a large size you need flags in their original detail — use a third-party package. In most sources flags are provided without redrawing.
+
+## Accessibility
+
+If the flag carries meaning (for example, it is the only indicator of the selected country), pass a text caption:
+
+```html
+
+```
+
+If there is already adjacent text (inline, option, block), the flag is decorative — mark it `decorative` so it is hidden from screen readers and does not duplicate the caption.
+
+```html
+
+Germany
+```
+
+In short: a flag must always have a text alternative — either visible text next to it (then the flag is `decorative`), or a `label` caption for the screen reader (when there is no visible text). Only the third case is not allowed — when there is neither.
+
+## Examples
+
+### With shadow and volume
+
+A gradient imitating folds is layered over the image; the flag is rounded and casts a shadow.
+
+
+
+### Circular flag
+
+Use the 1:1 aspect ratio version.
+
+
+
+### Integer sizes
+
+Choose a flag size whose dimensions are multiples of a pixel — otherwise the image edges will be blurry.
+
+
diff --git a/packages/components/flag/flag.ru.md b/packages/components/flag/flag.ru.md
new file mode 100644
index 000000000..474e990f0
--- /dev/null
+++ b/packages/components/flag/flag.ru.md
@@ -0,0 +1,106 @@
+Компонент `kbq-flag` показывает флаг страны. Он не содержит изображений, а декорирует переданное содержимое: управляет формой и тенью, обеспечивает доступность. Поэтому флаг выглядит одинаково в продукте независимо от источника изображения.
+
+Для работы с `kbq-flag` рекомендуется пакет [country-flag-icons](https://www.npmjs.com/package/country-flag-icons):
+
+```bash
+npm install country-flag-icons
+```
+
+Особенности пакета:
+
+- **Полное покрытие ISO 3166-1**. Все страны обозначаются двухбуквенным кодом (RU, DE, FR).
+- **Флаги перерисованы под мелкий размер**. Большинство наборов используют детализированные SVG с Викисклада. В интерфейсных размерах (16—24 px) мелкие гербы, надписи и эмблемы на них становятся неразборчивыми. В country-flag-icons детали упрощены, поэтому флаги остаются узнаваемыми даже в размере иконки.
+- Активная поддержка и лицензия MIT.
+
+## Форма
+
+### Прямоугольник
+
+Это основной формат — используйте его в большинстве сценариев: инлайн с текстом, списки, селекты. Флаги с другим соотношением (например, 4:3) поддерживаются через переопределение CSS-переменной `--kbq-flag-aspect-ratio`.
+
+
+
+### Квадрат
+
+Для квадратной формы берите изображения из директории `1x1/` пакета. Не обрезайте прямоугольный флаг самостоятельно — простое кадрирование по центру может убрать значимые элементы герба или флага. В пакете уже подготовлены версии 1:1 для каждой страны, а часть флагов — например, EU и KR — перерисована заново, чтобы не терять смысл при таком формате.
+
+
+
+### Круг
+
+Используйте изображение с соотношением сторон 1:1.
+
+
+
+## Внутренняя тень: отделение от фона
+
+Флагам добавляется тонкая внутренняя тень (inset-обводка) по краю. Без нее флаг сливается с фоном:
+
+- много белого цвета: флаг сливается со светлым фоном страницы.
+- в части флагов есть черный или темный цвет: он сливается с фоном в темной теме.
+
+Поэтому цвет тени зависит от темы: в светлой теме тень темная, в темной — светлеет.
+
+
+
+## Флаг — это не язык
+
+Флаг обозначает страну или регион, а не язык. Не используйте флаг для выбора языка интерфейса: один язык бывает в нескольких странах (испанский — не только Испания), а в одной стране бывает несколько языков.
+
+
+
+## Если в пакете нет нужного флага
+
+Сначала проверьте наличие флага через `hasFlag(code)` и не показывайте пустоту:
+
+```ts
+import { hasFlag } from 'country-flag-icons';
+
+hasFlag('RU'); // true
+hasFlag('ZZ'); // false
+```
+
+Дальше — по ситуации.
+
+**Неизвестный или некорректный код.** Показывайте нейтральную заглушку (серый прямоугольник, квадрат, круг или иконку глобуса). Пустота допустима, только если это не ломает верстку.
+
+
+
+**Нужен флаг не-страны** (организация, историческая или спорная территория, субъект федерации). Пакет — сторонний продукт, ограниченный стандартом ISO 3166-1: такие флаги в него не попадают. Заведите их во внутреннем наборе проекта, чтобы способ использования `kbq-flag` не менялся.
+
+**Наднациональные флаги.** Флаг ЕС доступен как отдельная версия 1:1; для остальных (ASEAN, ООН и т. п.) создавайте в проекте собственные изображения.
+
+**Это реальная страна, но флага нет.** Случай маловероятный — стандарт ISO 3166-1 покрыт полностью, — но сначала обновите версию пакета. Если флаг устарел или отсутствует, заведите задачу или PR в репозитории пакета, а в своем проекте временно используйте собственное изображение.
+
+**Нужно отобразить флаг в большом размере.** В пакете флаги перерисованы и упрощены для мелкого размера. Для показа в крупном размере используйте сторонний пакет с оригинальной детализацией — большинство таких источников её не упрощают.
+
+## Доступность
+
+Если флаг несет смысл (например, это единственный индикатор выбранной страны) — передавайте текстовую подпись:
+
+```html
+
+```
+
+Если рядом уже есть текст (инлайн, опция, блок) — флаг декоративный, помечайте его decorative, чтобы он скрывался от экранных дикторов и не дублировал подпись.
+
+```html
+
+Германия
+```
+
+Итого: у флага должна быть текстовая альтернатива — либо видимый текст рядом (тогда флаг decorative), либо подпись label для экранного диктора (когда видимого текста нет). Недопустим только третий случай — когда нет ни того, ни другого.
+
+## Примеры
+
+### С тенью и объёмом
+
+Компонент поддерживает стилизацию через CSS. Например, можно добавить скругление, тень и градиент, имитирующий складки.
+
+
+
+### Целочисленные размеры
+
+Выбирайте размер флага так, чтобы его габариты были кратны пикселю — иначе границы изображения будут размытыми.
+
+
diff --git a/packages/components/flag/flag.scss b/packages/components/flag/flag.scss
new file mode 100644
index 000000000..f65dfbdf0
--- /dev/null
+++ b/packages/components/flag/flag.scss
@@ -0,0 +1,53 @@
+.kbq-flag {
+ // Theme tokens. The faded contrast line resolves to a dark hairline in the light theme and a
+ // light one in the dark theme, so the flag stays separated from the background in both.
+ --kbq-flag-shadow-color: var(--kbq-line-contrast-fade);
+ --kbq-flag-empty-background: var(--kbq-states-background-disabled);
+ --kbq-flag-border-radius: 0;
+ --kbq-flag-aspect-ratio: 3 / 2;
+
+ display: inline-block;
+ position: relative;
+ box-sizing: border-box;
+ // Sizing follows the surrounding text; 3:2 gives width = 1.5 × line height. Override via
+ // font-size / width / height to resize.
+ height: 1em;
+ aspect-ratio: var(--kbq-flag-aspect-ratio);
+ vertical-align: middle;
+ overflow: hidden;
+ border-radius: var(--kbq-flag-border-radius);
+
+ // Size the flag image whether it is projected directly or bound via [innerHTML]
+ // (the injected svg may sit inside a wrapper element).
+ svg,
+ img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ // Inset hairline drawn on top of the flag, gated by the shadow input.
+ &.kbq-flag_shadow-inset::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ box-shadow: inset 0 0 0 1px var(--kbq-flag-shadow-color);
+ pointer-events: none;
+ }
+
+ &.kbq-flag_square,
+ &.kbq-flag_circle {
+ --kbq-flag-aspect-ratio: 1 / 1;
+ }
+
+ &.kbq-flag_circle {
+ border-radius: 50%;
+ }
+
+ // Neutral placeholder when no flag is projected (unknown / invalid country code).
+ &.kbq-flag_empty {
+ background: var(--kbq-flag-empty-background);
+ }
+}
diff --git a/packages/components/flag/flag.spec.ts b/packages/components/flag/flag.spec.ts
new file mode 100644
index 000000000..fa624c2ba
--- /dev/null
+++ b/packages/components/flag/flag.spec.ts
@@ -0,0 +1,157 @@
+import { Component, inject, Type } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By, DomSanitizer } from '@angular/platform-browser';
+import { KbqFlag } from './flag';
+
+const createComponent = (component: Type): ComponentFixture => {
+ TestBed.configureTestingModule({ imports: [component] }).compileComponents();
+ const fixture = TestBed.createComponent(component);
+
+ fixture.autoDetectChanges();
+
+ return fixture;
+};
+
+const getFlag = (fixture: ComponentFixture): HTMLElement =>
+ fixture.debugElement.query(By.directive(KbqFlag)).nativeElement;
+
+describe(KbqFlag.name, () => {
+ it('should project the provided flag content', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {}
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.classList).toContain('kbq-flag');
+ expect(flag.querySelector('img')).toBeTruthy();
+ });
+
+ it('should render an inline svg projected via innerHTML as a direct child', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {
+ private readonly sanitizer = inject(DomSanitizer);
+ readonly svg = this.sanitizer.bypassSecurityTrustHtml('');
+ }
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.querySelector(':scope > svg')).toBeTruthy();
+ });
+
+ it('should apply the inset shadow by default', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {}
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.classList).toContain('kbq-flag_shadow-inset');
+ });
+
+ it('should not apply the inset shadow when shadow is "none"', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {}
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.classList).not.toContain('kbq-flag_shadow-inset');
+ });
+
+ it('should apply the shape modifier class', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {}
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.classList).toContain('kbq-flag_circle');
+ expect(flag.classList).not.toContain('kbq-flag_square');
+ });
+
+ it('should apply the empty placeholder class', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {}
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.classList).toContain('kbq-flag_empty');
+ });
+
+ describe('accessibility', () => {
+ it('should expose role="img" and aria-label when labelled', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {}
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.getAttribute('role')).toBe('img');
+ expect(flag.getAttribute('aria-label')).toBe('Germany');
+ expect(flag.hasAttribute('aria-hidden')).toBe(false);
+ });
+
+ it('should hide the flag from assistive tech when decorative', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {}
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.getAttribute('aria-hidden')).toBe('true');
+ expect(flag.hasAttribute('role')).toBe(false);
+ expect(flag.hasAttribute('aria-label')).toBe(false);
+ });
+
+ it('should not set role/aria when neither labelled nor decorative', () => {
+ @Component({
+ imports: [KbqFlag],
+ template: `
+
+ `
+ })
+ class TestComponent {}
+
+ const flag = getFlag(createComponent(TestComponent));
+
+ expect(flag.hasAttribute('role')).toBe(false);
+ expect(flag.hasAttribute('aria-label')).toBe(false);
+ expect(flag.hasAttribute('aria-hidden')).toBe(false);
+ });
+ });
+});
diff --git a/packages/components/flag/flag.ts b/packages/components/flag/flag.ts
new file mode 100644
index 000000000..fb8dc3462
--- /dev/null
+++ b/packages/components/flag/flag.ts
@@ -0,0 +1,47 @@
+import { booleanAttribute, ChangeDetectionStrategy, Component, input, ViewEncapsulation } from '@angular/core';
+import { KbqFlagShadow, KbqFlagShape } from './flag.types';
+
+/**
+ * Presentational wrapper for a consumer-provided country flag (inline `