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. |
<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="".
<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.
<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. |