Chip
oge-chip is a compact tag, filter or person token; a selectable one is a toggle button, a removable one carries a real remove button. oge-chip-list groups chips behind one tab stop and picks its WAI-ARIA shape from what the chips can do.
The role follows the capability: selectable chips are a listbox (options with aria-selected), chips that can only be removed are a layout grid (so the remove button is a real, reachable control), and static chips are a plain list. A button inside an option would be a nested interactive control, so in a listbox the ✕ is decoration and Delete / Backspace — advertised with aria-keyshortcuts — is the keyboard path.
Chips
A plain chip is static text — no role and nothing focusable. A leading icon takes SVG path data; an avatar renders the image, then the initials, and is always aria-hidden because the label is the name.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeChip } from '@oge-ui/layout';
const STAR = 'M12 3l2.6 5.6 6.1.7-4.5 4.2 1.2 6L12 16.8 6.6 19.5l1.2-6L3.3 9.3l6.1-.7z';
@Component({
selector: 'demo-root',
imports: [OgeChip],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- A plain chip is static text — no role, nothing focusable.
icon takes SVG path data; avatar renders image → initials, aria-hidden. -->
<oge-chip label="Angular" />
<oge-chip label="Featured" [icon]="star" severity="accent" />
<oge-chip label="Ada Lovelace" [avatar]="{ name: 'Ada Lovelace' }" />
<oge-chip label="Passed" severity="success" stylingMode="outlined" />
<oge-chip label="Blocked" severity="danger" size="sm" />
`,
})
export class Demo {
protected readonly star = STAR;
}Selectable chips
selectable renders a toggle <button aria-pressed>. The selected state is a tint, an accent frame and a check glyph — it never rides on colour alone, and forced colors repaint it as a Highlight fill.
remote: true · full-time: false
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChip } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeChip],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- selectable renders a toggle <button aria-pressed>; the check glyph
and the accent frame show the state, never colour alone. -->
<oge-chip label="Remote" [selectable]="true" [(selected)]="remote" />
<oge-chip label="Full-time" [selectable]="true" [(selected)]="fullTime" />
<p>remote: {{ remote() }} · full-time: {{ fullTime() }}</p>
`,
})
export class Demo {
protected readonly remote = signal(true);
protected readonly fullTime = signal(false);
}Removable chips
removable adds a separate, real remove button named “Remove {label}” from the messages catalog. The chip moves no data — drop it in (removed).
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChip } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeChip],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- removable adds a separate, real remove button ("Remove Design").
The chip moves no data — drop it in (removed). -->
@for (tag of tags(); track tag) {
<oge-chip [label]="tag" [removable]="true" (removed)="drop(tag)" />
}
`,
})
export class Demo {
protected readonly tags = signal(['Design', 'Research', 'Writing']);
protected drop(tag: string): void {
this.tags.update((tags) => tags.filter((t) => t !== tag));
}
}Chip list — single selection
Selectable chips form an APG listbox with one tab stop: Left / Right (mirrored in RTL), Up / Down, Home and End move; Space or Enter toggles. Disabled chips are skipped. A second press clears a single selection — chips are filters.
size: m
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChipList } from '@oge-ui/layout';
import type { OgeChipItem, OgeChipKey } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeChipList],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Selectable → an APG listbox: one tab stop, arrows / Home / End move,
Space or Enter toggles. A second press clears a single selection. -->
<oge-chip-list
[items]="sizes"
selectionMode="single"
[(selectedKeys)]="size"
ariaLabel="Size"
/>
`,
})
export class Demo {
protected readonly sizes: OgeChipItem[] = [
{ key: 's', label: 'Small' },
{ key: 'm', label: 'Medium' },
{ key: 'l', label: 'Large' },
{ key: 'xl', label: 'X-Large', disabled: true },
];
protected readonly size = signal<readonly OgeChipKey[]>(['m']);
}Chip list — multiple selection (filters)
multiple adds aria-multiselectable; selectionChanged carries the previous and the next keys plus the toggled chip.
active: open · last: —
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChipList } from '@oge-ui/layout';
import type { OgeChipItem, OgeChipKey, OgeChipSelectionChangedEvent } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeChipList],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- multiple → aria-multiselectable; selectionChanged carries the
previous and the next keys plus the toggled chip. -->
<oge-chip-list
[items]="filters"
selectionMode="multiple"
[(selectedKeys)]="active"
ariaLabel="Filters"
(selectionChanged)="last.set($event.item.label)"
/>
<p>active: {{ active().join(', ') || 'none' }} · last: {{ last() }}</p>
`,
})
export class Demo {
protected readonly filters: OgeChipItem[] = [
{ key: 'open', label: 'Open' },
{ key: 'mine', label: 'Assigned to me' },
{ key: 'bug', label: 'Bug', severity: 'danger' },
{ key: 'docs', label: 'Docs', severity: 'accent' },
];
protected readonly active = signal<readonly OgeChipKey[]>(['open']);
protected readonly last = signal('—');
}Removable list (APG grid)
Chips that can only be removed form an APG layout grid: each chip is a row with a label cell and a cell holding a real remove button. Arrows walk the cells, Delete removes and focuses the chip that took its place, Backspace the previous one. A cancelable itemRemoving precedes itemRemoved.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChipList } from '@oge-ui/layout';
import type { OgeChipItem, OgeChipItemRemovedEvent } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeChipList],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Not selectable but removable → an APG layout grid: each chip is a
row with a label cell and a cell holding a real remove button. Arrows walk
the cells, Delete / Backspace remove, focus lands on the neighbour. -->
<oge-chip-list
[items]="people()"
[removable]="true"
ariaLabel="Recipients"
(itemRemoved)="remove($event)"
/>
`,
})
export class Demo {
protected readonly people = signal<OgeChipItem[]>([
{ key: 1, label: 'Ada Lovelace', avatar: { name: 'Ada Lovelace' } },
{ key: 2, label: 'Grace Hopper', avatar: { name: 'Grace Hopper' } },
{ key: 3, label: 'Alan Turing', avatar: { name: 'Alan Turing' } },
{ key: 4, label: 'Katherine Johnson', avatar: { name: 'Katherine Johnson' } },
]);
protected remove(event: OgeChipItemRemovedEvent): void {
this.people.update((people) => people.filter((p) => p.key !== event.item.key));
}
}Custom chip template
[ogeChipTemplate] replaces the label only: the list keeps the role, the focus, the check glyph and the remove affordance, so keep the template non-interactive.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChipList, OgeChipTemplate } from '@oge-ui/layout';
import type { OgeChipItem, OgeChipKey } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeChipList, OgeChipTemplate],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- [ogeChipTemplate] replaces the label only: the list keeps the role,
focus, check glyph and remove affordance. Keep it non-interactive. -->
<oge-chip-list
[items]="languages"
selectionMode="multiple"
[(selectedKeys)]="picked"
ariaLabel="Languages"
>
<ng-template ogeChipTemplate let-item let-selected="selected">
<strong>{{ item.label }}</strong>
<span class="opacity-70">{{ counts[item.key] }}</span>
</ng-template>
</oge-chip-list>
`,
})
export class Demo {
protected readonly languages: OgeChipItem[] = [
{ key: 'ts', label: 'TypeScript' },
{ key: 'rs', label: 'Rust' },
{ key: 'go', label: 'Go' },
];
protected readonly counts: Record<string, number> = { ts: 128, rs: 42, go: 37 };
protected readonly picked = signal<readonly OgeChipKey[]>(['ts']);
}