Chips

Chips

Small blocks for contacts, tags, and filters.

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.

TypeElementWhy
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 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.
  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.