Skip to content
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
Getting startedWebReactSwiftUIJetpack ComposeFlutterReact NativePresetsYour own design systemUsing Tanrei with AI agentsBrandDesign tokensDesign systems and customizationTheme StudioColorTypographyIconsShapeElevationMotionInteraction statesBehaviourLayoutAccessibilityWordsColor tokensTypescale tokensShape tokensElevation tokensMotion tokensState tokensSpace and measure tokensComponentsButtonMain action buttonSegmented buttonButton groupButton with menuToolbarCopy buttonCheckboxRadio buttonSwitchChipMenuSliderSlider fieldCalendarDateTimePickerField groupRadio groupCheckbox groupSelectSelect fieldMenubarFile uploadFile picker AdvancedDrop zoneText input AdvancedText fieldChip fieldFieldCombobox AdvancedCombobox fieldNumber input AdvancedNumber fieldColor input AdvancedColor fieldOne-time-code input AdvancedOne-time-code fieldEmbedded text input AdvancedChat composerCardDividerModal layer AdvancedDialogListList rowBottom sheetSide sheetCarouselCode blockTablePopoverStepsAccordionDescription listFigureApp shellPage headerTree viewTimelineImage previewHandleResizable frameScroll areaBadgeProgress indicatorMeterNumber tickerLoading indicatorSnackbarTooltipKeyboard shortcutTagInline codeCalloutAvatarEmpty stateTag groupSkeletonFile itemSwatchMarquee textTabsTab viewApp barNavigation barNavigation drawerSearchLinkPaginationPrevious and next linksStepperBreadcrumbsTable of contentsLink cardFAB menuStatusHow Tanrei is builtContributingLicense and attribution
HomeComponentsChip

Chip

A small labelled control in a row, for a suggested action or a link to somewhere, a filter that is on or off, or an item the user entered and can remove.

content_copyView as Markdown
Category
Selection
Tokens
comp.chip
Level
Base
Used in
Chip field

Overview

Web
tanrei-chip
schedule
SwiftUI
TanreiChip
schedule
Jetpack Compose
TanreiChip
schedule
Flutter
TanreiChip
schedule
React Native
Chip
schedule

Generated from docs/status/status.json, specs/chip.spec.json

Kinds

event

Usage

The Web and React samples show the current spec. SwiftUI, Compose, Flutter and React Native are lagging: they still ship the previous revision of the chip, with less rounded corners, smaller glyphs, a close glyph for the remove action, no destructiveRemove and no link form (href), and their samples show that revision until they catch up.

<tanrei-chip label="Add to calendar"><span slot="icon">event</span></tanrei-chip>

<!-- An action chip with href is a link. One that opens a new tab shows a mark and says so in your words. -->
<tanrei-chip label="Opening hours" href="/visit/hours/"></tanrei-chip>
<tanrei-chip label="Directions" href="https://example.com/map" target="new" target-label="opens in a new tab"></tanrei-chip>

<!-- A filter chip switches its own selected and fires change. -->
<tanrei-chip kind="filter" label="Vegetarian"></tanrei-chip>

<!-- An input chip has a second button, named by remove-label. -->
<tanrei-chip kind="input" label="Ada Lovelace" remove-label="Remove Ada Lovelace">
  <img slot="avatar" src="ada.jpg" alt="" />
</tanrei-chip>

<!-- Removing an attachment loses it: give the remove action the destructive look. -->
<tanrei-chip kind="input" label="report.pdf" remove-label="Remove report.pdf" destructive-remove></tanrei-chip>

<script type="module">
  import '@tanrei/web/components/chip';
  document.querySelector('[kind="filter"]').addEventListener('change', (e) => filter(e.detail.selected));
  // The chip stays in the DOM: the page removes it and moves focus to a neighbour.
  document.querySelector('[kind="input"]').addEventListener('remove', (e) => removeRecipient(e.target));
</script>
import { Chip } from '@tanrei/react';

<Chip label="Add to calendar" onClick={addToCalendar}><span slot="icon">event</span></Chip>
<Chip label="Opening hours" href="/visit/hours/" />
<Chip label="Directions" href="https://example.com/map" target="new" targetLabel="opens in a new tab" />
<Chip kind="filter" selected={vegetarian} label="Vegetarian" onChange={(e) => setVegetarian(e.detail.selected)} />
<Chip kind="input" label="Ada Lovelace" removeLabel="Remove Ada Lovelace" onRemove={() => removeRecipient(ada)} />
<Chip kind="input" label="report.pdf" removeLabel="Remove report.pdf" destructiveRemove onRemove={() => removeAttachment(report)} />
import Tanrei

// The initializer chooses the kind.
TanreiChip("Add to calendar", icon: "event", action: addToCalendar)
TanreiChip("Vegetarian", isSelected: $vegetarian)
TanreiChip("Ada Lovelace", removeLabel: "Remove Ada Lovelace", action: openContact, onRemove: removeAda)
import io.github.chnotchy.tanrei.components.TanreiChip
import io.github.chnotchy.tanrei.primitives.TanreiText

// The overload chooses the kind. onClick alone is an action chip.
TanreiChip(onClick = ::addToCalendar, label = "Add to calendar", icon = { TanreiText("event") })

// selected with onSelectedChange is a filter chip.
TanreiChip(selected = vegetarian, onSelectedChange = { vegetarian = it }, label = "Vegetarian")

// onClick with onRemove is an input chip. Remove it from your list and move focus yourself.
TanreiChip(
    onClick = { openContact(ada) },
    onRemove = { recipients -= ada },
    label = "Ada Lovelace",
    removeContentDescription = "Remove Ada Lovelace",
    avatar = { ContactPhoto(ada) },
)
import 'package:tanrei/tanrei.dart';

// The constructor chooses the kind.
TanreiChip(label: 'Add to calendar', icon: const Icon(AppSymbols.event), onPressed: addToCalendar);

TanreiChip.filter(
  label: 'Vegetarian',
  selected: vegetarian,
  onSelected: (bool value) => setState(() => vegetarian = value),
);

TanreiChip.input(
  label: 'Ada Lovelace',
  removeSemanticLabel: 'Remove Ada Lovelace',
  avatar: Image.asset('ada.png', fit: BoxFit.cover),
  onDeleted: () => setState(() => recipients.remove(ada)), // and move focus yourself
);
import { Chip } from '@tanrei/react-native';

<Chip label="Add to calendar" icon="event" onPress={addToCalendar} />
<Chip kind="filter" label="Vegetarian" selected={vegetarian} onSelectedChange={setVegetarian} />
<Chip
  kind="input"
  label="Ada Lovelace"
  avatar={{ uri: ada.photo }}
  removeAccessibilityLabel="Remove Ada Lovelace"
  onRemove={() => removeRecipient(ada)}
/>

Specs

Variants, sizes and disabled

eventeventevent

Remove actions: (default) and destructive; hover or focus them

Link chips: here, in a new tab, disabled

In the default system every chip is a pill, and stays one when larger text makes it taller. Its glyphs are as large as its height allows: 24 on a medium chip and 20 on a small one. The check of a selected filter chip is the glyph check of the system’s icon set, and the remove glyph is cancel, always drawn filled. The chip draws no mark of its own, so a design system that wants other marks chooses another icon set. The edge of an outlined chip is the hairline, the same quiet line a divider draws.

Anatomy

Anatomy

