Angular Chip Input Component

Chip Input

Release candidate (RC)

This component is in the Release Candidate phase and its API is considered stable. Minor adjustments may still occur before the final release.

Angular Chip Input component lets users enter multiple values as chips in one field, with keyboard entry, removal, selection and Angular forms support.

Available in Other JavaScript Frameworks

CoreUI Angular Chip Input Component is also available for Bootstrap, React, and Vue. Explore framework-specific implementations below:

Added in v5.7.36

Overview

The CoreUI Angular Chip Input component lets users enter multiple values as chips inside a single field. Use it as a tag input, multi-value selector, or token field for skills, categories, email recipients, and more. It supports keyboard-friendly entry, chip removal, and optional selection.

  • Type values and press Enter or the separator to create chips; pasted text is split the same way.
  • Chips can be removable and selectable.
  • The text field stays inline and grows as you type.
  • The value is a string[]: bind [(value)], or use formControlName, ngModel or [formField].

When to use Chip Input

Use the Angular Chip Input component when you need:

  • A tag input or token field for free-form multi-value entry
  • An email or recipient input where users add multiple addresses
  • A skills or category selector in forms
  • A multi-value field that works with Angular forms

Basic example

Use c-chip-input to render a multi-value field with predefined chips and an inline text field. label renders an inline label inside the container; the id goes to the text field, so the label, or an external <label for>, names it.

import { Component } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-basic',
  templateUrl: './chip-input-basic.component.html',
  imports: [ChipInputComponent]
})
export class ChipInputBasicComponent {}
<c-chip-input
  id="skillsInputBasic"
  label="Skills:"
  placeholder="Add a skill..."
  [value]="['JavaScript', 'TypeScript', 'Accessibility']"
/>

Variants

Use chipClassName to give chips contextual classes that represent categories, status, or priority.

import { Component } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-variants',
  templateUrl: './chip-input-variants.component.html',
  imports: [ChipInputComponent]
})
export class ChipInputVariantsComponent {
  readonly variants: Record<string, string> = {
    approved: 'chip-success',
    blocking: 'chip-danger',
    feature: 'chip-primary',
    'needs review': 'chip-warning'
  };

  variant = (value: string) => this.variants[value.trim().toLowerCase()] ?? '';
}
<c-chip-input
  aria-label="Labels"
  placeholder="Add label..."
  [chipClassName]="variant"
  [value]="['Feature', 'Approved', 'Needs review', 'Blocking']"
/>

In the example below, the chip color is assigned from the chip text by a chipClassName function, with a fallback class for any other value.

import { Component } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-class-name',
  templateUrl: './chip-input-class-name.component.html',
  imports: [ChipInputComponent]
})
export class ChipInputClassNameComponent {
  variant = (value: string) => {
    const variants: Record<string, string> = {
      approved: 'chip-success',
      blocking: 'chip-danger',
      feature: 'chip-primary'
    };
    return variants[value.trim().toLowerCase()] ?? 'chip-secondary';
  };
}
<c-chip-input
  aria-label="Labels"
  placeholder="Type and press Enter"
  [chipClassName]="variant"
  [value]="['Feature', 'Blocking', 'Approved']"
/>

Sizes

Use size="sm" and size="lg" to match surrounding form controls. The default size has no size modifier.

import { Component } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-sizes',
  templateUrl: './chip-input-sizes.component.html',
  imports: [ChipInputComponent]
})
export class ChipInputSizesComponent {}
<div class="mb-3">
  <c-chip-input id="skillsInputSm" label="Small" size="sm" placeholder="Add small tag..." [value]="['HTML']" />
</div>
<div class="mb-3">
  <c-chip-input id="skillsInputMd" label="Default" placeholder="Add default tag..." [value]="['JavaScript']" />
</div>
<div>
  <c-chip-input id="skillsInputLg" label="Large" size="lg" placeholder="Add large tag..." [value]="['TypeScript']" />
</div>

Empty state

Start with just the text field and let users add chips as they type.

import { Component } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-empty-state',
  templateUrl: './chip-input-empty-state.component.html',
  imports: [ChipInputComponent]
})
export class ChipInputEmptyStateComponent {}
<c-chip-input aria-label="Tags" placeholder="Type and press Enter" />

With label

