CSC Design System next
VueFlavour Vue React Angular TypeScript PrimaryCSC UI ColorsPrimarySecondaryAccentCustom ColorsRedOrangeGreenBluePurplePink
Guides Getting started Customization Data visualization Migration guide Componentsc-accordionc-alertc-autocompletec-badgec-buttonc-button-groupc-cardc-checkboxc-csc-logoc-data-tablec-dividerc-iconc-icon-buttonc-inputc-linkc-listc-loaderc-login-buttonsc-login-cardc-mainc-menuc-messagec-modalc-navigation-buttonc-otp-inputc-pagec-paginationc-popoverc-progress-barc-progress-circlec-radio-groupc-selectc-side-navigationc-sliderc-spinnerc-statusc-stepsc-switchc-tablec-tabsc-tagsc-text-fieldc-toastsc-toolbarc-tooltip

<c-autocomplete>

A filterable value-selection component: a readonly value field that opens a popover panel with a search input above the matching options.

Usage

Filtering

By default the component filters its options itself: the query typed into the search input is matched against the start of each option's label. Supply a filter predicate to change the matching — it receives the normalized option and the query, and keeps the option when it returns true (see the custom-filter example). Because filter is a function, it must be bound as a DOM property, not an attribute.

External data

Set external to hand filtering to your own code — for example a server search endpoint. The component then renders items verbatim and emits a change:query event carrying the query string: on every keystroke, and with an empty string when the panel opens (use that to load the initial, unfiltered list). Set loading while a request is in flight; the panel shows a loading row when there is nothing to display yet and keeps the current options on screen during a refresh.

The component ships no debounce and no minimum query length — debounce the requests in your handler and skip fetches for too-short queries yourself (see the external example). The closed field keeps showing the selected option's label even when a later fetch no longer includes it: the label is remembered when the option is committed, and a programmatically set value resolves its label from the current options, or from the object's name when return-object is used.

Examples

Basic
Vue React Angular TypeScript
<template>
  <div>
    <c-autocomplete
      v-model="language"
      clearable
      hint="Type to filter the options"
      label="Programming language"
      placeholder="Start typing to search"
    >
      <c-option value="js">
        <c-option-value>JavaScript</c-option-value>
      </c-option>

      <c-option value="ts">
        <c-option-value>TypeScript</c-option-value>
      </c-option>

      <c-option value="py">
        <c-option-value>Python</c-option-value>
      </c-option>

      <c-option value="rs">
        <c-option-value>Rust</c-option-value>
      </c-option>
    </c-autocomplete>

    <p>Value: {{ language ?? 'null' }}</p>
  </div>
</template>

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

const language = ref<string | null>(null);
</script>
Custom filter
Vue React Angular TypeScript
<template>
  <div>
    <!-- The default filter matches the start of the label; this one matches
         anywhere in it. -->
    <c-autocomplete
      v-model="country"
      :filter="filter"
      :items.prop="items"
      clearable
      hint="Matches anywhere in the label"
      label="Country"
      placeholder="Type to filter"
    />

    <p>Value: {{ country ?? 'null' }}</p>
  </div>
</template>

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

import type { CAutocompleteFilter, CAutocompleteItem } from '@cscfi/csc-ui';

const items: CAutocompleteItem[] = [
  { name: 'Austria', value: 'at' },
  { name: 'Denmark', value: 'dk' },
  { name: 'Estonia', value: 'ee' },
  { name: 'Finland', value: 'fi' },
  { name: 'France', value: 'fr' },
  { name: 'Germany', value: 'de' },
  { name: 'Iceland', value: 'is' },
  { name: 'Netherlands', value: 'nl' },
  { name: 'Norway', value: 'no' },
  { name: 'Sweden', value: 'se' },
];

