A search form with stylized autocomplete suggestions.
TypeaheadSearch contains a form with a text input, a submit button, and a slot for hidden inputs. The parent component must listen for changes in the search query (which are debounced by default), fetch or calculate search results, then provide them as an array of search results for display to the user in a dropdown menu.
At the end of the list of search results, a final option to go to the search page for the current search query is provided.
Events are emitted to the parent when a search result is selected and when the form is submitted, with data about the selected item (e.g. for analytics).
TextInput props apply
This component contains a TextInput component. You can bind TextInput props to this component and they will be passed to the TextInput within.
Attributes passed to input
This component will pass any HTML attributes applied to it, except for CSS class, to the <input> element within the component.
This implementation of TypeaheadSearch fetches articles from English Wikipedia. Note that the input expands on focus via the autoExpandWidth prop, thumbnails are enabled via the showThumbnail prop, and the "search" button is added via the buttonLabel prop. Open the console to see emitted events.
<template><div><cdx-typeahead-searchid="typeahead-search-wikipedia"form-action="https://en.wikipedia.org/w/index.php"button-label="Search"search-results-label="Search results":search-results="searchResults":search-footer-url="searchFooterUrl":show-thumbnail="true":highlight-query="true":auto-expand-width="true"placeholder="Search Wikipedia"@input="onInput"@search-result-click="onSearchResultClick"@submit="onSubmit"><template#default><inputtype="hidden"name="language"value="en"><inputtype="hidden"name="title"value="Special:Search"></template><template#search-footer-text="{ searchQuery }">
Search Wikipedia for pages containing
<strongclass="cdx-typeahead-search__search-footer__query">
{{ searchQuery }}
</strong></template></cdx-typeahead-search></div></template><scriptlang="ts">import{ defineComponent, ref }from'vue';import{ CdxTypeaheadSearch, SearchResult, SearchResultClickEvent }from'@wikimedia/codex';import{ RestResult }from'./types';/* eslint-disable no-console */exportdefaultdefineComponent({name:'TypeaheadSearchWikipedia',components:{ CdxTypeaheadSearch },setup(){const searchResults = ref<SearchResult[]>([]);const searchFooterUrl =ref('');const currentSearchTerm =ref('');functiononInput(value: string){
console.log('"input" event emitted with value: '+ value );// Internally track the current search term.
currentSearchTerm.value = value;// Unset search results and the search footer URL if there is no value.if(!value || value ===''){
searchResults.value =[];
searchFooterUrl.value ='';return;}/**
* Format search results for consumption by TypeaheadSearch.
*
* @param pages
* @return
*/functionadaptApiResponse(pages: RestResult[]): SearchResult[]{return pages.map(({ id, key, title, description, thumbnail })=>({label: title,value: id,description: description,url:`https://en.wikipedia.org/wiki/${encodeURIComponent( key )}`,thumbnail: thumbnail ?{url: thumbnail.url,width: thumbnail.width ??undefined,height: thumbnail.height ??undefined}:undefined}));}fetch(`https://en.wikipedia.org/w/rest.php/v1/search/title?q=${encodeURIComponent( value )}&limit=10&`).then((resp)=> resp.json()).then((data:{pages: RestResult[]})=>{// Make sure this data is still relevant first.if( currentSearchTerm.value === value ){// If there are results, format them into an array of// SearchResults to be passed into TypeaheadSearch for// display as a menu of suggestions.
searchResults.value = data.pages && data.pages.length >0?adaptApiResponse( data.pages ):[];// Set the search footer URL to a link to the search// page for the current search query.
searchFooterUrl.value =`https://en.wikipedia.org/w/index.php?title=Special%3ASearch&fulltext=1&search=${encodeURIComponent( value )}`;}}).catch(()=>{// On error, reset search results and search footer URL.
searchResults.value =[];
searchFooterUrl.value ='';});}functiononSearchResultClick(event: SearchResultClickEvent){
console.log('"search-result-click" event emitted with value:');
console.log( event );}functiononSubmit(event: SearchResultClickEvent){
console.log('"submit" event emitted with value:');
console.log( event );}return{
searchResults,
searchFooterUrl,
onInput,
onSearchResultClick,
onSubmit
};}});/* eslint-enable no-console */</script>
In this example, results are fetched from Wikidata. Thumbnails are disabled, and the input doesn't expand on focus. There is no button, because the buttonLabel prop is not set. Open the console to see emitted events.
<template><div><cdx-typeahead-searchid="typeahead-search-wikidata"form-action="https://www.wikidata.org/w/index.php"search-results-label="Search results":search-results="searchResults":search-footer-url="searchFooterUrl":highlight-query="true"placeholder="Search Wikidata"@input="onInput"@search-result-click="onSearchResultClick"@submit="onSubmit"><template#default><inputtype="hidden"name="language"value="en"><inputtype="hidden"name="title"value="Special:Search"></template><template#search-footer-text="{ searchQuery }">
Search Wikidata for pages containing
<strongclass="cdx-typeahead-search__search-footer__query">
{{ searchQuery }}
</strong></template></cdx-typeahead-search></div></template><scriptlang="ts">import{ defineComponent, ref }from'vue';import{ CdxTypeaheadSearch, SearchResult, SearchResultClickEvent }from'@wikimedia/codex';import{ Result }from'./types';/* eslint-disable no-console */exportdefaultdefineComponent({name:'TypeaheadSearchWikidata',components:{ CdxTypeaheadSearch },setup(){const searchResults = ref<SearchResult[]>([]);const searchFooterUrl =ref('');const currentSearchTerm =ref('');functiononInput(value: string){
console.log('"input" event emitted with value: '+ value );// Internally track the current search term.
currentSearchTerm.value = value;// Unset search results and the search footer URL if there is no value.if(!value || value ===''){
searchResults.value =[];
searchFooterUrl.value ='';return;}/**
* Format search results for consumption by TypeaheadSearch.
*
* @param pages
* @return
*/functionadaptApiResponse(pages: Result[]): SearchResult[]{return pages.map(({ id, label, url, match, description, display ={}})=>({value: id,
label,match: match.type ==='alias'?`(${match.text})`:'',
description,
url,language:{label: display?.label?.language,match: match.type ==='alias'? match.language :undefined,description: display?.description?.language
}}));}fetch(`https://www.wikidata.org/w/api.php?origin=*&action=wbsearchentities&format=json&search=${encodeURIComponent( value )}&language=en&uselang=en&type=item`).then((resp)=> resp.json()).then((data:{search: Result[]})=>{// Make sure this data is still relevant first.if( currentSearchTerm.value === value ){// If there are results, format them into an array of// SearchResults to be passed into TypeaheadSearch for// display as a menu of search results.
searchResults.value = data.search && data.search.length >0?adaptApiResponse( data.search ):[];// Set the search footer URL to a link to the search// page for the current search query.
searchFooterUrl.value =`https://www.wikidata.org/w/index.php?search=${encodeURIComponent( value )}&title=Special%3ASearch&fulltext=1`;}}).catch(()=>{// On error, reset search results and search footer URL.
searchResults.value =[];
searchFooterUrl.value ='';});}functiononSearchResultClick(event: SearchResultClickEvent){
console.log('"search-result-click" event emitted with value:');
console.log( event );}functiononSubmit(event: SearchResultClickEvent){
console.log('"submit" event emitted with value:');
console.log( event );}return{
searchResults,
searchFooterUrl,
onInput,
onSearchResultClick,
onSubmit
};}});/* eslint-enable no-console */</script>
The initialInputValue prop can be used to pass in the initial value of the TextInput. This is useful when replacing a server-rendered UI where the user may have started typing a search query, or for pre-populating the search term when a user navigates back to a page where they had previously entered one.
On mount, TypeaheadSearch will fetch search results for the initial input value if it's provided. After that, the input value is tracked internally and will be emitted up to the parent component when the value changes.
<template><div><cdx-typeahead-searchid="typeahead-search-default"form-action="https://en.wikipedia.org/w/index.php"button-label="Search"search-results-label="Search results":initial-input-value="initialInputValue":search-results="searchResults":search-footer-url="searchFooterUrl":show-thumbnail="true":highlight-query="true"placeholder="Search Wikipedia"@input="onInput"><template#search-footer-text="{ searchQuery }">
Search Wikipedia for pages containing
<strongclass="cdx-typeahead-search__search-footer__query">
{{ searchQuery }}
</strong></template></cdx-typeahead-search></div></template><scriptlang="ts">import{ defineComponent, ref }from'vue';import{ CdxTypeaheadSearch, SearchResult }from'@wikimedia/codex';import{ RestResult }from'./types';exportdefaultdefineComponent({name:'TypeaheadSearchInitialValue',components:{ CdxTypeaheadSearch },props:{/**
* For demo purposes, the initial input value "Color" has been passed in
* as a prop.
*/initialInputValue:{type: String,default:''}},setup(){const searchResults = ref<SearchResult[]>([]);const searchFooterUrl =ref('');const currentSearchTerm =ref('');functiononInput(value: string){
currentSearchTerm.value = value;if(!value || value ===''){
searchResults.value =[];
searchFooterUrl.value ='';return;}/**
* Format search results for consumption by TypeaheadSearch.
*
* @param pages
* @return
*/functionadaptApiResponse(pages: RestResult[]): SearchResult[]{return pages.map(({ id, key, title, description, thumbnail })=>({label: title,value: id,description: description,url:`https://en.wikipedia.org/wiki/${encodeURIComponent( key )}`,thumbnail: thumbnail ?{url: thumbnail.url,width: thumbnail.width ??undefined,height: thumbnail.height ??undefined}:undefined}));}fetch(`https://en.wikipedia.org/w/rest.php/v1/search/title?q=${encodeURIComponent( value )}&limit=10&`).then((resp)=> resp.json()).then((data:{pages: RestResult[]})=>{if( currentSearchTerm.value === value ){
searchResults.value = data.pages && data.pages.length >0?adaptApiResponse( data.pages ):[];
searchFooterUrl.value =`https://en.wikipedia.org/w/index.php?title=Special%3ASearch&fulltext=1&search=${encodeURIComponent( value )}`;}}).catch(()=>{
searchResults.value =[];
searchFooterUrl.value ='';});}return{
searchResults,
searchFooterUrl,
onInput
};}});</script>
Pending state indicators, including an inline progress bar and a message stating that results are pending, can be displayed to users with slower connections while search results are being fetched. To enable this, provide content in the search-results-pending slot.
The pending state indicators will display when a search takes longer than half a second, so you may need to throttle your connection to see them in the demo below.
<template><cdx-typeahead-searchid="typeahead-search-pending-state"form-action="https://en.wikipedia.org/w/index.php"button-label="Search"search-results-label="Search results":search-results="searchResults":search-footer-url="searchFooterUrl":show-thumbnail="true":highlight-query="true":auto-expand-width="true"placeholder="Search Wikipedia"@input="onInput"><template#default><inputtype="hidden"name="language"value="en"><inputtype="hidden"name="title"value="Special:Search"></template><template#search-results-pending>
Loading search results...
</template><template#search-footer-text="{ searchQuery }">
Search Wikipedia for pages containing
<strongclass="cdx-typeahead-search__search-footer__query">
{{ searchQuery }}
</strong></template></cdx-typeahead-search></template><scriptlang="ts">import{ defineComponent, ref }from'vue';import{ CdxTypeaheadSearch, SearchResult }from'@wikimedia/codex';import{ RestResult }from'./types';exportdefaultdefineComponent({name:'TypeaheadSearchPendingState',components:{ CdxTypeaheadSearch },setup(){const searchResults = ref<SearchResult[]>([]);const searchFooterUrl =ref('');const currentSearchTerm =ref('');/**
* Format search results for consumption by TypeaheadSearch.
*
* @param pages
* @return
*/functionadaptApiResponse(pages: RestResult[]): SearchResult[]{return pages.map(({ id, key, title, description, thumbnail })=>({label: title,value: id,description: description,url:`https://en.wikipedia.org/wiki/${encodeURIComponent( key )}`,thumbnail: thumbnail ?{url: thumbnail.url,width: thumbnail.width ??undefined,height: thumbnail.height ??undefined}:undefined}));}functiononInput(value: string){// Internally track the current search term.
currentSearchTerm.value = value;// Unset search results and the search footer URL if there is no value.if(!value || value ===''){
searchResults.value =[];
searchFooterUrl.value ='';return;}fetch(`https://en.wikipedia.org/w/rest.php/v1/search/title?q=${encodeURIComponent( value )}&limit=10&`).then((resp)=> resp.json()).then((data:{pages: RestResult[]})=>{// Make sure this data is still relevant first.if( currentSearchTerm.value === value ){// If there are results, format them into an array of// SearchResults to be passed into TypeaheadSearch for// display as a menu of suggestions.
searchResults.value = data.pages && data.pages.length >0?adaptApiResponse( data.pages ):[];// Set the search footer URL to a link to the search// page for the current search query.
searchFooterUrl.value =`https://en.wikipedia.org/w/index.php?title=Special%3ASearch&fulltext=1&search=${encodeURIComponent( value )}`;}}).catch(()=>{// On error, reset search results and search footer URL.
searchResults.value =[];
searchFooterUrl.value ='';});}return{
searchResults,
searchFooterUrl,
onInput
};}});</script>
A message prompt that no search results were found. To enable this, provide content in the search-no-results-text slot.
<template><cdx-typeahead-searchid="typeahead-search-no-result"form-action="https://en.wikipedia.org/w/index.php"button-label="Search"search-results-label="Search results":search-results="searchResults":search-footer-url="searchFooterUrl":show-thumbnail="true":highlight-query="true":auto-expand-width="true"placeholder="Search Wikipedia"@input="onInput"><template#default><inputtype="hidden"name="language"value="en"><inputtype="hidden"name="title"value="Special:Search"></template><template#search-no-results-text>
No results found
</template><template#search-footer-text="{ searchQuery }">
Search Wikipedia for pages containing
<strongclass="cdx-typeahead-search__search-footer__query">
{{ searchQuery }}
</strong></template></cdx-typeahead-search></template><scriptlang="ts">import{ defineComponent, ref }from'vue';import{ CdxTypeaheadSearch, SearchResult }from'@wikimedia/codex';import{ RestResult }from'./types';exportdefaultdefineComponent({name:'TypeaheadSearchNoResult',components:{ CdxTypeaheadSearch },setup(){const searchResults = ref<SearchResult[]>([]);const searchFooterUrl =ref('');const currentSearchTerm =ref('');/**
* Format search results for consumption by TypeaheadSearch.
*
* @param pages
* @return
*/functionadaptApiResponse(pages: RestResult[]): SearchResult[]{return pages.map(({ id, key, title, description, thumbnail })=>({label: title,value: id,description: description,url:`https://en.wikipedia.org/wiki/${encodeURIComponent( key )}`,thumbnail: thumbnail ?{url: thumbnail.url,width: thumbnail.width ??undefined,height: thumbnail.height ??undefined}:undefined}));}functiononInput(value: string){// Internally track the current search term.
currentSearchTerm.value = value;// Unset search results and the search footer URL if there is no value.if(!value || value ===''){
searchResults.value =[];
searchFooterUrl.value ='';return;}fetch(`https://en.wikipedia.org/w/rest.php/v1/search/title?q=${encodeURIComponent( value )}&limit=10&`).then((resp)=> resp.json()).then((data:{pages: RestResult[]})=>{// Make sure this data is still relevant first.if( currentSearchTerm.value === value ){// If there are results, format them into an array of// SearchResults to be passed into TypeaheadSearch for// display as a menu of suggestions.
searchResults.value = data.pages && data.pages.length >0?adaptApiResponse( data.pages ):[];// Set the search footer URL to a link to the search// page for the current search query.
searchFooterUrl.value =`https://en.wikipedia.org/w/index.php?title=Special%3ASearch&fulltext=1&search=${encodeURIComponent( value )}`;}}).catch(()=>{// On error, reset search results and search footer URL.
searchResults.value =[];
searchFooterUrl.value ='';});}return{
searchResults,
searchFooterUrl,
onInput
};}});</script>