Loading craft...
Loading craft...
A React implementation of the search experience from the Brave Browser's new tab page. It uses a compound component architecture so you can freely place any layout — tabs, cards, side content — around the search bar, and those elements will automatically fade and scale away when the user focuses the input.
I was using Brave as my browser and noticed how smooth the new tab page felt — the search bar that expands and moves up, the surrounding UI that gracefully steps aside when you start typing. I wanted to recreate that specific interaction in React, with the same fluid transition style and composition flexibility.
The component uses React Context to coordinate state between the individual pieces (Root, Background, Main, SearchBar, Results) so consumers can slot in their own UI without passing props through every layer.
While building the results dropdown, I ran into a specific React animation bug that's easy to miss but very distracting once you see it.
The glitch is not triggered by blurring the input — blurring just fades the container via CSS and looks fine. The problem happens when you clear the search query. When the query becomes empty, React instantly re-renders the dropdown to show "No results found" on the exact same frame the CSS opacity and scale exit animation begins. The inner content snaps to the new state while the container is still animating out — two conflicting visual updates at once.
Try it yourself in the demo below. Type any query, get results, then delete the text character by character and watch the snap:
To fix this, BraveSearch.Results wraps its inner content in a Freeze component powered by React.Suspense. As described by Cameron Barvian, this pauses React DOM commits inside the dropdown the moment it starts exiting. The CSS animation plays out on the frozen, unchanged content, and React discards it cleanly afterwards.
BraveSearch is a compound component. Wrap any background elements in BraveSearch.Background — they will auto-hide when the input is focused. Place SearchBar and Results inside BraveSearch.Main.
The context provider. Must wrap all other BraveSearch components. Manages isSearchFocused and searchQuery internally.
Wraps any UI that should disappear when the user focuses the search bar. Applies opacity, scale, and pointer-events transitions automatically.
Structural wrapper for the search bar and results. Lifts up visually when the user starts searching.
| Props | Description | Default |
|---|---|---|
placeholder | Placeholder text shown in the input when empty. | "Ask Brave Search" |
| Props | Description | Default |
|---|---|---|
results | Filtered array of SearchResult objects to render. Pass your own filtered data — the component handles visibility and the Freeze exit animation. | [] |
BraveSearch.useSearchContext()Exported hook for reading searchQuery and isSearchFocused inside any child of BraveSearch.Root. Use this to filter data without prop drilling.