shadimage
lumiui
uiautocompletesource: ok · hit

Autocomplete

An input that suggests options as you type.

npx shadcn add "https://www.lumiui.dev/r/autocomplete.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
ui-primitives@base-ui/react
iconslucide-react

Registry dependencies

utils@lumi-ui/button@lumi-ui/input

Files1 · source cached 10/12/2026, 1:25:49 AM

registry/ui/autocomplete.tsx
"use client";

import { Autocomplete as BaseAutocomplete } from "@base-ui/react/autocomplete";
import type { VariantProps } from "class-variance-authority";
import { ChevronDown, X } from "lucide-react";
import type * as React from "react";
import { cn } from "@/registry/lib/utils";
import { ArrowSvg } from "@/registry/ui/arrow-svg";
import { Button } from "@/registry/ui/button";
import { inputBaseVariants, inputVariants } from "@/registry/ui/input";

const Autocomplete = BaseAutocomplete.Root;

function AutocompleteValue({
  children,
  ...props
}: BaseAutocomplete.Value.Props) {
  return (
    <BaseAutocomplete.Value data-slot="autocomplete-value" {...props}>
      {children}
    </BaseAutocomplete.Value>
  );
}

function AutocompleteIcon({
  className,
  ...props
}: BaseAutocomplete.Icon.Props) {
  return (
    <BaseAutocomplete.Icon
      className={cn(
        "[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:text-muted-foreground",
        className,
      )}
      data-slot="autocomplete-icon"
      {...props}
    />
  );
}

function AutocompleteInput({
  className,
  variant = "default",
  inputSize = "default",
  ...props
}: BaseAutocomplete.Input.Props &
  VariantProps<typeof inputVariants> &
  VariantProps<typeof inputBaseVariants>) {
  return (
    <BaseAutocomplete.Input
      className={cn(
        inputVariants({ inputSize }),
        inputBaseVariants({ variant }),
        className,
      )}
      data-slot="autocomplete-input"
      {...props}
    />
  );
}

function AutocompleteClear({
  className,
  ...props
}: BaseAutocomplete.Clear.Props) {
  return (
    <BaseAutocomplete.Clear
      aria-label="Clear selection"
      className={cn(
        "outline-none pointer-coarse:after:absolute pointer-coarse:after:min-h-10 pointer-coarse:after:min-w-10",
        className,
      )}
      data-slot="autocomplete-clear"
      {...props}
    />
  );
}

function AutocompleteTrigger({
  className,
  ...props
}: BaseAutocomplete.Trigger.Props) {
  return (
    <BaseAutocomplete.Trigger
      className={cn(
        "outline-none pointer-coarse:after:absolute pointer-coarse:after:min-h-10 pointer-coarse:after:min-w-10",
        className,
      )}
      data-slot="autocomplete-trigger"
      {...props}
    />
  );
}

function AutocompleteList({
  className,
  ...props
}: BaseAutocomplete.List.Props) {
  return (
    <BaseAutocomplete.List
      className={cn(
        "outline-none py-1 data-empty:p-0",
        "min-h-0 overflow-y-auto scroll-py-2",
        className,
      )}
      data-slot="autocomplete-list"
      {...props}
    />
  );
}

function AutocompletePortal({
  className,
  ...props
}: BaseAutocomplete.Portal.Props) {
  return (
    <BaseAutocomplete.Portal
      className={className}
      data-slot="autocomplete-portal"
      {...props}
    />
  );
}

function AutocompleteBackdrop({
  className,
  ...props
}: BaseAutocomplete.Backdrop.Props) {
  return (
    <BaseAutocomplete.Backdrop
      className={className}
      data-slot="autocomplete-backdrop"
      {...props}
    />
  );
}

function AutocompletePositioner({
  className,
  ...props
}: BaseAutocomplete.Positioner.Props) {
  return (
    <BaseAutocomplete.Positioner
      className={cn("outline-none", className)}
      data-slot="autocomplete-positioner"
      {...props}
    />
  );
}

function AutocompletePopup({
  className,
  ...props
}: BaseAutocomplete.Popup.Props) {
  return (
    <BaseAutocomplete.Popup
      className={className}
      data-slot="autocomplete-popup"
      {...props}
    />
  );
}

