Skip to content

Selectable Chip

The FTSelectableChip component is a blue selectable chip used for origins, markets, and features. It replaces the legacy gray origin selector (DEV-13469). Each chip owns its own selection state via v-model, making it easy to compose multi-select lists where the parent array drives the UI.

Basic usage

vue
<script setup lang="ts">
import { ref } from 'vue';
import { FTSelectableChip } from '@fasttrack-solutions/vue-components-lib';
import type { IChipItem } from '@fasttrack-solutions/vue-components-lib';

const item = ref<IChipItem>({ id: 1, name: 'Malta', isSelected: false });
</script>

<template>
  <FTSelectableChip v-model="item" @item-clicked="console.log('toggled', item.isSelected)" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FTSelectableChip } from '@fasttrack-solutions/vue-components-lib';
import type { IChipItem } from '@fasttrack-solutions/vue-components-lib';

const item = ref<IChipItem>({ id: 1, name: 'Malta', isSelected: false });
</script>

<template>
  <FTSelectableChip v-model="item" @item-clicked="console.log('toggled', item.isSelected)" />
</template>

Playground

Origin name

Properties

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

const item = ref({
  "id": 1,
  "name": "Origin name",
  "isSelected": true,
  "isDisabled": false
});
</script>

<template>
  <FTSelectableChip v-model="item" />
</template>

IChipItem model shape

The component is driven entirely by the v-model object. All fields are optional.

FieldTypeDescription
idnumberOptional identifier — passed through in events but not rendered
namestringChip label text
iconstringFontAwesome icon string (e.g. "fas user"). Renders an FTIcon before the label. Takes precedence over logoUrl — if both are set, only the icon is shown.
logoUrlstringURL for a 24 × 24 circular logo or flag image. Flags are just a CDN image URL (e.g. ${cdnBase}/flags/se.svg) — no separate flag prop is needed.
countnumberTrailing numeric count (localized). Rendered when >= 0
isSelectedbooleanControls the selected visual state and aria-pressed
isDisabledbooleanPrevents interaction and mutes the chip visually
tooltipstringBalloon tooltip text shown on hover (suppressed when readOnly is true)

Multi-select list

The component owns its own toggle via v-model. The parent's @item-clicked handler is read-only — use it to react (e.g. log or sync to a server) without flipping isSelected again, which would double-toggle.

vue
<script setup lang="ts">
import { ref, computed } from 'vue';
import { FTSelectableChip } from '@fasttrack-solutions/vue-components-lib';
import type { IChipItem } from '@fasttrack-solutions/vue-components-lib';

const origins = ref<IChipItem[]>([
  { id: 1, name: 'Malta',   logoUrl: '/flags/mt.svg', isSelected: false },
  { id: 2, name: 'Sweden',  logoUrl: '/flags/se.svg', isSelected: true  },
  { id: 3, name: 'Germany', logoUrl: '/flags/de.svg', isSelected: false },
]);

const selectedNames = computed(() =>
  origins.value.filter((o) => o.isSelected).map((o) => o.name),
);

function onItemClicked(index: number) {
  // Read-only: the chip already updated origins[index].isSelected via v-model.
  // React here (e.g. analytics, server sync) — do NOT flip isSelected again.
  console.log('selection changed:', origins.value[index].name, origins.value[index].isSelected);
}
</script>

<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <FTSelectableChip
      v-for="(origin, i) in origins"
      :key="origin.id"
      v-model="origins[i]"
      @item-clicked="onItemClicked(i)"
    />
  </div>
  <p>Selected: {{ selectedNames.join(', ') || 'none' }}</p>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { FTSelectableChip } from '@fasttrack-solutions/vue-components-lib';
import type { IChipItem } from '@fasttrack-solutions/vue-components-lib';

const origins = ref<IChipItem[]>([
  { id: 1, name: 'Malta',   logoUrl: '/flags/mt.svg', isSelected: false },
  { id: 2, name: 'Sweden',  logoUrl: '/flags/se.svg', isSelected: true  },
  { id: 3, name: 'Germany', logoUrl: '/flags/de.svg', isSelected: false },
]);

const selectedNames = computed(() =>
  origins.value.filter((o) => o.isSelected).map((o) => o.name),
);

function onItemClicked(index: number) {
  // Read-only: the chip already updated origins[index].isSelected via v-model.
  // React here (e.g. analytics, server sync) — do NOT flip isSelected again.
  console.log('selection changed:', origins.value[index].name, origins.value[index].isSelected);
}
</script>

<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <FTSelectableChip
      v-for="(origin, i) in origins"
      :key="origin.id"
      v-model="origins[i]"
      @item-clicked="onItemClicked(i)"
    />
  </div>
  <p>Selected: {{ selectedNames.join(', ') || 'none' }}</p>
</template>

States

StateTriggerVisual
DefaultisSelected: falseMono-100 background, mono-300 border
HoverMouse over (default state)Mono-200 background, mono-400 border
SelectedisSelected: trueBlue-100 background, blue-500 border, bold label
Selected hoverMouse over (selected state)Blue-200 background
DisabledisDisabled: true or readOnly: trueDefault colors at 40 % opacity, pointer events blocked
Closableclosable: trueTrailing ×-mark button; click emits itemClosed (removal signal; parent removes the item from its array). The ✕ is hidden when the chip is disabled or read-only.
With countcount field >= 0Trailing localized number next to the label
With logologoUrl set24 × 24 circular image before the label; falls back to flagFallbackUrl on error
With iconicon setFTIcon before the label at 20 px; takes precedence over logoUrl

Props

NameDefaultDescription
modelValuerequiredIChipItem — the chip's data and state object (use v-model)
readOnlyfalseBlocks all interaction and suppresses tooltips; chip appears visually disabled
closablefalseShows a trailing ×-mark button. Clicking it emits itemClosed — the parent should remove the item from its array in response
flagFallbackUrl''Fallback image URL shown when logoUrl fails to load (e.g. a generic flag placeholder)

Events

NamePayloadDescription
itemClickedEmitted after the chip's selection state is toggled (not emitted when disabled)
itemClosedEmitted when the ×-mark close button is clicked (requires closable: true)
update:modelValueIChipItemUpdated chip object after toggle (emitted automatically by v-model/defineModel)

CSS custom properties

VariableDefaultDescription
--ft-selectable-chip-bgvar(--color-mono-100)Background colour in the default state
--ft-selectable-chip-bordervar(--color-mono-300)Border colour in the default state
--ft-selectable-chip-fgvar(--color-mono-700)Text and icon colour
--ft-selectable-chip-hover-bgvar(--color-mono-200)Background on hover (unselected)
--ft-selectable-chip-hover-bordervar(--color-mono-400)Border on hover (unselected)
--ft-selectable-chip-selected-bgvar(--color-brand-blue-100)Background when selected
--ft-selectable-chip-selected-bordervar(--color-brand-blue-500)Border when selected
--ft-selectable-chip-hover-selected-bgvar(--color-brand-blue-200)Background on hover when selected

Token caveat for older lib versions

--ft-selectable-chip-selected-bg defaults to var(--color-brand-blue-100) (#d8edf9). Apps running an older version of the library (e.g. 4.2.0) where --color-brand-blue-100 is not defined will see no selected background. Override the custom property at your app's root or the chip's parent to resolve:

css
/* In your app's global stylesheet or scoped to the chip's container */
--ft-selectable-chip-selected-bg: var(--color-brand-blue-200);
/* In your app's global stylesheet or scoped to the chip's container */
--ft-selectable-chip-selected-bg: var(--color-brand-blue-200);