メッセージ入力欄

入力内容に応じて高さが伸びるテキストエリアと、文字が入ると活性化する送信ボタン、絵文字と添付のアイコンを備えた入力欄です。

#css#js#chat

ライブデモ

コード

HTML
<!-- メッセージ入力欄: 内容に応じて伸びるテキストエリアと、入力があると活性化する送信ボタン、絵文字/添付アイコン -->
<div class="stage">
  <section class="cc-panel" aria-label="メッセージ入力デモ">
    <ul class="cc-history" id="ccHistory">
      <li class="cc-msg cc-msg--in">今日の打ち合わせ、資料はドライブに置いておいたよ</li>
      <li class="cc-msg cc-msg--out">ありがとう、確認するね!</li>
    </ul>

    <div class="cc-emoji" id="ccEmoji" hidden>
      <button type="button" class="cc-emoji__item">😀</button>
      <button type="button" class="cc-emoji__item">👍</button>
      <button type="button" class="cc-emoji__item">❤️</button>
      <button type="button" class="cc-emoji__item">😂</button>
      <button type="button" class="cc-emoji__item">🎉</button>
    </div>

    <form class="cc-composer" id="ccForm">
      <button type="button" class="cc-icon" id="ccAttach" aria-label="ファイルを添付">📎</button>
      <button type="button" class="cc-icon" id="ccEmojiToggle" aria-label="絵文字を選択" aria-expanded="false">🙂</button>
      <textarea class="cc-textarea" id="ccInput" rows="1" placeholder="メッセージを入力…" aria-label="メッセージ入力"></textarea>
      <button type="submit" class="cc-send" id="ccSend" disabled aria-label="送信"><span aria-hidden="true">➤</span></button>
    </form>
  </section>
</div>
CSS
/* メッセージ入力欄 */
* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 28px;
  font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  background:
    radial-gradient(circle at 90% 8%, rgba(167, 139, 250, .15) 0%, transparent 42%),
    radial-gradient(circle at 6% 92%, rgba(236, 72, 153, .1) 0%, transparent 45%),
    #120e1c;
  color: #ede9f7;
}

.cc-panel {
  width: min(380px, 92vw);
  border-radius: 22px;
  background: rgba(255, 255, 255, .045);
  border: 1px solid rgba(255, 255, 255, .09);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .75);
  overflow: hidden;
  display: grid;
  grid-template-rows: auto auto auto;
}

.cc-history {
  list-style: none;
  margin: 0;
  padding: 18px 16px 8px;
  display: grid;
  gap: 8px;
  max-height: 220px;
  overflow-y: auto;
}

