Skip to main content

Taginput

Overview​

The Taginput component provides a tag/chip input field for managing multiple tags. It supports autocomplete suggestions, custom tag styling, and various input behaviors.


Import​

import { Taginput } from '@allxsmith/bestax-bulma';

Props​

PropTypeDefaultDescription
valueTaginputTag[]—The current tags (controlled).
defaultValueTaginputTag[][]Default tags (uncontrolled).
datastring[][]Autocomplete suggestions.
placeholderstring—Placeholder text when no tags.
fieldstring'label'Object property to use as display field.
allowNewbooleantrueAllow creating new tags not in suggestions.
allowDuplicatesbooleanfalseAllow duplicate tags.
openOnFocusbooleanfalseOpen autocomplete dropdown on focus.
removeOnKeysbooleantrueRemove tag on backspace.
confirmKeysstring[]['Enter', ',']Keys to confirm tag creation.
closablebooleantrueShow close button on tags.
attachedbooleanfalseAttach tags visually.
maxTagsnumber—Maximum number of tags allowed.
maxlengthnumber—Maximum length of input.
disabledbooleanfalseWhether the input is disabled.
readonlybooleanfalseWhether the input is read-only.
color'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger'—Input color variant.
tagColor'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger' | 'dark' | 'light'—Tag color variant.
size'small' | 'medium' | 'large'—Size variant.
onChange(tags: TaginputTag[]) => void—Callback when tags change.
onAdd(tag: TaginputTag) => void—Callback when tag is added.
onRemove(tag: TaginputTag, index: number) => void—Callback when tag is removed.
onTyping(value: string) => void—Callback when typing in input.
tagTemplate(tag: TaginputTag) => React.ReactNode—Custom render for tags.
roundedbooleanfalseApplies rounded corners to the tags.
ellipsisbooleanfalseTruncates long tag text with ellipsis.
hasCounterbooleantrueShows a counter of the number of tags.
onPasteSeparatorsstring[][',']Characters that split pasted text into tags.
beforeAdding(tag: string) => boolean—Validation function called before adding a tag.
createTag(input: string) => TaginputTag—Custom function for creating tag objects from input.
keepFirstbooleanfalseKeeps the first autocomplete suggestion highlighted.
keepOpenbooleantrueKeeps the autocomplete dropdown open after selection.
loadingbooleanfalseShows a loading indicator.
ariaCloseLabelstring—ARIA label for tag close buttons.
iconstring—Icon name for the input.
iconLibrary'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols'—Icon library to use.
iconVariantstring—Icon style variant.
iconFeaturesstring | string[]—Additional icon modifiers.
classNamestring—Additional CSS classes.
refReact.Ref<HTMLElement>—Ref forwarded to the input element.
...All standard HTML and Bulma helper props(See Helper Props)

TaginputTag​

Can be either a string or an object with:

PropTypeDescription
valuestringThe tag value.
labelstringDisplay label (optional).

Usage​

Basic Taginput​

Simple tag input without suggestions.

function example() {
  const [tags, setTags] = useState(['React', 'TypeScript']);

  return (
    <Block>
      <Taginput value={tags} onChange={setTags} placeholder="Add a tag..." />
      <Paragraph mt="2" textColor="grey">
        Tags: {tags.join(', ')}
      </Paragraph>
    </Block>
  );
}


With Autocomplete​

Tag input with suggestion dropdown.

function example() {
  const [tags, setTags] = useState(['React']);
  const suggestions = [
    'React',
    'Vue',
    'Angular',
    'Svelte',
    'TypeScript',
    'JavaScript',
    'Python',
    'Go',
  ];

  return (
    <Taginput
      value={tags}
      onChange={setTags}
      data={suggestions}
      placeholder="Add frameworks..."
      openOnFocus
    />
  );
}


Restrict to Suggestions​

Only allow tags from the suggestion list.

