Forms inputchips

Chip/Tag Input

Interactive tag management control.

Preview

Usage

Copy the full block below to use this component with its imports.

astro
---
import { Chip/TagInput } from 'astro-component-kit';
---

<ChipInput label="Product Tags" initialChips={["New", "Sale"]} placeholder="Add tag..." />
--- import { Chip/TagInput } from 'astro-component-kit'; --- <ChipInput label="Product Tags" initialChips={["New", "Sale"]} placeholder="Add tag..." />

Manual Installation

If you are not using the npm package, create a new file src/components/lib/Chip/TagInput.astro and paste the following code:

astro
---
/**
 * ChipInput — An interactive input for managing a list of tags or chips.
 * 
 * @param {string} label - Input labeling title.
 * @param {string[]} initialChips - Optional array of strings to pre-populate.
 * @param {string} name - HTML name for form submission.
 * @param {string} placeholder - Placeholder text for the input area.
 */
interface Props {
  label?: string;
  initialChips?: string[];
  name?: string;
  placeholder?: string;
}

const { label, initialChips = [], name, placeholder = "Add tag..." } = Astro.props;
---

<div class="chip-input-container">
  {label && <label class="chip-input-label">{label}</label>}
  <div class="chip-input" data-chip-input>
    <div class="chip-input__list" data-chip-list>
      {initialChips.map(chip => (
        <span class="chip">
          {chip}
          <button type="button" aria-label={`Remove ${chip}`} data-remove-chip>×</button>
          <input type="hidden" name={`${name}[]`} value={chip} />
        </span>
      ))}
    </div>
    <input 
      type="text" 
      class="chip-input__field" 
      placeholder={initialChips.length > 0 ? "" : placeholder} 
      data-chip-field
    />
  </div>
</div>