// Typed via CAutocompleteFilter, so `option` and `query` are fully inferred.
// A custom-element binding can't infer an inline arrow's params, so define
// the predicate here rather than in the template.
const filter: CAutocompleteFilter = (option, query) =>
  option.label.toLowerCase().includes(query.toLowerCase());

const country = ref<string | null>(null);
</script>
External
Vue React Angular TypeScript
<template>
  <div>
    <!-- With `external`, the autocomplete renders `items` verbatim and only
         emits `change:query`; filtering happens in a simulated server request.
         The event also fires with an empty string when the panel opens, which
         is what loads the initial unfiltered list. The component ships no
         debounce — do it in the handler, as here. -->
    <c-autocomplete
      v-model="country"
      :items.prop="items"
      :loading="loading"
      external
      hint="Options are fetched as you type"
      label="Country"
      placeholder="Type to search"
      clearable
      @change:query="onQuery"
    />

    <p>Value: {{ country ?? 'null' }}</p>
  </div>
</template>

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

import type { CAutocompleteItem } from '@cscfi/csc-ui';

// ---- a pretend server ------------------------------------------------
const ALL: CAutocompleteItem[] = [
  { name: 'Austria', value: 'at' },
  { name: 'Denmark', value: 'dk' },
  { name: 'Estonia', value: 'ee' },
  { name: 'Finland', value: 'fi' },
  { name: 'France', value: 'fr' },
  { name: 'Germany', value: 'de' },
  { name: 'Iceland', value: 'is' },
  { name: 'Netherlands', value: 'nl' },
  { name: 'Norway', value: 'no' },
  { name: 'Sweden', value: 'se' },
];

const search = (query: string): Promise<CAutocompleteItem[]> =>
  new Promise((resolve) =>
    setTimeout(
      () =>
        resolve(
          ALL.filter((item) =>
            item.name.toLowerCase().includes(query.toLowerCase()),
          ),
        ),
      600,
    ),
  );
// -----------------------------------------------------------------------

const country = ref<string | null>(null);

const items = ref<CAutocompleteItem[]>([]);

const loading = ref(false);

let debounce: ReturnType<typeof setTimeout> | undefined;

// Drop responses a newer query has superseded.
let requestId = 0;

const load = async (query: string) => {
  const id = ++requestId;
  loading.value = true;

  const result = await search(query);

  if (id !== requestId) return;
  items.value = result;
  loading.value = false;
};

const onQuery = (event: Event) => {
  clearTimeout(debounce);

  const query = (event as CustomEvent<string>).detail;
  debounce = setTimeout(() => load(query), 300);
};

onBeforeUnmount(() => clearTimeout(debounce));
</script>
Small
Vue React Angular TypeScript
<template>
  <div>
    <c-autocomplete
      v-model="language"
      label="Programming language"
      size="small"
    >
      <c-option value="js">
        <c-option-value>JavaScript</c-option-value>
      </c-option>

      <c-option value="ts">
        <c-option-value>TypeScript</c-option-value>
      </c-option>

      <c-option value="py">
        <c-option-value>Python</c-option-value>
      </c-option>
    </c-autocomplete>
  </div>
</template>

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

const language = ref<string | null>(null);
</script>
Basic · c-option
Vue React Angular TypeScript
<template>
  <div>
    <c-select
      v-model="country"
      clearable
      hint="Each c-option provides a name and a value"
      label="Country"
      placeholder="Choose a country"
    >
      <c-option name="Finland" value="fi">Finland</c-option>

      <c-option name="Sweden" value="se">Sweden</c-option>

      <c-option name="Norway" value="no">Norway</c-option>

      <c-option name="Denmark" value="dk" disabled>Denmark</c-option>
    </c-select>

    <p>Value: {{ country ?? 'null' }}</p>
  </div>
</template>

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