function example() {
  const [tags, setTags] = useState([]);
  const categories = [
    'Bug',
    'Feature',
    'Documentation',
    'Enhancement',
    'Question',
  ];

  return (
    <Block>
      <Taginput
        value={tags}
        onChange={setTags}
        data={categories}
        allowNew={false}
        placeholder="Select categories..."
        openOnFocus
      />
      <Paragraph mt="2" textColor="grey" textSize="7">
        Only predefined categories can be selected
      </Paragraph>
    </Block>
  );
}


Tag Colors​

Tags with different color variants.

<Block display="flex" flexDirection="column" gap="4">
  <Taginput
    defaultValue={['Primary']}
    tagColor="primary"
    placeholder="Primary tags..."
  />
  <Taginput
    defaultValue={['Success']}
    tagColor="success"
    placeholder="Success tags..."
  />
  <Taginput
    defaultValue={['Info']}
    tagColor="info"
    placeholder="Info tags..."
  />
  <Taginput
    defaultValue={['Warning']}
    tagColor="warning"
    placeholder="Warning tags..."
  />
  <Taginput
    defaultValue={['Danger']}
    tagColor="danger"
    placeholder="Danger tags..."
  />
  <Taginput
    defaultValue={['Dark']}
    tagColor="dark"
    placeholder="Dark tags..."
  />
</Block>


Size Variants​

Tag inputs in different sizes.

<Block display="flex" flexDirection="column" gap="4">
  <Taginput
    defaultValue={['Small']}
    size="small"
    tagColor="primary"
    placeholder="Small..."
  />
  <Taginput
    defaultValue={['Normal']}
    tagColor="primary"
    placeholder="Normal..."
  />
  <Taginput
    defaultValue={['Medium']}
    size="medium"
    tagColor="primary"
    placeholder="Medium..."
  />
  <Taginput
    defaultValue={['Large']}
    size="large"
    tagColor="primary"
    placeholder="Large..."
  />
</Block>


Maximum Tags​

Limit the number of tags allowed.

function example() {
  const [tags, setTags] = useState(['One', 'Two']);

  return (
    <Block>
      <Taginput
        value={tags}
        onChange={setTags}
        maxTags={3}
        tagColor="info"
        placeholder="Max 3 tags..."
      />
      <Paragraph mt="2" textColor="grey" textSize="7">
        {3 - tags.length} tags remaining
      </Paragraph>
    </Block>
  );
}


Allow Duplicates​

Enable duplicate tag values.

function example() {
  const [tags, setTags] = useState(['Tag']);

  return (
    <Taginput
      value={tags}
      onChange={setTags}
      allowDuplicates
      tagColor="warning"
      placeholder="Duplicates allowed..."
    />
  );
}


Custom Confirm Keys​

Change which keys create a new tag.

function example() {
  const [tags, setTags] = useState([]);

  return (
    <Block>
      <Taginput
        value={tags}
        onChange={setTags}
        confirmKeys={['Enter', 'Tab', ' ']}
        tagColor="success"
        placeholder="Press Enter, Tab, or Space..."
      />
      <Paragraph mt="2" textColor="grey" textSize="7">
        Space also creates a new tag
      </Paragraph>
    </Block>
  );
}


Read-only and Disabled​

Display modes for tags.

<Block display="flex" flexDirection="column" gap="4">
  <Block>
    <Paragraph mb="1">Read-only:</Paragraph>
    <Taginput defaultValue={['React', 'TypeScript']} readonly tagColor="info" />
  </Block>
  <Block>
    <Paragraph mb="1">Disabled:</Paragraph>
    <Taginput defaultValue={['React', 'TypeScript']} disabled tagColor="info" />
  </Block>
</Block>


Non-closable Tags​

Tags without the delete button.

<Taginput
  defaultValue={['Fixed', 'Tags']}
  closable={false}
  tagColor="primary"
  placeholder="Cannot remove tags..."
/>


Context-Aware Rendering​

