Skip to content

Chip Input

Shared label and supporting-text behaviour with catnip-input is summarised on Inputs overview.

Draft input props are forwarded through catnipInput using the same prop names as catnip-input. This follows the composed components convention: parent-owned behaviour stays top-level, child-owned configuration goes under the child namespace.

The draft line is always single-line text. catnip-input-chip reserves these inner input props even if they are supplied in catnipInput: modelValue, type, hideLabel, name, and supportingText. Shared field state (size, intent, disabled, readonly, required) stays top-level so chips, Add, supporting text, and the draft line stay aligned.

Draft line

Committed chips live in modelValue. The draft (uncommitted typing) is separate. How you interact with draft depends on whether you use the default embedded catnip-input or the draftInput slot:

Default embedded catnip-inputCustom draftInput slot
Who owns draftChip (internal)Parent
Parent sets draftsetDraftValue() on a host ref.draftValue prop
Parent observes draft@update:draftValue (optional)Your own binding on the slotted control
After Add / Enter commitChip clears draftParent clears .draftValue in @update:modelValue
Enter to commitChip handlesParent handles
Configure the field.catnipInput="{ … }"You own slotted markup (catnipInput ignored)

Do not bind .draftValue when using the default input — it is ignored. Use setDraftValue() to prefill, reset, or programmatically change the draft (init on mount, clear after an external action, etc.).

Default embedded input — observe and reset:

vue
<catnip-input-chip
  ref="chipRef"
  label="Tags"
  .modelValue="tags"
  .catnipInput="{ placeholder: 'Add a tag' }"
  @update:modelValue="tags = $event.detail[0]"
  @update:draftValue="draft = $event.detail[0]"
/>
<button type="button" @click="chipRef?.setDraftValue?.('')">Clear draft</button>

Custom draftInput slot — parent-controlled draft (see Custom draft input below).

Props

Props are grouped by what you are trying to configure. Chip-input-owned behaviour stays top-level; draft-field-only Input props live under catnipInput.

Value & tag rules

PropTypeDefaultOptionsDescription
modelValuestring[][]Committed tags as catnip-chip rows; model-value in HTML / .modelValue in frameworks. Draft text is not included.
draftValuestring""Custom draftInput slot only. Parent-controlled draft text. Add reads this value; chip does not clear it after commit — parent clears in @update:modelValue. Ignored with the default embedded draft input (use setDraftValue() instead).
minChipsnumberundefinedMinimum committed tag count; chip removal is disabled while the count is at this value (HTML min-chips).
maxChipsnumberundefinedMaximum committed tag count; Add / Enter stop committing new tags once the count reaches this value (HTML max-chips).
maxVisibleChipsnumberundefinedMaximum chips to show before collapsing the rest behind a tertiary Show n more button (HTML max-visible-chips).

Commit controls

PropTypeDefaultOptionsDescription
showAddButtonbooleantruefalse hides Add; Enter still commits the draft.
addButtonCatnipInputChipAddButtonProps{}Props forwarded to the embedded Add catnip-button (same pattern as catnipInput). See Add button below.
showMoreChipsLabelstring(i18n inputChip.showMore)Label template for the collapsed tags control. Use {count} for the number of hidden tags (HTML show-more-chips-label).
showLessChipsLabelstring(i18n inputChip.showLess)Label for the expanded tags control that collapses the list again (HTML show-less-chips-label).

Add button

Same pattern as catnipInput: configure the embedded catnip-button without renaming child prop names. See Button — Specs for every forwardable prop.

Chip-input-reserved keys cannot be forwarded:

  • type is always "button" (Add never submits a form).
  • disabled is derived from validation (maxChips, empty / duplicate draft, top-level disabled).
  • size tracks the tag's size so chips, draft, and Add stay aligned.

The synthetic label is the visible button text — catnip-button itself takes label content via its default slot, so this field lives on the wrapper instead. Everything else (appearance, purpose, leftIcon, rightIcon, intent, ariaLabel, loading, name, value, form, autofocus, testSelectors) forwards as-is and defaults to appearance: "secondary", purpose: "utility", leftIcon: "plus".

vue
<catnip-input-chip
  label="Tags"
  .modelValue="tags"
  .addButton="{
    label: 'Add tag',
    ariaLabel: 'Add tag to list',
    appearance: 'primary',
    leftIcon: 'plus-circle'
  }"
/>

Field presentation

PropTypeDefaultOptionsDescription
labelstring""Visible label text when the label slot is empty.
optionalbooleanfalseOptional hint when not required.
optionalTextstring""Overrides optional hint (HTML optional-text).
infostring""Info tooltip text on the label (info wins over label.suffix when both are set).
alignmentstring"left"left, center, rightLabel row alignment.
requiredMarkerstring"*"Visual required marker when required (HTML required-marker).
requiredScreenReaderTextstring""Screen-reader-only supplement when required (HTML required-screen-reader-text).
supportingTextstring | string[] | { text, intent?, icon? } | { text, intent?, icon? }[]Supporting copy rendered below the chip list; draft aria-describedby still includes it when present. See Inputs overview — Multiple supporting messages.

State

PropTypeDefaultOptionsDescription
sizestring"m"s, m, lDimensions / typography on the draft input, chips, and tag controls.
intentstring"neutral"neutral, warning, danger, successStatus styling on the draft input and supporting text; danger sets aria-invalid on the draft.
disabledbooleanfalseDisables the draft, Add, chip remove, and draft clear affordance where applicable.
readonlybooleanfalseRead-only draft and chips where supported.
requiredbooleanfalseNative required state on the draft input and label marker state.

Draft input

