GrChipGroup

Package: @feugene/granularitycoreGroup: data

A set of chips sharing one value: list filters, record labels, a quick pick.

Machine-translated from the Russian original, not yet reviewed. Read the original

When to take it

  • the filters of a list in a line — statuses, categories, labels: every option is visible at once, and several may be selected;
  • a choice among short options instead of a list — the period of a report, a priority: there are up to a dozen options, and there is no point hiding them under a button;
  • a set of labels on a record that is edited in placeclosable gives every chip a cross, and Delete removes the one under focus;
  • a form field whose value is a set — the group reads the context of GrFormField and gives its value to a native form by name.

When to take something else

NeedTake
A single choice, few options, the look of a switchGrSegmented
A single choice with descriptions on the optionsGrRadioGroup
Multiple selection as a list with marksGrCheckboxGroup
There are dozens of options, searched by typingGrSelect / GrAutocomplete
A row of dismissible labels without selectionGrChip without a group

The role depends on the multiplicity of the selection

selection="multiple" (the default) declares the group a listbox with aria-multiselectable, and the chips option with aria-selected. selection="single" gives a radiogroup and radio with aria-checked, as in GrSegmented.

The chips get their role from the context and do not choose it themselves: otherwise a set of chips with different roles would be announced to a screen reader as anything but one widget.

One `Tab` stop, inside it the arrows

The group is composite, so it occupies one stop in the tab order: the active chip holds tabindex="0", the rest -1. The arrows work along both axes — chips wrap onto a new line, and “down” means the next chip just as “right” does. Home/End take you to the edges.

An arrow moves the focus only, even in single mode. Chips have a second action — dismissal with Delete — and moving the selection along with the focus would mean changing the model while trying to reach the chip you want. In a form radiogroup the opposite is customary, but there the elements have a single action.

Selecting again clears the mark

In single mode a click on an already selected chip resets the value to null. A set of filters with no value selected makes sense (“any”), and there would otherwise be no way to cancel the selection — unlike form switches, where an empty value is usually forbidden.

The contents are drawn by the consumer

The group has no options prop: every chip has a label, an icon and a tone of its own, and generating them from a flat array would mean introducing a second, poorer way to describe the same thing. Hence remove as well — the group only relays it, and the application removes the item from the array.

Limits

  • the group has no order of its own — it draws the chips in the order of the slot;
  • the group does not limit the number selected. The limit is a rule of the application, and so is the message about it: the group does not know what to show instead of a refusal;
  • there are no nested groups. A set inside a set poses an unanswerable question about whose arrow is handled.

Playground 10

Loading…

Code
<GrChipGroup />

Install

npm i @feugene/granularity

Import

import { GrChipGroup } from '@feugene/granularity/components/GrChipGroup'

API

Props

PropTypedefaultDescription
tone"primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefinedundefined
closableboolean | undefinedfalseA cross on every chip of the set. It is overridden pointwise by the prop of a chip.
modelValueGrChipValue | GrChipValue[] | null | undefinedundefinedA single selection is a value, a multiple one is an array.
disabledboolean | undefinedfalse
readonlyboolean | undefinedfalseThe selection is visible but does not change.
invalidboolean | undefinedfalse
requiredboolean | undefinedfalse
size"xs" | "sm" | "md" | "lg" | undefinedundefined
ariaLabelstring | undefinedundefined
namestring | undefinedundefinedThe name for a native form. A multiple selection gives one field per value.
darkboolean | undefinedundefined
radiusGrBadgeRadius | undefinedundefined
selectionGrChipSelection | undefined"multiple"

Slots

SlotTypeDescription
defaultanyThe chips of the set.

Events

EventTypeDescription
update:modelValue[value: GrChipValue | GrChipValue[] | null]
change[value: GrChipValue | GrChipValue[] | null]
focus[event: FocusEvent]
blur[event: FocusEvent]
remove[value: GrChipValue]

Methods / Expose

Methods / ExposeTypeDescription
focus() => void | undefined
blur() => void | undefined

Examples 3

Filters

Показываем: Открытые, На ревью

Filters
<script setup lang="ts">
import { computed, ref } from 'vue'

import { GrCard, GrChip, GrChipGroup } from '@feugene/granularity'

const statuses = [
  { value: 'open', label: 'Открытые' },
  { value: 'review', label: 'На ревью' },
  { value: 'blocked', label: 'Заблокированные' },
  { value: 'done', label: 'Готовые' },
]

const selected = ref<string[]>(['open', 'review'])

const summary = computed(() => (selected.value.length
  ? statuses.filter(status => selected.value.includes(status.value)).map(s => s.label).join(', ')
  : 'любой статус'))
</script>

<template>
  <div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px]">
    <GrChipGroup v-model="selected" aria-label="Статус задачи">
      <GrChip
        v-for="status in statuses"
        :key="status.value"
        :value="status.value"
        :label="status.label"
        tone="primary"
      />
    </GrChipGroup>

    <GrCard class="p-4 text-sm text-[var(--gr-muted-fg)]">
      Показываем: <span class="font-semibold text-[var(--gr-fg)]">{{ summary }}</span>
    </GrCard>
  </div>
</template>

Single

Период: week

Single
<script setup lang="ts">
import { ref } from 'vue'

import { GrCard, GrChip, GrChipGroup } from '@feugene/granularity'

const periods = [
  { value: 'day', label: 'День' },
  { value: 'week', label: 'Неделя' },
  { value: 'month', label: 'Месяц' },
  { value: 'quarter', label: 'Квартал' },
]

// Повторное нажатие на выбранный чип сбрасывает период в «любой».
const period = ref<string | null>('week')
</script>

<template>
  <div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px]">
    <GrChipGroup v-model="period" selection="single" tone="info" aria-label="Период отчёта">
      <GrChip
        v-for="item in periods"
        :key="item.value"
        :value="item.value"
        :label="item.label"
      />
    </GrChipGroup>

    <GrCard class="p-4 text-sm text-[var(--gr-muted-fg)]">
      Период: <span class="font-semibold text-[var(--gr-fg)]">{{ period ?? 'весь' }}</span>
    </GrCard>
  </div>
</template>

Removable

Стрелки водят фокус по набору, Delete снимает метку под фокусом.

Removable
<script setup lang="ts">
import { ref } from 'vue'

import type { GrChipValue } from '@feugene/granularity'
import { GrChip, GrChipGroup } from '@feugene/granularity'

// Метки можно и выбирать, и снимать: выбор ведёт группа, состав — приложение.
const labels = ref(['срочно', 'бэкенд', 'регресс', 'релиз 0.25'])
const active = ref<string[]>(['срочно'])

function drop(value: GrChipValue): void {
  const label = String(value)
  labels.value = labels.value.filter(item => item !== label)
  active.value = active.value.filter(item => item !== label)
}
</script>

<template>
  <div class="grid gap-3">
    <GrChipGroup v-model="active" closable aria-label="Метки записи" @remove="drop">
      <GrChip
        v-for="label in labels"
        :key="label"
        :value="label"
        :label="label"
        tone="warning"
      />
    </GrChipGroup>

    <p class="text-sm text-[var(--gr-muted-fg)]">
      Стрелки водят фокус по набору, Delete снимает метку под фокусом.
    </p>
  </div>
</template>

Accessibility

APG pattern
listbox / radiogroup (roving tabindex)

Full keyboard contract of the package

Component documentationAll components