<style>
  .chip-input-container { display: flex; flex-direction: column; gap: var(--sp-2, 0.5rem); width: 100%; }
  .chip-input-label { font-size: 0.85rem; font-weight: 600; color: var(--c-text-2, #94a3b8); margin-left: 0.5rem; }
  
  .chip-input { 
    display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.5rem; 
    background: var(--c-bg-elev, rgba(0,0,0,0.1)); 
    border: 1px solid var(--c-border, rgba(255,255,255,0.1)); 
    border-radius: var(--r-md, 10px); 
    transition: border-color 0.2s;
  }
  .chip-input:focus-within { border-color: var(--c-primary, #6366f1); }
  
  .chip-input__list { display: flex; flex-wrap: wrap; gap: 0.5rem; }
  
  .chip { 
    background: var(--c-primary, #6366f1); 
    color: #fff; 
    padding: 0.2rem 0.6rem; 
    border-radius: var(--r-sm, 6px); 
    font-size: 0.8rem; 
    display: flex; align-items: center; gap: 0.4rem; 
  }
  .chip button { 
    background: none; border: none; color: #fff; cursor: pointer; 
    font-size: 1.1rem; line-height: 1; padding: 0; 
    opacity: 0.8; transition: opacity 0.2s;
  }
  .chip button:hover { opacity: 1; }
  
  .chip-input__field { 
    background: none; border: none; outline: none; 
    color: var(--c-text-1, #fff); flex: 1; min-width: 100px;
    padding: 0.2rem; font-family: inherit; font-size: 0.9rem;
  }
</style>

<script>
  // Simplified client-side logic for adding/removing chips
  document.querySelectorAll('[data-chip-input]').forEach(container => {
    const list = container.querySelector('[data-chip-list]');
    const input = container.querySelector('[data-chip-field]') as HTMLInputElement;
    const name = "tags"; // Should ideally be passed from props but hidden inputs solve this for now

    const addChip = (value: string) => {
      if (!value.trim() || !list) return;
      const chip = document.createElement('span');
      chip.className = 'chip';
      chip.innerHTML = `${value}<button type="button" aria-label="Remove ${value}" data-remove-chip>×</button>`;
      
      const hidden = document.createElement('input');
      hidden.type = 'hidden';
      hidden.name = 'chips[]';
      hidden.value = value;
      chip.appendChild(hidden);
      
      list.appendChild(chip);
      input.value = '';
      input.placeholder = '';
    };

    input?.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        addChip(input.value);
      }
      if (e.key === 'Backspace' && !input.value && list?.lastElementChild) {
        list.removeChild(list.lastElementChild);
        if (list.children.length === 0) input.placeholder = "Add tag...";
      }
    });

    container.addEventListener('click', (e) => {
      const target = e.target as HTMLElement;
      if (target.hasAttribute('data-remove-chip')) {
        target.closest('.chip')?.remove();
        if (list?.children.length === 0) input.placeholder = "Add tag...";
      }
    });
  });
</script>
--- /** * ChipInput — An interactive input for managing a list of tags or chips. * * @param {string} label - Input labeling title. * @param {string[]} initialChips - Optional array of strings to pre-populate. * @param {string} name - HTML name for form submission. * @param {string} placeholder - Placeholder text for the input area. */ interface Props { label?: string; initialChips?: string[]; name?: string; placeholder?: string; } const { label, initialChips = [], name, placeholder = "Add tag..." } = Astro.props; --- <div class="chip-input-container"> {label && <label class="chip-input-label">{label}</label>} <div class="chip-input" data-chip-input> <div class="chip-input__list" data-chip-list> {initialChips.map(chip => ( <span class="chip"> {chip} <button type="button" aria-label={`Remove ${chip}`} data-remove-chip>×</button> <input type="hidden" name={`${name}[]`} value={chip} /> </span> ))} </div> <input type="text" class="chip-input__field" placeholder={initialChips.length > 0 ? "" : placeholder} data-chip-field /> </div> </div> <style> .chip-input-container { display: flex; flex-direction: column; gap: var(--sp-2, 0.5rem); width: 100%; } .chip-input-label { font-size: 0.85rem; font-weight: 600; color: var(--c-text-2, #94a3b8); margin-left: 0.5rem; } .chip-input { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.5rem; background: var(--c-bg-elev, rgba(0,0,0,0.1)); border: 1px solid var(--c-border, rgba(255,255,255,0.1)); border-radius: var(--r-md, 10px); transition: border-color 0.2s; } .chip-input:focus-within { border-color: var(--c-primary, #6366f1); } .chip-input__list { display: flex; flex-wrap: wrap; gap: 0.5rem; } .chip { background: var(--c-primary, #6366f1); color: #fff; padding: 0.2rem 0.6rem; border-radius: var(--r-sm, 6px); font-size: 0.8rem; display: flex; align-items: center; gap: 0.4rem; } .chip button { background: none; border: none; color: #fff; cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 0; opacity: 0.8; transition: opacity 0.2s; } .chip button:hover { opacity: 1; } .chip-input__field { background: none; border: none; outline: none; color: var(--c-text-1, #fff); flex: 1; min-width: 100px; padding: 0.2rem; font-family: inherit; font-size: 0.9rem; } </style> <script> // Simplified client-side logic for adding/removing chips document.querySelectorAll('[data-chip-input]').forEach(container => { const list = container.querySelector('[data-chip-list]'); const input = container.querySelector('[data-chip-field]') as HTMLInputElement; const name = "tags"; // Should ideally be passed from props but hidden inputs solve this for now const addChip = (value: string) => { if (!value.trim() || !list) return; const chip = document.createElement('span'); chip.className = 'chip'; chip.innerHTML = `${value}<button type="button" aria-label="Remove ${value}" data-remove-chip>×</button>`; const hidden = document.createElement('input'); hidden.type = 'hidden'; hidden.name = 'chips[]'; hidden.value = value; chip.appendChild(hidden); list.appendChild(chip); input.value = ''; input.placeholder = ''; }; input?.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addChip(input.value); } if (e.key === 'Backspace' && !input.value && list?.lastElementChild) { list.removeChild(list.lastElementChild); if (list.children.length === 0) input.placeholder = "Add tag..."; } }); container.addEventListener('click', (e) => { const target = e.target as HTMLElement; if (target.hasAttribute('data-remove-chip')) { target.closest('.chip')?.remove(); if (list?.children.length === 0) input.placeholder = "Add tag..."; } }); }); </script>

Quick Info

Category
Forms
Filename
Chip/TagInput.astro
Dependencies
None — pure Astro + CSS
Tags
inputchips