Installation
Registry — recommended
Copies the component into your configured shadcn UI directory and installs Motion.
npx shadcn@latest add https://suluu.site/r/search-morph.jsonnpm package
For centralized upgrades, install the optional ESM package and configure Tailwind source detection as shown below.
pnpm add suluu motionUsage
Submission is callback-only. Connect onSubmit to your search handler. The field does not render results.
import { SearchMorph } from "@/components/ui/search-morph"
export function Find() {
return (
<SearchMorph
onSubmit={(query) => runSearch(query)}
/>
)
}// app/globals.css
@import "suluu/styles.css";
@source "../node_modules/suluu/dist";
// component.tsx
import { SearchMorph } from "suluu/search-morph"
export function Find() {
return <SearchMorph onSubmit={(query) => runSearch(query)} />
}Controlled state
const [query, setQuery] = useState("")
const [open, setOpen] = useState(false)
<SearchMorph
expanded={open}
onExpandedChange={setOpen}
onSubmit={(next) => runSearch(next)}
onValueChange={setQuery}
value={query}
/>Pending state
Submitting swaps the action for a spinner. Left uncontrolled it settles after pendingDuration; pass pending to tie it to a real async search instead.
const [pending, setPending] = useState(false)
<SearchMorph
onSubmit={async (query) => {
setPending(true)
await runSearch(query)
setPending(false)
}}
pending={pending}
/>In context
The search action can sit quietly in a content header, then make room for a query when needed.
Knowledge base
84 notes across 6 spaces
Launch notes
Updated this week
Research archive
Updated this week
Props
SearchMorph also accepts safe native form attributes and forwards its ref to the form element.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | "Search" | CTA text in both states. |
| placeholder | string | "Search" | Search input placeholder. |
| className | string | — | Class name applied to the form. |
| collapseOnBlur | boolean | true | Collapse when focus leaves the widget. |
| disabled | boolean | false | Disable every interactive control. |
| value | string | — | Controlled query value. |
| defaultValue | string | "" | Initial uncontrolled query value. |
| onValueChange | (value) => void | — | Runs when the input changes. |
| expanded | boolean | — | Controlled expansion state. |
| defaultExpanded | boolean | false | Initial uncontrolled expansion state. |
| onExpandedChange | (expanded) => void | — | Runs for expansion requests. |
| motionIntensity | "subtle" | "default" | "expressive" | "default" | Animation character. |
| pending | boolean | — | Controlled in-flight state. Swaps the action for a spinner. |
| pendingDuration | number | 900 | Length of the built-in submit acknowledgement, in ms. |
| onSubmit | (query, event) => void | — | Runs on submit. Empty queries are allowed. |
Theming
Override these variables in your light and dark theme scopes. The registry installs the defaults automatically.
--suluu-search-backgroundContainer and input surface.--suluu-search-foregroundPrimary text and icon.--suluu-search-mutedPlaceholder text.--suluu-search-hoverCollapsed CTA hover surface.--suluu-search-accentExpanded submit button.--suluu-search-accent-foregroundSubmit button content.--suluu-search-ringKeyboard focus ring.--suluu-search-shadowExpanded action button shadow.Accessibility
SearchMorph is a native form with role="search". Expansion moves focus to the search field, Escape collapses and restores focus to the trigger, and reduced motion removes the morph. The clear button is labelled Clear search and hands focus back to the field; while a search is pending the action carries aria-busy alongside a polite live announcement.