The Taginput component is context-aware: it detects whether it is already inside a Field and adjusts its rendering accordingly. This means you can use it standalone with a label prop (it wraps itself in a Field), or inside a Field (it skips rendering its own).

note

Taginput does not use ControlContext, so the "With Field and Control Wrappers" example below uses Field wrapping only. The Control wrapper is shown for layout consistency but does not change the component's internal rendering.

Default (with label)​

The simplest usage — the component automatically renders its own Field wrapper.

<Taginput
  label="Tags"
  defaultValue={['React', 'TypeScript']}
  placeholder="Add a tag..."
  tagColor="primary"
/>


With Field Wrapper​

When you need manual control over the Field layout (e.g., horizontal forms), wrap the component in Field. The component detects it's inside a Field and skips rendering its own.

function example() {
  return (
    <Field horizontal label="Tags">
      <Field.Body>
        <Field>
          <Taginput
            defaultValue={['React', 'TypeScript']}
            placeholder="Add a tag..."
            tagColor="primary"
          />
        </Field>
      </Field.Body>
    </Field>
  );
}


With Field and Control Wrappers​

For full manual composition, wrap in both Field and Control. Taginput does not consume ControlContext, but the Field wrapper is still detected and its own Field is skipped.

For an icon, use Taginput's own icon prop rather than <Control iconLeftName> — Bulma's has-icons-left only adjusts padding on .input/.select, not on .taginput, so wrapping with Control's icon causes the icon to overlap the tags.

function example() {
  return (
    <Field horizontal label="Tags">
      <Field.Body>
        <Field>
          <Control>
            <Taginput
              icon="tags"
              defaultValue={['React', 'TypeScript']}
              placeholder="Add a tag..."
              tagColor="primary"
            />
          </Control>
        </Field>
      </Field.Body>
    </Field>
  );
}


Keyboard Navigation​

KeyAction
EnterCreate tag from input
,Create tag from input (default)
BackspaceRemove last tag (when input empty)
↓Open/navigate suggestions
↑Navigate suggestions up
EscapeClose suggestions dropdown

Controlled vs Uncontrolled​

Controlled Mode​

Use value and onChange to manage state externally:

const [tags, setTags] = useState(['React']);
<Taginput value={tags} onChange={setTags} />;

Uncontrolled Mode​

Use defaultValue for internal state management:

<Taginput defaultValue={['React', 'TypeScript']} />

Form Submission​

Taginput is an HTML form element. Pass a name prop and one hidden <input> per tag is rendered, producing standard form-encoded array submission (e.g., tags=react&tags=vue&tags=angular). Standard server-side parsers (PHP, Express body-parser, etc.) handle this format natively.

PropDescription
nameForm field name. When set, one hidden input per tag is rendered.
formOptional id of the form the hidden inputs belong to.
function TaginputFormDemo() {
  const [submitted, setSubmitted] = React.useState('');
  return (
    <form
      onSubmit={e => {
        e.preventDefault();
        const fd = new FormData(e.currentTarget);
        setSubmitted(JSON.stringify(Array.from(fd.entries()), null, 2));
      }}
    >
      <Taginput
        name="tags"
        defaultValue={['react', 'vue', 'angular']}
        placeholder="Add a tag…"
      />
      <div style={{ marginTop: '1rem' }}>
        <button type="submit" className="button is-primary">
          Submit
        </button>
      </div>
      {submitted && <pre style={{ marginTop: '1rem' }}>{submitted}</pre>}
    </form>
  );
}


Accessibility​

  • Input has aria-label="Add tag" for screen readers
  • Tags are keyboard navigable
  • Delete buttons have proper aria-label
  • Autocomplete dropdown follows ARIA listbox pattern

  • Autocomplete - For single value autocomplete
  • Input - For basic text input
  • Tag - For displaying tags

Additional Resources​

Pro Tip

Set allowNew={false} when you need strict control over allowed values, such as selecting from predefined categories or labels.