Skip to content

Selection

Chips

Compact elements for input, filters, suggestions and actions: assist, filter, input and suggestion chips.

Examples

Assist and suggestion chips

Actions related to the content, flat or elevated. A chip set is one tab stop: the arrow keys move between its chips.

The live demo runs with JavaScript. Its code is below.

React HTML
import { AssistChip, ChipSet, Icon, SuggestionChip } from '@kyralabs/m3exp-web/react';

export default function Assist() {
  return (
    <ChipSet aria-label="Trip actions">
      <AssistChip leadingIcon={<Icon name="event" />}>Add to calendar</AssistChip>
      <AssistChip variant="elevated" leadingIcon={<Icon name="directions" />}>
        Directions
      </AssistChip>
      <AssistChip isDisabled leadingIcon={<Icon name="share" />}>
        Share
      </AssistChip>
      <SuggestionChip>Window seat</SuggestionChip>
      <SuggestionChip variant="elevated" icon={<Icon name="lightbulb" />}>
        Pack an umbrella
      </SuggestionChip>
    </ChipSet>
  );
}
<m3e-chip-set label="Trip actions">
  <button type="button" class="m3e-assist-chip">
    <span class="m3e-icon" aria-hidden="true">event</span>
    <span class="m3e-assist-chip__label">Add to calendar</span>
  </button>
  <button type="button" class="m3e-assist-chip m3e-assist-chip--elevated">
    <span class="m3e-icon" aria-hidden="true">directions</span>
    <span class="m3e-assist-chip__label">Directions</span>
  </button>
  <button type="button" class="m3e-assist-chip" disabled>
    <span class="m3e-icon" aria-hidden="true">share</span>
    <span class="m3e-assist-chip__label">Share</span>
  </button>
  <button type="button" class="m3e-suggestion-chip">
    <span class="m3e-suggestion-chip__label">Window seat</span>
  </button>
  <button type="button" class="m3e-suggestion-chip m3e-suggestion-chip--elevated">
    <span class="m3e-icon" aria-hidden="true">lightbulb</span>
    <span class="m3e-suggestion-chip__label">Pack an umbrella</span>
  </button>
</m3e-chip-set>

Run the HTML version (custom elements, no framework).

Filter chips

Toggle filters: native checkboxes in the HTML, so they work in forms without JavaScript.

The live demo runs with JavaScript. Its code is below.

React HTML
import { ChipSet, FilterChip } from '@kyralabs/m3exp-web/react';

const FILTERS = ['Vegetarian', 'Gluten-free', 'Spicy', 'Under 30 minutes'];

export default function Filter() {
  return (
    <ChipSet aria-label="Recipe filters">
      {FILTERS.map((filter, i) => (
        <FilterChip key={filter} defaultSelected={i === 0} variant={i === 3 ? 'elevated' : 'flat'}>
          {filter}
        </FilterChip>
      ))}
    </ChipSet>
  );
}
<m3e-chip-set label="Recipe filters">
  <label class="m3e-filter-chip">
    <input class="m3e-filter-chip__input" type="checkbox" checked />
    <span class="m3e-filter-chip__leading" aria-hidden="true">
      <span class="m3e-icon m3e-filter-chip__check" data-fill>check</span>
    </span>
    <span class="m3e-filter-chip__label">Vegetarian</span>
  </label>
  <label class="m3e-filter-chip">
    <input class="m3e-filter-chip__input" type="checkbox" />
    <span class="m3e-filter-chip__leading" aria-hidden="true">
      <span class="m3e-icon m3e-filter-chip__check" data-fill>check</span>
    </span>
    <span class="m3e-filter-chip__label">Gluten-free</span>
  </label>
  <label class="m3e-filter-chip">
    <input class="m3e-filter-chip__input" type="checkbox" />
    <span class="m3e-filter-chip__leading" aria-hidden="true">
      <span class="m3e-icon m3e-filter-chip__check" data-fill>check</span>
    </span>
    <span class="m3e-filter-chip__label">Spicy</span>
  </label>
  <label class="m3e-filter-chip m3e-filter-chip--elevated">
    <input class="m3e-filter-chip__input" type="checkbox" />
    <span class="m3e-filter-chip__leading" aria-hidden="true">
      <span class="m3e-icon m3e-filter-chip__check" data-fill>check</span>
    </span>
    <span class="m3e-filter-chip__label">Under 30 minutes</span>
  </label>
</m3e-chip-set>

Run the HTML version (custom elements, no framework).

Input chips

Entities someone entered, removable with the close button, Backspace or Delete. The set is a grid: one row per chip.

The live demo runs with JavaScript. Its code is below.

React HTML
import { useState } from 'react';
import { InputChip, InputChipGroup } from '@kyralabs/m3exp-web/react';

const PEOPLE = [
  { id: 'ada', name: 'Ada Lovelace' },
  { id: 'grace', name: 'Grace Hopper' },
  { id: 'alan', name: 'Alan Turing' },
];

