Expandable Search
ProA 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.
Installation#
Usage#
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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | - | Controlled open state. A controlled change never moves focus; focus the field yourself through inputProps.ref. |
| defaultOpen | boolean | false | Initial 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. |
| placeholder | string | "Search" | Placeholder of the field. |
| label | string | - | Accessible name shared by the button and the field; defaults to placeholder. |
| inputProps | InputFieldProps | - | Forwarded to the <input type="search"> (value, onChange, name, ref...). Escape clears a controlled value through its own onChange. |
| children | ReactNode | - | Trailing content inside the open field, such as an InputSuffix holding a Kbd. |
| hasError | boolean | false | Error border and ring, as on an Input; cascades from a surrounding Field. |
| disabled | boolean | false | Disables the button and the field; cascades from a surrounding Field. |
| variant | "default" | "inline" | "default" | The Input's chrome variant, passed through to InputRoot. |
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed". |
| data-disabled | Present when disabled. |
| data-error | Present when hasError is set. |