Propel

Autocomplete Field

A labeled autocomplete input with a filtered suggestion popup and helper or error text.

Type or choose an image tag.

Show code
import { AutocompleteField } from "@makeplane/propel/components/autocomplete-field";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ChevronExpandOutline, CloseOutline } from "@makeplane/propel/icons";

const IMAGES = ["nginx:1.29-alpine", "node:22-slim", "postgres:18", "redis:8.2.2-alpine"];

export default function BasicDemo() {
  return (
    <AutocompleteField
      name="containerImage"
      label="Container image"
      description="Type or choose an image tag."
      size="lg"
      items={IMAGES}
      placeholder="e.g. node:22-slim"
      empty="No images found"
      clear={
        <IconButton
          variant="ghost"
          size="md"
          aria-label="Clear container image"
          icon={<Icon icon={CloseOutline} />}
        />
      }
      trigger={
        <IconButton
          variant="ghost"
          size="md"
          aria-label="Open container image"
          icon={<Icon icon={ChevronExpandOutline} />}
        />
      }
    />
  );
}

Installation

import { AutocompleteField } from "@makeplane/propel/components/autocomplete-field";

Usage

import { AutocompleteField } from "@makeplane/propel/components/autocomplete-field";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ChevronExpandOutline, CloseOutline } from "@makeplane/propel/icons";

const IMAGES = ["nginx:1.29-alpine", "node:22-slim", "postgres:18", "redis:8.2.2-alpine"];

export default function BasicDemo() {
  return (
    <AutocompleteField
      name="containerImage"
      label="Container image"
      description="Type or choose an image tag."
      size="lg"
      items={IMAGES}
      placeholder="e.g. node:22-slim"
      empty="No images found"
      clear={
        <IconButton
          variant="ghost"
          size="md"
          aria-label="Clear container image"
          icon={<Icon icon={CloseOutline} />}
        />
      }
      trigger={
        <IconButton
          variant="ghost"
          size="md"
          aria-label="Open container image"
          icon={<Icon icon={ChevronExpandOutline} />}
        />
      }
    />
  );
}

Result identity and limits

Primitive string or number options key as themselves — no getItemKey needed. Record options require getItemKey={(item) => item.id} (or another stable unique identity); TypeScript enforces this. Keys must remain stable when results reorder; display labels may repeat.

The field displays up to 50 matches by default. When the shown list hits that cap and the source list is longer, a polite status region announces truncatedMessage (default: “Keep typing to narrow results”; overridable / suppressible with null). The hint is possibly-truncated — it does not invent an exact remainder — so custom filter functions stay correct without a second full-catalog pass. The searchable catalog is never truncated. limit={-1} displays every match and should be reserved for bounded lists. limit={0} renders no rows, but the popup still opens and shows empty even when matches exist — it is not a way to hold suggestions back. The same limit applies to server-ranked filteredItems. Fetching stays application-owned; use value and onValueChange for the query and pass [] for an empty response. For browsing large, accumulated result lists, use Combobox Field.

Examples

Sizes

size steps the label, description, and helper text together with the input group and its input text.

Type or choose an image tag.

Type or choose an image tag.

Type or choose an image tag.

Show code
import { AutocompleteField, type FieldSize } from "@makeplane/propel/components/autocomplete-field";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton, type IconButtonSize } from "@makeplane/propel/components/icon-button";
import { ChevronExpandOutline, CloseOutline } from "@makeplane/propel/icons";

const IMAGES = ["nginx:1.29-alpine", "node:22-slim", "postgres:18", "redis:8.2.2-alpine"];

const SIZES: FieldSize[] = ["lg", "xl", "2xl"];

// The icon button rung inside each field's row rung: one ladder rung down, clamped at `lg` (the
// compact scale's ceiling) once the field size outgrows it.
const ICON_BUTTON_SIZE: Record<FieldSize, IconButtonSize> = {
  md: "sm",
  lg: "md",
  xl: "lg",
  "2xl": "lg",
};

export default function SizesDemo() {
  return (
    <div className="flex flex-col gap-4">
      {SIZES.map((size) => (
        <AutocompleteField
          key={size}
          name="containerImage"
          label={size}
          description="Type or choose an image tag."
          size={size}
          items={IMAGES}
          placeholder="e.g. node:22-slim"
          empty="No images found"
          clear={
            <IconButton
              variant="ghost"
              size={ICON_BUTTON_SIZE[size]}
              aria-label="Clear container image"
              icon={<Icon icon={CloseOutline} />}
            />
          }
          trigger={
            <IconButton
              variant="ghost"
              size={ICON_BUTTON_SIZE[size]}
              aria-label="Open container image"
              icon={<Icon icon={ChevronExpandOutline} />}
            />
          }
        />
      ))}
    </div>
  );
}