const country = ref<string | null>(null);
</script>
Basic · c-option-value
Vue React Angular TypeScript
<template>
  <div>
    <c-autocomplete
      v-model="language"
      clearable
      hint="c-option-value marks the text that gets match highlighting"
      label="Programming language"
      placeholder="Start typing to search"
    >
      <c-option value="js">
        <c-option-value>JavaScript</c-option-value>
      </c-option>

      <c-option value="ts">
        <c-option-value>TypeScript</c-option-value>
      </c-option>

      <c-option value="py">
        <c-option-value>Python</c-option-value>
      </c-option>

      <c-option value="rs">
        <c-option-value>Rust</c-option-value>
      </c-option>
    </c-autocomplete>
  </div>
</template>

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

const language = ref<string | null>(null);
</script>

API reference

<c-autocomplete>

A filterable value-selection component: a readonly value field that opens a popover panel with a search input above the matching options.

Properties

PropertyAttributeTypeDefaultDescription
clearableclearablebooleanfalseMake the selected value clearable
disableddisabledbooleanfalseDisable the input
errorMessageerror-messagestring''Error message shown in place of the hint while the autocomplete is invalid
externalexternalbooleanfalseThe consumer owns filtering: the component renders its options verbatim and only emits `change:query` as the user types. Pair with `loading` and an async data source feeding `items`
filterCAutocompleteFilterCustom filter predicate; receives a normalized option + the query. Ignored when `external` is set
hideDetailshide-detailsbooleanfalseHide the hint and error messages
hinthintstring''Hint text for the input
hostIdhost-idstring''Id of the element
itemsCAutocompleteItem[]() => []Dropdown items (when not using <c-option> elements)
itemsPerPageitems-per-pagenumber6Items per page before the list scrolls
labellabelstring''Element label
labelOnToplabel-on-topbooleanfalseLabel on top of the input
loadingloadingbooleanfalseShow loading state
namenamestring''Input field name
noResultsTextno-results-textstring'No matching data'Message shown when the query matches no options
placeholderplaceholderstring''Placeholder for the in-panel search input
requiredrequiredbooleanfalseSet the autocomplete as required
returnObjectreturn-objectbooleanfalseReturn object instead of value
shadowshadowbooleanfalseShadow variant
sizesizeCFieldSize'default'Field height: the 44px default or the 36px `small` box
validvalidbooleantrueSet the validity of the input
valuevalueCAutocompleteItem | null | number | stringnullSelected value (scalar, or object when return-object is set)

Events

EventDetailDescription
changevoidNative change event (no detail) dispatched whenever a selection is committed or cleared; bubbles through the shadow boundary for form-style listeners.
change:querystringFired whenever the query changes — on every keystroke in the search input, and with an empty string when the panel opens. Carries the query string. With `external`, drive your data source from this (debounce on your side) and feed the results back via `items`.
changeValueCAutocompleteItem | null | number | stringFired when the selected value changes (an option is committed or the selection is cleared), carrying the new value — the option's value, or the whole `{ name, value }` item when `return-object` is set; `null` when cleared. Also dispatched as `change-value` — bind that name in Vue templates.
inputvoidNative bubbling input event dispatched alongside every value change so a plain `v-model` stays in sync. Carries no detail.
update:valueCAutocompleteItem | null | number | stringFired alongside `changeValue` with the same detail — the `v-model` contract.

Methods

MethodSignatureDescription
reset()Reset autocomplete state

Slots

SlotDescription
preContent placed before the value field inside the input row
defaultThe c-option elements used as the data source (never rendered in place)
postContent placed after the value field inside the input row

CSS parts

Style from outside with c-autocomplete::part(name) — parts are the library's only styling customization API.

PartDescription
panelThe top-layer popover container anchored below the field
cardThe elevated surface inside the panel holding the search row and the list
searchThe search-input row at the top of the panel
listThe scrollable options listbox
infoThe info row: loading while `loading` with an empty list, otherwise no-results when the query matches no options

Types

Importable from the package root: import type { … } from '@cscfi/csc-ui'

CAutocompleteFilter

