遊べるUI 初級

展開する検索

丸い検索アイコンがクリックで横長バーへ弾性展開し、入力欄がスライドイン。もう一度押すかフォーカスを外すと丸に戻ります。ヘッダーの検索UIに。

#css#js#search#input

ライブデモ

コード

HTML
<!-- 展開する検索: 丸い検索アイコンがクリックで横長バーへ弾性展開する -->
<div class="stage">
  <form class="expand-search" role="search" autocomplete="off">
    <button type="button" class="expand-search__toggle" aria-label="検索を開く" aria-expanded="false">
      <svg class="expand-search__icon" viewBox="0 0 24 24" aria-hidden="true">
        <circle cx="10.5" cy="10.5" r="6.5" />
        <line x1="15.3" y1="15.3" x2="21" y2="21" />
      </svg>
    </button>
    <input type="search" class="expand-search__input" placeholder="サイト内を検索…" aria-label="検索キーワード" />
  </form>
  <p class="hint">アイコンをクリックして検索窓を開く</p>
</div>
CSS
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  overflow: hidden;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(circle at 50% 30%, #14202b 0%, #0d0e1a 72%);
  color: #f4f5fb;
}

.stage { display: grid; place-items: center; gap: 18px; }

.expand-search {
  --expanded: min(300px, 76vw);
  position: relative;
  display: flex;
  align-items: center;
  width: 52px;
  height: 52px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .08);
  box-shadow: 0 10px 26px rgba(0, 0, 0, .4);
  overflow: hidden;
  transition: width .5s cubic-bezier(.34, 1.56, .64, 1), background-color .3s ease, border-color .3s ease;
  margin: 0;
}
.expand-search.is-open {
  width: var(--expanded);
  background: rgba(255, 255, 255, .08);
  border-color: rgba(103, 232, 249, .35);
}

.expand-search__toggle {
  flex: none;
  width: 52px;
  height: 52px;
  border: none;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  display: grid;
  place-items: center;
  color: #cfd4e6;
  animation: search-idle 3.4s ease-in-out infinite;
}
.expand-search.is-open .expand-search__toggle { animation: none; color: #67e8f9; }
.expand-search__toggle:focus-visible { outline: 2px solid #67e8f9; outline-offset: -3px; border-radius: 50%; }

@keyframes search-idle {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.08); }
}

.expand-search__icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.expand-search__input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  border: none;
  background: transparent;
  color: #f4f5fb;
  font-size: 15px;
  padding: 0;
  opacity: 0;
  transform: translateX(8px);
  transition: opacity .25s ease, transform .3s ease;
}
.expand-search.is-open .expand-search__input {
  opacity: 1;
  transform: translateX(0);
  padding-right: 18px;
  transition-delay: .15s;
}
.expand-search__input::placeholder { color: rgba(244, 245, 251, .4); }
.expand-search__input:focus { outline: none; }
/* ネイティブの検索フィールドに付くクリアボタン(x)を装飾なしに */
.expand-search__input::-webkit-search-cancel-button { -webkit-appearance: none; }

.hint { margin: 0; font-size: 13px; color: rgba(244, 245, 251, .5); }

@media (prefers-reduced-motion: reduce) {
  .expand-search__toggle { animation: none; }
  .expand-search,
  .expand-search__input { transition-duration: .12s; transition-delay: 0s !important; }
}
JavaScript
// 展開する検索: クリックで丸ボタンが横長バーへ展開し、inputがスライドイン。
// もう一度クリック・Escape・外側へのblurで丸に戻る
(() => {
  const form = document.querySelector('.expand-search');
  const toggle = document.querySelector('.expand-search__toggle');
  const input = document.querySelector('.expand-search__input');
  const hint = document.querySelector('.hint');
  if (!form || !toggle || !input) return; // null安全

  const setOpen = (open) => {
    form.classList.toggle('is-open', open);
    toggle.setAttribute('aria-expanded', String(open));
    toggle.setAttribute('aria-label', open ? '検索を閉じる' : '検索を開く');
    if (open) {
      window.requestAnimationFrame(() => input.focus());
    } else {
      input.value = '';
    }
    if (hint) {
      hint.textContent = open ? 'Escかフォーカスを外すと閉じます' : 'アイコンをクリックして検索窓を開く';
    }
  };

  toggle.addEventListener('click', () => setOpen(!form.classList.contains('is-open')));

  input.addEventListener('blur', () => {
    // トグルボタンへフォーカスが移った場合はクリックハンドラに開閉を任せる
    window.setTimeout(() => {
      if (!form.contains(document.activeElement)) setOpen(false);
    }, 0);
  });

  input.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') { setOpen(false); toggle.focus(); }
  });

  form.addEventListener('submit', (e) => e.preventDefault());
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「展開する検索」の効果を追加してください。