Invalid

Setting error marks the field invalid, recolors the input group border, and replaces the description with the error text.

Enter a container image.
Show code
import { AutocompleteField } from "@makeplane/propel/components/autocomplete-field";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ChevronExpandOutline, CloseOutline } from "@makeplane/propel/icons";

const IMAGES = ["nginx:1.29-alpine", "node:22-slim", "postgres:18", "redis:8.2.2-alpine"];

export default function InvalidDemo() {
  return (
    <AutocompleteField
      name="containerImage"
      label="Container image"
      size="lg"
      items={IMAGES}
      placeholder="e.g. node:22-slim"
      empty="No images found"
      error="Enter a container image."
      clear={
        <IconButton
          variant="ghost"
          size="md"
          aria-label="Clear container image"
          icon={<Icon icon={CloseOutline} />}
        />
      }
      trigger={
        <IconButton
          variant="ghost"
          size="md"
          aria-label="Open container image"
          icon={<Icon icon={ChevronExpandOutline} />}
        />
      }
    />
  );
}

Disabled

Type or choose an image tag.

Show code
import { AutocompleteField } from "@makeplane/propel/components/autocomplete-field";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ChevronExpandOutline, CloseOutline } from "@makeplane/propel/icons";

const IMAGES = ["nginx:1.29-alpine", "node:22-slim", "postgres:18", "redis:8.2.2-alpine"];

export default function DisabledDemo() {
  return (
    <AutocompleteField
      name="containerImage"
      label="Container image"
      description="Type or choose an image tag."
      size="lg"
      disabled
      items={IMAGES}
      placeholder="e.g. node:22-slim"
      empty="No images found"
      clear={
        <IconButton
          variant="ghost"
          size="md"
          aria-label="Clear container image"
          icon={<Icon icon={CloseOutline} />}
        />
      }
      trigger={
        <IconButton
          variant="ghost"
          size="md"
          aria-label="Open container image"
          icon={<Icon icon={ChevronExpandOutline} />}
        />
      }
    />
  );
}

API Reference

AutocompleteField

Ready-to-use autocomplete field with label, input, popup items, and helper/error text. Record options require getItemKey. Primitive string/number options key as themselves when it is omitted.

PropTypeDefaultDescription
clear(required)ReactElement<unknown, string | JSXElementConstructor<any>>—The clear control (e.g. an IconButton) carrying its own localizable aria-label. Required here (unlike the bare AutocompleteInputGroup) — a labeled field is always presented as a complete picker, not a plain search box.
trigger(required)ReactElement<unknown, string | JSXElementConstructor<any>>—The popup-trigger control (e.g. an IconButton) carrying its own localizable aria-label. Required here for the same reason as clear.
empty(required)ReactNode—Message rendered when no item matches.
items(required)readonly Value[]—Searchable catalog in local mode, or loaded results with filteredItems in external mode. Only the first limit matches render. The limit bounds rendering, not full-catalog filtering; use external search for huge catalogs. Use ComboboxField to scroll accumulated results.
label(required)string—Visible field label.
size(required)"md" | "lg" | "xl" | "2xl"—Label and helper text size.
getItemKey(required)(item: Value) => Key—Stable, unique identity for each result, independent of its display label or current index. Required for record options: labels may repeat, and object identity is not enough when a server replaces or reorders results. Primitive string/number options key as themselves.
limitnumber50The maximum number of items to display in the list. Maximum displayed matches for local and external filtering. Use -1 only for a bounded list that must display every match. 0 renders no rows, but the popup still opens and shows empty even when matches exist — it is not a “hold suggestions back” mode. This never truncates the source catalog.
descriptionReactNode—Supporting text shown below the input. Replaced by error when an error is set.
errorReactNode—Error text shown below the control.
hintReactNode—Helper text shown below the control. Replaced by error when an error is set.
filteredItemsreadonly Value[]—Externally filtered or server-ranked matches. The same result limit applies to this list.
truncatedMessageReactNode"Showing the first {limit} matches. Keep typing to narrow results."Announced in the popup when the result cap may be hiding matches (shown count equals limit and the source list is longer). Override to localize; pass null to suppress the hint.
placeholderstring—Input placeholder.