# Chips

> Small blocks for contacts, tags, and filters.

## Introduction

A chip is a `.chip`, and the element says which kind it is — the four Material 3 chip types, plus a non-interactive display chip, across three root elements. Add `outlined` for a bordered style. Static chips are CSS. The JavaScript plugin lives on a `.chips` wrapper.

| Type | Element | Why |
| --- | --- | --- |
| Display | `<span class="chip">` | Presents information. Not a control, not in the tab order. |
| Assist, suggestion | `<button type="button" class="chip">` | One action on press. |
| Filter | `<input type="checkbox" class="chip-input">` + `<label class="chip">` | Multi-select and toggleable, and it carries a value into the form. No JavaScript. |
| Input | `<span class="chip">` + a nested `<button class="close">` | The chip is a token; the delete button is the control. |

Information Jane Doe Add to calendar  Flights Tag

```
<span class="chip outlined">Information</span>

<span class="chip">
  <img src="photo.jpg" alt=""> Jane Doe
</span>

<button type="button" class="chip">
  <span class="material-symbols" aria-hidden="true">event</span>
  Add to calendar
</button>

<input type="checkbox" class="chip-input" id="filter-flights">
<label class="chip" for="filter-flights">
  <span class="material-symbols" aria-hidden="true">check</span>
  Flights
</label>

<span class="chip">
  Tag
  <button type="button" class="close" aria-label="Remove Tag">
    <span class="material-symbols" aria-hidden="true">close</span>
  </button>
</span>
```

The delete button needs its own `aria-label` naming the chip it removes, because its only content is an icon. The icon is `aria-hidden` in every chip: the ligature is real text and is otherwise read out alongside the label.

A filter chip's selected state is `:checked` on its input, so it needs no script. Everywhere else the selected look is the `selected` class (`active` is the pre-0.8.0 name and still works).

Clicking `.close` removes the chip only when it sits inside a `.chips` container. Importing the bundle runs `Chips.Init()` on `DOMContentLoaded`, which wires that click. A lone `.chip` does not remove itself.

Before 0.8.0 every chip was a `<div class="chip">` and the delete affordance was an `<i class="close">` — focusable via `tabindex` but with no role and no name. That markup still renders; it is no longer correct and is not documented.

## Contacts

Put an image inside the chip. The name next to it is the accessible name already, so the image is decorative — `alt=""`.

Jane Doe

```
<span class="chip">
  <img src="photo.jpg" alt="">
  Jane Doe
</span>
```

## Tags

Put a `button.close` inside the chip. Give it `type="button"` so it cannot submit a surrounding form, and an `aria-label` naming what it removes.

Tag

```
<span class="chip">
  Tag
  <button type="button" class="close" aria-label="Remove Tag">
    <span class="material-symbols" aria-hidden="true">close</span>
  </button>
</span>
```

## Javascript Plugin

The plugin turns a `.chips` container into an editable tag field. Type a value and press Enter to add a chip. Delete with the chip's delete button, or select a chip and press Backspace or Delete. Selecting a chip marks it `selected` and moves focus to its delete button.

`allowUserInput` defaults to `false`. Without it there is no text field and rendered chips have no delete button. Pass `allowUserInput: true` for the interactive field. `AutoInit()` starts every `.chips` except `no-autoinit`, but it uses the defaults, so those wrappers stay display-only until you call `init` with options.

Empty field — type a tag and press Enter:

Initial tags from the `data` option:

Placeholders when the field is empty and after the first tag:

Autocomplete suggestions while typing:

```
<div class="chips"></div>
<div class="chips chips-initial"></div>
<div class="chips chips-placeholder"></div>
<div class="chips chips-autocomplete"></div>
<!-- Optional: provide your own input -->
<div class="chips">
  <input class="custom-class">
</div>
```

The classes `chips-initial`, `chips-placeholder`, and `chips-autocomplete` are only hooks for your selectors. They have no styles of their own.

## Initialization

The IIFE bundle exposes `Expressive.Chips`. Call `init` with `allowUserInput: true` (and any other options) for an editable field. Re-init after adding a container dynamically.

```
document.addEventListener('DOMContentLoaded', function() {
  const elems = document.querySelectorAll('.chips');
  const instances = Expressive.Chips.init(elems, {
    allowUserInput: true,
    placeholder: 'Enter a tag',
    secondaryPlaceholder: '+Tag',
    autocompleteOptions: {
      data: [
        { id: 12, text: 'Apple' },
        { id: 13, text: 'Microsoft' },
        { id: 42, text: 'Google', image: 'https://picsum.photos/id/64/250/250' }
      ]
    }
  });
});
```

Chip data object. `id` is required; a chip without an id is not rendered.

```
const chip = {
  id: '4711',
  text: 'Title',
  image: ''
};
```

## Options

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | Array | `[]` | Initial chips. Each item is a chip data object. |
| `placeholder` | String | `''` | Placeholder when there are no chips. Requires `allowUserInput`. |
| `secondaryPlaceholder` | String | `''` | Placeholder after at least one chip exists. |
| `closeIconClass` | String | `'material-symbols'` | Class on the icon inside the delete button. |
| `allowUserInput` | Boolean | `false` | If true, render a text field and a delete button per chip, so the user can add and remove chips. |
| `i18n` | Object | `{ remove: 'Remove' }` | Strings the component generates. `remove` prefixes the delete button's accessible name, giving "Remove Apple". |
| `autocompleteOptions` | Object | `{}` | Options passed to Autocomplete on the input. A non-empty object enables autocomplete. |
| `autocompleteOnly` | Boolean | `false` | If true, Enter will not add a value that is not in the autocomplete list. |
| `limit` | Number | `Infinity` | Maximum number of chips. |
| `onChipAdd` | Function | `null` | Called after a chip is added. Receives the container and the chip element. |
| `onChipSelect` | Function | `null` | Called when a chip is selected. Receives the container and the chip element. |
| `onChipDelete` | Function | `null` | Called after a chip is deleted. Receives the container and the chip element. |

## Methods

> All methods are called on the plugin instance. You can get the instance like this:

```
const instance = Expressive.Chips.getInstance(elem);
```

### .addChip();

Add a chip. Ignored if `id` is missing, already present, or the limit is reached.

```
instance.addChip({
  id: 1337,
  text: 'John Doe',
  image: ''
});
```

### .deleteChip();

Delete the chip at this index.

```
instance.deleteChip(3);
```

### .selectChip();

Focus the chip at this index.

```
instance.selectChip(2);
```

### .getData();

The current chips as an array of chip data objects.

```
instance.getData();
```

### .destroy();

Destroy the plugin instance, remove rendered chips, and tear down its event handlers.

```
instance.destroy();
```

## Properties

| Name | Type | Description |
| --- | --- | --- |
| `el` | Element | The container the plugin was initialized with. |
| `options` | Object | The options the instance was initialized with. |
| `chipsData` | Array | The current chip data. |
| `hasAutocomplete` | Boolean | Whether autocomplete is enabled. |
| `autocomplete` | Autocomplete | The Autocomplete instance, if any. |
