Chips

Assist chip

Outlined

<ChipSet>
	<AssistChip label="Assist chip" />
	<AssistChip label="With icon">
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</AssistChip>
</ChipSet>

Elevated

<ChipSet>
	<AssistChip elevated label="Assist chip" />
	<AssistChip elevated label="With icon">
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</AssistChip>
</ChipSet>

Disabled

<ChipSet>
	<AssistChip disabled label="Outlined" />
	<AssistChip disabled label="Outlined with icon">
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</AssistChip>
	<AssistChip disabled elevated label="Elevated" />
	<AssistChip disabled elevated label="Elevated with icon">
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</AssistChip>
</ChipSet>
<ChipSet>
	<AssistChip label="Visit website" href="https://noph.dev">
		{#snippet icon()}<Icon>open_in_new</Icon>{/snippet}
	</AssistChip>
</ChipSet>

Filter chip

Outlined

<ChipSet>
	<FilterChip label="Filter chip" />
	<FilterChip label="Selected" selected />
	<FilterChip label="With icon">
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</FilterChip>
	<FilterChip label="Selected with icon" selected>
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</FilterChip>
</ChipSet>

Elevated

<ChipSet>
	<FilterChip elevated label="Filter chip" />
	<FilterChip elevated label="Selected" selected />
	<FilterChip elevated label="With icon">
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</FilterChip>
	<FilterChip elevated label="Selected with icon" selected>
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</FilterChip>
</ChipSet>

Removable

<ChipSet>
	<FilterChip label="Removable" removable onremove={() => {}} />
	<FilterChip label="Selected" removable selected onremove={() => {}} />
	<FilterChip elevated label="Elevated" removable onremove={() => {}} />
</ChipSet>

Disabled

<ChipSet>
	<FilterChip disabled label="Outlined" />
	<FilterChip disabled label="Outlined selected" selected />
	<FilterChip disabled elevated label="Elevated" />
	<FilterChip disabled elevated label="Elevated selected" selected />
	<FilterChip disabled label="With icon">
		{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
	</FilterChip>
</ChipSet>

Group

let filterGroup = $state<string[]>([])

<ChipSet>
	<FilterChip label="Running" value="running" bind:group={filterGroup} />
	<FilterChip label="Walking" value="walking" bind:group={filterGroup} />
	<FilterChip label="Cycling" value="cycling" bind:group={filterGroup} />
	<FilterChip label="Swimming" value="swimming" bind:group={filterGroup} />
</ChipSet>

Input chip

Default

<ChipSet>
	<InputChip label="Input chip" />
	<InputChip label="With icon">
		{#snippet icon()}<Icon>person</Icon>{/snippet}
	</InputChip>
</ChipSet>

Selected

<ChipSet>
	<InputChip selected label="Selected" />
	<InputChip selected label="With icon">
		{#snippet icon()}<Icon>person</Icon>{/snippet}
	</InputChip>
</ChipSet>

Separate actions

The chip body is a button of its own, so it can be focused and activated with the keyboard. Use onclick for the primary action, for example selecting the chip or switching it into an edit mode, and onremove for the trailing icon.

<InputChip
	bind:selected
	label={label}
	ariaLabelRemove="Remove {label}"
	onclick={() => (selected = !selected)}
	onremove={() => remove()}
/>

Disabled

<ChipSet>
	<InputChip disabled label="Disabled" />
	<InputChip disabled selected label="Disabled selected" />
	<InputChip disabled label="With icon">
		{#snippet icon()}<Icon>person</Icon>{/snippet}
	</InputChip>
</ChipSet>

Chips with text fields

let pendingDeleteIndex: number | null = $state(null)

<TextField
	type="email"
	label="Emails"
	variant="outlined"
	placeholder="Add email..."
	bind:value={email}
	populated={emails.length > 0}
	onkeydown={(e) => {
		if (e.key === 'Enter') {
			e.preventDefault()
			if (e.currentTarget.value && e.currentTarget.reportValidity()) {
				emails.push(e.currentTarget.value)
				email = ''
			}
		} else if (e.key === 'Backspace' && !e.currentTarget.value && emails.length > 0) {
			e.preventDefault()
			if (pendingDeleteIndex !== null) {
				emails.splice(pendingDeleteIndex, 1)
				pendingDeleteIndex = null
			} else {
				pendingDeleteIndex = emails.length - 1
			}
		} else {
			pendingDeleteIndex = null
		}
	}}
	onfocus={() => { pendingDeleteIndex = null }}
>
	<ChipSet chipsCount={emails.length}>
		{#each emails as email, index (index)}
			<InputChip
				name="email"
				value={email}
				selected={pendingDeleteIndex === index}
				onremove={() => {
					if (index > -1) {
						emails.splice(index, 1)
						pendingDeleteIndex = null
					}
				}}
			/>
		{/each}
	</ChipSet>
</TextField>

Accessibility

Every chip type renders the element that matches what it does. An assist chip is a <button>, or an <a> when you pass an href. A filter chip is a checkbox with a label, so it reports its selected state to assistive technology on its own. An input chip is a button with a second button for removing it.

Wrap chips in a ChipSet to give them a single tab stop: Tab moves into and out of the set, and move between the chips and wrap around at the ends, and Home and End jump to the first and the last one.

A remove button carries no text of its own, so give it one through ariaLabelRemove. The default is the bare "Remove", which does not say what is being removed, so name the chip in it.

<InputChip
	label={email}
	ariaLabelRemove="Remove {email}"
	onremove={() => remove(email)}
/>

Theming

TokenDefault value
--np-assist-chip-label-text-color--np-color-on-surface
--np-assist-chip-outline-color--np-color-outline-variant
--np-filter-chip-container-shape--np-shape-corner-small
--np-filter-chip-outline-color--np-color-outline-variant
--np-input-chip-container-shape--np-shape-corner-small
--np-input-chip-outline-color--np-color-outline-variant

The colours of a filter and an input chip come from the theme's secondary-container and on-secondary-container roles while they are selected, and from on-surface-variant while they are not. An elevated chip takes its shadow from the elevation roles.

Example

<FilterChip
	label="Tertiary"
	--np-filter-chip-container-shape="var(--np-shape-corner-full)"
	--np-filter-chip-outline-color="var(--np-color-tertiary)"
/>

API

ChipSet attributes

Everything you pass is forwarded to the wrapping <div>, so class, style and aria-label work as expected.

AttributeTypeDefaultDescription
chipsCountnumber | undefinedundefinedNumber of chips in the set. Pass it when the set lives inside a text field, so the field can make room for the chips.

AssistChip attributes

An assist chip takes the Button attributes apart from variant, start and end, which the chip sets itself. That includes href, onclick and type.

AttributeTypeDefaultDescription
labelstring''Text of the chip.
iconSnippet | undefinedundefinedLeading icon.
elevatedbooleanfalseDrops the outline and gives the chip a shadow instead.
disabledbooleanfalseMakes the chip non-interactive.
elementHTMLDivElement | undefinedundefinedBindable reference to the chip element.

FilterChip attributes

AttributeTypeDefaultDescription
labelstring''Text of the chip.
selectedboolean | undefinedundefinedBindable. Whether the filter is on.
group(string | number)[] | null | undefinedundefinedBindable. Bind several chips that share a name to one array and their values are added and removed as they are toggled.
namestring | undefinedundefinedForm field name of the underlying checkbox.
valuestring | undefinedundefinedValue submitted with the form, and the entry used in group.
defaultSelectedboolean | null | undefinedundefinedSelected state a form reset returns the chip to.
removablebooleanfalseAdds a trailing remove button.
onremoveMouseEventHandler | undefinedundefinedCalled when the remove button is clicked.
ariaLabelRemovestring'Remove'Accessible name of the remove button.
iconSnippet | undefinedundefinedLeading icon, shown while the chip is not selected.
elevatedbooleanfalseDrops the outline and gives the chip a shadow instead.
disabledbooleanfalseMakes the chip non-interactive.
elementHTMLDivElement | undefinedundefinedBindable reference to the chip element.

InputChip attributes

AttributeTypeDefaultDescription
labelstring''Text of the chip. Falls back to value when it is empty.
selectedboolean | undefinedundefinedBindable. Highlights the chip, for instance to show which one Backspace would remove next.
namestring | undefinedundefinedForm field name. The value is submitted through a hidden input.
valuestring | number | undefinedundefinedValue submitted with the form.
onclickMouseEventHandler | undefinedundefinedCalled when the chip itself is clicked, not the remove button.
onremoveMouseEventHandler | undefinedundefinedCalled when the remove button is clicked.
ariaLabelRemovestring'Remove'Accessible name of the remove button.
iconSnippet | undefinedundefinedLeading icon or avatar.
disabledbooleanfalseMakes the chip non-interactive.
elementHTMLDivElement | undefinedundefinedBindable reference to the chip element.
actionElementHTMLButtonElement | undefinedundefinedBindable reference to the chip's own button.