A flexible, lightweight React tags input component with keyboard support
npm install react-flexible-tagsA lightweight, customizable, and feature-rich tag input component for React with TypeScript support.
- 🎨 Fully Customizable - Custom rendering for tags, input, and suggestions
- ⌨️ Keyboard Navigation - Arrow keys, Enter, Escape, and Backspace support
- 🔍 Async Suggestions - Built-in autocomplete with debouncing
- ✅ Validation - RegExp or custom function validation
- 🎯 Flexible Delimiters - Configure Enter, Tab, comma, semicolon, or custom keys
- 📦 Zero Dependencies - Lightweight and performant
- 🎭 TypeScript - Full TypeScript support with type definitions
- ♿ Accessible - Keyboard-friendly and screen reader compatible
``bash`
npm install react-flexible-tags
`bash`
yarn add react-flexible-tags
`bash`
pnpm add react-flexible-tags
`jsx
import { useState } from "react";
import FlexibleTags from "react-flexible-tags";
import "react-flexible-tags/dist/react-flexible-tags.css";
function App() {
const [tags, setTags] = useState([]);
return (
);
}
`
| Prop | Type | Default | Description |
| ------------------ | --------------------------------------------------------------- | ---------------------------- | ------------------------------- |
| value | string[] | [] | Current tags array |onChange
| | (tags: string[]) => void | - | Callback when tags change |placeholder
| | string | "Add tag..." | Input placeholder text |max
| | number | 20 | Maximum number of tags allowed |delimiters
| | Array | ["Enter", "Tab", ",", ";"] | Keys that trigger tag creation |validate
| | RegExp \| (tag: string) => boolean | - | Validation for new tags |renderTag
| | (tag: string, index: number, remove: () => void) => ReactNode | - | Custom tag renderer |renderInput
| | See below | - | Custom input renderer |asyncSuggestions
| | (query: string) => Promise | - | Async function for autocomplete |renderSuggestion
| | (suggestion: string, isActive: boolean) => ReactNode | - | Custom suggestion renderer |
`jsx
import { useState } from "react";
import FlexibleTags from "react-flexible-tags";
function BasicExample() {
const [tags, setTags] = useState(["react", "typescript"]);
return (
onChange={setTags}
placeholder="Type and press Enter..."
max={10}
/>
);
}
`
`jsx
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
onChange={setEmails}
placeholder="Enter email addresses..."
validate={emailRegex}
delimiters={["Enter", "Tab", ",", " "]}
/>;
`
`jsx`
onChange={setTags}
validate={(tag) => tag.length >= 3 && tag.length <= 20}
placeholder="Tags must be 3-20 characters..."
/>
`jsx/api/tags?q=${query}
async function fetchSuggestions(query) {
const response = await fetch();
const data = await response.json();
return data.suggestions;
}
onChange={setTags}
asyncSuggestions={fetchSuggestions}
placeholder="Start typing for suggestions..."
/>;
`
`jsx`
onChange={setTags}
renderTag={(tag, index, remove) => (
🏷️
{tag}
)}
/>
`jsx`
onChange={setTags}
renderInput={({ value, onChange, onKeyDown, ref, placeholder }) => (
ref={ref}
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={onKeyDown}
placeholder={placeholder}
className="my-custom-input"
style={{ fontSize: "16px", padding: "8px" }}
/>
)}
/>
`jsx`
onChange={setTags}
asyncSuggestions={fetchSuggestions}
renderSuggestion={(suggestion, isActive) => (
suggestion ${isActive ? "highlighted" : ""}}>
🔍
{suggestion}
)}
/>
`jsx
import { useState } from "react";
import FlexibleTags from "react-flexible-tags";
function AdvancedExample() {
const [skills, setSkills] = useState(["React", "TypeScript"]);
const popularSkills = [
"JavaScript",
"TypeScript",
"React",
"Vue",
"Angular",
"Node.js",
"Python",
"Java",
"C++",
"Go",
"Rust",
];
const getSuggestions = async (query) => {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 300));
return popularSkills.filter((skill) =>
skill.toLowerCase().includes(query.toLowerCase()),
);
};
return (
{skills.length} / 15 skills added
Keyboard Shortcuts
| Key | Action |
| --------------------------- | --------------------------------------- |
|
Enter / Tab / , / ; | Add current input as tag (configurable) |
| Backspace | Remove last tag when input is empty |
| Arrow Down | Navigate to next suggestion |
| Arrow Up | Navigate to previous suggestion |
| Enter | Select active suggestion |
| Escape | Close suggestions dropdown |Styling
The component comes with default styles. Import the CSS file:
`jsx
import "react-flexible-tags/dist/react-flexible-tags.css";
`$3
Override the default classes:
`css
.flexible-tags__container {
border: 2px solid #3b82f6;
border-radius: 8px;
padding: 8px;
}.flexible-tags__tag {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 6px 12px;
border-radius: 16px;
}
.flexible-tags__input {
font-size: 16px;
color: #1f2937;
}
.flexible-tags__suggestion {
padding: 12px;
font-weight: 500;
}
.flexible-tags__suggestion.active {
background: #3b82f6;
color: white;
}
`TypeScript
The package includes full TypeScript definitions:
`typescript
import FlexibleTags, { FlexibleTagsProps } from 'react-flexible-tags';interface MyComponentProps {
initialTags: string[];
}
const MyComponent: React.FC = ({ initialTags }) => {
const [tags, setTags] = useState(initialTags);
const handleChange = (newTags: string[]) => {
setTags(newTags);
// Do something with newTags
};
return ;
};
`Common Use Cases
$3
`jsx
validate={/^[^\s@]+@[^\s@]+\.[^\s@]+$/}
placeholder="Enter email addresses..."
/>
`$3
`jsx
validate={(tag) => tag.startsWith("#")}
placeholder="Add hashtags (e.g., #react)..."
/>
`$3
`jsx
validate={/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/}
placeholder="Enter hex colors (e.g., #FF5733)..."
/>
`$3
`jsx
validate={(tag) => {
try {
new URL(tag);
return true;
} catch {
return false;
}
}}
placeholder="Add website URLs..."
/>
``- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Contributions are welcome! Please feel free to submit a Pull Request.
MIT © CoderBoy M J H Nahid
- GitHub Repository
- NPM Package
- Report Issues
---
Made with ❤️ by CoderBoy M J H Nahid