Custom filter predicate for `c-autocomplete`. Return `true` to keep the option for the current query. The default matches the start of the label.

export type CAutocompleteFilter = (
  option: CAutocompleteOption,
  query: string,
) => boolean;
CAutocompleteItem

A `c-autocomplete` item. Identical shape to ; aliased for a name that reads naturally at the autocomplete call site.

export type CAutocompleteItem = CSelectItem;
CAutocompleteOption

The normalized option handed to a `c-autocomplete` `filter` predicate. `label` is the option's `name` (or its trimmed text content when authored as a slotted `<c-option>`).

export interface CAutocompleteOption {
  /** Whether the option is disabled. */
  disabled: boolean;
  /** The option's display label. */
  label: string;
  /** The option's value. */
  value: number | string;
}
CAutocompleteProps
export interface CAutocompleteProps {
  /** Make the selected value clearable */
  clearable?: boolean;
  /** Disable the input */
  disabled?: boolean;
  /**
   * Error message shown in place of the hint while the autocomplete is invalid
   *
   * @freeform
   */
  errorMessage?: string;
  /**
   * The consumer owns filtering: the component renders its options verbatim
   * and only emits `change:query` as the user types. Pair with `loading` and
   * an async data source feeding `items`
   */
  external?: boolean;
  /** Custom filter predicate; receives a normalized option + the query. Ignored when `external` is set */
  filter?: CAutocompleteFilter;
  /** Hide the hint and error messages */
  hideDetails?: boolean;
  /**
   * Hint text for the input
   *
   * @freeform
   */
  hint?: string;
  /**
   * Id of the element
   *
   * @freeform
   */
  hostId?: string;
  /** Dropdown items (when not using <c-option> elements) */
  items?: CAutocompleteItem[];
  /** Items per page before the list scrolls */
  itemsPerPage?: number;
  /**
   * Element label
   *
   * @freeform
   */
  label?: string;
  /** Label on top of the input */
  labelOnTop?: boolean;
  /** Show loading state */
  loading?: boolean;
  /**
   * Input field name
   *
   * @freeform
   */
  name?: string;
  /**
   * Message shown when the query matches no options
   *
   * @freeform
   */
  noResultsText?: string;
  /**
   * Placeholder for the in-panel search input
   *
   * @freeform
   */
  placeholder?: string;
  /** Set the autocomplete as required */
  required?: boolean;
  /** Return object instead of value */
  returnObject?: boolean;
  /** Shadow variant */
  shadow?: boolean;
  /** Field height: the 44px default or the 36px `small` box */
  size?: CFieldSize;
  /** Set the validity of the input */
  valid?: boolean;
  /** Selected value (scalar, or object when return-object is set) */
  value?: CAutocompleteItem | null | number | string;
}
CFieldSize shared

Field height of the form controls built on `c-input`. `default` is the 44px field; `small` is the 36px field. Owned here because the value passes from the wrapping control (`c-select`) into `c-input`.

export type CFieldSize = 'default' | 'small';
CSelectItem shared

A selectable item for the value-selection components (`c-select`, `c-autocomplete`) when options are supplied via the `items` prop instead of slotted `<c-option>` elements.

export interface CSelectItem {
  /** Disable the item so it cannot be selected. */
  disabled?: boolean;
  /** The item's display label. */
  name: string;
  /** The value emitted via v-model when the item is selected. */
  value: number | string;
}

<c-option>

Properties

PropertyAttributeTypeDefaultDescription
disableddisabledbooleanfalseSet option as disabled
namenamestringOption name (display label fallback)
selectedselectedbooleanfalseSet option as selected
valuevaluenumber | stringOption value

Slots

SlotDescription
defaultThe option's visible label content, shown in the c-select dropdown list

<c-option-value>

Slots

SlotDescription
defaultThe displayed option text, targeted by c-dropdown when highlighting autocomplete matches