function AutocompleteArrow({
  className,
  ...props
}: BaseAutocomplete.Arrow.Props) {
  return (
    <BaseAutocomplete.Arrow
      className={cn(
        "data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180",
        className,
      )}
      data-slot="autocomplete-arrow"
      {...props}
    >
      <ArrowSvg />
    </BaseAutocomplete.Arrow>
  );
}

function AutocompleteStatus({
  className,
  ...props
}: BaseAutocomplete.Status.Props) {
  return (
    <BaseAutocomplete.Status
      className={cn(
        "p-3 text-sm text-center text-muted-foreground flex gap-2 items-center empty:m-0 empty:p-0",
        className,
      )}
      data-slot="autocomplete-status"
      {...props}
    />
  );
}

function AutocompleteEmpty({
  className,
  ...props
}: BaseAutocomplete.Empty.Props) {
  return (
    <BaseAutocomplete.Empty
      className={cn(
        "p-3 text-sm text-center text-muted-foreground empty:m-0 empty:p-0",
        className,
      )}
      data-slot="autocomplete-empty"
      {...props}
    />
  );
}

function AutocompleteCollection(props: BaseAutocomplete.Collection.Props) {
  return (
    <BaseAutocomplete.Collection
      data-slot="autocomplete-collection"
      {...props}
    />
  );
}

function AutocompleteRow({ className, ...props }: BaseAutocomplete.Row.Props) {
  return (
    <BaseAutocomplete.Row
      className={className}
      data-slot="autocomplete-row"
      {...props}
    />
  );
}

function AutocompleteItem({
  className,
  ...props
}: BaseAutocomplete.Item.Props) {
  return (
    <BaseAutocomplete.Item
      className={cn(
        "flex items-center gap-2 py-1.5 text-sm pl-3.5 pr-8",
        "select-none cursor-default outline-none",
        "highlight-on-active",
        "[&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
        "data-disabled:pointer-events-none data-disabled:opacity-50",
        className,
      )}
      data-slot="autocomplete-item"
      {...props}
    />
  );
}

function AutocompleteGroup({
  className,
  ...props
}: BaseAutocomplete.Group.Props) {
  return (
    <BaseAutocomplete.Group
      className={className}
      data-slot="autocomplete-group"
      {...props}
    />
  );
}

function AutocompleteGroupLabel({
  className,
  ...props
}: BaseAutocomplete.GroupLabel.Props) {
  return (
    <BaseAutocomplete.GroupLabel
      className={cn(
        "px-3.5 py-1.5 text-xs text-muted-foreground font-medium select-none",
        className,
      )}
      data-slot="autocomplete-group-label"
      {...props}
    />
  );
}

function AutocompleteSeparator({
  className,
  ...props
}: BaseAutocomplete.Separator.Props) {
  return (
    <BaseAutocomplete.Separator
      className={cn("bg-border pointer-events-none my-1 h-px", className)}
      data-slot="autocomplete-separator"
      {...props}
    />
  );
}