export default function Input() {
  const [people, setPeople] = useState(PEOPLE);
  return (
    <InputChipGroup
      aria-label="Recipients"
      onRemove={(keys) => {
        setPeople((list) => list.filter((p) => !keys.has(p.id)));
      }}
    >
      {people.map((person) => (
        <InputChip
          key={person.id}
          id={person.id}
          avatar={<span aria-hidden="true">{person.name.charAt(0)}</span>}
        >
          {person.name}
        </InputChip>
      ))}
    </InputChipGroup>
  );
}
<m3e-chip-set label="Recipients">
  <div class="m3e-input-chip" role="row" tabindex="-1">
    <div class="m3e-input-chip__cell" role="gridcell">
      <span class="m3e-input-chip__avatar"><span aria-hidden="true">A</span></span>
      <span class="m3e-input-chip__label">Ada Lovelace</span>
      <button class="m3e-input-chip__remove" type="button" tabindex="-1" aria-label="Remove">
        <span class="m3e-icon" aria-hidden="true">close</span>
      </button>
    </div>
  </div>
  <div class="m3e-input-chip" role="row" tabindex="-1">
    <div class="m3e-input-chip__cell" role="gridcell">
      <span class="m3e-input-chip__avatar"><span aria-hidden="true">G</span></span>
      <span class="m3e-input-chip__label">Grace Hopper</span>
      <button class="m3e-input-chip__remove" type="button" tabindex="-1" aria-label="Remove">
        <span class="m3e-icon" aria-hidden="true">close</span>
      </button>
    </div>
  </div>
  <div class="m3e-input-chip" role="row" tabindex="-1">
    <div class="m3e-input-chip__cell" role="gridcell">
      <span class="m3e-input-chip__avatar"><span aria-hidden="true">A</span></span>
      <span class="m3e-input-chip__label">Alan Turing</span>
      <button class="m3e-input-chip__remove" type="button" tabindex="-1" aria-label="Remove">
        <span class="m3e-icon" aria-hidden="true">close</span>
      </button>
    </div>
  </div>
</m3e-chip-set>

Run the HTML version (custom elements, no framework).

API

React

<AssistChip>

Assist chip (COMPONENT_SPEC §18): React Aria behavior on the m3e-css .m3e-assist-chip classes. A smart or contextual action, e.g. "Add to calendar".

Prop Type Default Description
variant "flat" | "elevated" flat Default flat.
leadingIcon ReactNode Icon before the label, e.g. <Icon name="event" />.
trailingIcon ReactNode Icon after the label.
children ReactNode The label.
href string Renders a link (<a>) instead of a <button>.
focusableWhenDisabled boolean Keeps a disabled chip in the tab order (aria-disabled instead of disabled).
className string
target AnchorHTMLAttributes<HTMLAnchorElement>['target']
rel string
isDisabled boolean
onPress (e: PressEvent) => void
onPressStart (e: PressEvent) => void
onPressEnd (e: PressEvent) => void

Also accepts HTML attributes not listed here.

<FilterChip>

