レビュー済み·難易度: 初級·更新: 2026-04-18

React で検索入力欄にクリアボタンを追加する

検索フィールドに入力内容を一発でクリアする × ボタンを追加し、入力中だけ表示する制御と URL クエリへの反映パターンを示す例。

nextjssearch-filterui-component

対応バージョン

nextjs 15react 19

前提環境

React の useState と基本的なフォーム操作を理解していること

概要

検索フィールドに値が入力されているときだけ × ボタンを表示し、クリック時に入力値と URL クエリの両方をリセットする。useRouteruseSearchParams を組み合わせて URL 同期を維持する。

インストール

# 追加インストールは不要

実装

クリアボタン付き検索フィールド

// components/ClearableSearchInput.tsx
"use client";

import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { useCallback, useRef, useState } from "react";

type Props = {
  defaultValue?: string;
  placeholder?: string;
};

export function ClearableSearchInput({
  defaultValue = "",
  placeholder = "キーワードで検索…",
}: Props) {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const [value, setValue] = useState(defaultValue);
  const inputRef = useRef<HTMLInputElement>(null);

  const submit = useCallback(
    (q: string) => {
      const params = new URLSearchParams(searchParams.toString());
      if (q) {
        params.set("q", q);
      } else {
        params.delete("q");
      }
      params.delete("page");
      router.push(`${pathname}?${params.toString()}`);
    },
    [router, pathname, searchParams]
  );

  const handleClear = useCallback(() => {
    setValue("");
    submit("");
    inputRef.current?.focus();
  }, [submit]);

  return (
    <div className="relative">
      <input
        ref={inputRef}
        type="search"
        value={value}
        placeholder={placeholder}
        onChange={(e) => setValue(e.target.value)}
        onKeyDown={(e) => e.key === "Enter" && submit(value)}
        onBlur={() => submit(value)}
        className="w-full rounded border border-gray-200 py-2 pl-3 pr-8 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400"
      />
      {value && (
        <button
          type="button"
          onClick={handleClear}
          aria-label="検索をクリア"
          className="absolute inset-y-0 right-2 flex items-center text-gray-400 hover:text-gray-600"
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            className="h-4 w-4"
            viewBox="0 0 20 20"
            fill="currentColor"
            aria-hidden="true"
          >
            <path
              fillRule="evenodd"
              d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
              clipRule="evenodd"
            />
          </svg>
        </button>
      )}
    </div>
  );
}

Suspense でラップして使う

// app/samples/page.tsx(抜粋)
import { Suspense } from "react";
import { ClearableSearchInput } from "@/components/ClearableSearchInput";

export default async function SamplesPage({ searchParams }: Props) {
  const params = await searchParams;
  const q = typeof params.q === "string" ? params.q : "";

  return (
    <div>
      <Suspense>
        <ClearableSearchInput defaultValue={q} />
      </Suspense>
      {/* ...一覧 */}
    </div>
  );
}

type="search" のネイティブ × ボタンを非表示にする

ブラウザによっては type="search" に標準のクリアボタンが表示される。カスタムボタンと重複するため CSS で非表示にする。

/* app/globals.css */
input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

ポイント

  • value && (...) でボタンの表示を制御する。入力がない状態でクリアボタンを出さないことで、余分な UI ノイズを減らす
  • handleClear でステートのクリアと URL 更新を同時に行う。片方だけリセットすると表示と URL が乖離する
  • クリア後に inputRef.current?.focus() でフォーカスを戻すと、ユーザーが次のキーワードをすぐ入力できる
  • aria-label="検索をクリア" を必ずつける。アイコンのみのボタンはスクリーンリーダーが目的を読み取れない
  • type="search" のブラウザネイティブ × ボタンはカスタムボタンと重複するため CSS で非表示にする。Safari / Chrome / Edge で挙動が異なることがある
  • params.delete("page") でページリセットを忘れない。クリア後にページ 2 以降が残ると、1 ページ目の結果が表示されない

注意点

nextjs-url-filter-reset は URL 全体のリセット。react-keyword-empty-state は 0 件時の導線。これは検索入力欄の × ボタンで入力値と URL クエリを同時にクリアする入力補助 UI パターンに特化。

関連サンプル