Create a Tags Input Field With Autocomplete in React
Add to your RSS feed9 September 20247 min readTable of Contents
In this tutorial, we are going to create a tags input component with autocomplete using React JS without using any external packages. This guide demonstrates how to manage state, dynamically filter suggestions, and efficiently handle user interactions such as adding and removing tags. Perfect for developers looking to implement tag inputs with minimal dependencies and flexible design.
Project Setup
We will use Vite for a fast and simple project build. Vite offers instant module hot reloading, improved performance, and quicker build times compared to traditional bundlers.
npm create vite@latest react-tags-autocomplete -- --template react-ts
then
cd react-tags-autocomplete npm install npm run devClear the application
Remove file App.css, clean App.tsx (remove everything).
App.tsx
1 function App() {2 return <></>;3 }45 export default App;
Let's create components folder inside the src folder.
Create a tags input component
Create a TextInput.tsx file in the components folder
1 import { ChangeEvent, useState } from 'react';23 const TextInput = () => {4 const [tags, setTags] = useState<string[]>([]);5 const handleKeydown = (e: ChangeEvent<HTMLInputElement> & KeyboardEvent) => {6 if (e.key !== 'Enter') {7 return;8 }9 const value = e.target.value;10 if (!value.trim()) {11 return;12 }13 setTags([...tags, value]);14 e.target.value = '';15 };1617 const removeTag = (idx) => {18 setTags(tags.filter((el, i) => i !== idx));19 };20 return (21 <div className='text-input-container'>22 {tags.map((tag, i) => {23 return (24 <div className='tag-item' key={tag + i}>25 <span className='text'>{tag}</span>26 <span className='close' onClick={() => removeTag(i)}>27 ×28 </span>29 </div>30 );31 })}32 <input33 type='text'34 placeholder='Type something...'35 className='text-input'36 onKeyDown={handleKeydown}37 />38 </div>39 );40 };41 export default TextInput;
Explanation
- The input field captures user text, filtering the predefined suggestions (suggestions array).
- The filtered suggestions are shown as a dropdown. Clicking on a suggestion or pressing Enter adds it to the list of tags.
- The added tags are displayed with an option to remove them.
Styling the Component
Remove everything from index.css file, and put the next styles
1 * {2 margin: 0;3 padding: 0;4 }56 html,7 body {8 height: 100%;9 width: 100%;10 }1112 body {13 display: flex;14 justify-content: center;15 align-items: center;16 font-family: 'Courier New', monospace;17 }1819 label {20 margin-bottom: 4px;21 display: block;22 font-size: 1.125rem;23 line-height: 1.75rem;24 }2526 #root {27 display: flex;28 flex-direction: column;29 justify-content: center;30 max-width: 540px;31 margin-left: auto;32 margin-right: auto;33 padding-left: 1rem;34 padding-right: 1rem;35 margin-top: calc(1.5rem);36 margin-bottom: calc(1.5rem);37 color: #333333;38 }3940 .text-input__wrapper {41 border: 1px solid black;42 padding: 0.5rem;43 border-radius: 3px;44 width: min(80vw, 600px);45 margin-top: 1em;46 display: flex;47 align-items: center;48 flex-wrap: wrap;49 gap: 0.5em;50 }51 .tag-item {52 background-color: rgb(218, 216, 216);53 display: inline-block;54 padding: 0.5em 0.75em;55 border-radius: 20px;56 }57 .tag-item .close {58 width: 20px;59 height: 20px;60 background-color: rgb(48, 48, 48);61 color: #fff;62 border-radius: 50%;63 display: inline-flex;64 justify-content: center;65 align-items: center;66 margin-left: 0.5em;67 font-size: 18px;68 }69 .text-input {70 padding-left: 1rem;71 padding-right: 1rem;72 padding-top: 0.625rem;73 padding-bottom: 0.625rem;74 font-size: 1.125rem;75 line-height: 1.75rem;76 background-color: #ffffff;77 position: relative;78 flex-grow: 1;79 outline: none;80 border: none;81 outline: none;82 }
Run
npm run devWe got an almost finished component for adding tags, but it has one small bug. Right now, we can add duplicate tags in the input. To fix this, we need to modify the handleKeydown() function.
1 const handleKeydown = (e: ChangeEvent<HTMLInputElement> & KeyboardEvent) => {2 if (e.key !== 'Enter') {3 return;4 }5 const value = e.target.value;6 if (!value.trim()) {7 return;8 }910 setTags((tags: string[]) => {11 if (tags.some((tag) => tag.toLowerCase() === value.toLowerCase())) {12 return [...tags];13 } else {14 return [...tags, value];15 }16 });17 e.target.value = '';18 };
Now only unique tags will be added.
Create Autocomplete Component
Create AutoComplete.tsx file inside the components directory.
1 import React, { ChangeEvent, useState } from 'react';23 type AutoCompleteProps = {4 possibleValues: string[];5 handleKeydown: () => void;6 setTags: (values: string[]) => void;7 };89 function Autocomplete({ possibleValues, handleKeydown, setTags }: AutoCompleteProps) {10 const [inputValue, setInputValue] = useState('');11 const [suggestions, setSuggestions] = useState<string[]>([]);1213 const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {14 const value = event.target.value;1516 setInputValue(value);1718 if (value.length > 0) {19 const filteredSuggestions = possibleValues.filter((suggestion) =>20 suggestion.toLowerCase().includes(value.toLowerCase()),21 );22 setSuggestions(filteredSuggestions);23 } else {24 setSuggestions([]);25 }26 };2728 const handleSuggestionClick = (value: string) => {29 setTags((tags: string[]) => {30 if (tags.some((tag) => tag.toLowerCase() === value.toLowerCase())) {31 return [...tags];32 } else {33 return [...tags, value];34 }35 });3637 setSuggestions([]);38 setInputValue('');39 };4041 const onKeyDown = (e: ChangeEvent<HTMLInputElement> & KeyboardEvent) => {42 handleKeydown(e);43 if (e.key === 'Enter') {44 setInputValue('');45 setSuggestions([]);46 }47 };4849 return (50 <>51 <input52 type='text'53 value={inputValue}54 onChange={handleInputChange}55 aria-autocomplete='list'56 aria-controls='autocomplete-list'57 onKeyDown={onKeyDown}58 className='text-input'59 autoFocus60 />61 <div className='autocomplete-wrapper'>62 {suggestions.length > 0 && (63 <ul id='autocomplete-list' className='suggestions-list' role='listbox'>64 {suggestions.map((suggestion, index) => (65 <li key={index} onClick={() => handleSuggestionClick(suggestion)} role='option'>66 {suggestion}67 </li>68 ))}69 </ul>70 )}71 </div>72 </>73 );74 }7576 export default Autocomplete;
Key Features:
- Autocomplete Suggestions:
- The component provides a list of suggestions based on the user's input. When the user types into the input field, it filters through possible values and displays matching suggestions.
- Duplicate Prevention:
- The component ensures that duplicate tags are not added.
- Keyboard Navigation:
- The onKeyDown function handles key events, particularly preventing unwanted behavior when the Enter key is pressed.
Breakdown of the Code:
- State Management:
- inputValue: Keeps track of what the user is typing in the input field.
- suggestions: An array of filtered possible values that match the user's input.
1 const [inputValue, setInputValue] = useState('');2 const [suggestions, setSuggestions] = useState<string[]>([]);
- Handling Input Changes:
- handleInputChange: Updates inputValue based on user input and generates suggestions by filtering possibleValues.
- possibleValues is an array of strings passed as props. The filter checks if the input matches any of the possibleValues (case-insensitive).
1 const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {2 const value = event.target.value;3 setInputValue(value);45 if (value.length > 0) {6 const filteredSuggestions = possibleValues.filter((suggestion) =>7 suggestion.toLowerCase().includes(value.toLowerCase()),8 );9 setSuggestions(filteredSuggestions);10 } else {11 setSuggestions([]);12 }13 };
- Handling Suggestion Clicks:
- When a user clicks on a suggestion, the handleSuggestionClick function is triggered.
- This function adds the selected suggestion to the tags (managed by the parent component via setTags) if it's not already present.
- After adding the tag, it clears the suggestions and resets inputValue.
1 const handleSuggestionClick = (value: string) => {2 setTags((tags: string[]) => {3 if (tags.some((tag) => tag.toLowerCase() === value.toLowerCase())) {4 return [...tags]; // No duplicate tags5 } else {6 return [...tags, value]; // Add the new tag7 }8 });910 setSuggestions([]);11 setInputValue('');12 };
- Handling Keyboard Input:
- onKeyDown: Handles keyboard events, especially when the Enter key is pressed.
- It clears both the input and suggestions when Enter is pressed.
- It also invokes handleKeydown passed from the parent component for further customization or handling.
1 const onKeyDown = (e: ChangeEvent<HTMLInputElement> & KeyboardEvent) => {2 handleKeydown(e);3 if (e.key === 'Enter') {4 setInputValue('');5 setSuggestions([]);6 }7 };
- Rendering:
- The component consists of an input field and a suggestions list.
- The input field updates inputValue and triggers suggestions filtering, while the suggestions list shows filtered options.
- When there are matching suggestions, the component renders a list of options. Clicking an option adds it to the tags.
1 return (2 <>3 <input4 type='text'5 value={inputValue}6 onChange={handleInputChange}7 aria-autocomplete='list'8 aria-controls='autocomplete-list'9 onKeyDown={onKeyDown}10 className='text-input'11 autoFocus12 />13 <div className='autocomplete-wrapper'>14 {suggestions.length > 0 && (15 <ul id='autocomplete-list' className='suggestions-list' role='listbox'>16 {suggestions.map((suggestion, index) => (17 <li key={index} onClick={() => handleSuggestionClick(suggestion)} role='option'>18 {suggestion}19 </li>20 ))}21 </ul>22 )}23 </div>24 </>25 );
Props:
- possibleValues: Array of strings that act as potential autocomplete suggestions.
- handleKeydown: A function passed from the parent component to handle keyboard events.
- setTags: A function that updates the list of tags when a suggestion is selected.
We just need to replace the input with our custom AutoComplete component and pass all the necessary props to it.
1 <Autocomplete2 possibleValues={['css', 'html', 'react']}3 handleKeydown={handleKeydown}4 setTags={setTags}5 />
Add some styles to index.css
1 .autocomplete-wrapper {2 width: 100%;3 }45 .suggestions-list {6 top: 100%;7 border: 1px solid #ccc;8 background: white;9 list-style: none;10 padding: 0;11 margin: 0;12 border-radius: 3px;13 }1415 .suggestions-list li {16 padding: 8px;1718 cursor: pointer;19 }2021 .suggestions-list li:hover {22 background-color: #e9e9e9;23 }
Now, let's test it:
npm run devThere’s only one issue left: we lose focus after adding a tag through a suggestion. To fix this, we need to pass a ref to the input and manually set the focus.
Referencing Values with Refs
Define a ref at the top of AutoComplete complement:
1 const inputRef = useRef(null);
then provide it to the input tag
1 ref = { inputRef };
Now, at the very end of the handleSuggestionClick() and onKeyDown() functions (after all the code has been executed), add the line:
1 inputRef.current.focus();
Conclusion:
This component allows users to type and select from filtered suggestions. It prevents duplicate entries, handles keyboard events, and manages the internal state of user input and suggestions efficiently.
You can also view it on GitHub Gist: AutoComplete.tsx | TextInput.tsx