# 追加してほしい効果
展開する検索(遊べるUI)
丸い検索アイコンがクリックで横長バーへ弾性展開し、入力欄がスライドイン。もう一度押すかフォーカスを外すと丸に戻ります。ヘッダーの検索UIに。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 展開する検索: 丸い検索アイコンがクリックで横長バーへ弾性展開する -->
<div class="stage">
  <form class="expand-search" role="search" autocomplete="off">
    <button type="button" class="expand-search__toggle" aria-label="検索を開く" aria-expanded="false">
      <svg class="expand-search__icon" viewBox="0 0 24 24" aria-hidden="true">
        <circle cx="10.5" cy="10.5" r="6.5" />
        <line x1="15.3" y1="15.3" x2="21" y2="21" />
      </svg>
    </button>
    <input type="search" class="expand-search__input" placeholder="サイト内を検索…" aria-label="検索キーワード" />
  </form>
  <p class="hint">アイコンをクリックして検索窓を開く</p>
</div>

【CSS】
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  overflow: hidden;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(circle at 50% 30%, #14202b 0%, #0d0e1a 72%);
  color: #f4f5fb;
}

.stage { display: grid; place-items: center; gap: 18px; }

.expand-search {
  --expanded: min(300px, 76vw);
  position: relative;
  display: flex;
  align-items: center;
  width: 52px;
  height: 52px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .08);
  box-shadow: 0 10px 26px rgba(0, 0, 0, .4);
  overflow: hidden;
  transition: width .5s cubic-bezier(.34, 1.56, .64, 1), background-color .3s ease, border-color .3s ease;
  margin: 0;
}
.expand-search.is-open {
  width: var(--expanded);
  background: rgba(255, 255, 255, .08);
  border-color: rgba(103, 232, 249, .35);
}

.expand-search__toggle {
  flex: none;
  width: 52px;
  height: 52px;
  border: none;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  display: grid;
  place-items: center;
  color: #cfd4e6;
  animation: search-idle 3.4s ease-in-out infinite;
}
.expand-search.is-open .expand-search__toggle { animation: none; color: #67e8f9; }
.expand-search__toggle:focus-visible { outline: 2px solid #67e8f9; outline-offset: -3px; border-radius: 50%; }

@keyframes search-idle {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.08); }
}

.expand-search__icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.expand-search__input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  border: none;
  background: transparent;
  color: #f4f5fb;
  font-size: 15px;
  padding: 0;
  opacity: 0;
  transform: translateX(8px);
  transition: opacity .25s ease, transform .3s ease;
}
.expand-search.is-open .expand-search__input {
  opacity: 1;
  transform: translateX(0);
  padding-right: 18px;
  transition-delay: .15s;
}
.expand-search__input::placeholder { color: rgba(244, 245, 251, .4); }
.expand-search__input:focus { outline: none; }
/* ネイティブの検索フィールドに付くクリアボタン(x)を装飾なしに */
.expand-search__input::-webkit-search-cancel-button { -webkit-appearance: none; }

.hint { margin: 0; font-size: 13px; color: rgba(244, 245, 251, .5); }

@media (prefers-reduced-motion: reduce) {
  .expand-search__toggle { animation: none; }
  .expand-search,
  .expand-search__input { transition-duration: .12s; transition-delay: 0s !important; }
}

【JavaScript】
// 展開する検索: クリックで丸ボタンが横長バーへ展開し、inputがスライドイン。
// もう一度クリック・Escape・外側へのblurで丸に戻る
(() => {
  const form = document.querySelector('.expand-search');
  const toggle = document.querySelector('.expand-search__toggle');
  const input = document.querySelector('.expand-search__input');
  const hint = document.querySelector('.hint');
  if (!form || !toggle || !input) return; // null安全

  const setOpen = (open) => {
    form.classList.toggle('is-open', open);
    toggle.setAttribute('aria-expanded', String(open));
    toggle.setAttribute('aria-label', open ? '検索を閉じる' : '検索を開く');
    if (open) {
      window.requestAnimationFrame(() => input.focus());
    } else {
      input.value = '';
    }
    if (hint) {
      hint.textContent = open ? 'Escかフォーカスを外すと閉じます' : 'アイコンをクリックして検索窓を開く';
    }
  };

  toggle.addEventListener('click', () => setOpen(!form.classList.contains('is-open')));

  input.addEventListener('blur', () => {
    // トグルボタンへフォーカスが移った場合はクリックハンドラに開閉を任せる
    window.setTimeout(() => {
      if (!form.contains(document.activeElement)) setOpen(false);
    }, 0);
  });

  input.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') { setOpen(false); toggle.focus(); }
  });

  form.addEventListener('submit', (e) => e.preventDefault());
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。