Blocks · Marketing
Search bar
A short heading and one line in a centred panel, then the search field with its clear button and / shortcut.
9.6% of the 2,073 sites in The UI Index have a search (200 sites).
Find a guide
Search the docs, the changelog and the guides.
Results for your search
Find a guide
Search the docs, the changelog and the guides.
Results for your search
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ ui/ r/ search-bar.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ ui/ r/ astro/ search-bar.json
Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the themes.dev UI theme. The HTML needs only the compiled CSS.
/** A search bar (ported from the themes' sections/search): a short heading and one line in a centred panel, then
* the search field (the free search-field: magnifier, a native search input, a "/" shortcut, a clear button once
* there is text). A GET form that works without JavaScript. */
export const searchBar = {
root: 'bg-background px-6 py-16 text-foreground',
frame: 'mx-auto flex w-full max-w-3xl min-w-0 flex-col gap-6 rounded-xl border bg-card p-5 text-card-foreground sm:p-8',
head: 'flex flex-col gap-2',
heading: 'text-xl font-semibold tracking-[-0.02em] text-balance',
lead: 'text-base text-pretty text-muted-foreground',
};
export type SearchBarContent = {
/** The panel's short heading. */
heading: string;
lead?: string;
/** The field's accessible name, e.g. "Search the docs". */
label: string;
placeholder?: string;
/** Where the search form goes (GET); it works without JavaScript. */
action: string;
/** The query's name. Default: q. */
name?: string;
/** The text already in the field. */
value?: string;
/** Unique on the page; ids the heading and the field. */
id: string;
};import { SearchField } from '@/components/ui/search-field';
import { cn } from '@/lib/utils';
import { searchBar as c, type SearchBarContent } from './search-bar.classes';
export type SearchBarProps = SearchBarContent & { className?: string };
/** A search bar: a short heading and one line in a panel, then the search field. */
export function SearchBar({ heading, lead, label, placeholder, action, name, value, id, className }: SearchBarProps) {
return (
<section aria-labelledby={`${id}-title`} className={cn(c.root, className)}>
<div className={c.frame}>
<div className={c.head}>
<h2 id={`${id}-title`} className={c.heading}>{heading}</h2>
{lead && <p className={c.lead}>{lead}</p>}
</div>
<SearchField id={`${id}-q`} label={label} placeholder={placeholder} action={action} name={name} defaultValue={value} />
</div>
</section>
);
}---
import SearchField from '@/components/ui/search-field.astro';
import { cn } from '@/lib/utils';
import { searchBar as c, type SearchBarContent } from './search-bar.classes';
type Props = SearchBarContent & { class?: string };
const { heading, lead, label, placeholder, action, name, value, id, class: className } = Astro.props;
---
<section aria-labelledby={`${id}-title`} class={cn(c.root, className)}>
<div class={c.frame}>
<div class={c.head}>
<h2 id={`${id}-title`} class={c.heading}>{heading}</h2>
{lead && <p class={c.lead}>{lead}</p>}
</div>
<SearchField id={`${id}-q`} label={label} placeholder={placeholder} action={action} name={name} value={value} />
</div>
</section>/** A search bar (ported from the themes' sections/search): a short heading and one line in a centred panel, then
* the search field (the free search-field: magnifier, a native search input, a "/" shortcut, a clear button once
* there is text). A GET form that works without JavaScript. */
export const searchBar = {
root: 'bg-background px-6 py-16 text-foreground',
frame: 'mx-auto flex w-full max-w-3xl min-w-0 flex-col gap-6 rounded-xl border bg-card p-5 text-card-foreground sm:p-8',
head: 'flex flex-col gap-2',
heading: 'text-xl font-semibold tracking-[-0.02em] text-balance',
lead: 'text-base text-pretty text-muted-foreground',
};
export type SearchBarContent = {
/** The panel's short heading. */
heading: string;
lead?: string;
/** The field's accessible name, e.g. "Search the docs". */
label: string;
placeholder?: string;
/** Where the search form goes (GET); it works without JavaScript. */
action: string;
/** The query's name. Default: q. */
name?: string;
/** The text already in the field. */
value?: string;
/** Unique on the page; ids the heading and the field. */
id: string;
};<div class="grid w-full gap-4">
<section aria-labelledby="ex-search-bar-title" class="bg-background px-6 text-foreground py-10">
<div class="mx-auto flex w-full max-w-3xl min-w-0 flex-col gap-6 rounded-xl border bg-card p-5 text-card-foreground sm:p-8">
<div class="flex flex-col gap-2">
<h2 id="ex-search-bar-title" class="text-xl font-semibold tracking-[-0.02em] text-balance">Find a guide</h2>
<p class="text-base text-pretty text-muted-foreground">Search the docs, the changelog and the guides.</p>
</div>
<form role="search" action="#" class="relative w-full">
<label for="ex-search-bar-q" class="sr-only">Search the docs</label>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true" class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground"><path d="M7 12.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zM11 11l3.5 3.5"></path></svg>
<input type="search" id="ex-search-bar-q" name="q" placeholder="Search the docs" autocomplete="off" data-search class="w-full min-w-0 rounded-md border border-input bg-background px-3 text-sm text-foreground transition-colors placeholder:text-muted-foreground enabled:hover:border-foreground/85 enabled:active:bg-muted aria-invalid:enabled:hover:border-destructive aria-invalid:enabled:hover:bg-[color-mix(in_oklab,var(--destructive)_6%,var(--background))] aria-invalid:enabled:active:bg-[color-mix(in_oklab,var(--destructive)_10%,var(--background))] focus-visible:border-ring focus-visible:outline-2 focus-visible:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:outline-destructive peer h-9 py-1 pr-10 pl-9 [&::-webkit-search-cancel-button]:appearance-none">
<kbd class="pointer-events-none absolute top-1/2 right-2 inline-flex h-5 min-w-5 -translate-y-1/2 items-center justify-center rounded border bg-muted px-1 font-sans text-[11px] text-muted-foreground peer-focus:hidden peer-[:not(:placeholder-shown)]:hidden" aria-hidden="true">
/
</kbd>
<button type="button" data-search-clear aria-label="Clear search" class="absolute top-1/2 right-1.5 hidden size-7 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed peer-[:not(:placeholder-shown)]:inline-flex [&_svg]:size-3.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"></path></svg>
</button>
</form>
</div>
</section>
<section aria-labelledby="ex-search-bar-filled-title" class="bg-background px-6 text-foreground py-10">
<div class="mx-auto flex w-full max-w-3xl min-w-0 flex-col gap-6 rounded-xl border bg-card p-5 text-card-foreground sm:p-8">
<div class="flex flex-col gap-2">
<h2 id="ex-search-bar-filled-title" class="text-xl font-semibold tracking-[-0.02em] text-balance">Results for your search</h2>
</div>
<form role="search" action="#" class="relative w-full">
<label for="ex-search-bar-filled-q" class="sr-only">Search deploys</label>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true" class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground"><path d="M7 12.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zM11 11l3.5 3.5"></path></svg>
<input type="search" id="ex-search-bar-filled-q" name="q" value="pricing" placeholder="Search" autocomplete="off" data-search class="w-full min-w-0 rounded-md border border-input bg-background px-3 text-sm text-foreground transition-colors placeholder:text-muted-foreground enabled:hover:border-foreground/85 enabled:active:bg-muted aria-invalid:enabled:hover:border-destructive aria-invalid:enabled:hover:bg-[color-mix(in_oklab,var(--destructive)_6%,var(--background))] aria-invalid:enabled:active:bg-[color-mix(in_oklab,var(--destructive)_10%,var(--background))] focus-visible:border-ring focus-visible:outline-2 focus-visible:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:outline-destructive peer h-9 py-1 pr-10 pl-9 [&::-webkit-search-cancel-button]:appearance-none">
<kbd class="pointer-events-none absolute top-1/2 right-2 inline-flex h-5 min-w-5 -translate-y-1/2 items-center justify-center rounded border bg-muted px-1 font-sans text-[11px] text-muted-foreground peer-focus:hidden peer-[:not(:placeholder-shown)]:hidden" aria-hidden="true">
/
</kbd>
<button type="button" data-search-clear aria-label="Clear search" class="absolute top-1/2 right-1.5 hidden size-7 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed peer-[:not(:placeholder-shown)]:inline-flex [&_svg]:size-3.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"></path></svg>
</button>
</form>
</div>
</section>
</div>