Drawn over the fixture action-outlined, as the web package draws it.
Part Description
1 container The shape that holds the chip's content and carries hover, focus and press of the chip itself.
2 outline · optional The border, drawn inside the container's edge. Transparent where the look has none (elevated, selected).
3 icon · optional Leading glyph of the system's icon set. A selected filter chip shows the glyph `check` in its place.
4 avatar · optional Leading image of an input chip (a person, a file), in place of the icon.
5 label Text that names the choice, action or item.
6 remove · optional An input chip's trailing remove action: the glyph `cancel` of the system's icon set, drawn filled, on a round container that carries its own hover, focus and press and sits concentric with the chip's round end: as far from the end as from the top and the bottom. A separate button for assistive technology. It takes the error color while it is hovered, focused or pressed only while destructiveRemove is true.
7 focus-ring Gap-then-ring indicator shown for keyboard focus, around the chip or around the remove action, whichever has focus.

Measurements

Measurements

Length Token Value (px)
container padding-inline-with-icon tanrei.comp.chip.container.padding-inline-with-icon.medium 8
container gap tanrei.comp.chip.container.gap.medium 8
container height tanrei.comp.chip.container.height.medium 32
icon size tanrei.comp.chip.icon.size.medium 24
Drawn over the fixture action-outlined, as the web package draws it.

Properties

