Chips
Chips are compact actions and choices in the flow of the content. Material 3 has four: assist for a helpful action, suggestion for something the app proposes, filter for narrowing a list, and input for what someone has already entered.
Assist chip
Outlined
<script lang="ts">
import { AssistChip, ChipSet } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<ChipSet>
<AssistChip label="Assist chip" />
<AssistChip label="With icon">
{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
</AssistChip>
</ChipSet>
Elevated
<script lang="ts">
import { AssistChip, ChipSet } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<ChipSet>
<AssistChip variant="elevated" label="Assist chip" />
<AssistChip variant="elevated" label="With icon">
{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
</AssistChip>
</ChipSet>
Disabled
<script lang="ts">
import { AssistChip, ChipSet } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<ChipSet>
<AssistChip disabled label="Outlined" />
<AssistChip disabled label="Outlined with icon">
{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
</AssistChip>
<AssistChip disabled variant="elevated" label="Elevated" />
<AssistChip disabled variant="elevated" label="Elevated with icon">
{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
</AssistChip>
</ChipSet>
As link
<script lang="ts">
import { AssistChip, ChipSet } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<ChipSet>
<AssistChip label="Visit website" href="https://noph.dev" target="_blank" rel="noopener">
{#snippet icon()}<Icon>open_in_new</Icon>{/snippet}
</AssistChip>
</ChipSet>
Suggestion chip
A suggestion chip carries a generated suggestion rather than an action, so it is text only and takes no icon. Everything else matches an assist chip.
<script lang="ts">
import { ChipSet, SuggestionChip } from 'noph-ui'
</script>
<ChipSet>
<SuggestionChip label="Sounds good" />
<SuggestionChip label="On my way" />
<SuggestionChip variant="elevated" label="Elevated" />
<SuggestionChip disabled label="Disabled" />
</ChipSet>
Filter chip
Outlined
<script lang="ts">
import { ChipSet, FilterChip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<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
<script lang="ts">
import { ChipSet, FilterChip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<ChipSet>
<FilterChip variant="elevated" label="Filter chip" />
<FilterChip variant="elevated" label="Selected" selected />
<FilterChip variant="elevated" label="With icon">
{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
</FilterChip>
<FilterChip variant="elevated" label="Selected with icon" selected>
{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
</FilterChip>
</ChipSet>
Removable
<script lang="ts">
import { ChipSet, FilterChip, Snackbar } from 'noph-ui'
let snackbar: ReturnType<typeof Snackbar> | undefined = $state()
</script>
<ChipSet>
<FilterChip label="Removable" removable onremove={() => snackbar?.show()} />
<FilterChip label="Selected" removable selected onremove={() => snackbar?.show()} />
<FilterChip variant="elevated" label="Elevated" removable onremove={() => snackbar?.show()} />
</ChipSet>
<Snackbar bind:this={snackbar} label="Remove was clicked" />
Disabled
<script lang="ts">
import { ChipSet, FilterChip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<ChipSet>
<FilterChip disabled label="Outlined" />
<FilterChip disabled label="Outlined selected" selected />
<FilterChip disabled variant="elevated" label="Elevated" />
<FilterChip disabled variant="elevated" label="Elevated selected" selected />
<FilterChip disabled label="With icon">
{#snippet icon()}<Icon>bookmark</Icon>{/snippet}
</FilterChip>
</ChipSet>
Group
<script lang="ts">
import { ChipSet, FilterChip } from 'noph-ui'
let filterGroup = $state<string[]>([])
</script>
<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
<script lang="ts">
import { ChipSet, InputChip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<ChipSet>
<InputChip label="Input chip" />
<InputChip label="With icon">
{#snippet icon()}<Icon>person</Icon>{/snippet}
</InputChip>
</ChipSet>
Selected
<script lang="ts">
import { ChipSet, InputChip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<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.
<script lang="ts">
import { ChipSet, InputChip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
let editableLabel = $state('Fabian Reza')
let editableSelected = $state(false)
</script>
<ChipSet>
<InputChip
bind:selected={editableSelected}
label={editableLabel}
removeAriaLabel="Remove {editableLabel}"
onclick={() => (editableSelected = !editableSelected)}
onremove={() => (editableLabel = 'Removed')}
>
{#snippet icon()}<Icon>person</Icon>{/snippet}
</InputChip>
</ChipSet>
Disabled
<script lang="ts">
import { ChipSet, InputChip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<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
<script lang="ts">
import { ChipSet, InputChip, TextField } from 'noph-ui'
let emails: string[] = $state(['info@noph.dev'])
let email = $state('')
let pendingDeleteIndex: number | null = $state(null)
</script>
<TextField
type="email"
label="Emails"
variant="outlined"
placeholder="Add email..."
style="width:340px"
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
}}
onblur={(e) => {
if (e.currentTarget.value && e.currentTarget.reportValidity()) {
emails.push(e.currentTarget.value)
email = ''
} else {
pendingDeleteIndex = null
}
}}
>
<ChipSet>
{#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 removeAriaLabel. The default is the bare "Remove", which does not say
what is being removed, so name the chip in it.
<InputChip
label={email}
removeAriaLabel="Remove {email}"
onremove={() => remove(email)}
/>Theming
| Token | Default 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
<script lang="ts">
import { AssistChip, ChipSet, FilterChip } from 'noph-ui'
</script>
<ChipSet>
<FilterChip
label="Tertiary"
--np-filter-chip-container-shape="var(--np-shape-corner-full)"
--np-filter-chip-outline-color="var(--np-color-tertiary)"
/>
<AssistChip
label="Tertiary"
--np-assist-chip-outline-color="var(--np-color-tertiary)"
--np-assist-chip-label-text-color="var(--np-color-tertiary)"
/>
</ChipSet>
API
ChipSet attributes
Everything you pass is forwarded to the wrapping <div>, so class, style and aria-label work as expected. bind:element gives you that <div>. The set makes room for its
chips on its own, from whether it has any, so there is nothing to configure.
AssistChip attributes
An assist chip renders a <button>, or an <a> when you give
it an href, and takes that element's attributes: onclick, type, class, aria-* and the rest. bind:element gives you whichever element it rendered. It does not take size or shape: a chip has one of each.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | '' | Text of the chip. |
icon | Snippet | undefined | undefined | Leading icon. |
variant | 'outlined' | 'elevated' | 'outlined' | elevated drops the outline and gives the chip a shadow instead. |
disabled | boolean | false | Makes the chip non-interactive. |
element | HTMLDivElement | undefined | undefined | Bindable reference to the chip element. |
FilterChip attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | '' | Text of the chip. |
selected | boolean | undefined | undefined | Bindable. Whether the filter is on. |
group | (string | number)[] | null | undefined | undefined | Bindable. Bind several chips that share a name to one array and their values are added and removed as they are toggled. |
name | string | undefined | undefined | Form field name of the underlying checkbox. |
value | string | undefined | undefined | Value submitted with the form, and the entry used in group. |
defaultSelected | boolean | null | undefined | undefined | Selected state a form reset returns the chip to. |
removable | boolean | false | Adds a trailing remove button. |
onremove | MouseEventHandler | undefined | undefined | Called when the remove button is clicked. |
removeAriaLabel | string | 'Remove' | Accessible name of the remove button. |
icon | Snippet | undefined | undefined | Leading icon, shown while the chip is not selected. |
variant | 'outlined' | 'elevated' | 'outlined' | elevated drops the outline and gives the chip a shadow instead. |
disabled | boolean | false | Makes the chip non-interactive. |
element | HTMLDivElement | undefined | undefined | Bindable reference to the chip element. |
InputChip attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | '' | Text of the chip. Falls back to value when it is empty. |
selected | boolean | undefined | undefined | Bindable. Highlights the chip, for instance to show which one Backspace would remove next. |
name | string | undefined | undefined | Form field name. The value is submitted through a hidden input. |
value | string | number | undefined | undefined | Value submitted with the form. |
onclick | MouseEventHandler | undefined | undefined | Called when the chip itself is clicked, not the remove button. |
onremove | MouseEventHandler | undefined | undefined | Called when the remove button is clicked. |
removeAriaLabel | string | 'Remove' | Accessible name of the remove button. |
icon | Snippet | undefined | undefined | Leading icon or avatar. |
disabled | boolean | false | Makes the chip non-interactive. |
element | HTMLDivElement | undefined | undefined | Bindable reference to the chip element. |
actionElement | HTMLButtonElement | undefined | undefined | Bindable reference to the chip's own button. |