Use a standard form label for accessibility. Without a visible label, give the component an aria-label. aria-label, aria-labelledby and aria-describedby set on c-chip-input move to its text field, so a hint or a c-form-feedback can describe it; bind them as [aria-describedby], not [attr.aria-describedby], which does not reach the text field.

import { Component } from '@angular/core';
import { ChipInputComponent, FormLabelDirective, FormTextDirective } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-with-label',
  templateUrl: './chip-input-with-label.component.html',
  imports: [ChipInputComponent, FormLabelDirective, FormTextDirective]
})
export class ChipInputWithLabelComponent {}
<div class="mb-3">
  <label cLabel for="techStackInput">Tech stack</label>
  <c-chip-input
    id="techStackInput"
    aria-describedby="techStackHint"
    placeholder="Add package..."
    [value]="['Angular', 'Vite']"
  />
  <div id="techStackHint" cFormText>Press Enter or comma to add a value.</div>
</div>

Disabled

Set disabled to make the field and the chips non-interactive; the remove buttons are hidden.

import { Component } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-disabled',
  templateUrl: './chip-input-disabled.component.html',
  imports: [ChipInputComponent]
})
export class ChipInputDisabledComponent {}
<c-chip-input aria-label="Locked tags" disabled placeholder="Input disabled" [value]="['Read only', 'Locked']" />

Readonly

Use readonly when chips should stay visible, but values must not change: chips cannot be added, removed or toggled.

import { Component } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-readonly',
  templateUrl: './chip-input-readonly.component.html',
  imports: [ChipInputComponent]
})
export class ChipInputReadonlyComponent {}
<c-chip-input aria-label="Languages" readonly placeholder="Read-only values" [value]="['JavaScript', 'TypeScript']" />

Selectable chips

Enable selection for the chips managed by Chip Input and bind [(selected)] to the selected values. Focusing the text field clears the selection, so Tab from the chips into the field clears it too.

import { Component, signal } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-selectable',
  templateUrl: './chip-input-selectable.component.html',
  imports: [ChipInputComponent]
})
export class ChipInputSelectableComponent {
  readonly selected = signal<string[]>([]);
}
<c-chip-input
  aria-label="Teams"
  selectable
  placeholder="Select chips"
  [value]="['Design', 'Backend', 'QA', 'DevOps']"
  [(selected)]="selected"
/>
<p class="small text-body-secondary mt-2 mb-0">Selected: {{ selected().join(', ') || 'None' }}</p>

Forms

Chip Input works with reactive forms, template-driven forms and signal forms without a value accessor; the form value is a string[]. An error is shown once the control has been touched, that is after focus leaves the component, and validationState set by the page wins over the form. Place the message in a c-form-feedback right after the component and point aria-describedby at it. The field frame does not change color: the state is carried by the message and by aria-invalid on the text field.

Reactive

import { JsonPipe } from '@angular/common';
import { Component } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ChipInputComponent, FormFeedbackComponent, FormLabelDirective } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-reactive',
  templateUrl: './chip-input-reactive.component.html',
  imports: [ChipInputComponent, FormFeedbackComponent, FormLabelDirective, JsonPipe, ReactiveFormsModule]
})
export class ChipInputReactiveComponent {
  readonly form = new FormGroup({
    tags: new FormControl<string[]>(['Angular'], Validators.required)
  });

  readonly formValue = toSignal(this.form.valueChanges, { initialValue: this.form.value });
}
<form [formGroup]="form">
  <label cLabel for="tagsReactive">Tags</label>
  <c-chip-input
    id="tagsReactive"
    aria-describedby="tagsReactiveFeedback"
    formControlName="tags"
    placeholder="Add a tag..."
  />
  <c-form-feedback id="tagsReactiveFeedback" [valid]="false">Add at least one tag.</c-form-feedback>
</form>
<p class="small text-body-secondary mt-2 mb-0">Form value: {{ formValue() | json }}</p>

Template driven

Validator directives next to ngModel apply to the chips: required rejects an empty list and minlength sets the smallest number of chips.

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ChipInputComponent, FormFeedbackComponent, FormLabelDirective } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-template-driven',
  templateUrl: './chip-input-template-driven.component.html',
  imports: [ChipInputComponent, FormFeedbackComponent, FormLabelDirective, FormsModule]
})
export class ChipInputTemplateDrivenComponent {
  tags: string[] = ['Angular'];
}
<label cLabel for="tagsTemplate">Tags</label>
<c-chip-input
  id="tagsTemplate"
  aria-describedby="tagsTemplateFeedback"
  placeholder="Add a tag..."
  required
  [(ngModel)]="tags"