function AutocompleteInputGroupContent({
  className,
  showTrigger = false,
  showClear = false,
  variant = "default",
  inputSize = "default",
  inputClassName,
  addonIcon,
  ariaInvalid,
  embedded,
  ...props
}: BaseAutocomplete.Input.Props & {
  showTrigger?: boolean;
  showClear?: boolean;
  variant?: VariantProps<typeof inputBaseVariants>["variant"];
  inputSize?: VariantProps<typeof inputBaseVariants>["inputSize"];
  inputClassName?: string;
  addonIcon?: React.ReactNode;
  embedded?: boolean;
  ariaInvalid?: boolean;
}) {
  const paddingClass =
    showTrigger && showClear
      ? "pr-14"
      : showTrigger || showClear
        ? "pr-8"
        : "pr-3";

  return (
    <BaseAutocomplete.InputGroup
      aria-invalid={ariaInvalid}
      className={cn(
        "relative w-full inline-flex gap-1 items-center cursor-text",
        !embedded && inputBaseVariants({ variant, inputSize }),
        !embedded &&
          "focus-within:outline focus-within:outline-ring focus-within:ring-4 focus-within:ring-ring/10",
        "has-[[data-slot=combobox-clear]]:[&_[data-slot=combobox-input]]:pr-8",
        "has-[[data-slot=combobox-trigger]]:[&_[data-slot=combobox-input]]:pr-8",
        "has-[[data-slot=combobox-clear]]:has-[[data-slot=combobox-trigger]]:[&_[data-slot=combobox-input]]:pr-14",
        className,
      )}
      data-slot="autocomplete-input-group"
    >
      {addonIcon && (
        <BaseAutocomplete.Icon
          className="[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:text-muted-foreground absolute left-1 size-6 flex justify-center items-center"
          data-slot="autocomplete-icon"
        >
          {addonIcon}
        </BaseAutocomplete.Icon>
      )}
      <BaseAutocomplete.Input
        className={cn(
          inputVariants({ inputSize }),
          addonIcon && "pl-7.5",
          paddingClass,
          inputClassName,
        )}
        data-slot="autocomplete-input"
        {...props}
      />
      <div className="absolute right-1.5 top-0 h-full inline-flex items-center">
        {showClear && (
          <BaseAutocomplete.Clear
            aria-label="Clear selection"
            data-slot="autocomplete-clear"
            render={
              <Button
                className="[&_svg]:text-muted-foreground hover:[&_svg]:text-foreground"
                size="icon-xs"
                variant="ghost"
              />
            }
          >
            <X />
          </BaseAutocomplete.Clear>
        )}
        {showTrigger && (
          <BaseAutocomplete.Trigger
            data-slot="autocomplete-trigger"
            render={
              <Button
                className="[&_svg]:text-muted-foreground data-popup-open:[&_svg]:text-foreground hover:[&_svg]:text-foreground"
                size="icon-xs"
                variant="ghost"
              />
            }
          >
            <BaseAutocomplete.Icon data-slot="autocomplete-icon">
              <ChevronDown className="size-4" />
            </BaseAutocomplete.Icon>
          </BaseAutocomplete.Trigger>
        )}
      </div>
    </BaseAutocomplete.InputGroup>
  );
}

function AutocompleteContent({
  className,
  children,
  sideOffset = 6,
  align = "start",
  side = "bottom",
  alignOffset = 0,
  matchAnchorWidth = true,
  positionerAnchor,
  ...props
}: BaseAutocomplete.Popup.Props &
  Pick<
    BaseAutocomplete.Positioner.Props,
    "side" | "sideOffset" | "align" | "alignOffset"
  > & {
    matchAnchorWidth?: boolean;
    positionerAnchor?: React.RefObject<HTMLDivElement | null>;
  }) {
  return (
    <BaseAutocomplete.Portal data-slot="autocomplete-portal">
      <BaseAutocomplete.Positioner
        align={align}
        alignOffset={alignOffset}
        anchor={positionerAnchor}
        data-slot="autocomplete-positioner"
        side={side}
        sideOffset={sideOffset}
      >
        <BaseAutocomplete.Popup
          className={cn(
            "bg-popover text-popover-foreground rounded-md shadow-md",
            "flex flex-col overflow-hidden",
            "max-w-(--available-width) max-h-[min(23rem,var(--available-height))]",
            "overlay-outline",
            matchAnchorWidth && "w-(--anchor-width)",
            className,
          )}
          data-slot="autocomplete-content"
          {...props}
        >
          {children}
        </BaseAutocomplete.Popup>
      </BaseAutocomplete.Positioner>
    </BaseAutocomplete.Portal>
  );
}

const useAutocompleteFilter = BaseAutocomplete.useFilter;

export {
  Autocomplete,
  AutocompleteValue,
  AutocompleteIcon,
  AutocompleteInput,
  AutocompleteClear,
  AutocompleteTrigger,
  AutocompleteList,
  AutocompletePortal,
  AutocompleteBackdrop,
  AutocompletePositioner,
  AutocompletePopup,
  AutocompleteArrow,
  AutocompleteEmpty,
  AutocompleteGroupLabel,
  AutocompleteItem,
  AutocompleteSeparator,
  AutocompleteGroup,
  AutocompleteRow,
  AutocompleteStatus,
  AutocompleteCollection,
  useAutocompleteFilter,
  // Composite components
  AutocompleteInputGroupContent,
  AutocompleteContent,
};

More from lumiui

Browse all components in this library →