Expandable Search

Pro

A search that rests as an icon button and grows into a full field on click. One box widens under the magnifier, so the glyph never moves and the open field is the DS Input, pixel for pixel.

All files

128

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add expandable-search

Usage#

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

How it opens and closes#

A click opens it and puts the caret in the field. Escape steps back one layer at a time: the first press clears the query, the second folds the search away and hands focus back to the button. Clicking elsewhere folds an empty search, but one that holds a query stays open, so the text filtering the page never disappears behind an icon. The width transition names its properties and switches off under reduced motion.

Sizes#

The Input's own axis: sm 32px, md 36px, lg 40px. At rest it is a square of that height, the same size as a ghost icon Button beside it; unset, it follows the container's imposed size or the density.

sm
md
lg

With a shortcut#

Trailing content rides inside the open field, like a Kbd hint. Control open when something outside the component opens it: press / anywhere on this page.

States#

At rest, open with a query, and disabled. It is an InputRoot underneath, so hasError, a surrounding Field and the focus ring all behave exactly as they do on an Input.

In a navbar#

NavbarSearch collapsible is this component tuned for the bar: 36px and rounded-md at rest, the hamburger's chip, so it reads as one of the bar's icons until someone searches.

API reference#

ExpandableSearch

An InputRoot whose leading magnifier is the button that opens it; forwards all InputRoot props and native <div> props. className is merged last, and a width class there (default w-64) sets the open width only.

PropTypeDefaultDescription
openboolean-Controlled open state. A controlled change never moves focus; focus the field yourself through inputProps.ref.
defaultOpenbooleanfalseInitial open state when uncontrolled; mounting open does not take focus.
onOpenChange(open: boolean) => void-Called when the field expands or collapses.
size"sm" | "md" | "lg""sm"Height of the open field (32, 36 or 40px) and side of the closed square. Unset, it follows density, then the container's imposed size, then the nearest DensityProvider (compact sm, comfortable md).
density"comfortable" | "compact"-Only picks the default size (compact sm, comfortable md); an explicit size wins.
placeholderstring"Search"Placeholder of the field.
labelstring-Accessible name shared by the button and the field; defaults to placeholder.
inputPropsInputFieldProps-Forwarded to the <input type="search"> (value, onChange, name, ref...). Escape clears a controlled value through its own onChange.
childrenReactNode-Trailing content inside the open field, such as an InputSuffix holding a Kbd.
hasErrorbooleanfalseError border and ring, as on an Input; cascades from a surrounding Field.
disabledbooleanfalseDisables the button and the field; cascades from a surrounding Field.
variant"default" | "inline""default"The Input's chrome variant, passed through to InputRoot.
Data attributeDescription
data-state"open" or "closed".
data-disabledPresent when disabled.
data-errorPresent when hasError is set.

FAQ#

When the row can't spare a field's width until someone actually searches: a table toolbar, a dense app header, a marketing navbar with a full link set. Where search is the main way around the product, keep the always-open field (an `Input` with an `InputPrefix`, or `NavbarSearch`) so nobody has to find it first.