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.
placeholderstringPlaceholder 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.
maxTagsnumberMaximum number of tags allowed.
maxlengthnumberMaximum 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[]) => voidCallback when tags change.
onAdd(tag: TaginputTag) => voidCallback when tag is added.
onRemove(tag: TaginputTag, index: number) => voidCallback when tag is removed.
onTyping(value: string) => voidCallback when typing in input.
tagTemplate(tag: TaginputTag) => React.ReactNodeCustom 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) => booleanValidation function called before adding a tag.
createTag(input: string) => TaginputTagCustom function for creating tag objects from input.
keepFirstbooleanfalseKeeps the first autocomplete suggestion highlighted.
keepOpenbooleantrueKeeps the autocomplete dropdown open after selection.
loadingbooleanfalseShows a loading indicator.
ariaCloseLabelstringARIA label for tag close buttons.
iconstringIcon name for the input.
iconLibrary'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols'Icon library to use.
iconVariantstringIcon style variant.
iconFeaturesstring | string[]Additional icon modifiers.
classNamestringAdditional 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.