.cc-msg {
  width: fit-content;
  max-width: 82%;
  padding: 10px 14px;
  font-size: 13px;
  line-height: 1.55;
  border-radius: 15px;
  animation: cc-pop .35s cubic-bezier(.22, 1, .36, 1);
}
.cc-msg--in {
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .07);
  border-bottom-left-radius: 5px;
}
.cc-msg--out {
  margin-left: auto;
  background: linear-gradient(135deg, #a78bfa, #7c3aed);
  border-bottom-right-radius: 5px;
  color: #fff;
  box-shadow: 0 10px 22px -12px rgba(124, 58, 237, .7);
}
@keyframes cc-pop {
  from { opacity: 0; transform: translateY(8px) scale(.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* 絵文字ピッカー */
.cc-emoji {
  display: flex;
  gap: 6px;
  padding: 0 16px 10px;
  animation: cc-pop .25s cubic-bezier(.22, 1, .36, 1);
}
.cc-emoji[hidden] { display: none; }
.cc-emoji__item {
  font: inherit;
  font-size: 17px;
  line-height: 1;
  padding: 7px 9px;
  border: none;
  border-radius: 10px;
  background: rgba(255, 255, 255, .06);
  cursor: pointer;
  transition: background .15s ease, transform .15s ease;
}
.cc-emoji__item:hover { background: rgba(255, 255, 255, .14); transform: translateY(-2px); }

/* 入力バー */
.cc-composer {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  padding: 12px 12px 14px;
  border-top: 1px solid rgba(255, 255, 255, .08);
}

.cc-icon {
  flex: none;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  font-size: 16px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: rgba(237, 233, 247, .75);
  cursor: pointer;
  transition: background .15s ease, transform .15s ease;
}
.cc-icon:hover { background: rgba(255, 255, 255, .08); }
.cc-icon.is-active { transform: scale(.9); background: rgba(255, 255, 255, .14); }

.cc-textarea {
  flex: 1;
  resize: none;
  min-height: 34px;
  max-height: 120px;
  overflow-y: auto;
  padding: 8px 12px;
  font: inherit;
  font-size: 13.5px;
  line-height: 1.5;
  color: #ede9f7;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 16px;
  transition: border-color .2s ease, background .2s ease;
}
.cc-textarea:focus {
  outline: none;
  border-color: rgba(167, 139, 250, .55);
  background: rgba(255, 255, 255, .1);
}
.cc-textarea::placeholder { color: rgba(237, 233, 247, .38); }

.cc-send {
  flex: none;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  font-size: 15px;
  border: none;
  border-radius: 50%;
  color: rgba(237, 233, 247, .4);
  background: rgba(255, 255, 255, .07);
  cursor: not-allowed;
  transition: background .2s ease, color .2s ease, transform .15s cubic-bezier(.22, 1, .36, 1);
}
.cc-send:not(:disabled) {
  color: #fff;
  background: linear-gradient(135deg, #a78bfa, #7c3aed);
  cursor: pointer;
  box-shadow: 0 8px 18px -8px rgba(124, 58, 237, .75);
}
.cc-send:not(:disabled):hover { transform: translateY(-2px) scale(1.05); }
.cc-send:not(:disabled):active { transform: scale(.94); }

@media (prefers-reduced-motion: reduce) {
  .cc-msg { animation: none; }
  .cc-send:not(:disabled):hover { transform: none; }
}

@media (max-width: 380px) {
  .cc-panel { border-radius: 18px; }
  .cc-history { padding: 14px 12px 6px; }
}
JavaScript
// メッセージ入力欄: オートリサイズ・送信ボタンの活性化・絵文字挿入・送信で履歴に追記
(() => {
  const form = document.getElementById('ccForm');
  const input = document.getElementById('ccInput');
  const sendBtn = document.getElementById('ccSend');
  const history = document.getElementById('ccHistory');
  const attachBtn = document.getElementById('ccAttach');
  const emojiToggle = document.getElementById('ccEmojiToggle');
  const emojiPanel = document.getElementById('ccEmoji');
  if (!form || !input || !sendBtn || !history) return; // null安全

  const MAX_HEIGHT = 120;

  const autoResize = () => {
    input.style.height = 'auto';
    input.style.height = Math.min(input.scrollHeight, MAX_HEIGHT) + 'px';
  };

  const syncSendState = () => {
    const hasText = input.value.trim().length > 0;
    sendBtn.disabled = !hasText;
  };

  input.addEventListener('input', () => {
    autoResize();
    syncSendState();
  });

  // 添付ボタンは押した感触だけ返す装飾的な操作
  if (attachBtn) {
    attachBtn.addEventListener('click', () => {
      attachBtn.classList.add('is-active');
      setTimeout(() => attachBtn.classList.remove('is-active'), 160);
    });
  }

  // 絵文字パネルの開閉
  if (emojiToggle && emojiPanel) {
    emojiToggle.addEventListener('click', () => {
      const willShow = emojiPanel.hidden;
      emojiPanel.hidden = !willShow;
      emojiToggle.setAttribute('aria-expanded', String(willShow));
      if (willShow) input.focus();
    });

    emojiPanel.addEventListener('click', (e) => {
      const btn = e.target.closest('.cc-emoji__item');
      if (!btn) return;
      input.value += btn.textContent;
      autoResize();
      syncSendState();
      input.focus();
      emojiPanel.hidden = true;
      emojiToggle.setAttribute('aria-expanded', 'false');
    });
  }

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const text = input.value.trim();
    if (!text) return;

    const li = document.createElement('li');
    li.className = 'cc-msg cc-msg--out';
    li.textContent = text;
    history.appendChild(li);
    history.scrollTop = history.scrollHeight;

    input.value = '';
    autoResize();
    syncSendState();
    if (emojiPanel && !emojiPanel.hidden) {
      emojiPanel.hidden = true;
      if (emojiToggle) emojiToggle.setAttribute('aria-expanded', 'false');
    }
  });
})();

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

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

# 追加してほしい効果
メッセージ入力欄(チャット & メディア)
入力内容に応じて高さが伸びるテキストエリアと、文字が入ると活性化する送信ボタン、絵文字と添付のアイコンを備えた入力欄です。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- メッセージ入力欄: 内容に応じて伸びるテキストエリアと、入力があると活性化する送信ボタン、絵文字/添付アイコン -->
<div class="stage">
  <section class="cc-panel" aria-label="メッセージ入力デモ">
    <ul class="cc-history" id="ccHistory">
      <li class="cc-msg cc-msg--in">今日の打ち合わせ、資料はドライブに置いておいたよ</li>
      <li class="cc-msg cc-msg--out">ありがとう、確認するね!</li>
    </ul>

    <div class="cc-emoji" id="ccEmoji" hidden>
      <button type="button" class="cc-emoji__item">😀</button>
      <button type="button" class="cc-emoji__item">👍</button>
      <button type="button" class="cc-emoji__item">❤️</button>
      <button type="button" class="cc-emoji__item">😂</button>
      <button type="button" class="cc-emoji__item">🎉</button>
    </div>

    <form class="cc-composer" id="ccForm">
      <button type="button" class="cc-icon" id="ccAttach" aria-label="ファイルを添付">📎</button>
      <button type="button" class="cc-icon" id="ccEmojiToggle" aria-label="絵文字を選択" aria-expanded="false">🙂</button>
      <textarea class="cc-textarea" id="ccInput" rows="1" placeholder="メッセージを入力…" aria-label="メッセージ入力"></textarea>
      <button type="submit" class="cc-send" id="ccSend" disabled aria-label="送信"><span aria-hidden="true">➤</span></button>
    </form>
  </section>
</div>

【CSS】
/* メッセージ入力欄 */
* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 28px;
  font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  background:
    radial-gradient(circle at 90% 8%, rgba(167, 139, 250, .15) 0%, transparent 42%),
    radial-gradient(circle at 6% 92%, rgba(236, 72, 153, .1) 0%, transparent 45%),
    #120e1c;
  color: #ede9f7;
}

.cc-panel {
  width: min(380px, 92vw);
  border-radius: 22px;
  background: rgba(255, 255, 255, .045);
  border: 1px solid rgba(255, 255, 255, .09);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .75);
  overflow: hidden;
  display: grid;
  grid-template-rows: auto auto auto;
}

.cc-history {
  list-style: none;
  margin: 0;
  padding: 18px 16px 8px;
  display: grid;
  gap: 8px;
  max-height: 220px;
  overflow-y: auto;
}

.cc-msg {
  width: fit-content;
  max-width: 82%;
  padding: 10px 14px;
  font-size: 13px;
  line-height: 1.55;
  border-radius: 15px;
  animation: cc-pop .35s cubic-bezier(.22, 1, .36, 1);
}
.cc-msg--in {
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .07);
  border-bottom-left-radius: 5px;
}
.cc-msg--out {
  margin-left: auto;
  background: linear-gradient(135deg, #a78bfa, #7c3aed);
  border-bottom-right-radius: 5px;
  color: #fff;
  box-shadow: 0 10px 22px -12px rgba(124, 58, 237, .7);
}
@keyframes cc-pop {
  from { opacity: 0; transform: translateY(8px) scale(.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* 絵文字ピッカー */
.cc-emoji {
  display: flex;
  gap: 6px;
  padding: 0 16px 10px;
  animation: cc-pop .25s cubic-bezier(.22, 1, .36, 1);
}
.cc-emoji[hidden] { display: none; }
.cc-emoji__item {
  font: inherit;
  font-size: 17px;
  line-height: 1;
  padding: 7px 9px;
  border: none;
  border-radius: 10px;
  background: rgba(255, 255, 255, .06);
  cursor: pointer;
  transition: background .15s ease, transform .15s ease;
}
.cc-emoji__item:hover { background: rgba(255, 255, 255, .14); transform: translateY(-2px); }

/* 入力バー */
.cc-composer {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  padding: 12px 12px 14px;
  border-top: 1px solid rgba(255, 255, 255, .08);
}

.cc-icon {
  flex: none;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  font-size: 16px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: rgba(237, 233, 247, .75);
  cursor: pointer;
  transition: background .15s ease, transform .15s ease;
}
.cc-icon:hover { background: rgba(255, 255, 255, .08); }
.cc-icon.is-active { transform: scale(.9); background: rgba(255, 255, 255, .14); }

.cc-textarea {
  flex: 1;
  resize: none;
  min-height: 34px;
  max-height: 120px;
  overflow-y: auto;
  padding: 8px 12px;
  font: inherit;
  font-size: 13.5px;
  line-height: 1.5;
  color: #ede9f7;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 16px;
  transition: border-color .2s ease, background .2s ease;
}
.cc-textarea:focus {
  outline: none;
  border-color: rgba(167, 139, 250, .55);
  background: rgba(255, 255, 255, .1);
}
.cc-textarea::placeholder { color: rgba(237, 233, 247, .38); }

.cc-send {
  flex: none;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  font-size: 15px;
  border: none;
  border-radius: 50%;
  color: rgba(237, 233, 247, .4);
  background: rgba(255, 255, 255, .07);
  cursor: not-allowed;
  transition: background .2s ease, color .2s ease, transform .15s cubic-bezier(.22, 1, .36, 1);
}
.cc-send:not(:disabled) {
  color: #fff;
  background: linear-gradient(135deg, #a78bfa, #7c3aed);
  cursor: pointer;
  box-shadow: 0 8px 18px -8px rgba(124, 58, 237, .75);
}
.cc-send:not(:disabled):hover { transform: translateY(-2px) scale(1.05); }
.cc-send:not(:disabled):active { transform: scale(.94); }

@media (prefers-reduced-motion: reduce) {
  .cc-msg { animation: none; }
  .cc-send:not(:disabled):hover { transform: none; }
}

@media (max-width: 380px) {
  .cc-panel { border-radius: 18px; }
  .cc-history { padding: 14px 12px 6px; }
}

【JavaScript】
// メッセージ入力欄: オートリサイズ・送信ボタンの活性化・絵文字挿入・送信で履歴に追記
(() => {
  const form = document.getElementById('ccForm');
  const input = document.getElementById('ccInput');
  const sendBtn = document.getElementById('ccSend');
  const history = document.getElementById('ccHistory');
  const attachBtn = document.getElementById('ccAttach');
  const emojiToggle = document.getElementById('ccEmojiToggle');
  const emojiPanel = document.getElementById('ccEmoji');
  if (!form || !input || !sendBtn || !history) return; // null安全

  const MAX_HEIGHT = 120;

  const autoResize = () => {
    input.style.height = 'auto';
    input.style.height = Math.min(input.scrollHeight, MAX_HEIGHT) + 'px';
  };

  const syncSendState = () => {
    const hasText = input.value.trim().length > 0;
    sendBtn.disabled = !hasText;
  };

  input.addEventListener('input', () => {
    autoResize();
    syncSendState();
  });

  // 添付ボタンは押した感触だけ返す装飾的な操作
  if (attachBtn) {
    attachBtn.addEventListener('click', () => {
      attachBtn.classList.add('is-active');
      setTimeout(() => attachBtn.classList.remove('is-active'), 160);
    });
  }

  // 絵文字パネルの開閉
  if (emojiToggle && emojiPanel) {
    emojiToggle.addEventListener('click', () => {
      const willShow = emojiPanel.hidden;
      emojiPanel.hidden = !willShow;
      emojiToggle.setAttribute('aria-expanded', String(willShow));
      if (willShow) input.focus();
    });

    emojiPanel.addEventListener('click', (e) => {
      const btn = e.target.closest('.cc-emoji__item');
      if (!btn) return;
      input.value += btn.textContent;
      autoResize();
      syncSendState();
      input.focus();
      emojiPanel.hidden = true;
      emojiToggle.setAttribute('aria-expanded', 'false');
    });
  }

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const text = input.value.trim();
    if (!text) return;

    const li = document.createElement('li');
    li.className = 'cc-msg cc-msg--out';
    li.textContent = text;
    history.appendChild(li);
    history.scrollTop = history.scrollHeight;

    input.value = '';
    autoResize();
    syncSendState();
    if (emojiPanel && !emojiPanel.hidden) {
      emojiPanel.hidden = true;
      if (emojiToggle) emojiToggle.setAttribute('aria-expanded', 'false');
    }
  });
})();

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

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