Chips are compact elements that represent an input, attribute, or action — a filter that has been applied, a tag on a record, a value selected in a multi-select field.
import { Chip } from 'frontile';
import { Chip } from 'frontile';
<template>
<Chip>Chip</Chip>
</template>
default is a filled chip, outlined draws the intent color as a border on the
page background, and faded is a tinted surface with intent-colored text.
import { Chip } from 'frontile';
<template>
<div class='flex flex-wrap items-center gap-3'>
<Chip @appearance='default'>Default</Chip>
<Chip @appearance='outlined'>Outlined</Chip>
<Chip @appearance='faded'>Faded</Chip>
</div>
</template>
Every intent is available in every appearance. The label on each row is the
@appearance value; the chip labels are the @intent values.
@appearance='default'
@appearance='outlined'
@appearance='faded'
import { Chip } from 'frontile';
import { array } from '@ember/helper';
const intents = [
'default',
'primary',
'secondary',
'tertiary',
'success',
'warning',
'danger'
];
<template>
<div class='flex flex-col gap-6'>
{{#each (array 'default' 'outlined' 'faded') as |appearance|}}
<div>
<p class='font-code text-code-sm text-neutral-strong mb-2'>
@appearance='{{appearance}}'
</p>
<div class='flex flex-wrap items-center gap-3'>
{{#each intents as |intent|}}
<Chip @appearance={{appearance}} @intent={{intent}}>
{{intent}}
</Chip>
{{/each}}
</div>
</div>
{{/each}}
</div>
</template>
import { Chip } from 'frontile';
<template>
<div class='flex flex-wrap items-center gap-3'>
<Chip @size='sm'>Chip sm</Chip>
<Chip @size='md'>Chip md</Chip>
<Chip @size='lg'>Chip lg</Chip>
</div>
</template>
The dot and the close button scale with the chip, so a size change does not need any other adjustment.
@appearance='default'
@appearance='outlined'
@appearance='faded'
import { Chip } from 'frontile';
import { array } from '@ember/helper';
const noop = (): void => {};
<template>
<div class='flex flex-col gap-6'>
{{#each (array 'default' 'outlined' 'faded') as |appearance|}}
<div>
<p class='font-code text-code-sm text-neutral-strong mb-2'>
@appearance='{{appearance}}'
</p>
<div class='flex flex-wrap items-center gap-3'>
{{#each (array 'sm' 'md' 'lg') as |size|}}
<Chip
@appearance={{appearance}}
@intent='primary'
@size={{size}}
@withDot={{true}}
@onClose={{noop}}
@closeButtonTitle='Remove {{size}} chip'
>
{{size}}
</Chip>
{{/each}}
</div>
</div>
{{/each}}
</div>
</template>
full is the default. Use a smaller radius when chips sit alongside other
squared-off controls.
import { Chip } from 'frontile';
import { array } from '@ember/helper';
<template>
<div class='flex flex-wrap items-center gap-3'>
{{#each (array 'none' 'sm' 'lg' 'full') as |radius|}}
<Chip @appearance='outlined' @intent='primary' @radius={{radius}}>
{{radius}}
</Chip>
{{/each}}
</div>
</template>
@withDot adds a small intent-colored dot before the content — useful when the
chip stands for a status and the color needs to read at a glance.
@appearance='default'
@appearance='outlined'
@appearance='faded'
import { Chip } from 'frontile';
import { array } from '@ember/helper';
const intents = [
'default',
'primary',
'secondary',
'tertiary',
'success',
'warning',
'danger'
];
<template>
<div class='flex flex-col gap-6'>
{{#each (array 'default' 'outlined' 'faded') as |appearance|}}
<div>
<p class='font-code text-code-sm text-neutral-strong mb-2'>
@appearance='{{appearance}}'
</p>
<div class='flex flex-wrap items-center gap-3'>
{{#each intents as |intent|}}
<Chip
@appearance={{appearance}}
@intent={{intent}}
@withDot={{true}}
>
{{intent}}
</Chip>
{{/each}}
</div>
</div>
{{/each}}
</div>
</template>
Passing @onClose makes the close button visible.
@appearance='default'
@appearance='outlined'
@appearance='faded'
import { Chip } from 'frontile';
import { array, concat } from '@ember/helper';
const intents = [
'default',
'primary',
'secondary',
'tertiary',
'success',
'warning',
'danger'
];
const noop = (): void => {};
<template>
<div class='flex flex-col gap-6'>
{{#each (array 'default' 'outlined' 'faded') as |appearance|}}
<div>
<p class='font-code text-code-sm text-neutral-strong mb-2'>
@appearance='{{appearance}}'
</p>
<div class='flex flex-wrap items-center gap-3'>
{{#each intents as |intent|}}
<Chip
@appearance={{appearance}}
@intent={{intent}}
@onClose={{noop}}
@closeButtonTitle={{concat 'Remove ' intent}}
>
{{intent}}
</Chip>
{{/each}}
</div>
</div>
{{/each}}
</div>
</template>
@appearance='default'
@appearance='outlined'
@appearance='faded'
import { Chip } from 'frontile';
import { array, concat } from '@ember/helper';
const intents = [
'default',
'primary',
'secondary',
'tertiary',
'success',
'warning',
'danger'
];
const noop = (): void => {};
<template>
<div class='flex flex-col gap-6'>
{{#each (array 'default' 'outlined' 'faded') as |appearance|}}
<div>
<p class='font-code text-code-sm text-neutral-strong mb-2'>
@appearance='{{appearance}}'
</p>
<div class='flex flex-wrap items-center gap-3'>
{{#each intents as |intent|}}
<Chip
@appearance={{appearance}}
@intent={{intent}}
@withDot={{true}}
@onClose={{noop}}
@closeButtonTitle={{concat 'Remove ' intent}}
>
{{intent}}
</Chip>
{{/each}}
</div>
</div>
{{/each}}
</div>
</template>
Every close button is announced as "Close" unless you say otherwise, which does
not tell anyone what is being removed. When several closable chips sit together
— the usual case, since chips represent a set — give each one a
@closeButtonTitle naming its own value:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { concat, fn } from '@ember/helper';
import { Chip } from 'frontile';
export default class Example extends Component {
@tracked filters = ['Fiber', 'Metro', 'Wholesale'];
remove = (name: string): void => {
this.filters = this.filters.filter((filter) => filter !== name);
};
<template>
<div class='flex flex-wrap items-center gap-2'>
{{#each this.filters as |filter|}}
<Chip
@appearance='faded'
@intent='primary'
@onClose={{fn this.remove filter}}
@closeButtonTitle={{concat 'Remove ' filter}}
>
{{filter}}
</Chip>
{{else}}
<p class='text-neutral'>All filters removed.</p>
{{/each}}
</div>
</template>
}
@closeButtonTabIndex="-1" makes a chip's close button a pointer-only affordance.
Reach for it when chips sit inside another control — a multi-select field, say —
where each chip would otherwise cost a Tab stop before the control itself is
reachable. Select does exactly this in chips mode. If you do it, you owe keyboard
users another way to remove a chip (Select uses Backspace on the field); leaving
them with no route at all is worse than the extra tab stops.
@isDisabled dims the chip and disables its close button, so the value can no
longer be removed.
@appearance='default'
@appearance='outlined'
@appearance='faded'
import { Chip } from 'frontile';
import { array } from '@ember/helper';
const intents = [
'default',
'primary',
'secondary',
'tertiary',
'success',
'warning',
'danger'
];
const noop = (): void => {};
<template>
<div class='flex flex-col gap-6'>
{{#each (array 'default' 'outlined' 'faded') as |appearance|}}
<div>
<p class='font-code text-code-sm text-neutral-strong mb-2'>
@appearance='{{appearance}}'
</p>
<div class='flex flex-wrap items-center gap-3'>
{{#each intents as |intent|}}
<Chip
@appearance={{appearance}}
@intent={{intent}}
@withDot={{true}}
@onClose={{noop}}
@isDisabled={{true}}
>
{{intent}}
</Chip>
{{/each}}
</div>
</div>
{{/each}}
</div>
</template>
You can also use TailwindCSS classes to customize even further.
import { Chip } from 'frontile';
<template>
<Chip @appearance='outlined' @intent='primary' @class='px-20 py-2 italic'>
Chip
</Chip>
</template>
The argument @class overrides and merges TailwindCSS class names, while the HTML
attribute class just appends the class names passed in.
A Chip is a <div> holding text — it has no role of its own, because a chip is
not one thing. What it means depends on what you are using it for, and that
determines what you owe it:
@intent or @withDot alone to carry
the meaning: @intent='danger' reads as "failed" to a sighted user and as
nothing at all to a screen reader, so keep the word in the content.@onClose — the close button is the only
interactive part. It is a real <button>, reached with Tab and activated
with Enter or Space, and it needs a name that identifies the chip; see
above.Button or a link inside it, or use a Button instead — attaching a click
handler to the <div> leaves it unfocusable and unannounced.When chips represent a set that changes, the container should say so, or removals
happen silently for anyone not watching the screen. A role='list' wrapper gives
the set a size and position; an aria-live region announces the change:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { concat, fn } from '@ember/helper';
import { Chip } from 'frontile';
export default class Example extends Component {
@tracked tags = ['Design', 'Docs', 'Testing'];
@tracked announcement = '';
remove = (name: string): void => {
this.tags = this.tags.filter((tag) => tag !== name);
this.announcement = `${name} removed. ${this.tags.length} remaining.`;
};
<template>
<ul role='list' aria-label='Tags' class='flex flex-wrap items-center gap-2'>
{{#each this.tags as |tag|}}
<li>
<Chip
@appearance='outlined'
@onClose={{fn this.remove tag}}
@closeButtonTitle={{concat 'Remove ' tag}}
>
{{tag}}
</Chip>
</li>
{{/each}}
</ul>
<p aria-live='polite' class='text-neutral mt-3'>{{this.announcement}}</p>
</template>
}
@isDisabled does not hide the chip from assistive technology — the text is
still read, which is usually what you want for a value that is present but
locked.
Element: <span class="hljs-title class_">HTMLDivElement</span>
| Name | Type | Default | Description |
|---|---|---|---|
appearance
|
enum
|
'default'
|
The chip appearance |
class
|
string
|
- | Custom class name, it will override the default ones using Tailwind Merge library. |
closeButtonTabIndex
|
enum
|
undefined (the close button is a normal tab stop)
|
Set to |
closeButtonTitle
|
string
|
'Close'
|
The accessible name of the close button. Every close button would otherwise be announced as just "Close", which does not say what is being removed — worth setting when several chips sit together. |
intent
|
enum
|
'default'
|
The intent of the chip, which drives its color |
isDisabled
|
boolean
|
false
|
Dims the chip and disables its close button, if any. |
onClose
|
function
|
- | Function to be called when clicking on the close button. If you pass this argument, the close button will be visible. |
radius
|
enum
|
'full'
|
The border radius of the chip |
size
|
enum
|
'md'
|
The size of the chip |
withDot
|
boolean
|
false
|
Adds a dot before the content, colored by @intent. On the default
appearance the dot takes the chip's text color, since the chip's
background is already the intent color.
|
| Name | Type | Default | Description |
|---|---|---|---|
default
*
|
Array
|
- |