<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
<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>
<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>
<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>
<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>
<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>
<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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
clearable | clearable | boolean | false | Make the selected value clearable |
disabled | disabled | boolean | false | Disable the input |
errorMessage | error-message | string | '' | Error message shown in place of the hint while the autocomplete is invalid |
external | external | boolean | false | 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` |
filter | — | | — | Custom filter predicate; receives a normalized option + the query. Ignored when `external` is set |
hideDetails | hide-details | boolean | false | Hide the hint and error messages |
hint | hint | string | '' | Hint text for the input |
hostId | host-id | string | '' | Id of the element |
items | — | | () => [] | Dropdown items (when not using <c-option> elements) |
itemsPerPage | items-per-page | number | 6 | Items per page before the list scrolls |
label | label | string | '' | Element label |
labelOnTop | label-on-top | boolean | false | Label on top of the input |
loading | loading | boolean | false | Show loading state |
name | name | string | '' | Input field name |
noResultsText | no-results-text | string | 'No matching data' | Message shown when the query matches no options |
placeholder | placeholder | string | '' | Placeholder for the in-panel search input |
required | required | boolean | false | Set the autocomplete as required |
returnObject | return-object | boolean | false | Return object instead of value |
shadow | shadow | boolean | false | Shadow variant |
size | size | CFieldSize | 'default' | Field height: the 44px default or the 36px `small` box |
valid | valid | boolean | true | Set the validity of the input |
value | value | | null | Selected value (scalar, or object when return-object is set) |
Events
| Event | Detail | Description |
|---|---|---|
change | void | Native change event (no detail) dispatched whenever a selection is committed or cleared; bubbles through the shadow boundary for form-style listeners. |
change:query | string | Fired 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`. |
changeValue | CAutocompleteItem | null | number | string | Fired 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. |
input | void | Native bubbling input event dispatched alongside every value change so a plain `v-model` stays in sync. Carries no detail. |
update:value | CAutocompleteItem | null | number | string | Fired alongside `changeValue` with the same detail — the `v-model` contract. |
Methods
| Method | Signature | Description |
|---|---|---|
reset | () | Reset autocomplete state |
Slots
| Slot | Description |
|---|---|
pre | Content placed before the value field inside the input row |
default | The c-option elements used as the data source (never rendered in place) |
post | Content 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.
| Part | Description |
|---|---|
panel | The top-layer popover container anchored below the field |
card | The elevated surface inside the panel holding the search row and the list |
search | The search-input row at the top of the panel |
list | The scrollable options listbox |
info | The 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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
disabled | disabled | boolean | false | Set option as disabled |
name | name | string | — | Option name (display label fallback) |
selected | selected | boolean | false | Set option as selected |
value | value | number | string | — | Option value |
Slots
| Slot | Description |
|---|---|
default | The option's visible label content, shown in the c-select dropdown list |
<c-option-value>
Slots
| Slot | Description |
|---|---|
default | The displayed option text, targeted by c-dropdown when highlighting autocomplete matches |