/>
<c-form-feedback id="tagsTemplateFeedback" [valid]="false">Add at least one tag.</c-form-feedback>
<p class="small text-body-secondary mt-2 mb-0">Tags: {{ tags.join(', ') || 'none' }}</p>

Signal forms

required() in signal forms does not treat an empty array as empty, so add minLength(path, 1) to reject a field with no chips; required() still adds aria-required to the text field.

import { Component, signal } from '@angular/core';
import { form, FormField, minLength, required } from '@angular/forms/signals';
import { ChipInputComponent, FormFeedbackComponent, FormLabelDirective } from '@coreui/angular';

@Component({
  selector: 'docs-chip-input-signal-forms',
  templateUrl: './chip-input-signal-forms.component.html',
  imports: [ChipInputComponent, FormFeedbackComponent, FormField, FormLabelDirective]
})
export class ChipInputSignalFormsComponent {
  readonly model = signal({ tags: ['Angular'] });

  readonly tagsForm = form(this.model, (path) => {
    required(path.tags);
    minLength(path.tags, 1, { message: 'Add at least one tag.' });
  });
}
<label cLabel for="tagsSignal">Tags</label>
<c-chip-input
  id="tagsSignal"
  aria-describedby="tagsSignalFeedback"
  placeholder="Add a tag..."
  [formField]="tagsForm.tags"
/>
<c-form-feedback id="tagsSignalFeedback" [valid]="false">
  @for (error of tagsForm.tags().errors(); track error) {
    {{ error.message }}
  }
</c-form-feedback>
<p class="small text-body-secondary mt-2 mb-0">Tags: {{ model().tags.join(', ') || 'none' }}</p>

Keyboard behavior

When the text field is focused

KeyAction
EnterCreate chips from the text
, (or separator)Create a chip from the text before it
Backspace / DeleteWhen the field is empty, move focus to the last chip
ArrowLeftWhen the caret is at the start, move focus to the last chip (ArrowRight in right-to-left layouts)
EscapeClear the text and leave the field

When a chip is focused

KeyAction
Enter / SpaceToggle selection (selectable chips)
Backspace / DeleteRemove the chip (removable chips)
ArrowLeft / ArrowRightMove to the previous / next chip; ArrowRight on the last chip returns to the field (mirrored in right-to-left layouts)
Home / EndMove to the first / last chip
Any characterMove focus to the text field and type it

Accessibility

  • Name the text field with label, an external <label for> pointing at its id, or aria-label, which moves to the field like aria-describedby.
  • Focusable chips and the text field are two tab stops: the last chip and the field. Shift+Tab from the field reaches the last chip; the arrow keys move between chips.
  • Remove buttons are named after their chip, e.g. “Remove Angular”; ariaRemoveLabel sets the prefix.
  • Selectable chips are toggle buttons with aria-pressed. In a readonly control they are marked aria-disabled.
  • When the focused chip is removed, focus moves to a neighboring chip, or to the text field when no chip is left.
  • Added and removed chips are announced with their value through a page live region, one message per update: “Angular added”, “Angular removed”. Change the wording with ariaAddedAnnouncement and ariaRemovedAnnouncement.
  • With a form, aria-required and aria-invalid are set on the text field.

Customizing

CSS variables

Angular chip inputs use local CSS variables on .chip-input for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.

scss
--cui-chip-input-min-height: #{$chip-input-min-height};
--cui-chip-input-padding-y: #{$chip-input-padding-y};
--cui-chip-input-padding-x: #{$chip-input-padding-x};
--cui-chip-input-font-size: #{$chip-input-font-size};
--cui-chip-input-bg: #{$chip-input-bg};
--cui-chip-input-color: #{$chip-input-color};
--cui-chip-input-border-width: #{$chip-input-border-width};
--cui-chip-input-border-color: #{$chip-input-border-color};
--cui-chip-input-border-radius: #{$chip-input-border-radius};
--cui-chip-input-gap: #{$chip-input-gap};
--cui-chip-input-transition: #{$chip-input-transition};

Sass variables

