GrChip
An interactive tag: removable, selectable, with an icon. Where GrBadge only shows a status, a chip answers to a click.
Overview
When to take it
- a dismissible label on an entity — the tags of a task, the recipients of a letter, the selected files: a cross on each, the list kept by the application;
- a filter that is switched on and off —
selectablegives a toggle witharia-pressed, and a set of such filters is collected byGrChipGroup; - a quick choice among short options — a period, a status, a priority: chips take up a line rather than a dropdown list;
- a row of tags of your own beside a field — when the input and the storage are already written and all that is needed is a tile with a cross.
When to take something else
| Need | Take |
|---|---|
| A status or a tag without interaction | GrBadge |
| A field that collects tags from typing itself | GrInputTag |
| A counter on top of a button or an icon | GrBadgeWrap |
| A single choice among options in one line | GrSegmented |
| Many options that are searched by typing | GrSelect / GrAutocomplete |
Variants every tone and view, rendered by the real component
tone \ dark | false | true |
|---|---|---|
primary | ||
neutral | ||
success | ||
warning | ||
danger | ||
info | ||
slate | ||
azure |
Sizes
xssmmdlg
States
normaldisabled
The grid is live: hover and press it. Hover, focus and active states are not drawn here — they belong to the browser, not to a prop.
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrChip label="Design" />Examples 5 each with a live demo and its code
Labels you take off in one move
closableadds a real button named after the label — «Remove vat», not «Remove» five times in a row.removeis a request, not a fact: the chip does not hide itself, the list lives in the screen. A badge reports a state; a chip is a thing the object was labelled with, so it can be taken off.Export fails for invoices with reverse-charge VAT#1824 · opened by Grace Wu 2 days agoLabelsbuginvoicesvatcustomer-reportedRecipients, and who is outside
The
#iconslot tells a colleague from an outside address, and the tone backs it up. When the label alone does not say what the cross removes,remove-labelgives the whole name: «Remove Grace Wu from recipients».Send invoice INV-0418 ToAda LinGrace WuNorthwind AP1 recipient is outside Acme — the invoice leaves the workspace.
Filters that are switched on and off
selectableturns the chip into a toggle witharia-pressed, andv-model:selectedholds its state. The selected chip takes the solid fill of its tone — found at a glance, and kept apart by weight in monochrome too. An icon-only chip is named byaria-label;disabledkeeps an unavailable filter in sight.3 of 24 invoices · €4,035 due
Query tokens in a search bar
A token is closer to code than to a tag:
radius="semi"gives it the shape of a key label,size="sm"fits it into the field. The slot holds a key and a value in two weights, solabelwould not say what is removed —remove-labeldoes.status: overduecustomer: Northwindamount: > €1,0003 filters · 9 invoices
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
disabled | boolean | undefined | false | The chip is unavailable: it cannot be selected or dismissed, the cross is hidden. Inside a group — also from its `disabled`. |
tone |
| undefined | The tone of the chip — the same colours as `GrBadge`, so that they do not diverge in one row. Not set — from the group and `GrConfigProvider`, otherwise `neutral`. |
closable | boolean | undefined | false | A cross. Inside a widget it is not a button — see the section on roles below. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | A step of the control scale: a tap target of the same height as the neighbouring fields. Not set — from the group and `GrConfigProvider`, otherwise `md`. |
ariaLabel | string | undefined | undefined | An accessible name for when the label does not explain the purpose. |
value | GrChipValue | undefined | undefined | The value of the chip in a group. Outside `GrChipGroup` it is not used. |
dark | boolean | undefined | undefined | A fill in the tone instead of a soft surface — for a chip that has to stand out. `false` by default. |
radius | GrBadgeRadius | undefined | undefined | The shape: `round` — a pill, `semi` — a tag, `square` — a stamp. Not set — from the group and `GrConfigProvider`, otherwise `round`. |
label | string | undefined | undefined | The label. It is needed separately from the slot: the name of the dismiss button is built from it — "Remove" on twenty buttons in a row makes it impossible to pick the right one. |
selectable | boolean | undefined | false | The chip becomes a toggle: `aria-pressed`, `Enter`/`Space`. |
selectedv-model | boolean | undefined | false | The state of the toggle outside a group. Inside a group the value is run by the group. |
removeLabel | string | undefined | undefined | The name of the dismiss button as a whole. It overrides the one built from `label`. |
removeTabindex | number | undefined | undefined | The `tabindex` of the dismiss button — for a chip inside an input field. There the tab stop belongs to the `<input>`, and the row of chips is walked with the arrows: in `GrInputTag` the ring leaves exactly one `0`, in `GrAutocomplete` there are none at all. The prop is needed because the ring is run by the parent — it knows both the order of the chips and what lies beyond the edge of the row. |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | The label of the chip. Inside a selectable chip — phrasing content only. |
icon | any | An icon before the label. |
Events
| Event | Signature | Description |
|---|---|---|
remove | (event: "remove"): void | — |
update:selected | (event: "update:selected", value: boolean): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
focus | () => void | undefined | Focus on the chip from code. Works on an interactive chip: a plain chip is a `span` with no tab stop. |
blur | () => void | undefined | Remove focus from the chip. |
removeEl | HTMLButtonElement | null | The dismiss button — the target of the parent’s roving focus. It is given away as an element rather than as a `focusRemove` method: the ring needs the node itself, and it decides for itself when and where to move the focus. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrChipConfigurableProps, GrChipValue, GrChipSelection } from '@feugene/granularity/components/GrChip'Filedist/types/components/GrChip/config.d.ts
const grChipConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChip">;Filedist/types/components/GrChip/defaults.d.ts
interface GrChipConfigurableProps {
tone: GrChipTone;
size: GrChipSize;
radius: GrChipRadius;
dark: boolean;
}Filedist/types/components/shared/chipGroupContext.d.ts
type GrChipValue = string | number | boolean;type GrChipSelection = 'single' | 'multiple';type GrChipGroupContext = {
selection: ComputedRef<GrChipSelection>;
isSelected: (value: GrChipValue) => boolean;Filedist/types/components/GrChip/grChipStyles.d.ts
type GrChipTone = GrBadgeTone;type GrChipSize = GrComponentSize;type GrChipRadius = GrBadgeRadius;Filedist/types/components/GrChip/safelist.d.ts
const grChipSafelist: string[];Filedist/types/components/GrChip/GrChip.vue.d.ts
interface GrChipProps {
value?: GrChipValue;
label?: string;
tone?: GrChipTone;
dark?: boolean;
size?: GrChipSize;
radius?: GrChipRadius;
selectable?: boolean;
selected?: boolean;
closable?: boolean;
disabled?: boolean;
ariaLabel?: string;
removeLabel?: string;
removeTabindex?: number;
}interface GrChipEmits {
(e: 'update:selected', value: boolean): void;
(e: 'remove'): void;
}Filedist/types/components/GrChip/index.d.ts
type GrChipInstance = ComponentExposed<typeof GrChipComponent>;Accessibility
APG pattern
toggle button / option
Full contract of the package: docs/keyboard.md ↗
Usage from the library prose
The cross is not always a button
The markup of a chip depends on whether the chip itself is clicked.
The chip is not a widget (just a dismissible label) — the root is a <span>,
and the cross is a real <button> with a name of its own and a Tab stop of its
own. That is how the chips inside GrInputTag are built.
The chip is a widget (selectable or inside a group) — the root is a
<button>, and the cross inside it cannot be a button: the role declares its
descendants presentational (axe catches that as nested-interactive), and a
<button> inside a <button> is invalid by the HTML content model. The cross
therefore becomes an aria-hidden <span>, dismissal moves to
Delete/Backspace, and the chip announces that through aria-keyshortcuts. The
closable tabs of GrTabs live by the same device.
The practical consequence: a link must not be put inside a selectable chip. If a link is needed, the chip is not selectable.
The chip does not perform the dismissal
remove is a request, not a fact: the contents of the set belong to the consumer,
and only the consumer can remove an item from the array. The chip does not hide
itself and announces nothing into a live region — announcing “removed” before it
has happened would be a lie.
The label is needed separately from the slot
The label prop duplicates the content of the slot not by an oversight: the name of
the dismiss button is built from it. Twenty buttons named “Remove” in a row make it
impossible to pick the right one — the name has to name the tag itself, so by
default it is “Remove {label}”. If the content is more complex than a string, set
removeLabel by hand.
The selection is seen by the fill, not by the outline
A selected chip takes the solid variant of its tone — the same one dark gives.
The hue does not change in the process: a set of filters where the selected one is
recoloured into a neighbouring colour reads as a rainbow rather than as a state.
An outline and a slight thickening of the type do not carry that job: in a row of five filters the selected one is not found at a glance by them. A difference in lightness is found — and it survives monochrome and colour blindness, unlike “became slightly darker”.
The non-colour channel is kept nonetheless: a selected chip has
font-weight: 600. It is that channel that carries the case where dark is set for
the whole set and the fill of the selected and the unselected coincide.
Limits
- there is no virtualisation. A thousand chips in a row is not a row of chips
but a list: cut the set yourself or take
GrSelectwithvirtual; - chips are not reordered by dragging. The order is set by the consumer’s array;
if a manual order is needed —
GrSortableList; - a chip has no input of its own. It shows a ready value;
collecting a set from typing is
GrInputTag.
Props in this example