component — inputs

Search Field

Search input built on react-aria-components and the quebi Input, with a leading magnifying-glass icon and a clear button that appears while typing. Escape or the clear button empties the field.

  • form
  • input
  • search
  • field
  • interactive

03 examples

Default

A search field with a leading icon and a clear button that appears while typing.

States

Default, with an initial value, and disabled.

Controlled

You searched: nothing yet

source

search-field.tsx

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

"use client"

import { Search, X } from "lucide-react"
import {
  Button,
  composeRenderProps,
  SearchField as SearchFieldPrimitive,
  type SearchFieldProps as PrimitiveSearchFieldProps,
} from "react-aria-components"
import { Input, InputGroup } from "@/components/input"
import { cn } from "@/lib/utils"
import { fieldStyles } from "@/components/field"

/**
 * SearchField — quebi design system
 *
 * A search input built on react-aria-components, composed from the quebi
 * Input + InputGroup. A leading magnifying-glass icon and a trailing clear
 * button that appears only while the field has a value (Escape / clicking it
 * clears the field). Inherits `Input`'s frame — boxed by default, an
 * underline inside `quebi-editorial` — on the `raised` ground the design gives
 * a search box where fields are boxed.
 */
export interface SearchFieldProps extends PrimitiveSearchFieldProps {
  ref?: React.RefObject<HTMLDivElement>
}

export function SearchField({ className, ref, ...props }: SearchFieldProps) {
  return (
    <SearchFieldPrimitive
      ref={ref}
      data-slot="control"
      {...props}
      aria-label={props["aria-label"] ?? "Search"}
      className={composeRenderProps(className, (resolved) =>
        cn("group/search-field block", fieldStyles, resolved),
      )}
    />
  )
}

/** The input half of a SearchField. Takes `Input`'s props, including `size`. */
export function SearchInput(props: React.ComponentProps<typeof Input>) {
  return (
    // The search box sits on `raised` where fields are boxed; `--q-field-boxed`
    // is 0 inside `quebi-editorial`, so an underlined one keeps no ground.
    <InputGroup className="[--q-field-bg:color-mix(in_srgb,var(--q-raised)_calc(var(--q-field-boxed)*100%),transparent)]">
      <Search data-slot="icon" />
      <Input placeholder="Search" {...props} />
      <Button
        className={cn(
          "flex items-center justify-end ps-3 pe-(--q-field-px) text-quebi-fg-subtle outline-none",
          "transition-colors duration-150 hover:text-quebi-fg pressed:text-quebi-fg",
          "group-empty/search-field:invisible",
          "focus-visible:text-quebi-fg",
        )}
      >
        <X className="size-4" />
      </Button>
    </InputGroup>
  )
}