# ローディング状態付きボタンのCSS実装

> 押すとスピナーが回り「保存中…」に変わるボタン。二重送信を防ぎ、aria-busyで処理中であることを支援技術にも伝えます。

- カテゴリ: ボタン・操作
- URL: https://www.css-dictionary.com/recipes/loading-button/

## HTML

```html
<button type="button" id="save-btn" class="btn">
  <span class="spinner" aria-hidden="true"></span>
  <span class="label">保存する</span>
</button>
```

## CSS

```css
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 20px;
  border: 1px solid #b0413e;
  border-radius: 6px;
  background: #b0413e;
  color: #fff;
  font: inherit;
  cursor: pointer;
  transition: opacity 0.2s;
}

.btn:focus-visible {
  outline: 2px solid #1a1712;
  outline-offset: 2px;
}

.btn .spinner {
  display: none;
  width: 14px;
  height: 14px;
  border: 2px solid rgb(255 255 255 / 0.4);
  border-top-color: #fff;
  border-radius: 50%;
}

.btn.is-loading {
  opacity: 0.7;
  pointer-events: none;
}

.btn.is-loading .spinner {
  display: inline-block;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .btn.is-loading .spinner { animation: none; }
}
```

## JavaScript

```js
const btn = document.getElementById('save-btn');
btn.addEventListener('click', () => {
  btn.classList.add('is-loading');
  btn.setAttribute('aria-busy', 'true');
  btn.querySelector('.label').textContent = '保存中…';
  // デモ用: 2秒後に元へ戻す（実際は通信完了時に戻す）
  setTimeout(() => {
    btn.classList.remove('is-loading');
    btn.removeAttribute('aria-busy');
    btn.querySelector('.label').textContent = '保存する';
  }, 2000);
});
```

## 仕組みの解説

ローディング中は is-loading クラスで opacity を下げ、pointer-events: none で連打（二重送信）を防ぎます。スピナーは border の一辺だけ色を変えた円を回転させる定番の作りです。disabled 属性ではなく aria-busy を使うのは、disabled にするとフォーカスが外れてしまうためです。動きを減らす設定のユーザーには回転を止め、文言と薄化だけで状態を伝えます。

## 実装のポイント

フォーム送信ボタンの場合も type="button" でJS側から送信を制御すると、状態管理が単純になります。

## AIに依頼するときの文例

クリックするとスピナーが表示され「保存中…」になるローディング状態付きボタンを作ってください。aria-busyの付与、pointer-eventsでの二重送信防止、prefers-reduced-motionでアニメーション停止も含めてください。

## 使っているプロパティ

- [animation](https://www.css-dictionary.com/property/animation.md)
- [transform](https://www.css-dictionary.com/property/transform.md)
- [opacity](https://www.css-dictionary.com/property/opacity.md)
- [transition](https://www.css-dictionary.com/property/transition.md)
- [border-radius](https://www.css-dictionary.com/property/border-radius.md)

