Deck
Browse the catalog

Build / Molecules

SearchField

  • molecule
  • play-fn

The search input for Rolodex and other lists.

Search is the primary interaction in a contact list, so this is a distinct component rather than an Input with an icon: it renders type="search" (giving mobile keyboards a Search key), offers a clear control, and can announce a live result count so screen reader users hear the list change.

import { SearchField } from '@meetcard/deck'

Example

<SearchField label="Search connections" resultCount={n} value={q} onChange={(e) => setQ(e.target.value)} onClear={() => setQ('')} />

Stories

Every preview below is the real component, rendered in this page from the same story Storybook runs. The width control resizes each preview's container; CSS media queries still follow the browser window, so breakpoint behaviour is worth checking in Storybook.

Default
Source
const Default = () => <div style={{ maxWidth: 360 }}>
    <SearchField label="Search connections" placeholder="Search connections" />
</div>;

Label shown, for a search that is one field of several.

With Visible Label
Source
const WithVisibleLabel = () => <div style={{ maxWidth: 360 }}>
    <SearchField label="Search connections" placeholder="Search connections" showLabel />
</div>;

Search is the primary interaction in a contact list.

Filtering Rolodex
Source
const FilteringRolodex = () => {
    const [query, setQuery] = useState('')
    const results = useMemo(
      () =>
        people.filter((p) =>
          `${p.name} ${p.company}`.toLowerCase().includes(query.toLowerCase()),
        ),
      [query],
    )

    return (
        <Stack gap={16} style={{ maxWidth: 420 }}>
            <SearchField
                label="Search connections"
                placeholder="Search connections"
                value={query}
                resultCount={results.length}
                onChange={(event) => setQuery(event.target.value)}
                onClear={() => setQuery('')} />

            {results.length > 0 ? (
              <Stack as="ul" gap={8}>
                {results.map((person) => (
                  <li key={person.name}>
                    <PersonCard {...person} />
                  </li>
                ))}
              </Stack>
            ) : (
              <EmptyState
                size="sm"
                title="No matches"
                description={`Nothing matched "${query}".`}
              />
            )}
        </Stack>
    );
};

Every size, aligned with Input.

Sizes
Source
const Sizes = () => <Stack gap={12} style={{ maxWidth: 360 }}>
    <SearchField placeholder="Search connections" size="sm" label="Small" />
    <SearchField placeholder="Search connections" size="md" label="Medium" />
    <SearchField placeholder="Search connections" size="lg" label="Large" />
</Stack>;

Props

PropTypeDefaultDescription
labelrequiredReactNode—Accessible name. Usually hidden — the magnifier conveys purpose visually.
clearLabelstring'Clear search'—
onClear() => void—Called when the clear control is used, after the value is emptied.
resultCountnumber—Live result count, announced politely as the user types.
showLabelbooleanfalseShow the label above the field instead of only to assistive tech.
size'sm' | 'md' | 'lg''md'—

Also accepts everything from Omit, so id, data-* and event handlers pass straight through.

Open in Storybookfor controls, the a11y panel and the interaction runner.