Filter chip (COMPONENT_SPEC §18; Compose FilterChip / ElevatedFilterChip): React Aria useCheckbox / react-stately useToggleState on the m3e-css .m3e-filter-chip contract, a label around a native checkbox (Compose Role.Checkbox; owner decision M8 #10). Selected, it shows the leading check (Material Symbols check), which slides in through m3e-css. The ref is the native input. With isExpressive, the corners spring between the ChipsTokens shapes.

Prop Type Default Description
children ReactNode The label.
variant "flat" | "elevated" flat Default flat.
isExpressive boolean The expressive shapes overload (owner decision M8 #9): ChipsTokens corners morphing on fast spatial (rest 12dp, selected full, pressed 8dp), the tonal unselected leading icon and 4dp spacing next to icons. Default false: the baseline chip (fixed 8dp corners).
leadingIcon ReactNode Icon shown before the label while unselected (FilterChipWithLeadingIconSample); selected, the check replaces it. Without it, the check expands in when the chip is selected.
trailingIcon ReactNode Icon after the label, e.g. <Icon name="arrow_drop_down" />.
isSelected boolean Controlled selected state.
className string
defaultSelected boolean
isDisabled boolean
isReadOnly boolean
name string
value string
id string
autoFocus boolean
aria-label string
aria-labelledby string
aria-describedby string
aria-controls string
onChange (isSelected: boolean) => void Called with the new selected state.
onPressStart (e: PressEvent) => void
onPressEnd (e: PressEvent) => void
onFocus (e: React.FocusEvent) => void
onBlur (e: React.FocusEvent) => void

<InputChipGroup>

Input chips (COMPONENT_SPEC §18; Compose InputChip): React Aria useTagGroup / useTag on the m3e-css .m3e-chip-set grid of .m3e-input-chip rows (owner decision M8 #10), the DOM <m3e-chip-set> renders. Arrow keys move between the chips (React Aria wraps at the ends), Backspace or Delete calls onRemove for the focused chip, and each chip's remove button (Material Symbols close, labelled "Remove <label>") does too.

Prop Type Default Description
children required ReactNode <InputChip> elements.
isExpressive boolean The ChipsTokens morph, tonal colors and compact spacing (see <FilterChip isExpressive>).
singleLine boolean One scrolling row instead of wrapping lines.
selectionMode "none" | "single" | "multiple" none Selectable chips (Compose InputChip selected): aria-selected rows. Default none.
selectedKeys 'all' | Iterable<Key>
defaultSelectedKeys 'all' | Iterable<Key>
disallowEmptySelection boolean
disabledKeys Iterable<Key>
aria-label string
aria-labelledby string
aria-describedby string
className string
id string
onRemove (keys: Set<Key>) => void Called with the keys to remove: Backspace or Delete on a focused chip (its selected chips when it is selected), or its remove button. Without it the chips have no remove button.
onSelectionChange (keys: Selection) => void

<InputChip>

An input chip: a row of an <InputChipGroup> (a collection element, never rendered alone).

Prop Type Default Description
id Key The chip's key (else the element's key).
children ReactNode The label.
textValue string | undefined The chip's text for accessibility and typeahead, when children is not a string.
avatar string | ReactElement | undefined The avatar before the label (Compose's avatar, InputChipTokens 24dp, clipped round): an image URL, or content (initials, an <Icon>). It takes precedence over leadingIcon.
leadingIcon ReactNode Icon before the label, e.g. <Icon name="mail" />.
isDisabled boolean | undefined
aria-label string | undefined

<SuggestionChip>

Suggestion chip (COMPONENT_SPEC §18): a button (or, with href, a link) on the m3e-css .m3e-suggestion-chip classes that suggests a response or a query, e.g. a smart reply.

Prop Type Default Description
variant "flat" | "elevated" flat Default flat.
icon ReactNode Icon before the label (Compose's icon), e.g. <Icon name="lightbulb" />.
children ReactNode The label.
href string Renders a link (<a>) instead of a <button>.
focusableWhenDisabled boolean Keeps a disabled chip in the tab order (aria-disabled instead of disabled).
className string
target AnchorHTMLAttributes<HTMLAnchorElement>['target']
rel string
isDisabled boolean
onPress (e: PressEvent) => void
onPressStart (e: PressEvent) => void
onPressEnd (e: PressEvent) => void

Also accepts HTML attributes not listed here.

<ChipSet>

A group of chips (COMPONENT_SPEC §18): the m3e-css .m3e-chip-set (role="group"; name it with aria-label) with one tab stop and arrow keys between the chips, from m3e-elements' ChipSetLayout (the code <m3e-chip-set> runs).

Prop Type Default Description
children ReactNode Assist, filter or suggestion chips (for input chips use <InputChipGroup>).
singleLine boolean One scrolling row (ChipGroupSingleLineSample) instead of wrapping lines (ChipGroupReflowSample).

Also accepts HTML attributes not listed here.

assistChipClassName()

Classes for an assist chip (the m3e-css contract).

filterChipClassName()

Classes for a filter chip's root label (the m3e-css contract).

inputChipClassName()

Classes for an input chip row (the m3e-css contract).

suggestionChipClassName()

Classes for a suggestion chip (the m3e-css contract).

Custom elements

<m3e-chip-set>

import '@kyralabs/m3e-elements/chip-set.js';

<m3e-chip-set label="Recipients">…chips…</m3e-chip-set> (COMPONENT_SPEC §18; owner decisions M8 #10 and #15). Renders the m3e-css .m3e-chip-set contract inside itself (the host is display: contents) and moves its children, chips in the m3e-css markup, into it. It exists for what CSS cannot do (m3e-elements ChipSetLayout, shared with m3e-react's <ChipSet>):

  • One tab stop for the set and arrow keys between the chips, wrapping at the ends (Home / End; mirrored in RTL).
  • Input chips (.m3e-input-chip): the set becomes a role="grid" of rows wired as React Aria's TagGroup (m3e-react <InputChipGroup> renders the same DOM), and Backspace or Delete on a focused input chip, or a press on its remove button, emits remove (detail: { value, chip }, cancelable). Unless it is canceled, the chip is removed and the focus moves to the next chip (else the previous one, else the set).
  • Without input chips the set is a role="group".

Attributes: label (the set's accessible name), single-line (one scrolling row, Compose's ChipGroupSingleLineSample; default: chips wrap, ChipGroupReflowSample).

Attribute Property Type Notes
label label string
single-line singleLine boolean
Event Detail Description
remove ChipSetRemoveDetail An input chip asks to be removed; cancelable.

CSS

chip.css

@import '@kyralabs/m3e-css/chip.css';

Shared chip base (COMPONENT_SPEC §18): the layout, label and icon slots, elevation tweens, outline, touch target, state machinery and forced colors of every chip. It has no class of its own: assist-chip.css, filter-chip.css, input-chip.css and suggestion-chip.css import it and map their tokens onto its private properties.

Contract between the files (so their order in a bundle does not matter):

  • The chip files set private custom properties (--_m3e-chip-*) on the root, its modifiers and
its selected states, and real properties only on their own parts (or, for one rule, with a
higher specificity than this file's); never a real property this file sets on the root.
  • This file sets the real properties from them, and its state rules (hover, focus, pressed,
dragged, disabled) set only real properties and the elevation timing it owns.

Private properties each chip defines:

--_m3e-chip-height, --_m3e-chip-padding-start / -end, --_m3e-chip-label-start / -end (the
label's spacing to the slots: Compose's ChipArrangement), --_m3e-chip-shape,
--_m3e-chip-container, --_m3e-chip-container-disabled, --_m3e-chip-outline-width /
-outline-color (the flat border, drawn inside the shape), --_m3e-chip-shadow / -hovered /
-focused / -pressed / -dragged / -disabled, --_m3e-chip-label, --_m3e-chip-leading,
--_m3e-chip-trailing (content colors) and their -disabled colors, --_m3e-chip-state-layer,
--_m3e-chip-icon-size and the label type style (--_m3e-chip-font / -font-size /
-font-weight / -line-height / -tracking).
8 custom properties it reads
Custom property Default
--m3e-comp-chips-outline-width-unselected 1px
--m3e-comp-elevation-incoming-duration 120ms
--m3e-comp-elevation-incoming-easing cubic-bezier(0.4, 0, 0.2, 1)
--m3e-comp-elevation-outgoing-duration-hovered 120ms
--m3e-comp-elevation-outgoing-easing cubic-bezier(0.4, 0, 0.6, 1)
--m3e-comp-minimum-interactive-component-size 48px
--m3e-comp-selectable-chip-shape-duration var(--m3e-sys-motion-spring-fast-spatial-duration)
--m3e-comp-selectable-chip-shape-easing var(--m3e-sys-motion-spring-fast-spatial-easing)

assist-chip.css

@import '@kyralabs/m3e-css/assist-chip.css';

Assist chip: flat (default) and elevated (COMPONENT_SPEC §18). No JavaScript needed; m3e-react adds React Aria behavior on the same classes.

<button type="button" class="m3e-assist-chip">
  <span class="m3e-icon" aria-hidden="true">event</span>
  <span class="m3e-assist-chip__label">Add to calendar</span>
</button>
<button type="button" class="m3e-assist-chip m3e-assist-chip--elevated">…</button>

Icons go before (leading) or after (trailing) the label. The label element is required: Compose spaces it from both sides even when an icon is absent.

The layout, slots, states and touch target are the shared chip base (chip.css); this file maps the assist chip's tokens onto it.

29 custom properties it reads
Custom property Default
--m3e-comp-assist-chip-container-height 32px
--m3e-comp-assist-chip-container-shadow-dragged var(--m3e-sys-elevation-level4-shadow)
--m3e-comp-assist-chip-container-shape var(--m3e-sys-shape-corner-small)
--m3e-comp-assist-chip-content-padding-horizontal 8px
--m3e-comp-assist-chip-elevated-container-color var(--m3e-sys-color-surface-container-low)
--m3e-comp-assist-chip-elevated-container-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-assist-chip-elevated-container-opacity-disabled 0.12
--m3e-comp-assist-chip-elevated-container-shadow var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-assist-chip-elevated-container-shadow-disabled var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-assist-chip-elevated-container-shadow-focus var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-assist-chip-elevated-container-shadow-hover var(--m3e-sys-elevation-level2-shadow)
--m3e-comp-assist-chip-elevated-container-shadow-pressed var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-assist-chip-flat-outline-color var(--m3e-sys-color-outline-variant)
--m3e-comp-assist-chip-flat-outline-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-assist-chip-flat-outline-opacity-disabled 0.12
--m3e-comp-assist-chip-flat-outline-width 1px
--m3e-comp-assist-chip-horizontal-spacing 8px
--m3e-comp-assist-chip-icon-color var(--m3e-sys-color-primary)
--m3e-comp-assist-chip-icon-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-assist-chip-icon-opacity-disabled 0.38
--m3e-comp-assist-chip-icon-size 18px
--m3e-comp-assist-chip-label-text-color var(--m3e-sys-color-on-surface)
--m3e-comp-assist-chip-label-text-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-assist-chip-label-text-font var(--m3e-sys-typescale-label-large-font)
--m3e-comp-assist-chip-label-text-line-height var(--m3e-sys-typescale-label-large-line-height)
--m3e-comp-assist-chip-label-text-opacity-disabled 0.38
--m3e-comp-assist-chip-label-text-size var(--m3e-sys-typescale-label-large-size)
--m3e-comp-assist-chip-label-text-tracking var(--m3e-sys-typescale-label-large-tracking)
--m3e-comp-assist-chip-label-text-weight var(--m3e-sys-typescale-label-large-weight)

filter-chip.css

@import '@kyralabs/m3e-css/filter-chip.css';

Filter chip: flat (default) and elevated, baseline (default) and expressive (COMPONENT_SPEC §18). A native checkbox, so no JavaScript is needed: Space toggles it, it submits with a form, and the check slides in when it is checked. m3e-react's <FilterChip> renders the same DOM.

<label class="m3e-filter-chip">
  <input class="m3e-filter-chip__input" type="checkbox" checked />
  <span class="m3e-filter-chip__leading" aria-hidden="true">
    <span class="m3e-icon m3e-filter-chip__check" data-fill>check</span>
  </span>
  <span class="m3e-filter-chip__label">Vegetarian</span>
</label>
  • The leading slot holds the check (shown while checked) and, optionally, an icon shown while
unchecked (FilterChipWithLeadingIconSample: Done ↔ Home). Without one the slot is empty while
unchecked and the check expands into it. A trailing `.m3e-icon` may follow the label.
  • Modifiers: --elevated (ElevatedFilterChip), --expressive (the shapes overload: ChipsTokens
shapes morphing on fast spatial, tonal colors, 4dp spacing next to icons; owner decision M8
#9). Disabled: the input's `disabled`.
60 custom properties it reads
Custom property Default
--m3e-comp-chip-default-horizontal-spacing 8px
--m3e-comp-chips-shape-pressed var(--m3e-sys-shape-corner-small)
--m3e-comp-chips-shape-selected var(--m3e-sys-shape-corner-full)
--m3e-comp-chips-shape-unselected var(--m3e-sys-shape-corner-medium)
--m3e-comp-filter-chip-compact-horizontal-spacing 4px
--m3e-comp-filter-chip-container-height 32px
--m3e-comp-filter-chip-container-shadow-dragged var(--m3e-sys-elevation-level4-shadow)
--m3e-comp-filter-chip-container-shape var(--m3e-sys-shape-corner-small)
--m3e-comp-filter-chip-content-padding-horizontal 8px
--m3e-comp-filter-chip-elevated-container-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-filter-chip-elevated-container-color-selected var(--m3e-sys-color-secondary-container)
--m3e-comp-filter-chip-elevated-container-color-unselected var(--m3e-sys-color-surface-container-low)
--m3e-comp-filter-chip-elevated-container-opacity-disabled 0.12
--m3e-comp-filter-chip-elevated-container-shadow var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-filter-chip-elevated-container-shadow-disabled var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-filter-chip-elevated-container-shadow-focus var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-filter-chip-elevated-container-shadow-hover var(--m3e-sys-elevation-level2-shadow)
--m3e-comp-filter-chip-elevated-container-shadow-pressed var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-filter-chip-flat-container-color-disabled-selected var(--m3e-sys-color-on-surface)
--m3e-comp-filter-chip-flat-container-color-selected var(--m3e-sys-color-secondary-container)
--m3e-comp-filter-chip-flat-container-opacity-disabled-selected 0.12
--m3e-comp-filter-chip-flat-outline-color-disabled-unselected var(--m3e-sys-color-on-surface)
--m3e-comp-filter-chip-flat-outline-color-unselected var(--m3e-sys-color-outline-variant)
--m3e-comp-filter-chip-flat-outline-opacity-disabled-unselected 0.12
--m3e-comp-filter-chip-flat-outline-width-selected 0
--m3e-comp-filter-chip-flat-outline-width-unselected 1px
--m3e-comp-filter-chip-flat-shadow-dragged var(--m3e-sys-elevation-level4-shadow)
--m3e-comp-filter-chip-flat-shadow-hover var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-filter-chip-horizontal-spacing 8px
--m3e-comp-filter-chip-icon-size 18px
--m3e-comp-filter-chip-label-text-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-filter-chip-label-text-color-selected var(--m3e-sys-color-on-secondary-container)
--m3e-comp-filter-chip-label-text-color-unselected var(--m3e-sys-color-on-surface-variant)
--m3e-comp-filter-chip-label-text-font var(--m3e-sys-typescale-label-large-font)
--m3e-comp-filter-chip-label-text-line-height var(--m3e-sys-typescale-label-large-line-height)
--m3e-comp-filter-chip-label-text-opacity-disabled 0.38
--m3e-comp-filter-chip-label-text-size var(--m3e-sys-typescale-label-large-size)
--m3e-comp-filter-chip-label-text-tracking var(--m3e-sys-typescale-label-large-tracking)
--m3e-comp-filter-chip-label-text-weight var(--m3e-sys-typescale-label-large-weight)
--m3e-comp-filter-chip-leading-icon-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-filter-chip-leading-icon-color-selected var(--m3e-sys-color-on-secondary-container)
--m3e-comp-filter-chip-leading-icon-color-tonal var(--m3e-sys-color-on-surface-variant)
--m3e-comp-filter-chip-leading-icon-color-unselected var(--m3e-sys-color-primary)
--m3e-comp-filter-chip-leading-icon-opacity-disabled 0.38
--m3e-comp-filter-chip-trailing-icon-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-filter-chip-trailing-icon-color-selected var(--m3e-sys-color-on-secondary-container)
--m3e-comp-filter-chip-trailing-icon-color-unselected var(--m3e-sys-color-on-surface-variant)
--m3e-comp-filter-chip-trailing-icon-opacity-disabled 0.38
--m3e-comp-minimum-interactive-component-size 48px
--m3e-comp-selectable-chip-expressive-icon-expand-easing var(--m3e-sys-motion-spring-fast-spatial-easing)
--m3e-comp-selectable-chip-expressive-icon-fade-in-easing var(--m3e-sys-motion-spring-default-effects-easing)
--m3e-comp-selectable-chip-expressive-icon-shrink-easing var(--m3e-sys-motion-spring-fast-spatial-easing)
--m3e-comp-selectable-chip-icon-expand-duration var(--m3e-sys-motion-spring-fast-spatial-duration)
--m3e-comp-selectable-chip-icon-expand-easing var(--m3e-sys-motion-spring-fast-spatial-easing)
--m3e-comp-selectable-chip-icon-fade-in-duration var(--m3e-sys-motion-spring-slow-effects-duration)
--m3e-comp-selectable-chip-icon-fade-in-easing var(--m3e-sys-motion-spring-slow-effects-easing)
--m3e-comp-selectable-chip-icon-fade-out-duration var(--m3e-sys-motion-spring-fast-effects-duration)
--m3e-comp-selectable-chip-icon-fade-out-easing var(--m3e-sys-motion-spring-fast-effects-easing)
--m3e-comp-selectable-chip-icon-shrink-duration var(--m3e-sys-motion-spring-default-effects-duration)
--m3e-comp-selectable-chip-icon-shrink-easing var(--m3e-sys-motion-spring-default-effects-easing)

input-chip.css

@import '@kyralabs/m3e-css/input-chip.css';

Input chip: baseline (default) and expressive (COMPONENT_SPEC §18). Input chips are rows of a grid (a .m3e-chip-set with role="grid"; owner decision M8 #10): arrow keys move between them and Backspace / Delete removes the focused one. That needs JavaScript: <m3e-chip-set> (m3e-elements) or m3e-react's <InputChipGroup> (React Aria TagGroup) render the same DOM.

<div class="m3e-chip-set" role="grid" aria-label="Recipients">
  <div class="m3e-input-chip" role="row" tabindex="0">
    <div class="m3e-input-chip__cell" role="gridcell">
      <img class="m3e-input-chip__avatar" src="ada.jpg" alt="" />
      <span class="m3e-input-chip__label">Ada Lovelace</span>
      <button class="m3e-input-chip__remove" type="button" tabindex="-1" aria-label="Remove">
        <span class="m3e-icon" aria-hidden="true">close</span>
      </button>
    </div>
  </div>
</div>
  • Leading: an avatar (.m3e-input-chip__avatar, any element: an image, initials, an icon) or a
`.m3e-icon`; the avatar takes precedence. Trailing: the remove button (or an icon).
  • States: aria-selected="true" (a TagGroup with selection), aria-disabled="true".
  • Modifier: --expressive (the shapes overload: ChipsTokens shapes morphing on fast spatial,
4dp spacing next to the avatar / icons; owner decision M8 #9).
45 custom properties it reads
Custom property Default
--m3e-comp-chip-default-horizontal-spacing 8px
--m3e-comp-chips-shape-pressed var(--m3e-sys-shape-corner-small)
--m3e-comp-chips-shape-selected var(--m3e-sys-shape-corner-full)
--m3e-comp-chips-shape-unselected var(--m3e-sys-shape-corner-medium)
--m3e-comp-input-chip-avatar-opacity-disabled 0.38
--m3e-comp-input-chip-avatar-shape var(--m3e-sys-shape-corner-full)
--m3e-comp-input-chip-avatar-size 24px
--m3e-comp-input-chip-compact-horizontal-spacing 4px
--m3e-comp-input-chip-container-color-disabled-selected var(--m3e-sys-color-on-surface)
--m3e-comp-input-chip-container-color-selected var(--m3e-sys-color-secondary-container)
--m3e-comp-input-chip-container-height 32px
--m3e-comp-input-chip-container-opacity-disabled-selected 0.12
--m3e-comp-input-chip-container-shadow-dragged var(--m3e-sys-elevation-level4-shadow)
--m3e-comp-input-chip-container-shape var(--m3e-sys-shape-corner-small)
--m3e-comp-input-chip-content-padding-end 4px
--m3e-comp-input-chip-content-padding-end-icon 8px
--m3e-comp-input-chip-content-padding-start 4px
--m3e-comp-input-chip-content-padding-start-icon 8px
--m3e-comp-input-chip-horizontal-spacing 8px
--m3e-comp-input-chip-label-text-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-input-chip-label-text-color-selected var(--m3e-sys-color-on-secondary-container)
--m3e-comp-input-chip-label-text-color-unselected var(--m3e-sys-color-on-surface-variant)
--m3e-comp-input-chip-label-text-font var(--m3e-sys-typescale-label-large-font)
--m3e-comp-input-chip-label-text-line-height var(--m3e-sys-typescale-label-large-line-height)
--m3e-comp-input-chip-label-text-opacity-disabled 0.38
--m3e-comp-input-chip-label-text-size var(--m3e-sys-typescale-label-large-size)
--m3e-comp-input-chip-label-text-tracking var(--m3e-sys-typescale-label-large-tracking)
--m3e-comp-input-chip-label-text-weight var(--m3e-sys-typescale-label-large-weight)
--m3e-comp-input-chip-leading-icon-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-input-chip-leading-icon-color-selected var(--m3e-sys-color-primary)
--m3e-comp-input-chip-leading-icon-color-tonal var(--m3e-sys-color-on-surface-variant)
--m3e-comp-input-chip-leading-icon-color-unselected var(--m3e-sys-color-on-surface-variant)
--m3e-comp-input-chip-leading-icon-opacity-disabled 0.38
--m3e-comp-input-chip-leading-icon-size 18px
--m3e-comp-input-chip-outline-color-disabled-unselected var(--m3e-sys-color-on-surface)
--m3e-comp-input-chip-outline-color-unselected var(--m3e-sys-color-outline-variant)
--m3e-comp-input-chip-outline-opacity-disabled-unselected 0.12
--m3e-comp-input-chip-outline-width-selected 0
--m3e-comp-input-chip-outline-width-unselected 1px
--m3e-comp-input-chip-trailing-icon-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-input-chip-trailing-icon-color-selected var(--m3e-sys-color-on-secondary-container)
--m3e-comp-input-chip-trailing-icon-color-unselected var(--m3e-sys-color-on-surface-variant)
--m3e-comp-input-chip-trailing-icon-opacity-disabled 0.38
--m3e-comp-input-chip-trailing-icon-size 18px
--m3e-comp-minimum-interactive-component-size 48px

suggestion-chip.css

@import '@kyralabs/m3e-css/suggestion-chip.css';

Suggestion chip: flat (default) and elevated (COMPONENT_SPEC §18). No JavaScript needed; m3e-react adds React Aria behavior on the same classes. A button (or a link) that suggests a response or a search, e.g. a smart reply.

<button type="button" class="m3e-suggestion-chip">
  <span class="m3e-icon" aria-hidden="true">lightbulb</span>
  <span class="m3e-suggestion-chip__label">Suggestion</span>
</button>
<button type="button" class="m3e-suggestion-chip m3e-suggestion-chip--elevated">…</button>

Compose gives a suggestion chip one optional icon, before the label. The label element is required (spaced from both sides, as the assist chip's).

28 custom properties it reads
Custom property Default
--m3e-comp-chip-default-horizontal-spacing 8px
--m3e-comp-suggestion-chip-container-height 32px
--m3e-comp-suggestion-chip-container-shadow-dragged var(--m3e-sys-elevation-level4-shadow)
--m3e-comp-suggestion-chip-container-shape var(--m3e-sys-shape-corner-small)
--m3e-comp-suggestion-chip-content-padding-horizontal 8px
--m3e-comp-suggestion-chip-elevated-container-color var(--m3e-sys-color-surface-container-low)
--m3e-comp-suggestion-chip-elevated-container-shadow var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-suggestion-chip-elevated-container-shadow-disabled var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-suggestion-chip-elevated-container-shadow-focus var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-suggestion-chip-elevated-container-shadow-hover var(--m3e-sys-elevation-level2-shadow)
--m3e-comp-suggestion-chip-elevated-container-shadow-pressed var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-suggestion-chip-elevated-disabled-icon-color color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-suggestion-chip-flat-outline-color var(--m3e-sys-color-outline-variant)
--m3e-comp-suggestion-chip-flat-outline-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-suggestion-chip-flat-outline-opacity-disabled 0.12
--m3e-comp-suggestion-chip-flat-outline-width 1px
--m3e-comp-suggestion-chip-label-text-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-suggestion-chip-label-text-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-suggestion-chip-label-text-font var(--m3e-sys-typescale-label-large-font)
--m3e-comp-suggestion-chip-label-text-line-height var(--m3e-sys-typescale-label-large-line-height)
--m3e-comp-suggestion-chip-label-text-opacity-disabled 0.38
--m3e-comp-suggestion-chip-label-text-size var(--m3e-sys-typescale-label-large-size)
--m3e-comp-suggestion-chip-label-text-tracking var(--m3e-sys-typescale-label-large-tracking)
--m3e-comp-suggestion-chip-label-text-weight var(--m3e-sys-typescale-label-large-weight)
--m3e-comp-suggestion-chip-leading-icon-color var(--m3e-sys-color-primary)
--m3e-comp-suggestion-chip-leading-icon-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-suggestion-chip-leading-icon-opacity-disabled 0.38
--m3e-comp-suggestion-chip-leading-icon-size 18px

chip-set.css

@import '@kyralabs/m3e-css/chip-set.css';

Chip set (COMPONENT_SPEC §18): a group of chips that wraps onto more lines (Compose's ChipGroupReflowSample, a FlowRow) or, with --single-line, scrolls sideways (ChipGroupSingleLineSample, a Row with horizontalScroll).

<div class="m3e-chip-set" role="group" aria-label="Filters">…chips…</div>
<div class="m3e-chip-set" role="grid" aria-label="Recipients">…input chips…</div>

Layout only; keyboarding (one tab stop, arrow keys between chips, Backspace / Delete removing the focused input chip) comes from <m3e-chip-set> (m3e-elements) or m3e-react.

3 custom properties it reads
Custom property Default
--m3e-comp-chip-set-chip-padding-horizontal 4px
--m3e-comp-chips-height 32px
--m3e-comp-minimum-interactive-component-size 48px

Sources

Values come from androidx Compose Material3 1.5.0-beta01 at 19991f2c and from m3.material.io, as cited below from COMPONENT_SPEC §18 and the m3e-css sources.

assist chip verified (generated): AssistChipTokens.kt@19991f2c + AssistChipDefaults literals (Chip.kt L1962 content padding 8dp; spacing 8dp via DefaultHorizontalArrangement = SuggestionChipDefaults.HorizontalSpacing, L2918/L4306): height 32, shape small, label-large on-surface, icon 18dp primary; flat = transparent + 1dp outline-variant border, Level0 except dragged Level4; elevated = surface-container-low, Level1 (hover Level2), no border. Filter / input / suggestion verified (generated): FilterChipTokens.kt, InputChipTokens.kt, SuggestionChipTokens.kt, ChipsTokens.kt@19991f2c plus the Chip.kt / *ChipDefaults values Compose renders (components/chips-defaults.css; SOURCES register #35). All 32dp, label large, 18dp icons, flat = 1dp outline-variant border (none when selected), disabled colors at their token opacity (Color.copy(alpha)), dragged Level4. Colors pick by (enabled, selected) only: hover, focus and press change no color (the state layer only). Filter (defaultFilterChipColors / defaultElevatedFilterChipColors): unselected label and trailing icon on-surface-variant, leading icon primary; selected secondary-container with on-secondary-container content; elevated surface-container-low, Level1 (hover Level2). A flat filter chip's hover is Level1 selected or not (filterChipElevation L2390–2405 reads the Selected hover / focus / pressed elevations; owner decision M8 #11). Input (defaultInputChipColors): label, leading and trailing icons on-surface-variant, selected secondary-container with leading primary and on-secondary-container label / trailing; Level0 (dragged Level4); avatar 24dp, full, 0.38 opacity disabled. Suggestion: label on-surface-variant, icon primary; the elevated disabled container and icon read AssistChipTokens (same values). Spacing: the baseline arrangement spaces the label 8dp from both slots inside 8dp content padding (input chips: InputChipDefaults.contentPadding, start 4dp with an avatar or no leading icon / 8dp with one, end 8dp with a trailing icon / 4dp; an icon-less input label sits 12dp in). Chip sets: ChipSamples ChipGroupReflowSample / ChipGroupSingleLineSample pad each chip 4dp (8dp apart) in 48dp rows. State layer: Compose's ripple color, contentColorFor(container) (owner decision M8 #4): on-surface unselected (the ambient content color of a transparent chip, on-surface-container-low's on-surface), on-secondary-container selected.

chip.css sources

(compose/material3 @ 19991f2c, Chip.kt; lines in each chip file and PARITY.md):

  • Chip() L3186-3231 / SelectableChip() L3234-3346: a clickable Surface with the minimum interactive size (≥ 48dp target via ::after; ::before is the state layer), the chip's shape, container color, shadow elevation (animated by ChipElevation / SelectableChipElevation with the ElevationDefaults tweens, elevation-motion.css; disabled snaps) and border, drawn inside the shape (an inset shadow here, so it takes no layout space).
  • ChipContent L3355-3416 / AnimatingChipContent L3423-3520: [leading | label | trailing] in a Row with ChipArrangement L3147-3181 inside the content padding; icons and avatars centred.
  • The state layer is Compose's ripple color: the Surface's content color, contentColorFor(its container) (owner decision M8 #4). Selected (secondary-container): on-secondary-container; elevated (surface-container-low): on-surface; flat (transparent): the ambient LocalContentColor, on-surface inside Material surfaces. The m3.material.io token DB gives the label color instead (on-surface-variant unselected).
assist-chip.css sources

(compose/material3 @ 19991f2c, Chip.kt):

  • AssistChip / ElevatedAssistChip L135-167 / L377-409 → Chip() L3186-3231: a clickable Surface (role Button, minimum interactive size) with AssistChipTokens shape, height, label type style.
  • Colors: AssistChipDefaults.defaultAssistChipColors L2020-2040 (flat: transparent container) and defaultElevatedAssistChipColors L2165-2187. Disabled colors are the token color at its opacity, not composited (Color.copy(alpha)). ChipColors take only enabled: hover, focus, press and drag change no color (the state tokens equal the resting ones).
  • Elevation: assistChipElevation L2054-2069 (flat: Level0 in every state but dragged) and elevatedAssistChipElevation L2201-2216, animated by ChipElevation with the ElevationDefaults tweens (elevation-motion.css); disabled snaps without a transition (L3674-3676).
  • Border: assistChipBorder L2080-2088 (flat only; ElevatedAssistChip passes border = null). It is drawn inside the shape, so here it is an inset shadow that takes no layout space.
  • Layout: ChipContent L3355-3416 lays out [leading | label | trailing] with ChipArrangement L3147-3181, which spaces the label from both slots even when they are empty, inside AssistChipDefaults.ContentPadding (assist-chip-defaults.css).
  • State layer: the Surface ripple uses the content color, on-surface here (label color tokens).
filter-chip.css sources

(compose/material3 @ 19991f2c, Chip.kt; values in filter-chip.css / chips.css / chips-defaults.css, lines in PARITY.md):

  • FilterChip L635-669 (baseline: FilterChipDefaults.shape = FilterChipTokens.ContainerShape, filterChipColors, horizontalArrangement() = DefaultHorizontalArrangement) and L730-765 (shapes: tonalFilterChipColors, horizontalArrangement(leadingIcon != null, trailingIcon != null) L2244-2253); ElevatedFilterChip L910-944 / L1005-1040 (no border) → SelectableChip L3234-3346: Surface(selected) with Role.Checkbox, so a native checkbox here.
  • Colors: defaultFilterChipColors L2336-2368, defaultElevatedFilterChipColors L2510-2543, tonal L2370-2376 / L2545-2551 (the unselected leading icon on-surface-variant instead of primary). SelectableChipColors pick by (enabled, selected) only (L4070-4121): hover, focus and press change no color, and the colors do not animate.
  • Elevation: filterChipElevation L2390-2405 gives the Selected hover / focus / pressed elevations whether or not the chip is selected (SelectableChipElevation L3795-3805 picks by the last interaction only): a flat chip rises to Level1 on hover even when unselected (owner decision M8 #11; FilterChipTokens.FlatUnselectedHoverContainerElevation is not read). elevatedFilterChipElevation L2565-2580. The ElevationDefaults tweens (chip.css).
  • Border: filterChipBorder L2423-2443: outline-variant 1dp unselected; transparent and FlatSelectedOutlineWidth (0) when selected; on-surface 12% disabled.
  • Layout: FilterChipDefaults.ContentPadding 8dp; ChipArrangement L3147-3181 places the label after a non-empty leading slot by the leading spacing, else by the trailing spacing, and the trailing slot at the end. Baseline: 8dp both sides. Expressive: compact 4dp next to the leading icon, and next to the trailing one when both exist; with only a trailing icon the label sits 4dp after the start padding and 8dp before the icon (what Compose renders).
  • Leading slot motion (AnimatingChipContent L3423-3520): expandHorizontally from the start + fadeIn, shrinkHorizontally + fadeOut. Baseline (L3280-3283): fade in slow effects, fade out fast effects, expand fast spatial, shrink default effects. Expressive (L3340-3343): fades default effects, expand and shrink fast spatial. Here the slot's inline-size and opacity transition with the springs' linear() easings (the arrangement switches at once, as Compose). Under reduced motion the width jumps both ways (the baseline shrink is geometry on an effects spring: 0ms in chips-defaults.css, as the sheets and drawer); the fades keep their timing.
  • Expressive shape (shapeByInteraction L4269-4289, ChipShapes from ChipsTokens L2612-2621): pressed CornerSmall (8dp) > selected CornerFull > rest CornerMedium (12dp), on fast spatial (L3311-3313); CornerFull is half the height so the morph springs between finite radii.
  • The check: FilterChipSample (ChipSamples.kt) Icons.Filled.Done at FilterChipDefaults.IconSize (18dp) → the Material Symbols check glyph (the Icon primitive), FILL 1.
input-chip.css sources

(compose/material3 @ 19991f2c, Chip.kt; values in input-chip.css / chips.css / chips-defaults.css, lines in PARITY.md):

  • InputChip L1184-1242 (baseline: InputChipDefaults.shape = InputChipTokens.ContainerShape, inputChipColors, horizontalArrangement() = DefaultHorizontalArrangement) and L1298-1361 (shapes: tonalInputChipColors, horizontalArrangement(avatar, leading, trailing) L2650-2661) → SelectableChip L3234-3346 (Role.Checkbox; here a grid row, owner decision M8 #10, as m3.material.io's web column "gridcell").
  • Padding: InputChipDefaults.contentPadding L2678-2686: start 4dp with an avatar or without a leading icon, 8dp with a leading icon; end 8dp with a trailing icon, else 4dp. With the baseline 8dp arrangement an icon-less label sits 12dp from both edges.
  • Avatar (L1202-1221): clipped to InputChipTokens.AvatarShape (full), AvatarSize 24dp, at DisabledAvatarOpacity (0.38) when disabled.
  • Colors: defaultInputChipColors L2755-2784 (transparent container, label on-surface-variant, leading on-surface-variant, selected: secondary-container, label on-secondary-container, leading primary, trailing on-secondary-container); tonal L2786-2792 (the same unselected leading color). By (enabled, selected) only: no hover / focus / press colors.
  • Elevation: inputChipElevation L2806-2821, Level0 but dragged Level4; ElevationDefaults tweens.
  • Border: inputChipBorder L2839-2859 (outline-variant 1dp; selected transparent, 0 width).
  • Remove: Compose's trailing icon is plain content with no action of its own; m3.material.io (Chips › Accessibility) gives the remove icon its own 48 × 48dp target and the label "Remove <chip>", and Backspace / Delete removes the focused chip. The close Material Symbol at InputChipTokens.TrailingIconSize (18dp).
suggestion-chip.css sources

(compose/material3 @ 19991f2c, Chip.kt; values in chips-defaults.css, lines in PARITY.md):

  • SuggestionChip / ElevatedSuggestionChip L1528-1559 / L1761-1790 → Chip() L3186-3231 (role Button, minimum interactive size), SuggestionChipTokens shape, height and label type style; icon is the leading icon, there is no trailing one.
  • Colors: defaultSuggestionChipColors L3971-3989 (flat: transparent container, label on-surface-variant, icon primary) and defaultElevatedSuggestionChipColors L3091-3111, whose disabled container and icon read AssistChipTokens (the same values). Disabled colors are the token color at its opacity (Color.copy(alpha)). Hover, focus and press change no color.
  • Elevation: suggestionChipElevation L2985-3000 (flat: Level0 but dragged Level4) and elevatedSuggestionChipElevation L3125-3140; the ElevationDefaults tweens (chip.css).
  • Border: suggestionChipBorder L3011-3019 (flat only; ElevatedSuggestionChip passes null).
  • Layout: SuggestionChipDefaults.ContentPadding (8dp) and DefaultHorizontalArrangement (SuggestionChipDefaults.HorizontalSpacing, 8dp), the assist chip's.
chip-set.css sources

(compose/material3 @ 19991f2c): ChipSamples.kt ChipGroupReflowSample / ChipGroupSingleLineSample pad every chip 4dp on both sides (chips-defaults.css), so chips are 8dp apart and the first one 4dp in; each chip is laid out at the minimum interactive size (Chip.kt Surface → minimumInteractiveComponentSize), so the 32dp chips sit centred in 48dp rows.