scss
$chip-input-min-height:        $input-height !default;
$chip-input-padding-y:         .25rem !default;
$chip-input-padding-x:         .75rem !default;
$chip-input-font-size:         $input-font-size !default;
$chip-input-bg:                var(--cui-body-bg) !default;
$chip-input-color:             var(--cui-body-color) !default;
$chip-input-border-width:      var(--cui-border-width) !default;
$chip-input-border-color:      var(--cui-border-color) !default;
$chip-input-border-radius:     var(--cui-border-radius) !default;
$chip-input-gap:               .375rem !default;
$chip-input-transition:        $input-transition !default;

$chip-input-min-height-sm:     $input-height-sm !default;
$chip-input-padding-y-sm:      .125rem !default;
$chip-input-padding-x-sm:      .5rem !default;
$chip-input-font-size-sm:      $input-font-size-sm !default;
$chip-input-border-radius-sm:  var(--cui-border-radius-sm) !default;
$chip-input-gap-sm:            .125rem !default;

$chip-input-min-height-lg:     $input-height-lg !default;
$chip-input-padding-y-lg:      .375rem !default;
$chip-input-padding-x-lg:      1rem !default;
$chip-input-font-size-lg:      $input-font-size-lg !default;
$chip-input-border-radius-lg:  var(--cui-border-radius-lg) !default;
$chip-input-gap-lg:            .5rem !default;

API

Chip Input Module

ts
import { NgModule } from '@angular/core';
import { ChipInputModule } from '@coreui/angular';

@NgModule({
  imports: [ChipInputModule]
})
export class CustomAppModule {}

Chip Input Standalone

ts
import { Component, signal } from '@angular/core';
import { ChipInputComponent } from '@coreui/angular';

@Component({
  template: `<c-chip-input aria-label="Tags" placeholder="Add a tag..." [(value)]="tags" />`,
  imports: [ChipInputComponent]
})
export class CustomAppComponent {
  readonly tags = signal(['Angular']);
}

c-chip-input

component

jsx
import { ChipInputComponent } from '@coreui/angular'

Props

PropertyDefaultType
aria-describedbyundefinedstring

Id of the element that describes the text field, e.g. a hint or a c-form-feedback.

aria-labelundefinedstring

Accessible name of the text field when there is no visible label.

aria-labelledbyundefinedstring

Id of the element that names the text field.

ariaAddedAnnouncement'added'string

Wording announced to screen readers after a chip is added; the chip value is prepended.

ariaRemovedAnnouncement'removed'string

Wording announced to screen readers after a chip is removed; the chip value is prepended.

ariaRemoveLabel'Remove'string

Label of the remove buttons; each chip appends its value, e.g. 'Remove Angular'.

chipClassNameundefinedstring, object

Extra class for every chip, or a function that returns the class for a chip value.

createOnBlurtrueboolean

Turns the text left in the field into a chip when focus leaves the component.

disabledfalseboolean

Disables the field and every chip.

filterfalseboolean

Makes the chips filter chips: selectable, with a check icon while selected.

idgeneratedstring

Id of the text field, the target of an external <label for>.

invalidfalseboolean

Set by the form when the value is invalid; shown once the control is touched.

labelundefinedstring

Renders an inline label inside the Angular Chip Input component container.

maxChipsnullnumber, null

Maximum number of chips; null for no limit.

placeholder''string

Placeholder of the text field.

readonlyfalseboolean

Keeps the chips visible but blocks adding, removing and selecting them.

removabletrueboolean

Shows remove buttons on the chips.

requiredfalseboolean

Set by the form when a value is required; adds aria-required to the text field.

selectablefalseboolean

Makes the chips selectable.

selected[]string[]

Values of the selected chips, two-way bindable with [(selected)].

selectionMode'multiple''single', 'multiple'

Sets how many chips can be selected at once.

separator','string, null

Character that splits typed or pasted text into chips; null turns splitting off.

sizeundefined'sm', 'lg'

Size of the component.

touchedfalseboolean

Set by the form once the control has been touched.

validationStateundefined'valid', 'invalid'

Validation state set by the page; it wins over the state reported by the form.

value[]string[]

Chip values, two-way bindable with [(value)] or bound by a form.

Events

Event name
add

Emits the value of a chip added by the user.

  • $event string
inputChange

Emits the text of the field when it changes.

  • $event string
remove

Emits the value of a chip removed by the user.

  • $event string
selectedChange

Emitted when selected changes.

  • $event string[]
touch

Emits when focus leaves the component.

  • $event void
valueChange

Emitted when value changes.

  • $event string[]