Property Type Default Description
kind action | filter | input "action" What the chip does. action: a button that performs or suggests something, or with href a link to it. filter: a two-state control, on or off. input: an item the user entered, which the user can remove. Kind decides the role and the parts, not the look.
variant outlined | elevated "outlined" Look. Outlined sits flat on a plain surface; elevated is raised for chips over media or a busy background. A design system may add values.
size small | medium "medium" Visual height (24 / 32). Small is for dense places, such as entered items inside a field or tags in a table row. The hit area always meets the minimum target. A design system may add values.
selected boolean false Whether a filter chip is on. Ignored unless kind is filter.
value string "" What a component that holds the selection of a set of filter chips names this chip by: the Tag group's value and values (TGR-17). Unique within the set. Not drawn and not exposed; the chip itself does nothing with it.
disabled boolean false Prevents interaction and removes the chip, and its remove action, from the focus order.
href string "" The destination of an action chip: an address as the platform's links take one (a URL; on the web a path or a fragment as well). Not empty, it makes the chip a link and not a button: the link role, activated by pointer, touch or Enter and not by Space, opened by the platform as its own links are, and drawn exactly as the action chip it would otherwise be (CHP-33, CHP-34). Ignored unless kind is action: a filter chip holds a state and an input chip a second action (CHP-37).
target same | new "same" Where the destination opens. same: where the person is — the same tab on the web, the app's own handling of the address elsewhere. new: a new context — a new tab or window on the web, outside the current screen elsewhere — which the chip marks with the glyph open_in_new after its label and says through targetLabel (CHP-35). Ignored without href.
targetLabel string "" What assistive technology reads after the link's name while target is new ("opens in a new tab"). Tanrei ships no words of its own, so the product gives them in its own language, as it gives a chip its removeLabel. Required while target is new; ignored otherwise.
label string "" The visible text and the accessible name.
removeLabel string "" The accessible name of an input chip's remove action. It should name the item ("Remove Ada Lovelace"), because the remove action is a separate element. Required when kind is input; ignored otherwise.
destructiveRemove boolean false Whether an input chip's remove action reads as destructive: at rest it looks as it does without the flag; while it is hovered, keyboard-focused or pressed its glyph and state layer take the error color. Set it where removing the item loses something that cannot be put back in a step (an attachment, a recipient already invited); leave it off where the person can put the item back in one step (an applied filter, a search term). It changes color only: the remove action's name, glyph, size and behaviour are the same either way. Ignored unless kind is input.
removable boolean true Whether an input chip draws its remove action. False for an entry that cannot be taken away now (a read-only field's): no remove action is drawn, so none stands there doing nothing. Ignored unless kind is input.

Slots

Slot Description
icon Leading icon (a glyph of the system's icon set).
avatar Leading image of an input chip. Ignored for other kinds.

Events

Event Description
click The chip itself was activated by pointer, touch, keyboard or assistive technology. Not emitted by the remove action. A link chip emits it before its destination opens; a product that takes the navigation itself (a client-side router) cancels it (CHP-34).
change A filter chip was activated; carries the new value of selected.
remove The user asked to remove an input chip, with its remove action or with Backspace or Delete. The chip does not remove itself.

States

Behavior

Requirements in EARS form. Every platform implementation is tested against these ids.

ID Requirement
CHP-1 WHEN the user activates the chip by pointer, touch, Enter or Space — a link chip (CHP-33) by pointer, touch or Enter — THE chip SHALL emit click exactly once.
CHP-2 WHILE disabled is true THE chip SHALL NOT emit click, change or remove, SHALL NOT be focusable, nor its remove action, and SHALL expose the disabled state to assistive technology.
CHP-3 WHILE hovered, keyboard-focused or pressed THE chip SHALL render its container, outline, label, icon, elevation and opacity from the component tokens of the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state.
CHP-4 WHEN the chip receives keyboard focus THE chip SHALL show the focus ring without animation; focus gained by pointer or touch SHALL NOT show it.
CHP-5 WHERE the visual size is below the minimum target THE chip SHALL extend its hit area on both axes to sys.target.touch for touch input and sys.target.pointer for fine pointers, without changing its visual size; a platform that cannot tell the input type SHALL use sys.target.touch.
CHP-6 WHILE disabled is true THE chip SHALL render its container, outline, label, icon, remove action, elevation and opacity from the disabled component tokens of its variant and, for a filter chip, of its selection, and SHALL draw an avatar at sys.state.disabled-content-opacity.
CHP-7 WHEN variant or size names a value that the active design system does not have, or kind names a value this spec does not have, THE chip SHALL render and behave with the default value (kind action, and the default variant's or size's tokens), and in debug builds report the unknown value.
CHP-8 WHEN the chip is pressed by pointer, touch, Enter or Space THE chip SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner.
CHP-9 WHILE pressed THE chip SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; outline, label, icon, elevation, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.chip.motion.feedback-duration with comp.chip.motion.state-easing, fades over the state duration of its size (comp.chip.motion.state-duration.small or .medium) with comp.chip.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
CHP-10 THE chip SHALL ease its container corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show its keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README.
CHP-11 WHEN kind is filter and the user activates the chip THE chip SHALL emit click and then change with the opposite of selected, in that order (as a native checkbox does), and SHALL expose selected to assistive technology as pressed or not pressed; WHILE kind is not filter THE chip SHALL ignore selected and SHALL NOT expose a pressed state.
CHP-12 WHILE kind is filter and selected is true THE chip SHALL render its container, outline, label and icon from the selected parts of its variant (comp.chip.outlined.selected.container.color for outlined at rest, and so on); otherwise it SHALL render them from the variant's own parts (comp.chip.outlined.container.color, and so on).
CHP-13 WHILE kind is filter and selected is true THE chip SHALL show the glyph check of the system's icon set in the leading position, in place of its icon when it has one, and SHALL draw no mark of its own; WHEN selected changes THE chip SHALL show or hide the check, change its width with it, and change to or from the selected colors over comp.chip.motion.selection-duration with comp.chip.motion.selection-easing, and SHALL show the new condition at once where motion is reduced.
CHP-14 THE chip SHALL draw its icon, its check and its remove glyph at the icon size of its size (comp.chip.icon.size.medium for medium, and so on) and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, the icon and the remove glyph with sys.icon.weight and the check with comp.chip.check.weight (not sys.icon.weight: the check is a control's own mark, drawn on the selected fill to tell on from off, as the checkbox's is, CHK-12), the weight changing the stroke of the glyph only and never its box; the icon and the check SHALL take the family and fill of its selection (sys.icon.family.selected and sys.icon.fill.selected only while kind is filter and selected is true; otherwise sys.icon.family.unselected and sys.icon.fill.unselected) and the icon color of its look (comp.chip.outlined.icon.color, comp.chip.outlined.selected.icon.color, and so on); the remove glyph SHALL always be drawn filled, with sys.icon.family.selected and sys.icon.fill.selected, in the glyph color of the parts CHP-20 and CHP-27 name (comp.chip.outlined.destructive-remove.icon.color or comp.chip.outlined.remove.icon.color, and so on).
CHP-15 WHERE kind is input and an avatar is given THE chip SHALL draw it in the leading position, in place of an icon, as an image of the avatar size of its size (comp.chip.avatar.size.medium for medium, and so on) clipped to comp.chip.avatar.shape and hidden from assistive technology; WHERE kind is not input THE chip SHALL ignore an avatar and report it in debug builds.
CHP-16 THE chip SHALL draw a container at least the height of its size (comp.chip.container.height.medium for medium, and so on) with corners comp.chip.container.shape, never larger than half the container's height (a pill where the value is sys.shape.corner.full, at any height the chip grows to), and an outline comp.chip.outline.width wide inside its edge, and lay out in one row its leading icon, check or avatar, its label and its remove glyph, separated by comp.chip.container.gap.medium (and so on); each end SHALL be padded by comp.chip.container.padding-inline-with-icon.medium where it holds an icon or the check, by comp.chip.container.padding-inline-with-avatar.medium where it holds an avatar, by comp.chip.container.padding-inline-with-remove.medium where it holds the remove glyph, and by comp.chip.container.padding-inline.medium where the label meets it, and the row's tallest item (the label's line, a glyph's box or the avatar) SHALL be padded at the top and bottom by at least comp.chip.container.padding-block.medium (and so on for each size). The remove action's round container takes no part in this layout (CHP-18); where it lies follows from it (CHP-32).
CHP-17 THE chip SHALL set its label on one line in the text style of its size (comp.chip.label.typescale.medium, and so on) and the label color of its look, truncating it with an ellipsis only when the layout gives the chip less width than the label needs, and SHALL grow in height with larger text; the full label SHALL remain the accessible name.
CHP-18 WHERE kind is input THE chip SHALL draw a remove action at its inline end: the glyph cancel of the system's icon set, centred on a round container of the remove size of its size (comp.chip.remove.container.size.medium, and so on) with corners comp.chip.remove.container.shape. The round container is larger than the glyph and SHALL NOT take part in the layout of CHP-16: it reaches into the gap before the glyph and the padding after it, as far as CHP-32 says, without changing the chip's size. THE chip SHALL draw no mark of its own for it, and SHALL expose it as a separate element with the button role, named by removeLabel, that is not a stop in the Tab order: it is reached by pointer, touch and assistive technology, and from the keyboard through the chip itself, on which Backspace or Delete removes (CHP-19). WHEN the remove action is activated by pointer, touch, assistive technology, Enter or Space THE chip SHALL emit remove exactly once and SHALL NOT emit click.
CHP-19 WHEN kind is input and the chip itself has keyboard focus and the user presses Backspace or Delete THE chip SHALL emit remove exactly once, and SHALL NOT answer the repeats of a key that is held down, so that one press asks for one removal. WHILE kind is input, removable is true and disabled is false THE chip SHALL expose to assistive technology, on the chip itself, that Delete and Backspace remove it (the platform's keyboard-shortcut property where it has one: on the web aria-keyshortcuts="Delete Backspace"), so that a person who does not see the remove action, and who no longer meets it by Tab (CHP-18), learns the key.
CHP-20 WHILE the remove action is hovered, keyboard-focused or pressed THE chip SHALL render the remove action's container and glyph from the remove parts of the remove action's own highest-precedence state (comp.chip.outlined.remove.container.color at rest, and so on; the destructive-remove parts instead while destructiveRemove is true, CHP-27), with the precedence of CHP-3, SHALL keep the chip's container, outline, label and icon at the chip's own state (the chip is not hovered or pressed while the pointer or press is on the remove action), SHALL show the focus ring around the remove action's round container rather than around the chip, and SHALL answer a press of it as CHP-8 and CHP-9 name, within that round container.
CHP-21 WHERE kind is input THE chip SHALL give the remove action a hit area of at least sys.target.touch for touch input and sys.target.pointer for fine pointers on both axes, around its glyph, reaching toward the label no further than the gap before the glyph and extending outward, beyond the chip's visual bounds, as far as it needs — never over the label; the rest of the chip's hit area SHALL activate the chip.
CHP-22 WHEN the chip emits remove THE chip SHALL NOT remove itself or move focus: the owner removes it and moves focus to a neighbouring chip, or to the field the chips belong to.
CHP-23 THE chip SHALL be one tab stop — an input chip too: its remove action is not a stop (CHP-18), and Backspace or Delete on the chip removes it (CHP-19) — unless the component that holds it gives its set one stop and the keys to reach each chip (the chip field, CHF-11; a tag group that holds a selection, TGR-20) — and SHALL NOT handle arrow keys; moving between chips with arrow keys, choosing one or several filter chips of a set, and wrapping or scrolling a row belong to the layout that holds the chips.
CHP-24 WHEN the elevation or the colors of the chip change between states THE chip SHALL animate the change over the state duration of its size (comp.chip.motion.state-duration.small or .medium) with comp.chip.motion.state-easing, and SHALL change them at once where motion is reduced.
CHP-25 WHEN the layout direction is right-to-left THE chip SHALL mirror the order of its content and apply its paddings by logical start and end, so that the remove action sits at the left, and SHALL grow a ripple from the point of contact as in left-to-right.
CHP-26 WHILE label is not empty THE chip SHALL expose it as its accessible name, over a name the product gives it on the element (aria-label or aria-labelledby); WHILE label is empty and the product gives the chip a name of its own on the element — aria-label, or aria-labelledby naming an element — THE chip SHALL expose that name as its accessible name; WHILE label is empty and the product gives it none THE chip SHALL still render, and in debug builds report that it has no accessible name; WHILE kind is input and removeLabel is empty THE chip SHALL report the same of its remove action.
CHP-27 WHILE kind is input and destructiveRemove is true THE chip SHALL render the remove action's round container and glyph from the destructive-remove parts of its variant in the remove action's own state (comp.chip.outlined.destructive-remove.container.color and comp.chip.outlined.destructive-remove.icon.color at rest, and so on), whose hovered, focused and pressed container mixes comp.chip.outlined.destructive-remove.state-layer.color and whose hovered, focused and pressed glyph takes the error color (in the default system both are the error role); at rest and while disabled the remove action SHALL look as it does with destructiveRemove false. WHILE destructiveRemove is false THE chip SHALL render the remove action from the remove parts (CHP-20). WHERE kind is not input THE chip SHALL ignore destructiveRemove.
CHP-28 THE chip SHALL NOT make color the only sign that removing is destructive: whatever destructiveRemove is, the remove action SHALL keep the button role, its accessible name removeLabel ("Remove Ada Lovelace"), the glyph cancel, its size and its hit area, and SHALL announce nothing more and nothing less to assistive technology.
CHP-29 THE chip SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the chip is shown THE chip SHALL draw itself for the new direction at once, without a motion of its own, keeping its selection and its focus.
CHP-30 WHERE the layout direction is right-to-left THE chip SHALL draw the glyph of its icon slot mirrored left to right exactly when the system's icon set marks that glyph's name as mirrored or the product forces mirroring for that use, and unmirrored when the product forbids it: the icon helper mirrors the glyph inside its own box, about the box's vertical centre line, changing neither its place, size, color and fill nor the order CHP-25 gives. THE chip SHALL NOT mirror a glyph itself, and SHALL NOT mirror content the product gives as an image or a view of its own rather than by a glyph's name. The check, remove and cancel glyphs the chip draws itself are not marked and are never mirrored.
CHP-31 THE chip SHALL lay out its label as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE chip SHALL still place the label and align its lines by the layout direction, not by the text's own, and SHALL cut a label that does not fit (CHP-17) at the end of its own direction, so that a left-to-right label in a right-to-left layout loses its right end and never its beginning.
CHP-32 WHERE kind is input THE chip SHALL center the remove action's round container on the remove glyph, on the chip's midline, so that it lies from the chip's inline end comp.chip.container.padding-inline-with-remove.medium less half of what comp.chip.remove.container.size.medium exceeds comp.chip.icon.size.medium by (and so on for each size), and never less than nothing; the default system's values SHALL make that distance the same as the round container's distance from the chip's top and from its bottom at the height of its size, so that the round container is inset equally on three sides, its center is the center of the chip's round end, and the two curves are concentric, as an avatar's and the round end's are at the other end. The hover, focus and press of the remove action (CHP-20) and its focus ring SHALL be drawn in that same place.
CHP-33 WHILE kind is action and href is not empty THE chip SHALL be a link and not a button: it SHALL expose the link role, named by label, with no pressed state; SHALL be the platform's own link to href (the web's anchor element; SwiftUI's Link; on Compose, Flutter and React Native an element with the platform's link semantics whose address is opened by the platform's opener), so that whatever the platform gives a link — on the web its context menu, opening in a new tab or window, copying and dragging its address, and the address shown before it is followed — is the platform's, and not the chip's to draw or to take over; and SHALL draw its container, outline, label, icon, elevation and opacity in every state from the same tokens as an action chip of its variant and size that has no destination, with no look of its own for a link and none for a destination already visited.
CHP-34 WHEN the user activates a link chip by pointer, touch or Enter THE chip SHALL emit click exactly once and then open href where target names, unless the product cancelled that click, in which case it SHALL open nothing: the product's own router has taken the navigation. It SHALL show the pressed state as CHP-8 and CHP-9 say and SHALL NOT hold the navigation back for sys.motion.duration.press-hold. THE chip SHALL NOT activate a link chip on Space, which on the web scrolls the page as it does on any link, and SHALL NOT show the pressed state for it; WHERE the platform gives its links other keys or gestures (on the web a modified or a middle click that opens a new tab, on Apple platforms a long press that previews) THE chip SHALL leave them to the platform.
CHP-35 WHILE the chip is a link and target is new THE chip SHALL open href in a new context — on the web a new tab or window that is given no handle to the page that opened it; on the other platforms outside the screen the person is on, as the platform's opener decides — and SHALL draw the glyph open_in_new of the system's icon set at its inline end, after the label, where CHP-16 lays out an input chip's remove glyph: comp.chip.container.gap.medium from the label, at comp.chip.icon.size.medium, with that end padded by comp.chip.container.padding-inline-with-icon.medium at the medium size (and each of its own size otherwise); in the icon color of its look and state (comp.chip.outlined.icon.color for outlined at rest, and so on); through the icon helper, which mirrors it in a right-to-left layout because the icon set marks its name; and hidden from assistive technology. WHILE target is new THE chip SHALL expose targetLabel as its description, read after its name and role, and WHILE targetLabel is empty SHALL still render and in debug builds report that a link that opens a new context does not say so. WHILE target is same THE chip SHALL draw no mark and SHALL ignore targetLabel.
CHP-36 WHILE kind is action and href is not empty and disabled is true THE chip SHALL NOT be a link that can be followed: it SHALL render as CHP-6 says, the mark of CHP-35 in the disabled icon color, SHALL expose the link role with the disabled state and no destination, SHALL NOT be focusable, SHALL NOT emit click, and SHALL open nothing by any means the platform gives a link (on the web the anchor carries no href, so there is no context menu entry, no drag and no new tab for it).
CHP-37 WHILE kind is filter or input THE chip SHALL ignore href, target and targetLabel and stay the chip its kind makes it, and in debug builds SHALL report that only an action chip can be a link: a control that holds a state, or that carries a second action, is not a destination. WHEN target names a value the chip does not define THE chip SHALL behave as same, and in debug builds report the unknown value. IF href is only white space, or names a script to run rather than a place to go (the web's javascript: scheme), or cannot be read as an address by the platform, THE chip SHALL be an action chip without a destination, and in the last two cases report it in debug builds.
CHP-38 WHILE kind is input and removable is false THE chip SHALL draw no remove action, SHALL have no stop for one and SHALL emit no remove, and SHALL keep the padding of a chip without one. WHILE kind is not input THE chip SHALL ignore removable.
CHP-39 WHEN a pointer or touch press lands on an input chip anywhere but on its remove action THE chip SHALL NOT let that press give the remove action focus: focus SHALL go where the platform puts it for a press on a button — to the chip itself where a pressed button takes focus, and nowhere in the chip where it takes none — so that the next Enter or Space activates the chip and never removes it. THE remove action SHALL still take focus when it is pressed itself or moved to by assistive technology or by the product, and the chip SHALL keep the Tab order of CHP-23.
CHP-40 WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE chip SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on the element that carries its role — an id reference by the elements it names in the host's tree or a tree around it, an error message as part of the description while the chip is invalid — except those the chip owns (aria-pressed, aria-disabled), which it SHALL move and not expose. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE chip SHALL take it off that element. WHEN the product sets an aria- attribute the chip owns THE chip SHALL report it in debug builds.

Platform name mapping

Parity does not mean identical APIs. Each platform uses its own idiom for the same contract.

Spec Web SwiftUI Jetpack Compose Flutter React Native
Component tanrei-chip TanreiChip TanreiChip TanreiChip Chip
kind · Property kind isSelected / onRemove onSelectedChange / onRemove TanreiChip / TanreiChip.filter / TanreiChip.input kind
variant · Property variant variant variant variant variant
size · Property size size size size size
selected · Property selected isSelected selected selected selected
value · Property value value value value value
disabled · Property disabled disabled enabled isEnabled disabled
href · Property href destination href href href
target · Property target target target target target
targetLabel · Property targetLabel targetLabel targetLabel targetSemanticLabel targetLabel
label · Property label title label label label
removeLabel · Property removeLabel removeLabel removeContentDescription removeSemanticLabel removeAccessibilityLabel
destructiveRemove · Property destructiveRemove destructiveRemove destructiveRemove destructiveRemove destructiveRemove
removable · Property removable removable removable removable removable
click · Event click action onClick onPressed onPress
change · Event change isSelected onSelectedChange onSelected onSelectedChange
remove · Event remove onRemove onRemove onDeleted onRemove
Notes removeLabel is reflected as the remove-label attribute. destructiveRemove is a boolean property that defaults to false, reflected as the destructive-remove attribute. The icon is the icon slot and the avatar the avatar slot. A filter chip switches its own selected and fires change, as a toggle Button does; remove is a custom event and the element stays in the DOM until the page removes it. With href an action chip's shadow root holds an <a> in place of its <button>, with the same classes, parts and listeners. The link form: href is the href attribute; target is the target attribute with the values same and new (the HTML spellings _self and _blank are read as those two; anything else is same and reported); targetLabel is the target-label attribute. Web-only, forwarded to the anchor and to nothing else: rel (to which new adds noopener) and download. Cancelling click (preventDefault()) cancels the navigation, which is how a client-side router takes it; a modified or a middle click is the browser's. A disabled link is the anchor without href, with role="link" and aria-disabled="true", as the navigation drawer's destination already is. Space is not handed to the press controller. element.click() follows the link. removable is a boolean property that defaults to true; its attribute is the inverse, not-removable, because an HTML boolean attribute cannot default to true. The remove action's <button> has tabindex="-1" (CHP-18): pressable by pointer and assistive technology, focusable by focus(), passed by Tab; while the chip removes, its own button or link carries aria-keyshortcuts="Delete Backspace" (CHP-19). A component that holds chips as a set and gives the set one stop (CHP-23, the chip field) takes each chip and its remove action out of the Tab order in the way the toolbar holds its Buttons. A mouse press anywhere on an input chip but its remove action takes the remove action's tabindex away until that press has placed focus (CHP-39): Safari on macOS gives a pressed <button> no focus and delegates the press to the first element with a tabindex in the shadow root, which would otherwise be the remove action. Kind is chosen by initializer: init(_:action:) is an action chip, init(_:isSelected:) (a Binding<Bool>) a filter chip, init(_:removeLabel:destructiveRemove:action:onRemove:) an input chip, with destructiveRemove defaulting to false. The remove action is a second Button placed beside the chip's own, never inside it, so VoiceOver reaches it as its own element. disabled follows .disabled(_:). The link form, not built on this platform yet: the initializer init(_:destination:), which takes a URL in place of action: (the filter and input initializers take none) draws the same control as a SwiftUI Link, so the address is opened by the environment's openURL — an app that routes addresses itself provides an OpenURLAction, which is how the navigation is cancelled — and VoiceOver reads the link trait. target: TanreiLinkTarget = .same and targetLabel (the accessibility hint) are parameters of that initializer alone. Not built on this platform yet: the remove action out of the focus order, with the keys of CHP-19 told to assistive technology — here it is still a second stop after the chip (CHP-18, CHP-19, CHP-23); removable, default true — an input chip that draws no remove action while it is false (CHP-38); and a holder that gives a set of chips one stop takes each chip's own stops away (CHP-23). Lagging behind CHP-14 (the check drawn at comp.chip.check.weight): its check is drawn at sys.icon.weight. Kind is chosen by overload: onClick alone is an action chip, selected + onSelectedChange a filter chip, onClick + onRemove an input chip. enabled is the inverse flag. The remove action is its own semantics node with Role.Button. The link form, not built on this platform yet: the action overload with href: String (the filter and input overloads take none) opens the address through LocalUriHandler.current after onClick; an app that routes addresses itself provides its own UriHandler. Compose has no link role for a clickable element (only link annotations inside text), so the node drops Role.Button and carries no role, and the README says so. target: TanreiLinkTarget = TanreiLinkTarget.Same; targetLabel is appended to the node's state description. Not built on this platform yet: the remove action out of the focus order, with the keys of CHP-19 told to assistive technology — here it is still a second stop after the chip (CHP-18, CHP-19, CHP-23); removable, default true — an input chip that draws no remove action while it is false (CHP-38); and a holder that gives a set of chips one stop takes each chip's own stops away (CHP-23). Lagging behind CHP-14 (the check drawn at comp.chip.check.weight): its check is drawn at sys.icon.weight. Kind is chosen by constructor: the default constructor is an action chip, .filter a filter chip, .input an input chip (Flutter's own FilterChip and InputChip names for the callbacks). isEnabled: false disables it, as Flutter's chips do. The remove action is its own Semantics node with the button flag. The link form, not built on this platform yet: the TanreiChip.link constructor (href is a Uri) has Semantics(link: true) with the address as its linkUrl. The package has no plugin dependency, so the address is opened by the nearest TanreiLinkOpener, an inherited widget the app provides once (calling url_launcher, or its own router); without one the control opens nothing and reports it in debug builds. target is a TanreiLinkTarget; targetSemanticLabel is the semantics hint. Not built on this platform yet: the remove action out of the focus order, with the keys of CHP-19 told to assistive technology — here it is still a second stop after the chip (CHP-18, CHP-19, CHP-23); removable, default true — an input chip that draws no remove action while it is false (CHP-38); and a holder that gives a set of chips one stop takes each chip's own stops away (CHP-23). Lagging behind CHP-14 (the check drawn at comp.chip.check.weight): its check is drawn at sys.icon.weight. The icon is the icon prop (a glyph name) and the avatar the avatar prop (an image source). The remove action is a second accessible Pressable with the button role, placed beside the chip's own rather than inside it, so a screen reader reaches it as its own element. The link form, not built on this platform yet: an action Chip with href has accessibilityRole="link" and opens the address with Linking.openURL after onPress, unless onPress called event.preventDefault(). target is "same" | "new"; targetLabel is the accessibilityHint. Not built on this platform yet: the remove action out of the focus order, with the keys of CHP-19 told to assistive technology — here it is still a second stop after the chip (CHP-18, CHP-19, CHP-23); removable, default true — an input chip that draws no remove action while it is false (CHP-38); and a holder that gives a set of chips one stop takes each chip's own stops away (CHP-23). Lagging behind CHP-14 (the check drawn at comp.chip.check.weight): its check is drawn at sys.icon.weight.

Tokens

Token Aliases Resolved (light)
--tanrei-comp-chip-avatar-shape sys.shape.corner.full 9999px
--tanrei-comp-chip-avatar-size-medium — 24px
--tanrei-comp-chip-avatar-size-small — 16px
--tanrei-comp-chip-check-weight sys.icon.mark.weight 500
--tanrei-comp-chip-container-gap-medium sys.space.space100 8px
--tanrei-comp-chip-container-gap-small sys.space.space50 4px
--tanrei-comp-chip-container-height-medium — 32px
--tanrei-comp-chip-container-height-small — 24px
--tanrei-comp-chip-container-padding-block-medium — 4px
--tanrei-comp-chip-container-padding-block-small sys.space.space25 2px
--tanrei-comp-chip-container-padding-inline-with-avatar-medium sys.space.space50 4px
--tanrei-comp-chip-container-padding-inline-with-avatar-small sys.space.space50 4px
--tanrei-comp-chip-container-padding-inline-with-icon-medium sys.space.space100 8px
--tanrei-comp-chip-container-padding-inline-with-icon-small sys.space.space75 6px
--tanrei-comp-chip-container-padding-inline-with-remove-medium sys.space.space50 4px
--tanrei-comp-chip-container-padding-inline-with-remove-small sys.space.space25 2px
--tanrei-comp-chip-container-padding-inline-medium sys.space.space200 16px
--tanrei-comp-chip-container-padding-inline-small sys.space.space100 8px
--tanrei-comp-chip-container-shape sys.shape.corner.full 9999px
--tanrei-comp-chip-disabled-opacity — 1
--tanrei-comp-chip-elevated-container-color sys.color.surface-container-lowest #ffffff
--tanrei-comp-chip-elevated-container-elevation sys.elevation.level2 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-destructive-remove-container-color — #000000 · 0%
--tanrei-comp-chip-elevated-destructive-remove-icon-color comp.chip.elevated.remove.icon.color #4b4b4b
--tanrei-comp-chip-elevated-destructive-remove-state-layer-color sys.color.error #cd262f
--tanrei-comp-chip-elevated-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-chip-elevated-disabled-container-elevation —
--tanrei-comp-chip-elevated-disabled-destructive-remove-container-color — #000000 · 0%
--tanrei-comp-chip-elevated-disabled-destructive-remove-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-destructive-remove-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-disabled-remove-container-color — #000000 · 0%
--tanrei-comp-chip-elevated-disabled-remove-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-remove-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-selected-container-color — #1b1b1b · 12%
--tanrei-comp-chip-elevated-disabled-selected-container-elevation —
--tanrei-comp-chip-elevated-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-focused-container-color — #e8e8e8
--tanrei-comp-chip-elevated-focused-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-focused-destructive-remove-container-color — #cd262f · 10%
--tanrei-comp-chip-elevated-focused-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-elevated-focused-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-elevated-focused-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-focused-label-color — #1b1b1b
--tanrei-comp-chip-elevated-focused-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-focused-remove-container-color — #4b4b4b · 10%
--tanrei-comp-chip-elevated-focused-remove-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-focused-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-elevated-focused-selected-container-color — #dadada
--tanrei-comp-chip-elevated-focused-selected-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-focused-selected-icon-color — #262626
--tanrei-comp-chip-elevated-focused-selected-label-color — #262626
--tanrei-comp-chip-elevated-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-focused-selected-state-layer-color — #262626
--tanrei-comp-chip-elevated-focused-state-layer-color — #1b1b1b
--tanrei-comp-chip-elevated-hovered-container-color — #ededed
--tanrei-comp-chip-elevated-hovered-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 1px 0px rgb(0 0 0 / 0.04), 0px 4px 8px -4px rgb(0 0 0 / 0.04), 0px 16px 24px -8px rgb(0 0 0 / 0.06)
--tanrei-comp-chip-elevated-hovered-destructive-remove-container-color — #cd262f · 8%
--tanrei-comp-chip-elevated-hovered-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-elevated-hovered-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-elevated-hovered-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-hovered-label-color — #1b1b1b
--tanrei-comp-chip-elevated-hovered-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-hovered-remove-container-color — #4b4b4b · 8%
--tanrei-comp-chip-elevated-hovered-remove-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-hovered-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-elevated-hovered-selected-container-color — #dedede
--tanrei-comp-chip-elevated-hovered-selected-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 1px 0px rgb(0 0 0 / 0.04), 0px 4px 8px -4px rgb(0 0 0 / 0.04), 0px 16px 24px -8px rgb(0 0 0 / 0.06)
--tanrei-comp-chip-elevated-hovered-selected-icon-color — #262626
--tanrei-comp-chip-elevated-hovered-selected-label-color — #262626
--tanrei-comp-chip-elevated-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-hovered-selected-state-layer-color — #262626
--tanrei-comp-chip-elevated-hovered-state-layer-color — #1b1b1b
--tanrei-comp-chip-elevated-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-chip-elevated-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-chip-elevated-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-pressed-container-color — #e8e8e8
--tanrei-comp-chip-elevated-pressed-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-pressed-destructive-remove-container-color — #cd262f · 10%
--tanrei-comp-chip-elevated-pressed-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-elevated-pressed-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-elevated-pressed-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-pressed-label-color — #1b1b1b
--tanrei-comp-chip-elevated-pressed-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-pressed-remove-container-color — #4b4b4b · 10%
--tanrei-comp-chip-elevated-pressed-remove-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-pressed-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-elevated-pressed-selected-container-color — #dadada
--tanrei-comp-chip-elevated-pressed-selected-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-pressed-selected-icon-color — #262626
--tanrei-comp-chip-elevated-pressed-selected-label-color — #262626
--tanrei-comp-chip-elevated-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-pressed-selected-state-layer-color — #262626
--tanrei-comp-chip-elevated-pressed-state-layer-color — #1b1b1b
--tanrei-comp-chip-elevated-remove-container-color — #000000 · 0%
--tanrei-comp-chip-elevated-remove-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-chip-elevated-remove-state-layer-color comp.chip.elevated.remove.icon.color #4b4b4b
--tanrei-comp-chip-elevated-selected-container-color sys.color.secondary-container #eeeeee
--tanrei-comp-chip-elevated-selected-container-elevation sys.elevation.level2 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-selected-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-chip-elevated-selected-label-color sys.color.on-secondary-container #262626
--tanrei-comp-chip-elevated-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-selected-state-layer-color comp.chip.elevated.selected.label.color #262626
--tanrei-comp-chip-elevated-state-layer-color comp.chip.elevated.label.color #1b1b1b
--tanrei-comp-chip-focused-opacity — 1
--tanrei-comp-chip-hovered-opacity — 1
--tanrei-comp-chip-icon-size-medium sys.icon.medium 24px
--tanrei-comp-chip-icon-size-small sys.icon.small 20px
--tanrei-comp-chip-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-chip-motion-selection-duration sys.motion.role.selection.mark 250ms
--tanrei-comp-chip-motion-selection-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-chip-motion-state-duration-medium sys.motion.role.state.default 250ms
--tanrei-comp-chip-motion-state-duration-small sys.motion.role.state.compact 200ms
--tanrei-comp-chip-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-chip-outline-width sys.border.hairline 1px
--tanrei-comp-chip-outlined-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-container-elevation sys.elevation.level0
--tanrei-comp-chip-outlined-destructive-remove-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-destructive-remove-icon-color comp.chip.outlined.remove.icon.color #4b4b4b
--tanrei-comp-chip-outlined-destructive-remove-state-layer-color sys.color.error #cd262f
--tanrei-comp-chip-outlined-disabled-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-disabled-container-elevation —
--tanrei-comp-chip-outlined-disabled-destructive-remove-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-disabled-destructive-remove-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-destructive-remove-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-outline-color — #000000 · 8%
--tanrei-comp-chip-outlined-disabled-remove-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-disabled-remove-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-remove-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-selected-container-color — #1b1b1b · 12%
--tanrei-comp-chip-outlined-disabled-selected-container-elevation —
--tanrei-comp-chip-outlined-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-focused-container-color — #1b1b1b · 10%
--tanrei-comp-chip-outlined-focused-container-elevation —
--tanrei-comp-chip-outlined-focused-destructive-remove-container-color — #cd262f · 10%
--tanrei-comp-chip-outlined-focused-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-outlined-focused-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-outlined-focused-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-focused-label-color — #1b1b1b
--tanrei-comp-chip-outlined-focused-outline-color — #000000 · 8%
--tanrei-comp-chip-outlined-focused-remove-container-color — #4b4b4b · 10%
--tanrei-comp-chip-outlined-focused-remove-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-focused-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-outlined-focused-selected-container-color — #dadada
--tanrei-comp-chip-outlined-focused-selected-container-elevation —
--tanrei-comp-chip-outlined-focused-selected-icon-color — #262626
--tanrei-comp-chip-outlined-focused-selected-label-color — #262626
--tanrei-comp-chip-outlined-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-focused-selected-state-layer-color — #262626
--tanrei-comp-chip-outlined-focused-state-layer-color — #1b1b1b
--tanrei-comp-chip-outlined-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-chip-outlined-hovered-container-elevation —
--tanrei-comp-chip-outlined-hovered-destructive-remove-container-color — #cd262f · 8%
--tanrei-comp-chip-outlined-hovered-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-outlined-hovered-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-outlined-hovered-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-hovered-label-color — #1b1b1b
--tanrei-comp-chip-outlined-hovered-outline-color — #000000 · 8%
--tanrei-comp-chip-outlined-hovered-remove-container-color — #4b4b4b · 8%
--tanrei-comp-chip-outlined-hovered-remove-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-hovered-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-outlined-hovered-selected-container-color — #dedede
--tanrei-comp-chip-outlined-hovered-selected-container-elevation —
--tanrei-comp-chip-outlined-hovered-selected-icon-color — #262626
--tanrei-comp-chip-outlined-hovered-selected-label-color — #262626
--tanrei-comp-chip-outlined-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-hovered-selected-state-layer-color — #262626
--tanrei-comp-chip-outlined-hovered-state-layer-color — #1b1b1b
--tanrei-comp-chip-outlined-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-chip-outlined-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-chip-outlined-outline-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-chip-outlined-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-chip-outlined-pressed-container-elevation —
--tanrei-comp-chip-outlined-pressed-destructive-remove-container-color — #cd262f · 10%
--tanrei-comp-chip-outlined-pressed-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-outlined-pressed-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-outlined-pressed-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-pressed-label-color — #1b1b1b
--tanrei-comp-chip-outlined-pressed-outline-color — #000000 · 8%
--tanrei-comp-chip-outlined-pressed-remove-container-color — #4b4b4b · 10%
--tanrei-comp-chip-outlined-pressed-remove-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-pressed-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-outlined-pressed-selected-container-color — #dadada
--tanrei-comp-chip-outlined-pressed-selected-container-elevation —
--tanrei-comp-chip-outlined-pressed-selected-icon-color — #262626
--tanrei-comp-chip-outlined-pressed-selected-label-color — #262626
--tanrei-comp-chip-outlined-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-pressed-selected-state-layer-color — #262626
--tanrei-comp-chip-outlined-pressed-state-layer-color — #1b1b1b
--tanrei-comp-chip-outlined-remove-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-remove-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-chip-outlined-remove-state-layer-color comp.chip.outlined.remove.icon.color #4b4b4b
--tanrei-comp-chip-outlined-selected-container-color sys.color.secondary-container #eeeeee
--tanrei-comp-chip-outlined-selected-container-elevation sys.elevation.level0
--tanrei-comp-chip-outlined-selected-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-chip-outlined-selected-label-color sys.color.on-secondary-container #262626
--tanrei-comp-chip-outlined-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-selected-state-layer-color comp.chip.outlined.selected.label.color #262626
--tanrei-comp-chip-outlined-state-layer-color comp.chip.outlined.label.color #1b1b1b
--tanrei-comp-chip-pressed-opacity — 1
--tanrei-comp-chip-remove-container-shape sys.shape.corner.full 9999px
--tanrei-comp-chip-remove-container-size-medium — 28px
--tanrei-comp-chip-remove-container-size-small — 24px
--tanrei-comp-chip-label-typescale-medium sys.typescale.label 500 14px/1.4286
--tanrei-comp-chip-label-typescale-small sys.typescale.label-small 500 12px/1.3333

Fixtures

Canonical renderings. Every platform snapshots each fixture under the same id so the results can be compared.

Generated from specs/chip.spec.json, tokens/comp/chip.tokens.json, design/tokens/

Guidelines

When to use

Use chips for small, related choices shown in a row: filters above a list, suggested replies under a message, the recipients of an email. A button is too heavy for a row of ten, a checkbox reads as part of a form, and a badge or a tag cannot be pressed.

Choosing a kind

kind decides what the chip does, its role and its parts. It does not change the look.

Kind What it is Use for
Action (default) A button, or with href a link An assist action tied to the content, such as “Add to calendar” next to a date, or a suggestion, such as the reply “Sounds good”. With a destination, a shortcut to a related page.
Filter A button that is on or off Narrowing a list: “Vegetarian”, “Under 30 min”. When on, it shows a check and the selected colors.
Input An item the user entered, with a remove action Recipients, tags, search terms. It can show an avatar in place of an icon.

Material Design 3 has four chip families. Tanrei counts three kinds: its assist and suggestion chips are both buttons with a label and an optional icon, so both are the action kind. A design system that wants suggestions to look quieter adds its own variant for them.

Choosing a variant and size

Variant Use for
Outlined (default) Chips on a plain surface. The edge is the hairline: it sets the chip apart quietly, and the label carries the meaning.
Elevated Chips over a photo, a map or a busy background, where an edge alone would get lost.

Use medium (32) for most chips, and small (24) in dense places, such as entered items inside a field or filters above a table. A static label in a table row is a tag. Both sizes meet the minimum target.

Chip, tag or badge

A chip is a control: it is pressed, switched or removed, takes focus and has states. A tag is a static label on one item, such as a status or a category, and never answers a pointer. A badge is a dot or a count attached to the corner of another element.

  • If it is pressed, selected or removed, use a chip.
  • If it only says something about the item it sits on, use a tag.
  • If it is a number or a dot on top of something else, use a badge.

Don’t use a chip that does nothing as a label, and don’t disable a chip to make it one.

A control with a destination is a link. An action chip that only takes the person somewhere takes href, and is then a real link and not a button: on the web an anchor, which can be opened in a new tab and whose address can be copied. It looks exactly like an action chip in every state, and there is no visited state.

  • Enter follows a link chip. Space does not: it does what it does on any link.
  • click fires first, then the destination opens. Cancel the click to take the navigation yourself, as a client-side router does.
  • With target set to new the chip shows the open_in_new glyph after its label, and needs targetLabel, the words read after its name, such as “opens in a new tab”. Tanrei ships no words of its own.
  • A disabled link chip cannot be followed and leaves the focus order.
  • Only an action chip can be a link. A filter chip holds a state and an input chip carries a second action, so both ignore href, and debug builds report it.

The remove action

An input chip ends with a remove action: the filled cancel glyph on a round container a little larger than it, which shows as a ring around the glyph on hover, focus and press. In the default system the round container is concentric with the chip’s round end, as far from the end as from the top and the bottom. It takes no room of its own, so it does not make the chip wider or taller. The remove action is a separate button with its own name, its own hover, focus and press, and its own hit area, which extends outward and never covers the label. Pressing it, or pressing Backspace or Delete while the chip has focus, asks to remove the chip. The chip never removes itself. Your code removes the item and moves focus: to the next chip, the previous one, or the field the chips belong to.

The remove action can read as destructive (destructiveRemove, off by default). At rest it looks as it would without the flag. While it is hovered, focused or pressed, its glyph and its state layer take the error color. Most removals take away something the person can put back in one step, such as an applied filter or a search term, so the remove action is plain unless you set the flag. Set it where removing loses something that cannot be put back in a step, such as an attachment or a recipient already invited. On the web the attribute is destructive-remove. The flag changes colors and nothing else, and it is ignored on the other kinds.

An input chip that cannot be taken away now, such as an entry of a read-only field, draws no remove action: set removable to false. No action stands there doing nothing, Backspace and Delete ask for nothing, and the chip is padded as a chip without one. On the web the attribute is the inverse, not-removable. It is ignored on the other kinds.

Used in the chip field, where each entry is a small input chip. There the field gives the whole set one Tab stop and its own keys, removes a chip that asks, and moves focus. On the web a component that holds chips this way sets tabindex="-1" on each chip, which takes the chip and its remove action out of the Tab order and leaves both focusable.

  • Keep labels short, one to three words. A chip stays on one line and cuts its label with an ellipsis only when the layout gives it too little width.
  • Use one variant for a row of chips.
  • Name the item in the remove label: “Remove Ada Lovelace”, not “Remove”.
  • Give the remove action the destructive look only where removing loses something that cannot be put back in a step, such as an attachment. Leave it plain where the item comes back in one step, such as a filter or a search term.
  • On the web, put a row of chips in a tag group: it wraps them onto lines, keeps the distance between them and exposes them as one list. On the other platforms, and for a row that scrolls, lay the chips out yourself.

Don’t

  • Don’t use a filter chip for a setting that applies at once and stands alone. Use a switch.
  • Don’t use a chip for the main action of a screen. Use a button or a main action button.
  • Don’t navigate from an action chip’s click handler. A chip that goes somewhere takes href.
  • Don’t mix kinds in one row, such as filters beside suggested actions.
  • Don’t give a filter chip a label that changes with its state. The state is announced separately.

Accessibility

Role
button (link while an action chip has href)
Labeling
label is the chip's accessible name. An input chip's remove action is a separate button named by removeLabel, which names the item and the action ("Remove Ada Lovelace"), with or without destructiveRemove. The icon, the check, the cancel glyph and the avatar are decorative: a filter chip's state is announced from its pressed state, never from the check.
Minimum target
The hit area meets the minimum target size even when the visual size is smaller. minimum 32px · pointer 44px · spacing 8px · touch 48px

Keyboard

Keys Action
Tab Moves focus to the chip. An input chip is one stop too: its remove action is not in the Tab order, and is reached by pointer, touch and assistive technology.
Enter / Space Activates the chip (a filter chip switches selected); on the remove action, removes the input chip. A link chip is activated by Enter alone: Space does what it does on any link (on the web it scrolls the page).
Backspace / Delete On a focused input chip, removes it (CHP-19). The chip tells assistive technology of these keys (on the web aria-keyshortcuts="Delete Backspace"), since its remove action is not met by Tab.

Notes

  • A filter chip is a button with a pressed state (aria-pressed on the web, the selected trait on Apple platforms, a toggleable state on Android), as a toggle Button is, not a checkbox.
  • An input chip is two elements on every platform: the chip (role button) and its remove action (role button, named by removeLabel). On the web the remove action is a second `<button>` in the shadow root; on the other platforms it is its own accessibility element with its own hit area. It is one Tab stop: the remove action stays out of the Tab order, as the delete icon of MUI's Chip, the remove button of React Aria's TagGroup and the dismiss of Fluent's Tag do, and the keyboard removes the chip with Backspace or Delete on the chip itself, as a person who removes several entries in a row expects — one press each, without a second stop per chip to pass.
  • The chip does not group itself. A row of chips is laid out in wrapping lines, exposed as one list and given focus after a removal by the Tag group (TGR-1, TGR-7, TGR-9), or by the layout that holds the chips; one Tab stop with arrow keys, and single or multiple selection among filter chips, are a Tag group's that holds a selection (TGR-17 to TGR-21), or a Toolbar's.
  • destructiveRemove adds a color, never a meaning: the name removeLabel, the cancel glyph and the button role already say what the action does, so a person who cannot tell the error color from the resting one loses nothing (WCAG 1.4.1). The error glyph is a graphical object and keeps at least 3:1 against the chip's container in every scheme of the default system: the error role is held to 4.5:1 on every surface role by the token invariants, and the outlined chip shows a surface through its clear container while the elevated one is surface-container-lowest; under the state layer the lowest ratio is 3.98:1 (light scheme, on surface-container-highest, at the focus and pressed opacity).
  • The label does not wrap; at 200% text size the chip grows in height and the layout that holds a row lets it wrap to the next line or scroll.
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.
  • In the default system the outline of an outlined chip is the hairline (the divider's color), far below 3:1 against the surface. That is within WCAG 1.4.11: the edge is not what identifies the chip. Its label is text held to 4.5:1, a selected filter chip is shown by its fill and its check, and focus by the focus ring. A product whose chips must show a 3:1 edge sets comp.chip.outlined.outline.color to sys.color.outline in a layer.
  • An action chip with a destination is a link: the link role, the platform's own link, Enter and not Space. A filter chip and an input chip take no destination. No visited state is drawn.
  • A link chip that opens a new context shows the open_in_new mark after its label and says so through targetLabel; the words are the product's.

Generated from specs/chip.spec.json, tokens/sys/measure.tokens.json

What the component handles

  • Every chip exposes the button role, the label as its name, and its disabled state. The icon, the check and the avatar are hidden from assistive technology.
  • An action chip with href exposes the link role instead, with the same name. The mark of a link that opens a new tab is hidden from assistive technology; targetLabel is the link’s description, read after its name and role.
  • A filter chip also exposes whether it is on, as a toggle button does: a pressed state on the web, the selected trait on Apple platforms, a toggleable state on Android. Its state is never announced from the check.
  • An input chip is two elements, the chip and its remove action, a separate button named by the remove label, but one stop in the focus order: Tab reaches the chip and passes the remove action, which stays pressable by pointer, touch and assistive technology. On the focused chip, Backspace or Delete asks to remove it, and the chip tells assistive technology so (on the web with aria-keyshortcuts).
  • Enter and Space activate the chip, or the remove action when it has focus. A link chip is activated by Enter alone. Each shows its own focus indicator, for keyboard focus only.
  • The chip’s hit area meets the minimum target at both sizes. The remove action has a hit area of its own that meets it as well, without covering the label.
  • The destructive remove look is never the only cue. With destructiveRemove on or off, the remove action keeps its role, its name, its glyph, its size and its hit area, and announces nothing more. The name, “Remove Ada Lovelace”, says what it does.
  • The full label stays the accessible name even when it is cut. At larger text sizes the chip grows in height and stays a pill.
  • In the default system the edge of an outlined chip is the hairline, which is below 3:1 against the surface. The edge is not what identifies the chip: the label is text at full contrast, a selected filter chip shows its fill and its check, and focus shows the focus ring. A product that needs a 3:1 edge sets comp.chip.outlined.outline.color to sys.color.outline in a layer.
  • Debug builds report a missing label, a missing remove label on an input chip, a link that opens a new tab without a targetLabel, a destination on a filter or an input chip, and an unknown kind, which renders and behaves as an action chip.

Known platform gaps:

  • React Native: Backspace and Delete are delivered through a key event whose delivery on iOS and Android has not been checked on a device. The remove action stays reachable by Tab either way.
  • React Native: that VoiceOver and TalkBack reach the chip and its remove action as two elements in that order, and the remove action’s hit area, have been tested without a device only. On Android a hit area cannot reach past the parent view, so leave room around a row of input chips.
  • Flutter: the remove action’s hit area reaches past the chip’s end, and Flutter makes room for it by widening the input chip’s layout, so a row of input chips is spaced a little wider.
  • Compose: the remove action’s hit area reaches past the chip’s end without widening the chip, so a chip placed right after it takes presses where the two overlap. A touch there, keys through the platform’s key handling and TalkBack have been tested under Robolectric only, not yet on a device.
  • SwiftUI: Backspace and Delete on the chip, and Tab from the chip to its remove action, are tested on their parts but not yet driven end to end. Where a press dims the control (cupertino), the remove glyph dims one frame after the chip.
  • SwiftUI and Compose keep the icon, the check, the remove glyph and the avatar at their own size as text grows, while the label grows. Whether they should grow with the label is not yet decided.
  • SwiftUI, Compose, Flutter and React Native, which ship the previous revision, still make an input chip’s remove action a second stop after the chip, and do not yet tell assistive technology that Backspace and Delete remove it. Each takes the remove action out of the focus order when it catches up.
  • The web draws the check and the remove glyph as the icon set’s check and cancel, by the names the spec gives (CHP-13, CHP-18). SwiftUI, Compose, Flutter and React Native, which ship the previous revision, still draw the remove action with close (Flutter by a Material Symbols code point), and without an icon font Compose and SwiftUI draw the check and the cross as vectors of their own. Each draws cancel and check by name, and no vector of its own, when it catches up.

What you need to do

  • When the user removes an input chip, remove it and move focus to a neighbouring chip or to the field. The chip does nothing after it asks.
  • Give every input chip a remove label that names its item. Several “Remove” buttons in a row cannot be told apart.
  • Give a link chip that opens a new tab or window a targetLabel in the language of your product.
  • Give a row of filter chips a visible heading or a group name that says what they filter.
  • Provide the keyboard model of a row yourself if you need one, such as arrow keys between chips. Each chip handles only Tab, Enter, Space and, on an input chip, Backspace and Delete.