PropTypeDefaultOptionsDescription
catnipInputCatnipInputControlProps{}Props forwarded to the default embedded catnip-input draft field. Ignored when the draftInput slot is used.

Common catnipInput values include id, placeholder, autocomplete, autofocus, spellcheck, leftIcon, rightIcon, rightIconClickable, rightIconButtonAriaLabel, showClearButton, clearButtonAriaLabel, maxlength, pattern, ariaLabel, ariaDescribedby, and testSelectors. Extra ariaDescribedby ids are appended to the draft input alongside the generated supporting-text id when supportingText is set.

vue
<catnip-input-chip
  label="Tags"
  .modelValue="tags"
  .catnipInput="{
    placeholder: 'Add a tag',
    leftIcon: 'search',
    showClearButton: true
  }"
/>

Advanced

PropTypeDefaultOptionsDescription
testSelectorsobjectSee Test selectorsdata-test overrides for the host, supporting text, Add, Show more, and Show less (see Test selectors below).

Slots

Label row

NamePurpose
labelReplaces label prop content; richer markup inside the label element (same pattern as catnip-input).
label.actionTrailing action in the label row (slot action on the internal label row).
label.suffixSlot suffix on the internal label — custom markup; info wins when non-empty.

Draft input adornments

NamePurpose
draftInputReplaces the default embedded draft input with any markup. Bind .draftValue from the parent; catnipInput does not apply.
catnipInput.leftIconSlotForwarded leading icon slot for the default embedded catnip-input draft line.
catnipInput.rightIconSlotForwarded trailing icon slot for the default embedded catnip-input draft line.

Custom draft input (draftInput slot)

When draftInput is set, the parent fully controls draft text via .draftValue. The chip uses that prop for Add disabled / duplicate checks and to commit on Add click. It does not clear draft after commit — clear .draftValue in your @update:modelValue handler.

  • Enter to commit — not handled by the chip in slot mode; wire Enter in your slotted control if needed (often by calling the same logic as Add).
  • Double-click chip to edit — chip emits update:draftValue with the chip text; set your draft ref from that event and focus your control if needed.
vue
<catnip-input-chip
  .modelValue="tags"
  .draftValue="draft"
  @update:modelValue="onChips"
>
  <input
    slot="draftInput"
    type="text"
    placeholder="Add a tag"
    .value="draft"
    @input="draft = $event.target.value"
  />
</catnip-input-chip>

<script setup>
import { ref } from "vue";

const tags = ref([]);
const draft = ref("");

function onChips(e) {
  tags.value = e.detail[0];
  draft.value = ""; // parent clears after commit
}
</script>

Methods

For the default embedded draft input only. When the draftInput slot is used, bind .draftValue instead — setDraftValue() logs a dev warning and has no effect.

MethodDescription
setDraftValue(value: string)Set or reset the draft line on the default embedded catnip-input. Does not commit a chip. Typical uses: prefill on mount, clear from a parent button, reset after a side action. Use a host ref (same pattern as catnip-tooltip reposition()).
vue
<template>
  <catnip-input-chip
    ref="chipRef"
    label="Tags"
    .modelValue="tags"
    @update:modelValue="onChips"
    @update:draftValue="onDraft"
  />
  <button type="button" @click="prefillDraft">Prefill draft</button>
  <button type="button" @click="clearDraft">Clear draft</button>
</template>
<script setup>
import { onMounted, ref } from "vue";

const chipRef = ref(null);
const tags = ref([]);
const draft = ref("");

function onChips(e) {
  tags.value = e.detail[0];
}

function onDraft(e) {
  draft.value = e.detail[0]; // optional — observe chip-owned draft
}

function prefillDraft() {
  chipRef.value?.setDraftValue?.("prefilled");
}

function clearDraft() {
  chipRef.value?.setDraftValue?.("");
}

onMounted(() => {
  chipRef.value?.setDraftValue?.("edit me"); // optional init
});
</script>

Events

NamePayloadDescription
update:modelValuestring[]New tags array after a chip is added or removed.
update:draftValuestringDefault embedded input: emitted when draft changes (typing, clear after commit, chip double-click edit, or setDraftValue()). Optional to listen — you do not need to write back. draftInput slot: emitted on chip double-click edit so the parent can set .draftValue.

Forms

The name you set on <catnip-input-chip> is what participates in traditional form serialization (along with form if used). The submitted value is the committed tags joined with commas (see below). The inner draft <input> uses an empty name so the unsent line is never submitted.

For ElementInternals / attachInternals, the host’s form value is updated when the component emits update:modelValue (same comma-joined string). Draft text is never included.

Test selectors

General guidance for role-based keys, repeated children, and nested forwarding lives in Component API — Test selectors (see Multiple subcomponents of the same type).

Chip-input-owned pieces accept a flat string that becomes that element's data-test. Composed children accept either a flat string (used as the child's root) or a nested object that is forwarded as the child's full testSelectors — same convention as catnipInput prop forwarding.

KeyDefault data-testType
rootcatnip-input-chipstring
draftRowcatnip-input-chip-draft-rowstring
chipscatnip-input-chip-chipsstring
supportingTextcatnip-input-supporting-textstring
addButtoncatnip-input-chip-addstring or CatnipButton selectors object
showMoreButtoncatnip-input-chip-show-morestring or CatnipButton selectors object
showLessButtoncatnip-input-chip-show-lessstring or CatnipButton selectors object
catnipInput(uses CatnipInput defaults)CatnipInput selectors object — forwarded to the draft input
ts
.testSelectors="{
  root: 'permissions-tag',
  addButton: 'permissions-add',
  catnipInput: {
    leftAdornment: 'permissions-left-adornment'
  }
}"

See Component API — Props.

Catnip Design System by Signicat